Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.4.0 Fixes #38 and #28 and introduces more 3.1 schema support. #40

Merged
merged 3 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 154 additions & 38 deletions datamodel/high/base/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ import (
"sync"
)

// DynamicValue is used to hold multiple possible values for a schema property. There are two values, a left
// value (A) and a right value (B). The left value (A) is a 3.0 schema property value, the right value (B) is a 3.1
// schema value.
//
// OpenAPI 3.1 treats a Schema as a real JSON schema, which means some properties become incompatible, or others
// now support more than one primitive type or structure.
// The N value is a bit to make it each to know which value (A or B) is used, this prevents having to
// if/else on the value to determine which one is set.
type DynamicValue[A any, B any] struct {
N int // 0 == A, 1 == B
A A
B B
}

// IsA will return true if the 'A' or left value is set. (OpenAPI 3)
func (s *DynamicValue[A, B]) IsA() bool {
return s.N == 0
}

// IsB will return true if the 'B' or right value is set (OpenAPI 3.1)
func (s *DynamicValue[A, B]) IsB() bool {
return s.N == 1
}

// Schema represents a JSON Schema that support Swagger, OpenAPI 3 and OpenAPI 3.1
//
// Until 3.1 OpenAPI had a strange relationship with JSON Schema. It's been a super-set/sub-set
Expand All @@ -26,16 +50,12 @@ type Schema struct {
SchemaTypeRef string

// In versions 2 and 3.0, this ExclusiveMaximum can only be a boolean.
ExclusiveMaximumBool *bool

// In version 3.1, ExclusiveMaximum is an integer.
ExclusiveMaximum *int64
ExclusiveMaximum *DynamicValue[bool, int64]

// In versions 2 and 3.0, this ExclusiveMinimum can only be a boolean.
ExclusiveMinimum *int64

// In version 3.1, ExclusiveMinimum is an integer.
ExclusiveMinimumBool *bool
ExclusiveMinimum *DynamicValue[bool, int64]

// In versions 2 and 3.0, this Type is a single value, so array will only ever have one value
// in version 3.1, Type can be multiple values
Expand All @@ -55,9 +75,24 @@ type Schema struct {
// in 3.1 prefixItems provides tuple validation support.
PrefixItems []*SchemaProxy

// 3.1 Specific properties
Contains *SchemaProxy
MinContains *int64
MaxContains *int64
If *SchemaProxy
Else *SchemaProxy
Then *SchemaProxy
DependentSchemas map[string]*SchemaProxy
PatternProperties map[string]*SchemaProxy
PropertyNames *SchemaProxy
UnevaluatedItems *SchemaProxy
UnevaluatedProperties *SchemaProxy

// in 3.1 Items can be a Schema or a boolean
Items *DynamicValue[*SchemaProxy, bool]

// Compatible with all versions
Not []*SchemaProxy
Items []*SchemaProxy
Not *SchemaProxy
Properties map[string]*SchemaProxy
Title string
MultipleOf *int64
Expand Down Expand Up @@ -107,19 +142,27 @@ func NewSchema(schema *base.Schema) *Schema {
}
// if we're dealing with a 3.0 spec using a bool
if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsA() {
s.ExclusiveMaximumBool = &schema.ExclusiveMaximum.Value.A
s.ExclusiveMaximum = &DynamicValue[bool, int64]{
A: schema.ExclusiveMaximum.Value.A,
}
}
// if we're dealing with a 3.1 spec using an int
if !schema.ExclusiveMaximum.IsEmpty() && schema.ExclusiveMaximum.Value.IsB() {
s.ExclusiveMaximum = &schema.ExclusiveMaximum.Value.B
s.ExclusiveMaximum = &DynamicValue[bool, int64]{
B: schema.ExclusiveMaximum.Value.B,
}
}
// if we're dealing with a 3.0 spec using a bool
if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsA() {
s.ExclusiveMinimumBool = &schema.ExclusiveMinimum.Value.A
s.ExclusiveMinimum = &DynamicValue[bool, int64]{
A: schema.ExclusiveMinimum.Value.A,
}
}
// if we're dealing with a 3.1 spec, using an int
if !schema.ExclusiveMinimum.IsEmpty() && schema.ExclusiveMinimum.Value.IsB() {
s.ExclusiveMinimum = &schema.ExclusiveMinimum.Value.B
s.ExclusiveMinimum = &DynamicValue[bool, int64]{
B: schema.ExclusiveMinimum.Value.B,
}
}
if !schema.MaxLength.IsEmpty() {
s.MaxLength = &schema.MaxLength.Value
Expand All @@ -139,6 +182,57 @@ func NewSchema(schema *base.Schema) *Schema {
if !schema.MinProperties.IsEmpty() {
s.MinProperties = &schema.MinProperties.Value
}

if !schema.MaxContains.IsEmpty() {
s.MaxContains = &schema.MaxContains.Value
}
if !schema.MinContains.IsEmpty() {
s.MinContains = &schema.MinContains.Value
}
if !schema.Contains.IsEmpty() {
s.Contains = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Contains.ValueNode,
Value: schema.Contains.Value,
}}
}

if !schema.If.IsEmpty() {
s.If = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.If.ValueNode,
Value: schema.If.Value,
}}
}
if !schema.Else.IsEmpty() {
s.Else = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Else.ValueNode,
Value: schema.Else.Value,
}}
}
if !schema.Then.IsEmpty() {
s.Then = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Then.ValueNode,
Value: schema.Then.Value,
}}
}
if !schema.PropertyNames.IsEmpty() {
s.PropertyNames = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.PropertyNames.ValueNode,
Value: schema.PropertyNames.Value,
}}
}
if !schema.UnevaluatedItems.IsEmpty() {
s.UnevaluatedItems = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedItems.ValueNode,
Value: schema.UnevaluatedItems.Value,
}}
}
if !schema.UnevaluatedProperties.IsEmpty() {
s.UnevaluatedProperties = &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.UnevaluatedProperties.ValueNode,
Value: schema.UnevaluatedProperties.Value,
}}
}

