Progressing through mutability use cases in models.

This commit is contained in:
Dave Shanley
2023-03-02 12:21:14 -05:00
parent 88709c389a
commit fc9a230847
7 changed files with 82 additions and 42 deletions

View File

@@ -4,7 +4,9 @@
package base package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3"
) )
// Discriminator is only used by OpenAPI 3+ documents, it represents a polymorphic discriminator used for schemas // Discriminator is only used by OpenAPI 3+ documents, it represents a polymorphic discriminator used for schemas
@@ -16,8 +18,8 @@ import (
// When using the discriminator, inline schemas will not be considered. // When using the discriminator, inline schemas will not be considered.
// v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object // v3 - https://spec.openapis.org/oas/v3.1.0#discriminator-object
type Discriminator struct { type Discriminator struct {
PropertyName string PropertyName string `json:"propertyName,omitempty" yaml:"propertyName,omitempty"`
Mapping map[string]string Mapping map[string]string `json:"mapping,omitempty" yaml:"mapping,omitempty"`
low *low.Discriminator low *low.Discriminator
} }
@@ -38,3 +40,17 @@ func NewDiscriminator(disc *low.Discriminator) *Discriminator {
func (d *Discriminator) GoLow() *low.Discriminator { func (d *Discriminator) GoLow() *low.Discriminator {
return d.low return d.low
} }
// Render will return a YAML representation of the Discriminator object as a byte slice.
func (d *Discriminator) Render() ([]byte, error) {
return yaml.Marshal(d)
}
// MarshalYAML will create a ready to render YAML representation of the Discriminator object.
func (d *Discriminator) MarshalYAML() (interface{}, error) {
if d == nil {
return nil, nil
}
nb := high.NewNodeBuilder(d, d.low)
return nb.Render(), nil
}

View File

@@ -9,6 +9,7 @@ import (
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings"
"testing" "testing"
) )
@@ -18,7 +19,7 @@ func TestNewDiscriminator(t *testing.T) {
yml := `propertyName: coffee yml := `propertyName: coffee
mapping: mapping:
fogCleaner: in the morning` fogCleaner: in the morning`
_ = yaml.Unmarshal([]byte(yml), &cNode) _ = yaml.Unmarshal([]byte(yml), &cNode)
@@ -33,6 +34,10 @@ mapping:
assert.Equal(t, "in the morning", highDiscriminator.Mapping["fogCleaner"]) assert.Equal(t, "in the morning", highDiscriminator.Mapping["fogCleaner"])
assert.Equal(t, 3, highDiscriminator.GoLow().FindMappingValue("fogCleaner").ValueNode.Line) assert.Equal(t, 3, highDiscriminator.GoLow().FindMappingValue("fogCleaner").ValueNode.Line)
// render the example as YAML
rendered, _ := highDiscriminator.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
} }
func ExampleNewDiscriminator() { func ExampleNewDiscriminator() {

View File

@@ -7,16 +7,17 @@ import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
lowmodel "github.com/pb33f/libopenapi/datamodel/low" lowmodel "github.com/pb33f/libopenapi/datamodel/low"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3"
) )
// Example represents a high-level Example object as defined by OpenAPI 3+ // Example represents a high-level Example object as defined by OpenAPI 3+
// v3 - https://spec.openapis.org/oas/v3.1.0#example-object // v3 - https://spec.openapis.org/oas/v3.1.0#example-object
type Example struct { type Example struct {
Summary string Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
Value any Value any `json:"value,omitempty" yaml:"value,omitempty"`
ExternalValue string ExternalValue string `json:"externalValue,omitempty" yaml:"externalValue,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.Example low *low.Example
} }
@@ -37,6 +38,20 @@ func (e *Example) GoLow() *low.Example {
return e.low return e.low
} }
// Render will return a YAML representation of the Example object as a byte slice.
func (e *Example) Render() ([]byte, error) {
return yaml.Marshal(e)
}
// MarshalYAML will create a ready to render YAML representation of the Example object.
func (e *Example) MarshalYAML() (interface{}, error) {
if e == nil {
return nil, nil
}
nb := high.NewNodeBuilder(e, e.low)
return nb.Render(), nil
}
// ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate. // ExtractExamples will convert a low-level example map, into a high level one that is simple to navigate.
// no fidelity is lost, everything is still available via GoLow() // no fidelity is lost, everything is still available via GoLow()
func ExtractExamples(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Example]) map[string]*Example { func ExtractExamples(elements map[lowmodel.KeyReference[string]]lowmodel.ValueReference[*low.Example]) map[string]*Example {

View File

@@ -9,6 +9,7 @@ import (
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings"
"testing" "testing"
) )
@@ -39,6 +40,11 @@ x-hack: code`
assert.Equal(t, "code", highExample.Extensions["x-hack"]) assert.Equal(t, "code", highExample.Extensions["x-hack"])
assert.Equal(t, "a thing", highExample.Value) assert.Equal(t, "a thing", highExample.Value)
assert.Equal(t, 4, highExample.GoLow().ExternalValue.ValueNode.Line) assert.Equal(t, 4, highExample.GoLow().ExternalValue.ValueNode.Line)
// render the example as YAML
rendered, _ := highExample.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
} }
func TestExtractExamples(t *testing.T) { func TestExtractExamples(t *testing.T) {

View File

@@ -6,6 +6,7 @@ package base
import ( import (
"github.com/pb33f/libopenapi/datamodel/high" "github.com/pb33f/libopenapi/datamodel/high"
low "github.com/pb33f/libopenapi/datamodel/low/base" low "github.com/pb33f/libopenapi/datamodel/low/base"
"gopkg.in/yaml.v3"
) )
// ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3 // ExternalDoc represents a high-level External Documentation object as defined by OpenAPI 2 and 3
@@ -14,9 +15,9 @@ import (
// v2 - https://swagger.io/specification/v2/#externalDocumentationObject // v2 - https://swagger.io/specification/v2/#externalDocumentationObject
// v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object // v3 - https://spec.openapis.org/oas/v3.1.0#external-documentation-object
type ExternalDoc struct { type ExternalDoc struct {
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
URL string URL string `json:"url,omitempty" yaml:"url,omitempty"`
Extensions map[string]any Extensions map[string]any `json:"-" yaml:"-"`
low *low.ExternalDoc low *low.ExternalDoc
} }
@@ -41,4 +42,18 @@ func (e *ExternalDoc) GoLow() *low.ExternalDoc {
func (e *ExternalDoc) GetExtensions() map[string]any { func (e *ExternalDoc) GetExtensions() map[string]any {
return e.Extensions return e.Extensions
} }
// Render will return a YAML representation of the ExternalDoc object as a byte slice.
func (e *ExternalDoc) Render() ([]byte, error) {
return yaml.Marshal(e)
}
// MarshalYAML will create a ready to render YAML representation of the ExternalDoc object.
func (e *ExternalDoc) MarshalYAML() (interface{}, error) {
if e == nil {
return nil, nil
}
nb := high.NewNodeBuilder(e, e.low)
return nb.Render(), nil
}

View File

@@ -9,6 +9,7 @@ import (
lowbase "github.com/pb33f/libopenapi/datamodel/low/base" lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings"
"testing" "testing"
) )
@@ -36,6 +37,11 @@ x-hack: code`
wentLow := highExt.GoLow() wentLow := highExt.GoLow()
assert.Equal(t, 2, wentLow.URL.ValueNode.Line) assert.Equal(t, 2, wentLow.URL.ValueNode.Line)
assert.Len(t, highExt.GetExtensions(), 1) assert.Len(t, highExt.GetExtensions(), 1)
// render the high-level object as YAML
rendered, _ := highExt.Render()
assert.Equal(t, strings.TrimSpace(string(rendered)), yml)
} }
func ExampleNewExternalDoc() { func ExampleNewExternalDoc() {

View File

@@ -14,7 +14,6 @@
package high package high
import ( import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/datamodel/low"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"reflect" "reflect"
@@ -118,11 +117,7 @@ func (n *NodeBuilder) add(key string) {
// if the key is 'Extensions' then we need to extract the keys from the map // if the key is 'Extensions' then we need to extract the keys from the map
// and add them to the node builder. // and add them to the node builder.
if key == "Extensions" { if key == "Extensions" {
extensions := reflect.ValueOf(n.High).Elem().FieldByName(key) extensions := reflect.ValueOf(n.High).Elem().FieldByName(key)
//for _, k := range extensions.MapKeys() {
// n.add(k.String())
//}
for _, e := range extensions.MapKeys() { for _, e := range extensions.MapKeys() {
v := extensions.MapIndex(e) v := extensions.MapIndex(e)
@@ -135,17 +130,6 @@ func (n *NodeBuilder) add(key string) {
f := fieldValue.Interface() f := fieldValue.Interface()
value := reflect.ValueOf(f) value := reflect.ValueOf(f)
switch value.Kind() { switch value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
panic("not supported yet")
case reflect.String:
panic("not supported yet")
case reflect.Ptr:
panic("not supported yet")
case reflect.Struct:
nb := f.(low.HasValueNodeUntyped).GetValueNode()
if nb != nil {
nodeEntry.Line = nb.Line
}
case reflect.Map: case reflect.Map:
if j, ok := n.Low.(low.HasExtensionsUntyped); ok { if j, ok := n.Low.(low.HasExtensionsUntyped); ok {
originalExtensions := j.GetExtensions() originalExtensions := j.GetExtensions()
@@ -156,9 +140,6 @@ func (n *NodeBuilder) add(key string) {
} }
} }
default: default:
if j, ok := n.Low.(low.HasExtensionsUntyped); ok {
fmt.Print(j)
}
panic("not supported yet") panic("not supported yet")
} }
} }
@@ -189,30 +170,26 @@ func (n *NodeBuilder) add(key string) {
case reflect.Ptr: case reflect.Ptr:
nodeEntry.Value = f nodeEntry.Value = f
case reflect.Map: case reflect.Map:
nodeEntry.Value = value nodeEntry.Value = f
} }
// if there is no low level object, then we cannot extract line numbers, // if there is no low level object, then we cannot extract line numbers,
// so skip and default to 0, which means a new entry to the spec. // so skip and default to 0, which means a new entry to the spec.
// this will place new content and the top of the rendered object. // this will place new content and the top of the rendered object.
if !reflect.ValueOf(n.Low).IsZero() { if !reflect.ValueOf(n.Low).IsZero() {
fieldValue = reflect.ValueOf(n.Low).Elem().FieldByName(key) lowFieldValue := reflect.ValueOf(n.Low).Elem().FieldByName(key)
f = fieldValue.Interface() fLow := lowFieldValue.Interface()
value = reflect.ValueOf(f) value = reflect.ValueOf(fLow)
switch value.Kind() { switch value.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
panic("not supported yet")
case reflect.String:
panic("not supported yet")
case reflect.Ptr:
panic("not supported yet")
case reflect.Struct: case reflect.Struct:
nb := value.Interface().(low.HasValueNodeUntyped).GetValueNode() nb := value.Interface().(low.HasValueNodeUntyped).GetValueNode()
if nb != nil { if nb != nil {
nodeEntry.Line = nb.Line nodeEntry.Line = nb.Line
} }
default: default:
panic("not supported yet") // everything else, weight it to the bottom of the rendered object.
// this is things that we have no way of knowing where they should be placed.
nodeEntry.Line = 9999
} }
} }
n.Nodes = append(n.Nodes, nodeEntry) n.Nodes = append(n.Nodes, nodeEntry)