diff --git a/fixtures/anyof_from_jsonschemaext.json b/fixtures/anyof_from_jsonschemaext.json new file mode 100644 index 0000000..9415610 --- /dev/null +++ b/fixtures/anyof_from_jsonschemaext.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/invopop/jsonschema/root-any-of-extended", + "$ref": "#/$defs/RootAnyOfExtended", + "$defs": { + "RootAnyOfExtended": { + "additionalProperties": false, + "type": "object", + "properties": { + "root1": { + "enum": [ + "boulou", + "billy" + ], + "type": "string" + }, + "root2": { + "type": "string" + }, + "root3": { + "type": "string" + } + }, + "anyOf": [ + { + "properties": { + "root1": { + "const": "boulou" + } + }, + "required": [ + "root2" + ] + }, + { + "properties": { + "root1": { + "const": "billy" + } + }, + "required": [ + "root3" + ] + } + ] + } + } +} diff --git a/fixtures/ifthen_from_jsonschemaext.json b/fixtures/ifthen_from_jsonschemaext.json new file mode 100644 index 0000000..a46737e --- /dev/null +++ b/fixtures/ifthen_from_jsonschemaext.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/invopop/jsonschema/struct-with-ext", + "$ref": "#/$defs/StructWithExt", + "$defs": { + "StructWithExt": { + "additionalProperties": false, + "type": "object", + "properties": { + "root1": { + "enum": [ + "child", + "root" + ], + "type": "string" + }, + "root2": { + "type": "string" + }, + "inner": { + "type": "object", + "additionalProperties":false, + "properties": { + "child1": { + "type": "string" + } + }, + "required": ["child1"] + } + }, + "if": { + "properties": { + "root1": { + "const": "child" + } + } + }, + "then": { + "required": ["inner"] + } + } + } +} diff --git a/reflect.go b/reflect.go index af36886..67fe196 100644 --- a/reflect.go +++ b/reflect.go @@ -108,7 +108,12 @@ type customSchemaImpl interface { JSONSchema() *Schema } +type extendSchemaImpl interface { + JSONSchemaExt(base *Schema) +} + var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() +var extendSchemaType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() // customSchemaGetFieldDocString type customSchemaGetFieldDocString interface { @@ -355,6 +360,8 @@ func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) return st } + r.reflectSchemaExtended(definitions, t, st) + // Defined format types for JSON Schema Validation // RFC draft-wright-json-schema-validation-00, section 7.3 // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7 @@ -422,6 +429,19 @@ func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) return nil } +func (r *Reflector) reflectSchemaExtended(definitions Definitions, t reflect.Type, s *Schema) *Schema { + if t.Implements(extendSchemaType) { + v := reflect.New(t) + o := v.Interface().(extendSchemaImpl) + o.JSONSchemaExt(s) + if ref := r.refDefinition(definitions, t); ref != nil { + return ref + } + } + + return s +} + func (r *Reflector) reflectSliceOrArray(definitions Definitions, t reflect.Type, st *Schema) { if t == rawMessageType { return diff --git a/reflect_test.go b/reflect_test.go index 1442bd3..4bfdea3 100644 --- a/reflect_test.go +++ b/reflect_test.go @@ -147,6 +147,50 @@ type ChildOneOf struct { Child4 string `json:"child4" jsonschema:"oneof_required=group1"` } +type RootAnyOfExtended struct { + Root1 string `json:"root1" jsonschema:"enum=boulou,enum=billy"` + Root2 string `json:"root2"` + Root3 string `json:"root3"` +} + +func (RootAnyOfExtended) JSONSchemaExt(base *Schema) { + anyOfCond1 := orderedmap.New() + anyOfCond1.Set("root1", &Schema{Const: "boulou"}) + anyOf1 := &Schema{ + Properties: anyOfCond1, + Required: []string{"root2"}, + } + anyOfCond2 := orderedmap.New() + anyOfCond2.Set("root1", &Schema{Const: "billy"}) + anyOf2 := &Schema{ + Properties: anyOfCond2, + Required: []string{"root3"}, + } + cond := make([]*Schema, 2) + cond[0] = anyOf1 + cond[1] = anyOf2 + base.AnyOf = cond +} + +type StructWithExt struct { + Root1 string `json:"root1" jsonschema:"enum=child,enum=root"` + Root2 string `json:"root2"` + Inner struct { + Child1 string `json:"child1" jsonschema:"required"` + } `json:"inner,omitempty"` +} + +func (StructWithExt) JSONSchemaExt(base *Schema) { + ifProps := orderedmap.New() + ifProps.Set("root1", &Schema{Const: "child"}) + base.If = &Schema{ + Properties: ifProps, + } + base.Then = &Schema{ + Required: []string{"inner"}, + } +} + type Text string type TextNamed string @@ -336,6 +380,8 @@ func TestSchemaGeneration(t *testing.T) { {&TestUser{}, &Reflector{DoNotReference: true}, "fixtures/no_reference.json"}, {&TestUser{}, &Reflector{DoNotReference: true, AssignAnchor: true}, "fixtures/no_reference_anchor.json"}, {&RootOneOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/oneof.json"}, + {&RootAnyOfExtended{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/anyof_from_jsonschemaext.json"}, + {&StructWithExt{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/ifthen_from_jsonschemaext.json"}, {&CustomTypeField{}, &Reflector{ Mapper: func(i reflect.Type) *Schema { if i == reflect.TypeOf(CustomTime{}) {