package jsonschema import ( "encoding/json" "fmt" "math/big" "strconv" ) type objCompiler struct { c *Compiler obj map[string]any up urlPtr r *root res *resource q *queue } func (c *objCompiler) compile(s *Schema) error { // id -- if id := c.res.dialect.draft.getID(c.obj); id != "" { s.ID = id } // anchor -- if s.DraftVersion < 2019 { // anchor is specified in id id := c.string(c.res.dialect.draft.id) if id != "" { _, f := split(id) if f != "" { var err error s.Anchor, err = decode(f) if err != nil { return &ParseAnchorError{URL: s.Location} } } } } else { s.Anchor = c.string("$anchor") } if err := c.compileDraft4(s); err != nil { return err } if s.DraftVersion >= 6 { if err := c.compileDraft6(s); err != nil { return err } } if s.DraftVersion >= 7 { if err := c.compileDraft7(s); err != nil { return err } } if s.DraftVersion >= 2019 { if err := c.compileDraft2019(s); err != nil { return err } } if s.DraftVersion >= 2020 { if err := c.compileDraft2020(s); err != nil { return err } } // vocabularies vocabs := c.res.dialect.activeVocabs(c.c.roots.assertVocabs, c.c.roots.vocabularies) for _, vocab := range vocabs { v := c.c.roots.vocabularies[vocab] if v == nil { continue } ext, err := v.Compile(&CompilerContext{c}, c.obj) if err != nil { return err } if ext != nil { s.Extensions = append(s.Extensions, ext) } } return nil } func (c *objCompiler) compileDraft4(s *Schema) error { var err error if c.hasVocab("core") { if s.Ref, err = c.enqueueRef("$ref"); err != nil { return err } if s.DraftVersion < 2019 && s.Ref != nil { // All other properties in a "$ref" object MUST be ignored return nil } } if c.hasVocab("applicator") { s.AllOf = c.enqueueArr("allOf") s.AnyOf = c.enqueueArr("anyOf") s.OneOf = c.enqueueArr("oneOf") s.Not = c.enqueueProp("not") if s.DraftVersion < 2020 { if items, ok := c.obj["items"]; ok { if _, ok := items.([]any); ok { s.Items = c.enqueueArr("items") s.AdditionalItems = c.enqueueAdditional("additionalItems") } else { s.Items = c.enqueueProp("items") } } } s.Properties = c.enqueueMap("properties") if m := c.enqueueMap("patternProperties"); m != nil { s.PatternProperties = map[Regexp]*Schema{} for pname, sch := range m { re, err := c.c.roots.regexpEngine(pname) if err != nil { return &InvalidRegexError{c.up.format("patternProperties"), pname, err} } s.PatternProperties[re] = sch } } s.AdditionalProperties = c.enqueueAdditional("additionalProperties") if m := c.objVal("dependencies"); m != nil { s.Dependencies = map[string]any{} for pname, pvalue := range m { if arr, ok := pvalue.([]any); ok { s.Dependencies[pname] = toStrings(arr) } else { ptr := c.up.ptr.append2("dependencies", pname) s.Dependencies[pname] = c.enqueuePtr(ptr) } } } } if c.hasVocab("validation") { if t, ok := c.obj["type"]; ok { s.Types = newTypes(t) } if arr := c.arrVal("enum"); arr != nil { s.Enum = newEnum(arr) } s.MultipleOf = c.numVal("multipleOf") s.Maximum = c.numVal("maximum") if c.boolean("exclusiveMaximum") { s.ExclusiveMaximum = s.Maximum s.Maximum = nil } else { s.ExclusiveMaximum = c.numVal("exclusiveMaximum") } s.Minimum = c.numVal("minimum") if c.boolean("exclusiveMinimum") { s.ExclusiveMinimum = s.Minimum s.Minimum = nil } else { s.ExclusiveMinimum = c.numVal("exclusiveMinimum") } s.MinLength = c.intVal("minLength") s.MaxLength = c.intVal("maxLength") if pat := c.strVal("pattern"); pat != nil { s.Pattern, err = c.c.roots.regexpEngine(*pat) if err != nil { return &InvalidRegexError{c.up.format("pattern"), *pat, err} } } s.MinItems = c.intVal("minItems") s.MaxItems = c.intVal("maxItems") s.UniqueItems = c.boolean("uniqueItems") s.MaxProperties = c.intVal("maxProperties") s.MinProperties = c.intVal("minProperties") if arr := c.arrVal("required"); arr != nil { s.Required = toStrings(arr) } } // format -- if c.assertFormat(s.DraftVersion) { if f := c.strVal("format"); f != nil { if *f == "regex" { s.Format = &Format{ Name: "regex", Validate: c.c.roots.regexpEngine.validate, } } else { s.Format = c.c.formats[*f] if s.Format == nil { s.Format = formats[*f] } } } } // annotations -- s.Title = c.string("title") s.Description = c.string("description") if v, ok := c.obj["default"]; ok { s.Default = &v } return nil } func (c *objCompiler) compileDraft6(s *Schema) error { if c.hasVocab("applicator") { s.Contains = c.enqueueProp("contains") s.PropertyNames = c.enqueueProp("propertyNames") } if c.hasVocab("validation") { if v, ok := c.obj["const"]; ok { s.Const = &v } } return nil } func (c *objCompiler) compileDraft7(s *Schema) error { if c.hasVocab("applicator") { s.If = c.enqueueProp("if") if s.If != nil { b := c.boolVal("if") if b == nil || *b { s.Then = c.enqueueProp("then") } if b == nil || !*b { s.Else = c.enqueueProp("else") } } } if c.c.assertContent { if ce := c.strVal("contentEncoding"); ce != nil { s.ContentEncoding = c.c.decoders[*ce] if s.ContentEncoding == nil { s.ContentEncoding = decoders[*ce] } } if cm := c.strVal("contentMediaType"); cm != nil { s.ContentMediaType = c.c.mediaTypes[*cm] if s.ContentMediaType == nil { s.ContentMediaType = mediaTypes[*cm] } } } // annotations -- s.Comment = c.string("$comment") s.ReadOnly = c.boolean("readOnly") s.WriteOnly = c.boolean("writeOnly") if arr, ok := c.obj["examples"].([]any); ok { s.Examples = arr } return nil } func (c *objCompiler) compileDraft2019(s *Schema) error { var err error if c.hasVocab("core") { if s.RecursiveRef, err = c.enqueueRef("$recursiveRef"); err != nil { return err } s.RecursiveAnchor = c.boolean("$recursiveAnchor") } if c.hasVocab("validation") { if s.Contains != nil { s.MinContains = c.intVal("minContains") s.MaxContains = c.intVal("maxContains") } if m := c.objVal("dependentRequired"); m != nil { s.DependentRequired = map[string][]string{} for pname, pvalue := range m { if arr, ok := pvalue.([]any); ok { s.DependentRequired[pname] = toStrings(arr) } } } } if c.hasVocab("applicator") { s.DependentSchemas = c.enqueueMap("dependentSchemas") } var unevaluated bool if s.DraftVersion == 2019 { unevaluated = c.hasVocab("applicator") } else { unevaluated = c.hasVocab("unevaluated") } if unevaluated { s.UnevaluatedItems = c.enqueueProp("unevaluatedItems") s.UnevaluatedProperties = c.enqueueProp("unevaluatedProperties") } if c.c.assertContent { if s.ContentMediaType != nil && s.ContentMediaType.UnmarshalJSON != nil { s.ContentSchema = c.enqueueProp("contentSchema") } } // annotations -- s.Deprecated = c.boolean("deprecated") return nil } func (c *objCompiler) compileDraft2020(s *Schema) error { if c.hasVocab("core") { sch, err := c.enqueueRef("$dynamicRef") if err != nil { return err } if sch != nil { dref := c.strVal("$dynamicRef") _, frag, err := splitFragment(*dref) if err != nil { return err } var anch string if anchor, ok := frag.convert().(anchor); ok { anch = string(anchor) } s.DynamicRef = &DynamicRef{sch, anch} } s.DynamicAnchor = c.string("$dynamicAnchor") } if c.hasVocab("applicator") { s.PrefixItems = c.enqueueArr("prefixItems") s.Items2020 = c.enqueueProp("items") } return nil } // enqueue helpers -- func (c *objCompiler) enqueuePtr(ptr jsonPointer) *Schema { up := urlPtr{c.up.url, ptr} return c.c.enqueue(c.q, up) } func (c *objCompiler) enqueueRef(pname string) (*Schema, error) { ref := c.strVal(pname) if ref == nil { return nil, nil } baseURL := c.res.id // baseURL := c.r.baseURL(c.up.ptr) uf, err := baseURL.join(*ref) if err != nil { return nil, err } up, err := c.r.resolve(*uf) if err != nil { return nil, err } if up != nil { // local ref return c.enqueuePtr(up.ptr), nil } // remote ref up_, err := c.c.roots.resolveFragment(*uf) if err != nil { return nil, err } return c.c.enqueue(c.q, up_), nil } func (c *objCompiler) enqueueProp(pname string) *Schema { if _, ok := c.obj[pname]; !ok { return nil } ptr := c.up.ptr.append(pname) return c.enqueuePtr(ptr) } func (c *objCompiler) enqueueArr(pname string) []*Schema { arr := c.arrVal(pname) if arr == nil { return nil } sch := make([]*Schema, len(arr)) for i := range arr { ptr := c.up.ptr.append2(pname, strconv.Itoa(i)) sch[i] = c.enqueuePtr(ptr) } return sch } func (c *objCompiler) enqueueMap(pname string) map[string]*Schema { obj := c.objVal(pname) if obj == nil { return nil } sch := make(map[string]*Schema) for k := range obj { ptr := c.up.ptr.append2(pname, k) sch[k] = c.enqueuePtr(ptr) } return sch } func (c *objCompiler) enqueueAdditional(pname string) any { if b := c.boolVal(pname); b != nil { return *b } if sch := c.enqueueProp(pname); sch != nil { return sch } return nil } // -- func (c *objCompiler) hasVocab(name string) bool { return c.res.dialect.hasVocab(name) } func (c *objCompiler) assertFormat(draftVersion int) bool { if c.c.assertFormat || draftVersion < 2019 { return true } if draftVersion == 2019 { return c.hasVocab("format") } else { return c.hasVocab("format-assertion") } } // value helpers -- func (c *objCompiler) boolVal(pname string) *bool { v, ok := c.obj[pname] if !ok { return nil } b, ok := v.(bool) if !ok { return nil } return &b } func (c *objCompiler) boolean(pname string) bool { b := c.boolVal(pname) return b != nil && *b } func (c *objCompiler) strVal(pname string) *string { v, ok := c.obj[pname] if !ok { return nil } s, ok := v.(string) if !ok { return nil } return &s } func (c *objCompiler) string(pname string) string { if s := c.strVal(pname); s != nil { return *s } return "" } func (c *objCompiler) numVal(pname string) *big.Rat { v, ok := c.obj[pname] if !ok { return nil } switch v.(type) { case json.Number, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: if n, ok := new(big.Rat).SetString(fmt.Sprint(v)); ok { return n } } return nil } func (c *objCompiler) intVal(pname string) *int { if n := c.numVal(pname); n != nil && n.IsInt() { n := int(n.Num().Int64()) return &n } return nil } func (c *objCompiler) objVal(pname string) map[string]any { v, ok := c.obj[pname] if !ok { return nil } obj, ok := v.(map[string]any) if !ok { return nil } return obj } func (c *objCompiler) arrVal(pname string) []any { v, ok := c.obj[pname] if !ok { return nil } arr, ok := v.([]any) if !ok { return nil } return arr } // -- type InvalidRegexError struct { URL string Regex string Err error } func (e *InvalidRegexError) Error() string { return fmt.Sprintf("invalid regex %q at %q: %v", e.Regex, e.URL, e.Err) } // -- func toStrings(arr []any) []string { var strings []string for _, item := range arr { if s, ok := item.(string); ok { strings = append(strings, s) } } return strings }