s.Pattern = schema.Pattern.Value
s.Format = schema.Format.Value

Expand Down Expand Up @@ -201,27 +295,26 @@ func NewSchema(schema *base.Schema) *Schema {
// any polymorphic properties need to be handled in their own threads
// any properties each need to be processed in their own thread.
// we go as fast as we can.

polyCompletedChan := make(chan bool)
propsChan := make(chan bool)
errChan := make(chan error)

// for every item, build schema async
buildSchema := func(sch lowmodel.ValueReference[*base.SchemaProxy], bChan chan *SchemaProxy) {
p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.ValueNode,
Value: sch.Value,
}}
bChan <- p
}

// schema async
buildOutSchema := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy,
buildOutSchemas := func(schemas []lowmodel.ValueReference[*base.SchemaProxy], items *[]*SchemaProxy,
doneChan chan bool, e chan error) {
bChan := make(chan *SchemaProxy)

// for every item, build schema async
buildSchemaChild := func(sch lowmodel.ValueReference[*base.SchemaProxy], bChan chan *SchemaProxy) {
p := &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: sch.ValueNode,
Value: sch.Value,
}}
bChan <- p
}
totalSchemas := len(schemas)
for v := range schemas {
go buildSchemaChild(schemas[v], bChan)
go buildSchema(schemas[v], bChan)
}
j := 0
for j < totalSchemas {
Expand All @@ -237,7 +330,7 @@ func NewSchema(schema *base.Schema) *Schema {
// props async
var plock sync.Mutex
var buildProps = func(k lowmodel.KeyReference[string], v lowmodel.ValueReference[*base.SchemaProxy], c chan bool,
props map[string]*SchemaProxy) {
props map[string]*SchemaProxy, sw int) {
plock.Lock()
props[k.Value] = &SchemaProxy{
schema: &lowmodel.NodeReference[*base.SchemaProxy]{
Expand All @@ -247,51 +340,74 @@ func NewSchema(schema *base.Schema) *Schema {
},
}
plock.Unlock()
s.Properties = props

switch sw {
case 0:
s.Properties = props
case 1:
s.DependentSchemas = props
case 2:
s.PatternProperties = props
}
c <- true
}

props := make(map[string]*SchemaProxy)
for k, v := range schema.Properties.Value {
go buildProps(k, v, propsChan, props)
go buildProps(k, v, propsChan, props, 0)
}

dependents := make(map[string]*SchemaProxy)
for k, v := range schema.DependentSchemas.Value {
go buildProps(k, v, propsChan, dependents, 1)
}
patternProps := make(map[string]*SchemaProxy)
for k, v := range schema.PatternProperties.Value {
go buildProps(k, v, propsChan, patternProps, 2)
}

var allOf []*SchemaProxy
var oneOf []*SchemaProxy
var anyOf []*SchemaProxy
var not []*SchemaProxy
var items []*SchemaProxy
var not *SchemaProxy
var items *DynamicValue[*SchemaProxy, bool]
var prefixItems []*SchemaProxy

children := 0
if !schema.AllOf.IsEmpty() {
children++
go buildOutSchema(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
go buildOutSchemas(schema.AllOf.Value, &allOf, polyCompletedChan, errChan)
}
if !schema.AnyOf.IsEmpty() {
children++
go buildOutSchema(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
go buildOutSchemas(schema.AnyOf.Value, &anyOf, polyCompletedChan, errChan)
}
if !schema.OneOf.IsEmpty() {
children++
go buildOutSchema(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
go buildOutSchemas(schema.OneOf.Value, &oneOf, polyCompletedChan, errChan)
}
if !schema.Not.IsEmpty() {
children++
go buildOutSchema(schema.Not.Value, &not, polyCompletedChan, errChan)
not = NewSchemaProxy(&schema.Not)
}
if !schema.Items.IsEmpty() {
children++
go buildOutSchema(schema.Items.Value, &items, polyCompletedChan, errChan)
if schema.Items.Value.IsA() {
items = &DynamicValue[*SchemaProxy, bool]{A: &SchemaProxy{schema: &lowmodel.NodeReference[*base.SchemaProxy]{
ValueNode: schema.Items.ValueNode,
Value: schema.Items.Value.A,
KeyNode: schema.Items.KeyNode,
}}}
} else {
items = &DynamicValue[*SchemaProxy, bool]{B: schema.Items.Value.B}
}
}
if !schema.PrefixItems.IsEmpty() {
children++
go buildOutSchema(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan)
go buildOutSchemas(schema.PrefixItems.Value, &prefixItems, polyCompletedChan, errChan)
}

completeChildren := 0
completedProps := 0
totalProps := len(schema.Properties.Value)
totalProps := len(schema.Properties.Value) + len(schema.DependentSchemas.Value) + len(schema.PatternProperties.Value)
if totalProps+children > 0 {
allDone:
for true {
Expand Down
Loading