pax_global_header00006660000000000000000000000064144750434170014523gustar00rootroot0000000000000052 comment=26903a48396914ab7c274abd96664cf78918827d jsonschema-0.1.7/000077500000000000000000000000001447504341700136625ustar00rootroot00000000000000jsonschema-0.1.7/.github/000077500000000000000000000000001447504341700152225ustar00rootroot00000000000000jsonschema-0.1.7/.github/workflows/000077500000000000000000000000001447504341700172575ustar00rootroot00000000000000jsonschema-0.1.7/.github/workflows/lint.yaml000066400000000000000000000005011447504341700211050ustar00rootroot00000000000000name: Lint on: push: tags: - v* branches: - main pull_request: jobs: lint: name: golangci-lint runs-on: ubuntu-latest steps: - name: Check out code uses: actions/checkout@v2 - name: Lint uses: golangci/golangci-lint-action@v2 with: version: v1.45 jsonschema-0.1.7/.github/workflows/release.yaml000066400000000000000000000015411447504341700215640ustar00rootroot00000000000000# # Automatically tag a merge with master, or build a new image from the tag. # # Secrets required: # * `DO_TOKEN` - DigitalOcean private token # * `DO_REGISTRY_NAME` - DigitalOcean name of the registry # * `GO_MOD_USER` - Machine username to read private repos # * `GO_MOD_PASS` - Machine password to read private repos # name: Release on: push: branches: - release #paths-ignore: # - "docs/**" jobs: tag-build-publish: name: Tag runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 with: fetch-depth: "0" # make sure we get all commits! - name: Bump version and push tag id: bump uses: anothrNick/github-tag-action@1.36.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_BRANCHES: release WITH_V: true jsonschema-0.1.7/.github/workflows/test.yaml000066400000000000000000000007311447504341700211230ustar00rootroot00000000000000name: Test Go on: [push, pull_request] jobs: lint-test-build: name: Lint, Test runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v1 with: go-version: '1.16.2' id: go - name: Check out code uses: actions/checkout@v2 - name: Install Dependencies env: GOPROXY: https://proxy.golang.org,direct run: go mod download - name: Test run: go test -tags unit -race ./... jsonschema-0.1.7/.golangci.yml000066400000000000000000000042211447504341700162450ustar00rootroot00000000000000run: tests: true max-same-issues: 50 skip-dirs: - resources - old skip-files: - cmd/protopkg/main.go output: print-issued-lines: false linters: enable-all: true disable: - maligned - megacheck - lll - typecheck # `go build` catches this, and it doesn't currently work with Go 1.11 modules - goimports # horrendously slow with go modules :( - dupl # has never been actually useful - gochecknoglobals - gochecknoinits - interfacer # author deprecated it because it provides bad suggestions - funlen - whitespace - godox - wsl - dogsled - gomnd - gocognit - gocyclo - scopelint - godot - nestif - testpackage - goerr113 - gci - gofumpt - exhaustivestruct - nlreturn - forbidigo - cyclop - paralleltest - ifshort # so annoying - golint - tagliatelle - forcetypeassert - wrapcheck - revive - structcheck - stylecheck - exhaustive - varnamelen linters-settings: govet: check-shadowing: true use-installed-packages: true dupl: threshold: 100 goconst: min-len: 8 min-occurrences: 3 gocyclo: min-complexity: 20 gocritic: disabled-checks: - ifElseChain issues: max-per-linter: 0 max-same: 0 exclude-use-default: false exclude: # Captured by errcheck. - '^(G104|G204):' # Very commonly not checked. - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*Print(f|ln|)|os\.(Un)?Setenv). is not checked' # Weird error only seen on Kochiku... - 'internal error: no range for' - 'exported method `.*\.(MarshalJSON|UnmarshalJSON|URN|Payload|GoString|Close|Provides|Requires|ExcludeFromHash|MarshalText|UnmarshalText|Description|Check|Poll|Severity)` should have comment or be unexported' - 'composite literal uses unkeyed fields' - 'declaration of "err" shadows declaration' - 'by other packages, and that stutters' - 'Potential file inclusion via variable' - 'at least one file in a package should have a package comment' - 'bad syntax for struct tag pair' jsonschema-0.1.7/COPYING000066400000000000000000000020371447504341700147170ustar00rootroot00000000000000Copyright (C) 2014 Alec Thomas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jsonschema-0.1.7/README.md000066400000000000000000000302201447504341700151360ustar00rootroot00000000000000# Go JSON Schema Reflection [![Lint](https://github.com/authelia/jsonschema/actions/workflows/lint.yaml/badge.svg)](https://github.com/authelia/jsonschema/actions/workflows/lint.yaml) [![Test Go](https://github.com/authelia/jsonschema/actions/workflows/test.yaml/badge.svg)](https://github.com/authelia/jsonschema/actions/workflows/test.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/authelia/jsonschema)](https://goreportcard.com/report/github.com/authelia/jsonschema) [![GoDoc](https://godoc.org/github.com/authelia/jsonschema?status.svg)](https://godoc.org/github.com/authelia/jsonschema) ![Latest Tag](https://img.shields.io/github/v/tag/invopop/jsonschema) This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection. - Supports arbitrarily complex types, including `any`, maps, slices, etc. - Supports json-schema features such as minLength, maxLength, pattern, format, etc. - Supports simple string and numeric enums. - Supports custom property fields via the `jsonschema_extras` struct tag. This repository is a fork of the original [jsonschema](https://github.com/alecthomas/jsonschema) by [@alecthomas](https://github.com/alecthomas). At [Invopop](https://invopop.com) we use jsonschema as a cornerstone in our [GOBL library](https://github.com/invopop/gobl), and wanted to be able to continue building and adding features without taking up Alec's time. There have been a few significant changes that probably mean this version is a not compatible with with Alec's: - The original was stuck on the draft-04 version of JSON Schema, we've now moved to the latest JSON Schema Draft 2020-12. - Schema IDs are added automatically from the current Go package's URL in order to be unique, and can be disabled with the `Anonymous` option. - Support for the `FullyQualifyTypeName` option has been removed. If you have conflicts, you should use multiple schema files with different IDs, set the `DoNotReference` option to true to hide definitions completely, or add your own naming strategy using the `Namer` property. - Support for `yaml` tags and related options has been dropped for the sake of simplification. There were a [few inconsistencies](https://github.com/authelia/jsonschema/pull/21) around this that have now been fixed. ## Versions This project is still under v0 scheme, as per Go convention, breaking changes are likely. Please pin go modules to branches, and reach out if you think something can be improved. ## Example The following Go type: ```go type TestUser struct { ID int `json:"id"` Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` Tags map[string]any `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` Metadata any `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` } ``` Results in following JSON Schema: ```go jsonschema.Reflect(&TestUser{}) ``` ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/SampleUser", "$defs": { "SampleUser": { "oneOf": [ { "required": ["birth_date"], "title": "date" }, { "required": ["year_of_birth"], "title": "year" } ], "properties": { "id": { "type": "integer" }, "name": { "type": "string", "title": "the name", "description": "The name of a friend", "default": "alex", "examples": ["joe", "lucy"] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "The list of IDs, omitted when empty" }, "tags": { "type": "object", "a": "b", "foo": ["bar", "bar1"] }, "birth_date": { "type": "string", "format": "date-time" }, "year_of_birth": { "type": "string" }, "metadata": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "fav_color": { "type": "string", "enum": ["red", "green", "blue"] } }, "additionalProperties": false, "type": "object", "required": ["id", "name"] } } } ``` ## YAML Support for `yaml` tags has now been removed. If you feel very strongly about this, we've opened a discussion to hear your comments: https://github.com/authelia/jsonschema/discussions/28 The recommended approach if you need to deal with YAML data is to first convert to JSON. The [invopop/yaml](https://github.com/invopop/yaml) library will make this trivial. ## Configurable behaviour The behaviour of the schema generator can be altered with parameters when a `jsonschema.Reflector` instance is created. ### ExpandedStruct If set to `true`, makes the top level struct not to reference itself in the definitions. But type passed should be a struct type. eg. ```go type GrandfatherType struct { FamilyName string `json:"family_name" jsonschema:"required"` } type SomeBaseType struct { SomeBaseProperty int `json:"some_base_property"` // The jsonschema required tag is nonsensical for private and ignored properties. // Their presence here tests that the fields *will not* be required in the output // schema, even if they are tagged required. somePrivateBaseProperty string `json:"i_am_private" jsonschema:"required"` SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` SomeSchemaIgnoredProperty string `jsonschema:"-,required"` SomeUntaggedBaseProperty bool `jsonschema:"required"` someUnexportedUntaggedBaseProperty bool Grandfather GrandfatherType `json:"grand"` } ``` will output: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "required": ["some_base_property", "grand", "SomeUntaggedBaseProperty"], "properties": { "SomeUntaggedBaseProperty": { "type": "boolean" }, "grand": { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/definitions/GrandfatherType" }, "some_base_property": { "type": "integer" } }, "type": "object", "$defs": { "GrandfatherType": { "required": ["family_name"], "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object" } } } ``` ### Using Go Comments Writing a good schema with descriptions inside tags can become cumbersome and tedious, especially if you already have some Go comments around your types and field definitions. If you'd like to take advantage of these existing comments, you can use the `AddGoComments(base, path string)` method that forms part of the reflector to parse your go files and automatically generate a dictionary of Go import paths, types, and fields, to individual comments. These will then be used automatically as description fields, and can be overridden with a manual definition if needed. Take a simplified example of a User struct which for the sake of simplicity we assume is defined inside this package: ```go package main // User is used as a base to provide tests for comments. type User struct { // Unique sequential identifier. ID int `json:"id" jsonschema:"required"` // Name of the user Name string `json:"name"` } ``` To get the comments provided into your JSON schema, use a regular `Reflector` and add the go code using an import module URL and path. Fully qualified go module paths cannot be determined reliably by the `go/parser` library, so we need to introduce this manually: ```go r := new(Reflector) if err := r.AddGoComments("github.com/authelia/jsonschema", "./"); err != nil { // deal with error } s := r.Reflect(&User{}) // output ``` Expect the results to be similar to: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/User", "$defs": { "User": { "required": ["id"], "properties": { "id": { "type": "integer", "description": "Unique sequential identifier." }, "name": { "type": "string", "description": "Name of the user" } }, "additionalProperties": false, "type": "object", "description": "User is used as a base to provide tests for comments." } } } ``` ### Custom Key Naming In some situations, the keys actually used to write files are different from Go structs'. This is often the case when writing a configuration file to YAML or JSON from a Go struct, or when returning a JSON response for a Web API: APIs typically use snake_case, while Go uses PascalCase. You can pass a `func(string) string` function to `Reflector`'s `KeyNamer` option to map Go field names to JSON key names and reflect the aforementioned transformations, without having to specify `json:"..."` on every struct field. For example, consider the following struct ```go type User struct { GivenName string PasswordSalted []byte `json:"salted_password"` } ``` We can transform field names to snake_case in the generated JSON schema: ```go r := new(jsonschema.Reflector) r.KeyNamer = strcase.SnakeCase // from package github.com/stoewer/go-strcase r.Reflect(&User{}) ``` Will yield ```diff { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/User", "$defs": { "User": { "properties": { - "GivenName": { + "given_name": { "type": "string" }, "salted_password": { "type": "string", "contentEncoding": "base64" } }, "additionalProperties": false, "type": "object", - "required": ["GivenName", "salted_password"] + "required": ["given_name", "salted_password"] } } } ``` As you can see, if a field name has a `json:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag. ### Custom Type Definitions Sometimes it can be useful to have custom JSON Marshal and Unmarshal methods in your structs that automatically convert for example a string into an object. To override auto-generating an object type for your type, implement the `JSONSchema() *Schema` method and whatever is defined will be provided in the schema definitions. Take the following simplified example of a `CompactDate` that only includes the Year and Month: ```go type CompactDate struct { Year int Month int } func (d *CompactDate) UnmarshalJSON(data []byte) error { if len(data) != 9 { return errors.New("invalid compact date length") } var err error d.Year, err = strconv.Atoi(string(data[1:5])) if err != nil { return err } d.Month, err = strconv.Atoi(string(data[7:8])) if err != nil { return err } return nil } func (d *CompactDate) MarshalJSON() ([]byte, error) { buf := new(bytes.Buffer) buf.WriteByte('"') buf.WriteString(fmt.Sprintf("%d-%02d", d.Year, d.Month)) buf.WriteByte('"') return buf.Bytes(), nil } func (CompactDate) JSONSchema() *Schema { return &Schema{ Type: "string", Title: "Compact Date", Description: "Short date that only includes year and month", Pattern: "^[0-9]{4}-[0-1][0-9]$", } } ``` The resulting schema generated for this struct would look like: ```json { "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/CompactDate", "$defs": { "CompactDate": { "pattern": "^[0-9]{4}-[0-1][0-9]$", "type": "string", "title": "Compact Date", "description": "Short date that only includes year and month" } } } ``` jsonschema-0.1.7/comment_extractor.go000066400000000000000000000045361447504341700177560ustar00rootroot00000000000000package jsonschema import ( "fmt" "go/ast" "go/doc" "go/parser" "go/token" "io/fs" "path/filepath" "strings" ) // ExtractGoComments will read all the go files contained in the provided path, // including sub-directories, in order to generate a dictionary of comments // associated with Types and Fields. The results will be added to the `commentsMap` // provided in the parameters and expected to be used for Schema "description" fields. // // The `go/parser` library is used to extract all the comments and unfortunately doesn't // have a built-in way to determine the fully qualified name of a package. The `base` paremeter, // the URL used to import that package, is thus required to be able to match reflected types. // // When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase // only. Field comments, which tend to be much shorter, will include everything. func ExtractGoComments(base, path string, commentMap map[string]string) error { fset := token.NewFileSet() dict := make(map[string][]*ast.Package) err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { d, err := parser.ParseDir(fset, path, nil, parser.ParseComments) if err != nil { return err } for _, v := range d { // paths may have multiple packages, like for tests k := pathJoinModule(base, path) dict[k] = append(dict[k], v) } } return nil }) if err != nil { return err } for pkg, p := range dict { for _, f := range p { gtxt := "" typ := "" ast.Inspect(f, func(n ast.Node) bool { switch x := n.(type) { case *ast.TypeSpec: typ = x.Name.String() if !ast.IsExported(typ) { typ = "" } else { txt := x.Doc.Text() if txt == "" && gtxt != "" { txt = gtxt gtxt = "" } txt = doc.Synopsis(txt) commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt) } case *ast.Field: txt := x.Doc.Text() if typ != "" && txt != "" { for _, n := range x.Names { if ast.IsExported(n.String()) { k := fmt.Sprintf("%s.%s.%s", pkg, typ, n) commentMap[k] = strings.TrimSpace(txt) } } } case *ast.GenDecl: // remember for the next type gtxt = x.Doc.Text() } return true }) } } return nil } jsonschema-0.1.7/comment_extractor_nonwin.go000066400000000000000000000002741447504341700213410ustar00rootroot00000000000000//go:build !windows package jsonschema import ( "path" "strings" ) func pathJoinModule(base, subpath string) string { return path.Join(base, strings.ReplaceAll(subpath, `\`, `/`)) } jsonschema-0.1.7/comment_extractor_win.go000066400000000000000000000010041447504341700206160ustar00rootroot00000000000000//go:build windows package jsonschema import ( "path" "strings" ) func pathJoinModule(elem ...string) string { size := 0 for i, e := range elem { size += len(e) if strings.ContainsRune(e, '\\') { elem[i] = strings.ReplaceAll(e, `\`, `/`) } } if size == 0 { return "" } buf := make([]byte, 0, size+len(elem)-1) for _, e := range elem { if len(buf) > 0 || e != "" { if len(buf) > 0 { buf = append(buf, '/') } buf = append(buf, e...) } } return path.Clean(string(buf)) } jsonschema-0.1.7/const.go000066400000000000000000000073231447504341700153440ustar00rootroot00000000000000package jsonschema const ( keywordSep = "," keywordArraySep = ";" keywordKeyValueSep = "=" kwDefault = "default" kwFormat = "format" kwPattern = "pattern" kwTitle = "title" kwDescription = "description" kwExample = "example" kwAnchor = "anchor" kwEnum = "enum" kwDeprecated = "deprecated" kwMinLength = "minLength" kwMaxLength = "maxLength" kwExclusiveMinimum = "exclusiveMinimum" kwExclusiveMaximum = "exclusiveMaximum" kwMinimum = "minimum" kwMaximum = "maximum" kwMinItems = "minItems" kwMaxItems = "maxItems" kwUniqueItems = "uniqueItems" kwMultipleOf = "multipleOf" kwType = "type" kwAnyOfType = "anyof_type" kwOneOfType = "oneof_type" kwAnyOfRequired = "anyof_required" kwOneOfRequired = "oneof_required" kwOmitEmpty = "omitempty" kwNullable = "nullable" kwReadOnly = "readOnly" kwWriteOnly = "writeOnly" valueT = "t" valueTrue = "true" valueFalse = "false" tagJSON = "json" tagJSONSchema = "jsonschema" tagJSONSchemaDescription = "jsonschema_description" tagJSONSchemaExtras = "jsonschema_extras" tagRequired = "required" ) const ( // TypeString represents the string JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/string.html#string for more information. TypeString = "string" // TypeNumber represents the numeric JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/numeric.html#number for more information. TypeNumber = "number" // TypeInteger represents the integer JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/numeric.html#integer for more information. TypeInteger = "integer" // TypeObject represents the object JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/object.html#object for more information. TypeObject = "object" // TypeArray represents the object JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/array.html#array for more information. TypeArray = "array" // TypeBoolean represents the boolean JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/boolean.html#boolean for more information. TypeBoolean = "boolean" // TypeNull represents the null JSON Schema type. // See https://json-schema.org/understanding-json-schema/reference/null.html#null for more information. TypeNull = "null" ) // A built-in string format. // See https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats for more information. const ( FormatStringDateTime = "date-time" FormatStringTime = "time" FormatStringDate = "date" FormatStringDuration = "duration" FormatStringEmail = "email" FormatStringInternationalizedEmail = "idn-email" FormatStringHostname = "hostname" FormatStringInternationalizedHostname = "idn-hostname" FormatStringIPv4 = "ipv4" FormatStringIPv6 = "ipv6" FormatStringUUID = "uuid" FormatStringURI = "uri" FormatStringURIReference = "uri-reference" FormatStringURITemplate = "uri-template" FormatStringIRI = "iri" FormatStringIRIReference = "iri-reference" FormatStringRegex = "regex" FormatStringJSONPointer = "json-pointer" FormatStringRelativeJSONPointer = "relative-json-pointer" ) jsonschema-0.1.7/examples/000077500000000000000000000000001447504341700155005ustar00rootroot00000000000000jsonschema-0.1.7/examples/nested/000077500000000000000000000000001447504341700167625ustar00rootroot00000000000000jsonschema-0.1.7/examples/nested/nested.go000066400000000000000000000010061447504341700205700ustar00rootroot00000000000000package nested // Pet defines the user's fury friend. type Pet struct { // Name of the animal. Name string `json:"name" jsonschema:"title=Name"` } // Pets is a collection of Pet objects. type Pets []*Pet // NamedPets is a map of animal names to pets. type NamedPets map[string]*Pet type ( // Plant represents the plants the user might have and serves as a test // of structs inside a `type` set. Plant struct { Variant string `json:"variant" jsonschema:"title=Variant"` // This comment will be ignored } ) jsonschema-0.1.7/examples/user.go000066400000000000000000000016141447504341700170070ustar00rootroot00000000000000package examples import ( "github.com/authelia/jsonschema/examples/nested" ) // User is used as a base to provide tests for comments. // Don't forget to checkout the nested path. type User struct { // Unique sequential identifier. ID int `json:"id" jsonschema:"required"` // This comment will be ignored Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex"` Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` Tags map[string]any `json:"tags,omitempty"` // An array of pets the user cares for. Pets nested.Pets `json:"pets"` // Set of animal names to pets NamedPets nested.NamedPets `json:"named_pets"` // Set of plants that the user likes Plants []*nested.Plant `json:"plants" jsonschema:"title=Plants"` } jsonschema-0.1.7/examples_test.go000066400000000000000000000061221447504341700170670ustar00rootroot00000000000000package jsonschema_test import ( "encoding/json" "fmt" "time" "github.com/authelia/jsonschema" ) type SampleUser struct { ID int `json:"id"` Name string `json:"name" jsonschema:"title=the name,description=The name of a friend,example=joe,example=lucy,default=alex"` Friends []int `json:"friends,omitempty" jsonschema_description:"The list of IDs, omitted when empty"` Tags map[string]any `json:"tags,omitempty" jsonschema_extras:"a=b,foo=bar,foo=bar1"` BirthDate time.Time `json:"birth_date,omitempty" jsonschema:"oneof_required=date"` YearOfBirth string `json:"year_of_birth,omitempty" jsonschema:"oneof_required=year"` Metadata any `json:"metadata,omitempty" jsonschema:"oneof_type=string;array"` FavColor string `json:"fav_color,omitempty" jsonschema:"enum=red,enum=green,enum=blue"` } func ExampleReflect() { s := jsonschema.Reflect(&SampleUser{}) data, err := json.MarshalIndent(s, "", " ") if err != nil { panic(err.Error()) } fmt.Println(string(data)) // Output: // { // "$schema": "https://json-schema.org/draft/2020-12/schema", // "$id": "https://github.com/authelia/jsonschema_test/sample-user", // "$ref": "#/$defs/SampleUser", // "$defs": { // "SampleUser": { // "oneOf": [ // { // "required": [ // "birth_date" // ], // "title": "date" // }, // { // "required": [ // "year_of_birth" // ], // "title": "year" // } // ], // "properties": { // "id": { // "type": "integer" // }, // "name": { // "type": "string", // "title": "the name", // "description": "The name of a friend", // "default": "alex", // "examples": [ // "joe", // "lucy" // ] // }, // "friends": { // "items": { // "type": "integer" // }, // "type": "array", // "description": "The list of IDs, omitted when empty" // }, // "tags": { // "type": "object", // "a": "b", // "foo": [ // "bar", // "bar1" // ] // }, // "birth_date": { // "type": "string", // "format": "date-time" // }, // "year_of_birth": { // "type": "string" // }, // "metadata": { // "oneOf": [ // { // "type": "string" // }, // { // "type": "array" // } // ] // }, // "fav_color": { // "type": "string", // "enum": [ // "red", // "green", // "blue" // ] // } // }, // "additionalProperties": false, // "type": "object", // "required": [ // "id", // "name" // ] // } // } // } } jsonschema-0.1.7/fixtures/000077500000000000000000000000001447504341700155335ustar00rootroot00000000000000jsonschema-0.1.7/fixtures/.gitignore000066400000000000000000000000121447504341700175140ustar00rootroot00000000000000*.out.jsonjsonschema-0.1.7/fixtures/allow_additional_props.json000066400000000000000000000112731447504341700231630ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.1.7/fixtures/anyof.json000066400000000000000000000033041447504341700175420ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/root-any-of", "$ref": "#/$defs/RootAnyOf", "$defs": { "ChildAnyOf": { "anyOf": [ { "required": [ "child1", "child4" ], "title": "group1" }, { "required": [ "child2", "child3" ], "title": "group2" } ], "properties": { "child1": { "type": "string" }, "child2": { "type": "string" }, "child3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "child4": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "RootAnyOf": { "anyOf": [ { "required": [ "field1", "field4" ], "title": "group1" }, { "required": [ "field2" ], "title": "group2" } ], "properties": { "field1": { "type": "string" }, "field2": { "type": "string" }, "field3": { "anyOf": [ { "type": "string" }, { "type": "array" } ] }, "field4": { "type": "string" }, "child": { "$ref": "#/$defs/ChildAnyOf" } }, "additionalProperties": false, "type": "object" } } }jsonschema-0.1.7/fixtures/array_type.json000066400000000000000000000004201447504341700206010ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/array-type", "$ref": "#/$defs/ArrayType", "$defs": { "ArrayType": { "items": { "type": "string" }, "type": "array" } } }jsonschema-0.1.7/fixtures/base_schema_id.json000066400000000000000000000013531447504341700213360ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$ref": "#/$defs/LookupUser", "$defs": { "LookupName": { "properties": { "first": { "type": "string" }, "surname": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "first", "surname" ] }, "LookupUser": { "properties": { "name": { "$ref": "#/$defs/LookupName" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.1.7/fixtures/commas_in_pattern.json000066400000000000000000000007631447504341700221360ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/pattern-test", "$ref": "#/$defs/PatternTest", "$defs": { "PatternTest": { "properties": { "with_pattern": { "type": "string", "maxLength": 50, "minLength": 1, "pattern": "[0-9]{1,4}" } }, "additionalProperties": false, "type": "object", "required": [ "with_pattern" ] } } }jsonschema-0.1.7/fixtures/compact_date.json000066400000000000000000000005621447504341700210540ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/compact-date", "$ref": "#/$defs/CompactDate", "$defs": { "CompactDate": { "type": "string", "pattern": "^[0-9]{4}-[0-1][0-9]$", "title": "Compact Date", "description": "Short date that only includes year and month" } } }jsonschema-0.1.7/fixtures/custom_additional.json000066400000000000000000000010171447504341700221270ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/grandfather-type", "$ref": "#/$defs/GrandfatherType", "$defs": { "GrandfatherType": { "properties": { "family_name": { "type": "string" }, "ip_addr": { "type": "string", "format": "ipv4" } }, "additionalProperties": false, "type": "object", "required": [ "family_name", "ip_addr" ] } } }jsonschema-0.1.7/fixtures/custom_base_schema_id.json000066400000000000000000000104351447504341700227310ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$id": "http://example.com/schema/TestUser", "$ref": "#/$defs/TestUser", "$defs": { "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "TestUser": { "properties": { "some_base_property": { "type": "integer" }, "some_base_property_yaml": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "id": { "type": "integer" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "some_base_property", "some_base_property_yaml", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "id", "name", "password", "TestFlag", "age", "email", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.1.7/fixtures/custom_map_type.json000066400000000000000000000013611447504341700216370ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/custom-map-outer", "$ref": "#/$defs/CustomMapOuter", "$defs": { "CustomMapOuter": { "properties": { "my_map": { "$ref": "#/$defs/CustomMapType" } }, "additionalProperties": false, "type": "object", "required": [ "my_map" ] }, "CustomMapType": { "items": { "properties": { "key": { "type": "string" }, "value": { "type": "string" } }, "type": "object", "required": [ "key", "value" ] }, "type": "array" } } }jsonschema-0.1.7/fixtures/custom_slice_type.json000066400000000000000000000012041447504341700221550ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/custom-slice-outer", "$ref": "#/$defs/CustomSliceOuter", "$defs": { "CustomSliceOuter": { "properties": { "slice": { "$ref": "#/$defs/CustomSliceType" } }, "additionalProperties": false, "type": "object", "required": [ "slice" ] }, "CustomSliceType": { "oneOf": [ { "type": "string" }, { "items": { "type": "string" }, "type": "array" } ] } } }jsonschema-0.1.7/fixtures/custom_type.json000066400000000000000000000007031447504341700210010ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/custom-type-field", "$ref": "#/$defs/CustomTypeField", "$defs": { "CustomTypeField": { "properties": { "CreatedAt": { "type": "string", "format": "date-time" } }, "additionalProperties": false, "type": "object", "required": [ "CreatedAt" ] } } }jsonschema-0.1.7/fixtures/custom_type_extend.json000066400000000000000000000010171447504341700223470ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/schema-extend-test", "$ref": "#/$defs/SchemaExtendTest", "$defs": { "SchemaExtendTest": { "properties": { "LastName": { "type": "string", "description": "some extra words" }, "middle_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "LastName" ] } } }jsonschema-0.1.7/fixtures/custom_type_with_interface.json000066400000000000000000000011001447504341700240440ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/custom-type-field-with-interface", "$ref": "#/$defs/CustomTypeFieldWithInterface", "$defs": { "CustomTimeWithInterface": { "type": "string", "format": "date-time" }, "CustomTypeFieldWithInterface": { "properties": { "CreatedAt": { "$ref": "#/$defs/CustomTimeWithInterface" } }, "additionalProperties": false, "type": "object", "required": [ "CreatedAt" ] } } }jsonschema-0.1.7/fixtures/defaults_expanded_toplevel.json000066400000000000000000000076431447504341700240310ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" } }, "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.1.7/fixtures/go_comments.json000066400000000000000000000052241447504341700207430ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/examples/user", "$ref": "#/$defs/User", "$defs": { "NamedPets": { "patternProperties": { ".*": { "$ref": "#/$defs/Pet" } }, "type": "object", "description": "NamedPets is a map of animal names to pets." }, "Pet": { "properties": { "name": { "type": "string", "title": "Name", "description": "Name of the animal." } }, "additionalProperties": false, "type": "object", "required": [ "name" ], "description": "Pet defines the user's fury friend." }, "Pets": { "items": { "$ref": "#/$defs/Pet" }, "type": "array", "description": "Pets is a collection of Pet objects." }, "Plant": { "properties": { "variant": { "type": "string", "title": "Variant" } }, "additionalProperties": false, "type": "object", "required": [ "variant" ], "description": "Plant represents the plants the user might have and serves as a test of structs inside a `type` set." }, "User": { "properties": { "id": { "type": "integer", "description": "Unique sequential identifier." }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "examples": [ "joe", "lucy" ] }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "type": "object" }, "pets": { "$ref": "#/$defs/Pets", "description": "An array of pets the user cares for." }, "named_pets": { "$ref": "#/$defs/NamedPets", "description": "Set of animal names to pets" }, "plants": { "items": { "$ref": "#/$defs/Plant" }, "type": "array", "title": "Plants", "description": "Set of plants that the user likes" } }, "additionalProperties": false, "type": "object", "required": [ "id", "name", "pets", "named_pets", "plants" ], "description": "User is used as a base to provide tests for comments." } } }jsonschema-0.1.7/fixtures/ignore_type.json000066400000000000000000000112151447504341700207520ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": {}, "additionalProperties": false, "type": "object" }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.1.7/fixtures/inlining_embedded.json000066400000000000000000000010551447504341700220470ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/outer-named", "$defs": { "Inner": { "properties": { "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] } }, "properties": { "text": { "type": "string" }, "inner": { "$ref": "#/$defs/Inner" } }, "additionalProperties": false, "type": "object", "required": [ "inner" ] }jsonschema-0.1.7/fixtures/inlining_embedded_anchored.json000066400000000000000000000011421447504341700237070ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/outer-named", "$anchor": "OuterNamed", "$defs": { "Inner": { "$anchor": "Inner", "properties": { "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] } }, "properties": { "text": { "type": "string" }, "inner": { "$ref": "#/$defs/Inner" } }, "additionalProperties": false, "type": "object", "required": [ "inner" ] }jsonschema-0.1.7/fixtures/inlining_inheritance.json000066400000000000000000000005731447504341700226130ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/outer", "properties": { "TextNamed": { "type": "string" }, "Text": { "type": "string" }, "Foo": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "TextNamed", "Foo" ] }jsonschema-0.1.7/fixtures/inlining_ptr.json000066400000000000000000000004751447504341700211300ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/outer-ptr", "properties": { "Foo": { "type": "string" }, "Text": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "Foo" ] }jsonschema-0.1.7/fixtures/keynamed.json000066400000000000000000000025051447504341700202250ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/key-named", "$ref": "#/$defs/KeyNamed", "$defs": { "KeyNamed": { "properties": { "ThisWasLeftAsIs": { "type": "string" }, "coming_from_json_tag": { "type": "boolean" }, "nested_not_renamed": { "$ref": "#/$defs/KeyNamedNested" }, "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓": { "type": "string" }, "20.01": { "type": "integer", "description": "Description was preserved" } }, "additionalProperties": false, "type": "object", "required": [ "ThisWasLeftAsIs", "coming_from_json_tag", "nested_not_renamed", "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓", "20.01" ] }, "KeyNamedNested": { "properties": { "nested-renamed-property": { "type": "string" }, "NotRenamed": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "nested-renamed-property", "NotRenamed" ] } } }jsonschema-0.1.7/fixtures/lookup.json000066400000000000000000000007271447504341700177450ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$ref": "#/$defs/LookupUser", "$defs": { "LookupUser": { "properties": { "name": { "$ref": "https://example.com/schemas/lookup-name" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.1.7/fixtures/lookup_expanded.json000066400000000000000000000005631447504341700216130ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/schemas/lookup-user", "$anchor": "LookupUser", "properties": { "name": { "$ref": "https://example.com/schemas/lookup-name" }, "alias": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] }jsonschema-0.1.7/fixtures/map_type.json000066400000000000000000000003301447504341700202400ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/map-type", "$ref": "#/$defs/MapType", "$defs": { "MapType": { "type": "object" } } }jsonschema-0.1.7/fixtures/no_reference.json000066400000000000000000000073361447504341700210710ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "type": "object" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.1.7/fixtures/no_reference_anchor.json000066400000000000000000000074331447504341700224210ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$anchor": "TestUser", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$anchor": "GrandfatherType", "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "type": "object" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "type": "string", "contentEncoding": "base64" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] }jsonschema-0.1.7/fixtures/nullable.json000066400000000000000000000010151447504341700202210ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-nullable", "$ref": "#/$defs/TestNullable", "$defs": { "TestNullable": { "properties": { "child1": { "oneOf": [ { "type": "string" }, { "type": "null" } ] } }, "additionalProperties": false, "type": "object", "required": [ "child1" ] } } }jsonschema-0.1.7/fixtures/oneof.json000066400000000000000000000033041447504341700175340ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/root-one-of", "$ref": "#/$defs/RootOneOf", "$defs": { "ChildOneOf": { "oneOf": [ { "required": [ "child1", "child4" ], "title": "group1" }, { "required": [ "child2", "child3" ], "title": "group2" } ], "properties": { "child1": { "type": "string" }, "child2": { "type": "string" }, "child3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "child4": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "RootOneOf": { "oneOf": [ { "required": [ "field1", "field4" ], "title": "group1" }, { "required": [ "field2" ], "title": "group2" } ], "properties": { "field1": { "type": "string" }, "field2": { "type": "string" }, "field3": { "oneOf": [ { "type": "string" }, { "type": "array" } ] }, "field4": { "type": "string" }, "child": { "$ref": "#/$defs/ChildOneOf" } }, "additionalProperties": false, "type": "object" } } }jsonschema-0.1.7/fixtures/recursive.json000066400000000000000000000010461447504341700204360ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/recursive-example", "$ref": "#/$defs/RecursiveExample", "$defs": { "RecursiveExample": { "properties": { "text": { "type": "string" }, "children": { "items": { "$ref": "#/$defs/RecursiveExample" }, "type": "array" } }, "additionalProperties": false, "type": "object", "required": [ "text" ] } } }jsonschema-0.1.7/fixtures/required_from_jsontags.json000066400000000000000000000110611447504341700232000ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "SomeUntaggedBaseProperty", "id", "name", "photo", "photo2" ] } } }jsonschema-0.1.7/fixtures/schema_with_expression.json000066400000000000000000000007171447504341700232050ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/expression", "$ref": "#/$defs/Expression", "$defs": { "Expression": { "properties": { "value": { "type": "integer", "foo": "bar=='baz'" } }, "additionalProperties": false, "type": "object", "required": [ "value" ] } } }jsonschema-0.1.7/fixtures/schema_with_minimum.json000066400000000000000000000006371447504341700224620ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/min-value", "$ref": "#/$defs/MinValue", "$defs": { "MinValue": { "properties": { "value4": { "type": "integer", "minimum": 0 } }, "additionalProperties": false, "type": "object", "required": [ "value4" ] } } }jsonschema-0.1.7/fixtures/test_description_override.json000066400000000000000000000014301447504341700237050ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-description-override", "$ref": "#/$defs/TestDescriptionOverride", "$defs": { "TestDescriptionOverride": { "properties": { "FirstName": { "type": "string", "description": "test2" }, "LastName": { "type": "string", "description": "test3" }, "age": { "type": "integer", "description": "test4" }, "middle_name": { "type": "string", "description": "test5" } }, "additionalProperties": false, "type": "object", "required": [ "FirstName", "LastName", "age" ] } } }jsonschema-0.1.7/fixtures/test_user.json000066400000000000000000000114051447504341700204440ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.1.7/fixtures/test_user_assign_anchor.json000066400000000000000000000115061447504341700233440ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-user", "$ref": "#/$defs/TestUser", "$defs": { "Bytes": { "type": "string", "contentEncoding": "base64" }, "GrandfatherType": { "$anchor": "GrandfatherType", "properties": { "family_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "family_name" ] }, "MapType": { "type": "object" }, "TestUser": { "$anchor": "TestUser", "properties": { "id": { "type": "integer" }, "some_base_property": { "type": "integer" }, "grand": { "$ref": "#/$defs/GrandfatherType" }, "SomeUntaggedBaseProperty": { "type": "boolean" }, "PublicNonExported": { "type": "integer" }, "MapType": { "$ref": "#/$defs/MapType" }, "name": { "type": "string", "maxLength": 20, "minLength": 1, "pattern": ".*", "title": "the name", "description": "this is a property", "default": "alex", "readOnly": true, "examples": [ "joe", "lucy" ] }, "password": { "type": "string", "writeOnly": true }, "friends": { "items": { "type": "integer" }, "type": "array", "description": "list of IDs, omitted when empty" }, "tags": { "patternProperties": { ".*": { "type": "string" } }, "type": "object" }, "options": { "type": "object" }, "TestFlag": { "type": "boolean" }, "TestFlagFalse": { "type": "boolean", "default": false }, "TestFlagTrue": { "type": "boolean", "default": true }, "birth_date": { "type": "string", "format": "date-time" }, "website": { "type": "string", "format": "uri" }, "network_address": { "type": "string", "format": "ipv4" }, "photo": { "type": "string", "contentEncoding": "base64" }, "photo2": { "$ref": "#/$defs/Bytes" }, "feeling": { "oneOf": [ { "type": "string" }, { "type": "integer" } ] }, "age": { "type": "integer", "maximum": 120, "exclusiveMaximum": true, "minimum": 18, "exclusiveMinimum": true }, "email": { "type": "string", "format": "email" }, "uuid": { "type": "string", "format": "uuid" }, "Baz": { "type": "string", "foo": [ "bar", "bar1" ], "hello": "world" }, "bool_extra": { "type": "string", "isFalse": false, "isTrue": true }, "color": { "type": "string", "enum": [ "red", "green", "blue" ] }, "rank": { "type": "integer", "enum": [ 1, 2, 3 ] }, "mult": { "type": "number", "enum": [ 1, 1.5, 2 ] }, "roles": { "items": { "type": "string", "enum": [ "admin", "moderator", "user" ] }, "type": "array" }, "priorities": { "items": { "type": "integer", "enum": [ -1, 0, 1 ] }, "type": "array" }, "offsets": { "items": { "type": "number", "enum": [ 1.570796, 3.141592, 6.283185 ] }, "type": "array" }, "anything": true, "raw": true }, "additionalProperties": false, "type": "object", "required": [ "id", "some_base_property", "grand", "SomeUntaggedBaseProperty", "PublicNonExported", "MapType", "name", "password", "TestFlag", "age", "email", "uuid", "Baz", "color", "roles", "raw" ] } } }jsonschema-0.1.7/fixtures/test_yaml_and_json_prefer_yaml.json000066400000000000000000000011721447504341700246700ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/test-yaml-and-json", "$ref": "#/$defs/TestYamlAndJson", "$defs": { "TestYamlAndJson": { "properties": { "first_name": { "type": "string" }, "LastName": { "type": "string" }, "age": { "type": "integer" }, "middle_name": { "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "first_name", "LastName", "age" ] } } }jsonschema-0.1.7/fixtures/user_with_anchor.json000066400000000000000000000006621447504341700217750ustar00rootroot00000000000000{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/authelia/jsonschema/user-with-anchor", "$ref": "#/$defs/UserWithAnchor", "$defs": { "UserWithAnchor": { "properties": { "name": { "$anchor": "Name", "type": "string" } }, "additionalProperties": false, "type": "object", "required": [ "name" ] } } }jsonschema-0.1.7/fixtures/yaml_inline.json000066400000000000000000000011541447504341700207270ustar00rootroot00000000000000{ "$schema": "http://json-schema.org/draft/2020-12/schema", "$ref": "#/$defs/TestYamlInline", "definitions": { "Inner": { "required": ["foo"], "properties": { "foo": { "type": "string" } }, "additionalProperties": false, "type": "object" }, "TestYamlInline": { "required": [ "Inlined" ], "properties": { "Inlined": { "$schema": "http://json-schema.org/draft-04/schema#", "$ref": "#/definitions/Inner" } }, "additionalProperties": false, "type": "object" } } } jsonschema-0.1.7/go.mod000066400000000000000000000004341447504341700147710ustar00rootroot00000000000000module github.com/authelia/jsonschema go 1.21 require ( github.com/iancoleman/orderedmap v0.3.0 github.com/stretchr/testify v1.8.4 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) jsonschema-0.1.7/go.sum000066400000000000000000000020501447504341700150120ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= jsonschema-0.1.7/id.go000066400000000000000000000033071447504341700146100ustar00rootroot00000000000000package jsonschema import ( "errors" "fmt" "net/url" "strings" ) // ID represents a Schema ID type which should always be a URI. // See draft-bhutton-json-schema-00 section 8.2.1 type ID string // EmptyID is used to explicitly define an ID with no value. const EmptyID ID = "" // Validate is used to check if the ID looks like a proper schema. // This is done by parsing the ID as a URL and checking it has all the // relevant parts. func (id ID) Validate() error { u, err := url.Parse(id.String()) if err != nil { return fmt.Errorf("invalid URL: %w", err) } if u.Hostname() == "" { return errors.New("missing hostname") } if !strings.Contains(u.Hostname(), ".") { return errors.New("hostname does not look valid") } if u.Path == "" { return errors.New("path is expected") } if u.Scheme != "https" && u.Scheme != "http" { return errors.New("unexpected schema") } return nil } // Anchor sets the anchor part of the schema URI. func (id ID) Anchor(name string) ID { b := id.Base() return ID(b.String() + "#" + name) } // Def adds or replaces a definition identifier. func (id ID) Def(name string) ID { b := id.Base() return ID(b.String() + "#/$defs/" + name) } // Add appends the provided path to the id, and removes any // anchor data that might be there. func (id ID) Add(path string) ID { b := id.Base() if !strings.HasPrefix(path, "/") { path = "/" + path } return ID(b.String() + path) } // Base removes any anchor information from the schema func (id ID) Base() ID { s := id.String() i := strings.LastIndex(s, "#") if i != -1 { s = s[0:i] } s = strings.TrimRight(s, "/") return ID(s) } // String provides string version of ID func (id ID) String() string { return string(id) } jsonschema-0.1.7/id_test.go000066400000000000000000000024301447504341700156430ustar00rootroot00000000000000package jsonschema_test import ( "testing" "github.com/authelia/jsonschema" "github.com/stretchr/testify/assert" ) func TestID(t *testing.T) { base := "https://invopop.com/schema" id := jsonschema.ID(base) assert.Equal(t, base, id.String()) id = id.Add("user") assert.EqualValues(t, base+"/user", id) id = id.Anchor("Name") assert.EqualValues(t, base+"/user#Name", id) id = id.Anchor("Title") assert.EqualValues(t, base+"/user#Title", id) id = id.Def("Name") assert.EqualValues(t, base+"/user#/$defs/Name", id) } func TestIDValidation(t *testing.T) { id := jsonschema.ID("https://invopop.com/schema/user") assert.NoError(t, id.Validate()) id = "https://encoding/json" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "hostname does not look valid") } id = "time" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "hostname") } id = "http://invopop.com" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "path") } id = "foor://invopop.com/schema/user" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "schema") } id = "invopop.com\n/test" if assert.Error(t, id.Validate()) { assert.Contains(t, id.Validate().Error(), "invalid URL") } } jsonschema-0.1.7/reflect.go000066400000000000000000001046001447504341700156360ustar00rootroot00000000000000// Package jsonschema uses reflection to generate JSON Schemas from Go types [1]. // // If json tags are present on struct fields, they will be used to infer // property names and if a property is required (omitempty is present). // // [1] http://json-schema.org/latest/json-schema-validation.html package jsonschema import ( "bytes" "encoding/json" "net" "net/url" "reflect" "strconv" "strings" "time" "github.com/iancoleman/orderedmap" ) // Version is the JSON Schema version. var Version = "https://json-schema.org/draft/2020-12/schema" // Schema represents a JSON Schema object type. // RFC draft-bhutton-json-schema-00 section 4.3 type Schema struct { // RFC draft-bhutton-json-schema-00 Version string `json:"$schema,omitempty"` // section 8.1.1 ID ID `json:"$id,omitempty"` // section 8.2.1 Anchor string `json:"$anchor,omitempty"` // section 8.2.2 Ref string `json:"$ref,omitempty"` // section 8.2.3.1 DynamicRef string `json:"$dynamicRef,omitempty"` // section 8.2.3.2 Definitions Definitions `json:"$defs,omitempty"` // section 8.2.4 Comments string `json:"$comment,omitempty"` // section 8.3 // RFC draft-bhutton-json-schema-00 section 10.2.1 (Sub-schemas with logic) AllOf []*Schema `json:"allOf,omitempty"` // section 10.2.1.1 AnyOf []*Schema `json:"anyOf,omitempty"` // section 10.2.1.2 OneOf []*Schema `json:"oneOf,omitempty"` // section 10.2.1.3 Not *Schema `json:"not,omitempty"` // section 10.2.1.4 // RFC draft-bhutton-json-schema-00 section 10.2.2 (Apply sub-schemas conditionally) If *Schema `json:"if,omitempty"` // section 10.2.2.1 Then *Schema `json:"then,omitempty"` // section 10.2.2.2 Else *Schema `json:"else,omitempty"` // section 10.2.2.3 DependentSchemas map[string]*Schema `json:"dependentSchemas,omitempty"` // section 10.2.2.4 // RFC draft-bhutton-json-schema-00 section 10.3.1 (arrays) PrefixItems []*Schema `json:"prefixItems,omitempty"` // section 10.3.1.1 Items *Schema `json:"items,omitempty"` // section 10.3.1.2 (replaces additionalItems) Contains *Schema `json:"contains,omitempty"` // section 10.3.1.3 // RFC draft-bhutton-json-schema-00 section 10.3.2 (sub-schemas) Properties *orderedmap.OrderedMap `json:"properties,omitempty"` // section 10.3.2.1 PatternProperties map[string]*Schema `json:"patternProperties,omitempty"` // section 10.3.2.2 AdditionalProperties *Schema `json:"additionalProperties,omitempty"` // section 10.3.2.3 PropertyNames *Schema `json:"propertyNames,omitempty"` // section 10.3.2.4 // RFC draft-bhutton-json-schema-validation-00, section 6 Type string `json:"type,omitempty"` // section 6.1.1 Enum []any `json:"enum,omitempty"` // section 6.1.2 Const any `json:"const,omitempty"` // section 6.1.3 MultipleOf int `json:"multipleOf,omitempty"` // section 6.2.1 Maximum int `json:"maximum,omitempty"` // section 6.2.2 ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 6.2.3 Minimum int `json:"minimum,omitempty"` // section 6.2.4 ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 6.2.5 MaxLength int `json:"maxLength,omitempty"` // section 6.3.1 MinLength int `json:"minLength,omitempty"` // section 6.3.2 Pattern string `json:"pattern,omitempty"` // section 6.3.3 MaxItems int `json:"maxItems,omitempty"` // section 6.4.1 MinItems int `json:"minItems,omitempty"` // section 6.4.2 UniqueItems bool `json:"uniqueItems,omitempty"` // section 6.4.3 MaxContains uint `json:"maxContains,omitempty"` // section 6.4.4 MinContains uint `json:"minContains,omitempty"` // section 6.4.5 MaxProperties int `json:"maxProperties,omitempty"` // section 6.5.1 MinProperties int `json:"minProperties,omitempty"` // section 6.5.2 Required []string `json:"required,omitempty"` // section 6.5.3 DependentRequired map[string][]string `json:"dependentRequired,omitempty"` // section 6.5.4 // RFC draft-bhutton-json-schema-validation-00, section 7 Format string `json:"format,omitempty"` // RFC draft-bhutton-json-schema-validation-00, section 8 ContentEncoding string `json:"contentEncoding,omitempty"` // section 8.3 ContentMediaType string `json:"contentMediaType,omitempty"` // section 8.4 ContentSchema *Schema `json:"contentSchema,omitempty"` // section 8.5 // RFC draft-bhutton-json-schema-validation-00, section 9 Title string `json:"title,omitempty"` // section 9.1 Description string `json:"description,omitempty"` // section 9.1 Default any `json:"default,omitempty"` // section 9.2 Deprecated bool `json:"deprecated,omitempty"` // section 9.3 ReadOnly bool `json:"readOnly,omitempty"` // section 9.4 WriteOnly bool `json:"writeOnly,omitempty"` // section 9.4 Examples []any `json:"examples,omitempty"` // section 9.5 Extras map[string]any `json:"-"` // Special boolean representation of the Schema - section 4.3.2 boolean *bool } var ( // TrueSchema defines a schema with a true value TrueSchema = &Schema{boolean: &[]bool{true}[0]} // FalseSchema defines a schema with a false value FalseSchema = &Schema{boolean: &[]bool{false}[0]} ) // customSchemaImpl is used to detect if the type provides it's own // custom Schema Type definition to use instead. Very useful for situations // where there are custom JSON Marshal and Unmarshal methods. type customSchemaImpl interface { JSONSchema() *Schema } // Function to be run after the schema has been generated. // this will let you modify a schema afterwards type extendSchemaImpl interface { JSONSchemaExtend(*Schema) } var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem() var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem() // customSchemaGetFieldDocString type customSchemaGetFieldDocString interface { GetFieldDocString(fieldName string) string } type customGetFieldDocString func(fieldName string) string var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem() // Reflect reflects to Schema from a value using the default Reflector func Reflect(v any) *Schema { return ReflectFromType(reflect.TypeOf(v)) } // ReflectFromType generates root schema using the default Reflector func ReflectFromType(t reflect.Type) *Schema { r := &Reflector{} return r.ReflectFromType(t) } // A Reflector reflects values into a Schema. type Reflector struct { // BaseSchemaID defines the URI that will be used as a base to determine Schema // IDs for models. For example, a base Schema ID of `https://invopop.com/schemas` // when defined with a struct called `User{}`, will result in a schema with an // ID set to `https://invopop.com/schemas/user`. // // If no `BaseSchemaID` is provided, we'll take the type's complete package path // and use that as a base instead. Set `Anonymous` to try if you do not want to // include a schema ID. BaseSchemaID ID // Anonymous when true will hide the auto-generated Schema ID and provide what is // known as an "anonymous schema". As a rule, this is not recommended. Anonymous bool // AssignAnchor when true will use the original struct's name as an anchor inside // every definition, including the root schema. These can be useful for having a // reference to the original struct's name in CamelCase instead of the snake-case used // by default for URI compatibility. // // Anchors do not appear to be widely used out in the wild, so at this time the // anchors themselves will not be used inside generated schema. AssignAnchor bool // AllowAdditionalProperties will cause the Reflector to generate a schema // without additionalProperties set to 'false' for all struct types. This means // the presence of additional keys in JSON objects will not cause validation // to fail. Note said additional keys will simply be dropped when the // validated JSON is unmarshaled. AllowAdditionalProperties bool // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema // that requires any key tagged with `jsonschema:required`, overriding the // default of requiring any key *not* tagged with `json:,omitempty`. RequiredFromJSONSchemaTags bool // Do not reference definitions. This will remove the top-level $defs map and // instead cause the entire structure of types to be output in one tree. The // list of type definitions (`$defs`) will not be included. DoNotReference bool // ExpandedStruct when true will include the reflected type's definition in the // root as opposed to a definition with a reference. ExpandedStruct bool // IgnoredTypes defines a slice of types that should be ignored in the schema, // switching to just allowing additional properties instead. IgnoredTypes []any // Lookup allows a function to be defined that will provide a custom mapping of // types to Schema IDs. This allows existing schema documents to be referenced // by their ID instead of being embedded into the current schema definitions. // Reflected types will never be pointers, only underlying elements. Lookup func(reflect.Type) ID // Mapper is a function that can be used to map custom Go types to jsonschema schemas. Mapper func(reflect.Type) *Schema // Namer allows customizing of type names. The default is to use the type's name // provided by the reflect package. Namer func(reflect.Type) string // Requirer custom the requiring of any struct field, its behaves after the // `json:,omitempty` or `jsonschema:required` parsed. Requirer func(reflect.StructField, bool) bool // KeyNamer allows customizing of key names. // The default is to use the key's name as is, or the json tag if present. // If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name. KeyNamer func(string) string // AdditionalFields allows adding structfields for a given type AdditionalFields func(reflect.Type) []reflect.StructField // CommentMap is a dictionary of fully qualified go types and fields to comment // strings that will be used if a description has not already been provided in // the tags. Types and fields are added to the package path using "." as a // separator. // // Type descriptions should be defined like: // // map[string]string{"github.com/authelia/jsonschema.Reflector": "A Reflector reflects values into a Schema."} // // And Fields defined as: // // map[string]string{"github.com/authelia/jsonschema.Reflector.DoNotReference": "Do not reference definitions."} // // See also: AddGoComments CommentMap map[string]string } // Reflect reflects to Schema from a value. func (r *Reflector) Reflect(v any) *Schema { return r.ReflectFromType(reflect.TypeOf(v)) } // ReflectFromType generates root schema func (r *Reflector) ReflectFromType(t reflect.Type) *Schema { if t.Kind() == reflect.Ptr { t = t.Elem() // re-assign from pointer } name := r.typeName(t) s := new(Schema) definitions := Definitions{} s.Definitions = definitions bs := r.reflectTypeToSchemaWithID(definitions, t) if r.ExpandedStruct { *s = *definitions[name] delete(definitions, name) } else { *s = *bs } // Attempt to set the schema ID if !r.Anonymous && s.ID == EmptyID { baseSchemaID := r.BaseSchemaID if baseSchemaID == EmptyID { id := ID("https://" + t.PkgPath()) if err := id.Validate(); err == nil { // it's okay to silently ignore URL errors baseSchemaID = id } } if baseSchemaID != EmptyID { s.ID = baseSchemaID.Add(ToSnakeCase(name)) } } s.Version = Version if !r.DoNotReference { s.Definitions = definitions } return s } // Definitions hold schema definitions. // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26 // RFC draft-wright-json-schema-validation-00, section 5.26 type Definitions map[string]*Schema // Available Go defined types for JSON Schema Validation. // RFC draft-wright-json-schema-validation-00, section 7.3 var ( timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1 ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5 uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6 ) // Byte slices will be encoded as base64 var byteSliceType = reflect.TypeOf([]byte(nil)) // Except for json.RawMessage var rawMessageType = reflect.TypeOf(json.RawMessage{}) // Go code generated from protobuf enum types should fulfil this interface. type protoEnum interface { EnumDescriptor() ([]byte, []int) } var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem() // SetBaseSchemaID is a helper use to be able to set the reflectors base // schema ID from a string as opposed to then ID instance. func (r *Reflector) SetBaseSchemaID(id string) { r.BaseSchemaID = ID(id) } func (r *Reflector) refOrReflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { id := r.lookupID(t) if id != EmptyID { return &Schema{ Ref: id.String(), } } // Already added to definitions? if def := r.refDefinition(definitions, t); def != nil { return def } return r.reflectTypeToSchemaWithID(definitions, t) } func (r *Reflector) reflectTypeToSchemaWithID(defs Definitions, t reflect.Type) *Schema { s := r.reflectTypeToSchema(defs, t) if s != nil { if r.Lookup != nil { id := r.Lookup(t) if id != EmptyID { s.ID = id } } } return s } func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema { // only try to reflect non-pointers if t.Kind() == reflect.Ptr { return r.refOrReflectTypeToSchema(definitions, t.Elem()) } // Do any pre-definitions exist? if r.Mapper != nil { if t := r.Mapper(t); t != nil { return t } } if rt := r.reflectCustomSchema(definitions, t); rt != nil { return rt } // Prepare a base to which details can be added st := new(Schema) // jsonpb will marshal protobuf enum options as either strings or integers. // It will unmarshal either. if t.Implements(protoEnumType) { st.OneOf = []*Schema{ {Type: TypeString}, {Type: TypeInteger}, } return 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 if t == ipType { // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5 st.Type = TypeString st.Format = FormatStringIPv4 return st } switch t.Kind() { case reflect.Struct: r.reflectStruct(definitions, t, st) case reflect.Slice, reflect.Array: r.reflectSliceOrArray(definitions, t, st) case reflect.Map: r.reflectMap(definitions, t, st) case reflect.Interface: // empty case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: st.Type = TypeInteger case reflect.Float32, reflect.Float64: st.Type = TypeNumber case reflect.Bool: st.Type = TypeBoolean case reflect.String: st.Type = TypeString default: panic("unsupported type " + t.String()) } r.reflectSchemaExtend(definitions, t, st) // Always try to reference the definition which may have just been created if def := r.refDefinition(definitions, t); def != nil { return def } return st } func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) *Schema { if t.Kind() == reflect.Ptr { return r.reflectCustomSchema(definitions, t.Elem()) } if t.Implements(customType) { v := reflect.New(t) o := v.Interface().(customSchemaImpl) st := o.JSONSchema() r.addDefinition(definitions, t, st) if ref := r.refDefinition(definitions, t); ref != nil { return ref } return st } return nil } func (r *Reflector) reflectSchemaExtend(definitions Definitions, t reflect.Type, s *Schema) *Schema { if t.Implements(extendType) { v := reflect.New(t) o := v.Interface().(extendSchemaImpl) o.JSONSchemaExtend(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 } r.addDefinition(definitions, t, st) if st.Description == "" { st.Description = r.lookupComment(t, "") if !st.Deprecated { st.Deprecated = isDeprecatedComment(st.Description) } } else if !st.Deprecated { st.Deprecated = r.lookupDeprecated(t, "") } if t.Kind() == reflect.Array { st.MinItems = t.Len() st.MaxItems = st.MinItems } if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() { st.Type = TypeString // NOTE: ContentMediaType is not set here st.ContentEncoding = "base64" } else { st.Type = TypeArray st.Items = r.refOrReflectTypeToSchema(definitions, t.Elem()) } } func (r *Reflector) reflectMap(definitions Definitions, t reflect.Type, st *Schema) { r.addDefinition(definitions, t, st) st.Type = TypeObject if st.Description == "" { st.Description = r.lookupComment(t, "") if !st.Deprecated { st.Deprecated = isDeprecatedComment(st.Description) } } else if !st.Deprecated { st.Deprecated = r.lookupDeprecated(t, "") } switch t.Key().Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: st.PatternProperties = map[string]*Schema{ "^[0-9]+$": r.refOrReflectTypeToSchema(definitions, t.Elem()), } st.AdditionalProperties = FalseSchema return } if t.Elem().Kind() != reflect.Interface { st.PatternProperties = map[string]*Schema{ ".*": r.refOrReflectTypeToSchema(definitions, t.Elem()), } } } // Reflects a struct to a JSON Schema type. func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type, s *Schema) { // Handle special types switch t { case timeType: // date-time RFC section 7.3.1 s.Type = TypeString s.Format = FormatStringDateTime return case uriType: // uri RFC section 7.3.6 s.Type = TypeString s.Format = FormatStringURI return } r.addDefinition(definitions, t, s) s.Type = TypeObject s.Properties = orderedmap.New() s.Description = r.lookupComment(t, "") s.Deprecated = isDeprecatedComment(s.Description) if r.AssignAnchor { s.Anchor = t.Name() } if !r.AllowAdditionalProperties { s.AdditionalProperties = FalseSchema } ignored := false for _, it := range r.IgnoredTypes { if reflect.TypeOf(it) == t { ignored = true break } } if !ignored { r.reflectStructFields(s, definitions, t) } } func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t reflect.Type) { if t.Kind() == reflect.Ptr { t = t.Elem() } if t.Kind() != reflect.Struct { return } var getFieldDocString customGetFieldDocString if t.Implements(customStructGetFieldDocString) { v := reflect.New(t) o := v.Interface().(customSchemaGetFieldDocString) getFieldDocString = o.GetFieldDocString } handleField := func(f reflect.StructField) { name, shouldEmbed, required, nullable := r.reflectFieldName(f) // if anonymous and exported type should be processed recursively // current type should inherit properties of anonymous one if name == "" { if shouldEmbed { r.reflectStructFields(st, definitions, f.Type) } return } property := r.refOrReflectTypeToSchema(definitions, f.Type) property.structKeywordsFromTags(f, st, name) if property.Description == "" { property.Description = r.lookupComment(t, f.Name) if !property.Deprecated { property.Deprecated = isDeprecatedComment(property.Description) } } else if !property.Deprecated { property.Deprecated = r.lookupDeprecated(t, "") } if getFieldDocString != nil { property.Description = getFieldDocString(f.Name) } if nullable { property = &Schema{ OneOf: []*Schema{ property, { Type: TypeNull, }, }, } } st.Properties.Set(name, property) if required { st.Required = appendUniqueString(st.Required, name) } } for i := 0; i < t.NumField(); i++ { f := t.Field(i) handleField(f) } if r.AdditionalFields != nil { if af := r.AdditionalFields(t); af != nil { for _, sf := range af { handleField(sf) } } } } func appendUniqueString(base []string, value string) []string { for _, v := range base { if v == value { return base } } return append(base, value) } func (r *Reflector) lookupDeprecated(t reflect.Type, name string) bool { if comment := r.lookupComment(t, name); comment == "" { return false } else { return isDeprecatedComment(comment) } } func (r *Reflector) lookupComment(t reflect.Type, name string) string { if r.CommentMap == nil { return "" } n := fullyQualifiedTypeName(t) if name != "" { n = n + "." + name } return r.CommentMap[n] } // addDefinition will append the provided schema. If needed, an ID and anchor will also be added. func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) { name := r.typeName(t) if name == "" { return } definitions[name] = s } // refDefinition will provide a schema with a reference to an existing definition. func (r *Reflector) refDefinition(definitions Definitions, t reflect.Type) *Schema { if r.DoNotReference { return nil } name := r.typeName(t) if name == "" { return nil } if _, ok := definitions[name]; !ok { return nil } return &Schema{ Ref: "#/$defs/" + name, } } func (r *Reflector) lookupID(t reflect.Type) ID { if r.Lookup != nil { if t.Kind() == reflect.Ptr { t = t.Elem() } return r.Lookup(t) } return EmptyID } func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, propertyName string) { t.Description = f.Tag.Get(tagJSONSchemaDescription) tags := splitOnUnescapedCommas(f.Tag.Get(tagJSONSchema)) t.genericKeywords(tags, parent, propertyName) switch t.Type { case TypeString: t.stringKeywords(tags) case TypeNumber: t.numericKeywords(tags) case TypeInteger: t.numericKeywords(tags) case TypeArray: t.arrayKeywords(tags) case TypeBoolean: t.booleanKeywords(tags) } extras := strings.Split(f.Tag.Get(tagJSONSchemaExtras), keywordSep) t.extraKeywords(extras) } // read struct tags for generic keywords func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) { for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) switch len(nameValue) { case 1: switch nameValue[0] { case kwDeprecated: t.Deprecated = true } case 2: name, val := nameValue[0], nameValue[1] switch name { case kwTitle: t.Title = val case kwDescription: t.Description = val case kwType: t.Type = val case kwAnchor: t.Anchor = val case kwDeprecated: if i, err := strconv.ParseBool(val); err == nil { t.Deprecated = i } case kwOneOfRequired: var typeFound *Schema for i := range parent.OneOf { if parent.OneOf[i].Title == nameValue[1] { typeFound = parent.OneOf[i] } } if typeFound == nil { typeFound = &Schema{ Title: nameValue[1], Required: []string{}, } parent.OneOf = append(parent.OneOf, typeFound) } typeFound.Required = append(typeFound.Required, propertyName) case kwAnyOfRequired: var typeFound *Schema for i := range parent.AnyOf { if parent.AnyOf[i].Title == nameValue[1] { typeFound = parent.AnyOf[i] } } if typeFound == nil { typeFound = &Schema{ Title: nameValue[1], Required: []string{}, } parent.AnyOf = append(parent.AnyOf, typeFound) } typeFound.Required = append(typeFound.Required, propertyName) case kwOneOfType: if t.OneOf == nil { t.OneOf = make([]*Schema, 0, 1) } t.Type = "" types := strings.Split(nameValue[1], keywordArraySep) for _, ty := range types { t.OneOf = append(t.OneOf, &Schema{ Type: ty, }) } case kwAnyOfType: if t.AnyOf == nil { t.AnyOf = make([]*Schema, 0, 1) } t.Type = "" types := strings.Split(nameValue[1], keywordArraySep) for _, ty := range types { t.AnyOf = append(t.AnyOf, &Schema{ Type: ty, }) } case kwEnum: switch t.Type { case TypeString: t.Enum = append(t.Enum, val) case TypeInteger: if i, err := strconv.Atoi(val); err == nil { t.Enum = append(t.Enum, i) } case TypeNumber: if f, err := strconv.ParseFloat(val, 64); err == nil { t.Enum = append(t.Enum, f) } } } } } } // read struct tags for boolean type keywords func (t *Schema) booleanKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) if len(nameValue) != 2 { continue } name, val := nameValue[0], nameValue[1] if name == kwDefault { if val == valueTrue { t.Default = true } else if val == valueFalse { t.Default = false } } } } // read struct tags for string type keywords func (t *Schema) stringKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) if len(nameValue) < 2 { continue } name, val := nameValue[0], nameValue[1] switch name { case kwMinLength: if i, err := strconv.Atoi(val); err == nil { t.MinLength = i } case kwMaxLength: if i, err := strconv.Atoi(val); err == nil { t.MaxLength = i } case kwPattern: t.Pattern = val case kwFormat: if isStringFormat(val) { t.Format = val break } case kwReadOnly: if i, err := strconv.ParseBool(val); err == nil { t.ReadOnly = i } case kwWriteOnly: if i, err := strconv.ParseBool(val); err == nil { t.WriteOnly = i } case kwDefault: t.Default = val case kwExample: t.Examples = append(t.Examples, val) } } } // read struct tags for numeric type keywords func (t *Schema) numericKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) if len(nameValue) < 2 { continue } name, val := nameValue[0], nameValue[1] switch name { case kwMultipleOf: if i, err := strconv.Atoi(val); err == nil { t.MultipleOf = i } case kwMinimum: if i, err := strconv.Atoi(val); err == nil { t.Minimum = i } case kwMaximum: if i, err := strconv.Atoi(val); err == nil { t.Maximum = i } case kwExclusiveMaximum: if b, err := strconv.ParseBool(val); err == nil { t.ExclusiveMaximum = b } case kwExclusiveMinimum: if b, err := strconv.ParseBool(val); err == nil { t.ExclusiveMinimum = b } case kwDefault: if i, err := strconv.Atoi(val); err == nil { t.Default = i } case kwExample: if i, err := strconv.Atoi(val); err == nil { t.Examples = append(t.Examples, i) } } } } // read struct tags for object type keywords // func (t *Type) objectKeywords(tags []string) { // for _, tag := range tags{ // nameValue := strings.Split(tag, keywordKeyValueSep) // name, val := nameValue[0], nameValue[1] // switch name{ // case "dependencies": // t.Dependencies = val // break; // case "patternProperties": // t.PatternProperties = val // break; // } // } // } // read struct tags for array type keywords func (t *Schema) arrayKeywords(tags []string) { var defaultValues []any for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) switch len(nameValue) { case 1: switch nameValue[0] { case kwUniqueItems: t.UniqueItems = true } case 2: name, val := nameValue[0], nameValue[1] switch name { case kwMinItems: if i, err := strconv.Atoi(val); err == nil { t.MinItems = i } case kwMaxItems: if i, err := strconv.Atoi(val); err == nil { t.MaxItems = i } case kwDefault: switch t.Items.Type { case TypeInteger: if i, err := strconv.Atoi(val); err == nil { defaultValues = append(defaultValues, i) } case TypeNumber: if f, err := strconv.ParseFloat(val, 64); err == nil { defaultValues = append(defaultValues, f) } default: defaultValues = append(defaultValues, val) } case kwEnum: switch t.Items.Type { case TypeString: t.Items.Enum = append(t.Items.Enum, val) case TypeInteger: if i, err := strconv.Atoi(val); err == nil { t.Items.Enum = append(t.Items.Enum, i) } case TypeNumber: if f, err := strconv.ParseFloat(val, 64); err == nil { t.Items.Enum = append(t.Items.Enum, f) } } case kwFormat: t.Items.Format = val } } } if len(defaultValues) > 0 { t.Default = defaultValues } } func (t *Schema) extraKeywords(tags []string) { for _, tag := range tags { nameValue := strings.SplitN(tag, keywordKeyValueSep, 2) if len(nameValue) < 2 { continue } t.setExtra(nameValue[0], nameValue[1]) } } func (t *Schema) setExtra(key, val string) { if t.Extras == nil { t.Extras = map[string]any{} } if existingVal, ok := t.Extras[key]; ok { switch existingVal := existingVal.(type) { case string: t.Extras[key] = []string{existingVal, val} case []string: t.Extras[key] = append(existingVal, val) case int: t.Extras[key], _ = strconv.Atoi(val) case bool: t.Extras[key] = val == valueTrue || val == valueT } } else { switch key { case kwMinimum: t.Extras[key], _ = strconv.Atoi(val) default: var x any if val == valueTrue { x = true } else if val == valueFalse { x = false } else { x = val } t.Extras[key] = x } } } func isStringFormat(format string) bool { switch format { case FormatStringDateTime, FormatStringTime, FormatStringDate, FormatStringDuration, FormatStringEmail, FormatStringInternationalizedEmail, FormatStringHostname, FormatStringInternationalizedHostname, FormatStringIPv4, FormatStringIPv6, FormatStringUUID, FormatStringURI, FormatStringURIReference, FormatStringURITemplate, FormatStringIRI, FormatStringIRIReference, FormatStringJSONPointer, FormatStringRelativeJSONPointer, FormatStringRegex: return true default: return false } } func requiredFromJSONTags(tags []string) bool { if ignoredByJSONTags(tags) { return false } for _, tag := range tags[1:] { if tag == kwOmitEmpty { return false } } return true } func requiredFromJSONSchemaTags(tags []string) bool { if ignoredByJSONTags(tags) { return false } for _, tag := range tags { if tag == tagRequired { return true } } return false } func nullableFromJSONSchemaTags(tags []string) bool { if ignoredByJSONTags(tags) { return false } for _, tag := range tags { if tag == kwNullable { return true } } return false } func ignoredByJSONTags(tags []string) bool { return len(tags) != 0 && tags[0] == "-" } func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) { jsonTagString, _ := f.Tag.Lookup(tagJSON) jsonTags := strings.Split(jsonTagString, keywordSep) if ignoredByJSONTags(jsonTags) { return "", false, false, false } schemaTags := strings.Split(f.Tag.Get(tagJSONSchema), keywordSep) if ignoredByJSONTags(schemaTags) { return "", false, false, false } required := requiredFromJSONTags(jsonTags) if r.RequiredFromJSONSchemaTags { required = requiredFromJSONSchemaTags(schemaTags) } // Custom struct field requiring. if r.Requirer != nil { required = r.Requirer(f, required) } nullable := nullableFromJSONSchemaTags(schemaTags) if f.Anonymous && jsonTags[0] == "" { // As per JSON Marshal rules, anonymous structs are inherited if f.Type.Kind() == reflect.Struct { return "", true, false, false } // As per JSON Marshal rules, anonymous pointer to structs are inherited if f.Type.Kind() == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct { return "", true, false, false } } // Try to determine the name from the different combos name := f.Name if jsonTags[0] != "" { name = jsonTags[0] } if !f.Anonymous && f.PkgPath != "" { // field not anonymous and not export has no export name name = "" } else if r.KeyNamer != nil { name = r.KeyNamer(name) } return name, false, required, nullable } // UnmarshalJSON is used to parse a schema object or boolean. func (t *Schema) UnmarshalJSON(data []byte) error { if bytes.Equal(data, []byte(valueTrue)) { *t = *TrueSchema return nil } else if bytes.Equal(data, []byte(valueFalse)) { *t = *FalseSchema return nil } type Schema_ Schema aux := &struct { *Schema_ }{ Schema_: (*Schema_)(t), } return json.Unmarshal(data, aux) } func (t *Schema) MarshalJSON() ([]byte, error) { if t.boolean != nil { if *t.boolean { return []byte(valueTrue), nil } else { return []byte(valueFalse), nil } } if reflect.DeepEqual(&Schema{}, t) { // Don't bother returning empty schemas return []byte(valueTrue), nil } type Schema_ Schema b, err := json.Marshal((*Schema_)(t)) if err != nil { return nil, err } if t.Extras == nil || len(t.Extras) == 0 { return b, nil } m, err := json.Marshal(t.Extras) if err != nil { return nil, err } if len(b) == 2 { return m, nil } b[len(b)-1] = ',' return append(b, m[1:]...), nil } func (r *Reflector) typeName(t reflect.Type) string { if r.Namer != nil { if name := r.Namer(t); name != "" { return name } } return t.Name() } // Split on commas that are not preceded by `\`. // This way, we prevent splitting regexes func splitOnUnescapedCommas(tagString string) []string { ret := make([]string, 0) separated := strings.Split(tagString, keywordSep) ret = append(ret, separated[0]) i := 0 for _, nextTag := range separated[1:] { if len(ret[i]) == 0 { ret = append(ret, nextTag) i++ continue } if ret[i][len(ret[i])-1] == '\\' { ret[i] = ret[i][:len(ret[i])-1] + keywordSep + nextTag } else { ret = append(ret, nextTag) i++ } } return ret } func fullyQualifiedTypeName(t reflect.Type) string { return t.PkgPath() + "." + t.Name() } // AddGoComments will update the reflectors comment map with all the comments // found in the provided source directories. See the #ExtractGoComments method // for more details. func (r *Reflector) AddGoComments(base, path string) error { if r.CommentMap == nil { r.CommentMap = make(map[string]string) } return ExtractGoComments(base, path, r.CommentMap) } jsonschema-0.1.7/reflect_test.go000066400000000000000000000401051447504341700166740ustar00rootroot00000000000000package jsonschema import ( "encoding/json" "flag" "fmt" "net" "net/url" "os" "path/filepath" "reflect" "strings" "testing" "time" "github.com/iancoleman/orderedmap" "github.com/authelia/jsonschema/examples" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var updateFixtures = flag.Bool("update", false, "set to update fixtures") var compareFixtures = flag.Bool("compare", false, "output failed fixtures with .out.json") type GrandfatherType struct { FamilyName string `json:"family_name" jsonschema:"required"` } type SomeBaseType struct { ID string `json:"id"` // to test composition override SomeBaseProperty int `json:"some_base_property"` // The jsonschema required tag is nonsensical for private and ignored properties. // Their presence here tests that the fields *will not* be required in the output // schema, even if they are tagged required. somePrivateBaseProperty string `jsonschema:"required"` SomeIgnoredBaseProperty string `json:"-" jsonschema:"required"` SomeSchemaIgnoredProperty string `jsonschema:"-,required"` Grandfather GrandfatherType `json:"grand"` SomeUntaggedBaseProperty bool `jsonschema:"required"` someUnexportedUntaggedBaseProperty bool } type MapType map[string]any type ArrayType []string type nonExported struct { PublicNonExported int privateNonExported int } type ProtoEnum int32 func (ProtoEnum) EnumDescriptor() ([]byte, []int) { return []byte(nil), []int{0} } const ( Unset ProtoEnum = iota Great ) type TestUser struct { SomeBaseType nonExported MapType ID int `json:"id" jsonschema:"required"` Name string `json:"name" jsonschema:"required,minLength=1,maxLength=20,pattern=.*,description=this is a property,title=the name,example=joe,example=lucy,default=alex,readOnly=true"` Password string `json:"password" jsonschema:"writeOnly=true"` Friends []int `json:"friends,omitempty" jsonschema_description:"list of IDs, omitted when empty"` Tags map[string]string `json:"tags,omitempty"` Options map[string]any `json:"options,omitempty"` TestFlag bool TestFlagFalse bool `json:",omitempty" jsonschema:"default=false"` TestFlagTrue bool `json:",omitempty" jsonschema:"default=true"` IgnoredCounter int `json:"-"` // Tests for RFC draft-wright-json-schema-validation-00, section 7.3 BirthDate time.Time `json:"birth_date,omitempty"` Website url.URL `json:"website,omitempty"` IPAddress net.IP `json:"network_address,omitempty"` // Tests for RFC draft-wright-json-schema-hyperschema-00, section 4 Photo []byte `json:"photo,omitempty" jsonschema:"required"` Photo2 Bytes `json:"photo2,omitempty" jsonschema:"required"` // Tests for jsonpb enum support Feeling ProtoEnum `json:"feeling,omitempty"` Age int `json:"age" jsonschema:"minimum=18,maximum=120,exclusiveMaximum=true,exclusiveMinimum=true"` Email string `json:"email" jsonschema:"format=email"` UUID string `json:"uuid" jsonschema:"format=uuid"` // Test for "extras" support Baz string `jsonschema_extras:"foo=bar,hello=world,foo=bar1"` BoolExtra string `json:"bool_extra,omitempty" jsonschema_extras:"isTrue=true,isFalse=false"` // Tests for simple enum tags Color string `json:"color" jsonschema:"enum=red,enum=green,enum=blue"` Rank int `json:"rank,omitempty" jsonschema:"enum=1,enum=2,enum=3"` Multiplier float64 `json:"mult,omitempty" jsonschema:"enum=1.0,enum=1.5,enum=2.0"` // Tests for enum tags on slices Roles []string `json:"roles" jsonschema:"enum=admin,enum=moderator,enum=user"` Priorities []int `json:"priorities,omitempty" jsonschema:"enum=-1,enum=0,enum=1,enun=2"` Offsets []float64 `json:"offsets,omitempty" jsonschema:"enum=1.570796,enum=3.141592,enum=6.283185"` // Test for raw JSON Anything any `json:"anything,omitempty"` Raw json.RawMessage `json:"raw"` } type CustomTime time.Time type CustomTypeField struct { CreatedAt CustomTime } type CustomTimeWithInterface time.Time type CustomTypeFieldWithInterface struct { CreatedAt CustomTimeWithInterface } func (CustomTimeWithInterface) JSONSchema() *Schema { return &Schema{ Type: "string", Format: "date-time", } } type RootOneOf struct { Field1 string `json:"field1" jsonschema:"oneof_required=group1"` Field2 string `json:"field2" jsonschema:"oneof_required=group2"` Field3 any `json:"field3" jsonschema:"oneof_type=string;array"` Field4 string `json:"field4" jsonschema:"oneof_required=group1"` Field5 ChildOneOf `json:"child"` } type ChildOneOf struct { Child1 string `json:"child1" jsonschema:"oneof_required=group1"` Child2 string `json:"child2" jsonschema:"oneof_required=group2"` Child3 any `json:"child3" jsonschema:"oneof_required=group2,oneof_type=string;array"` Child4 string `json:"child4" jsonschema:"oneof_required=group1"` } type RootAnyOf struct { Field1 string `json:"field1" jsonschema:"anyof_required=group1"` Field2 string `json:"field2" jsonschema:"anyof_required=group2"` Field3 any `json:"field3" jsonschema:"anyof_type=string;array"` Field4 string `json:"field4" jsonschema:"anyof_required=group1"` Field5 ChildAnyOf `json:"child"` } type ChildAnyOf struct { Child1 string `json:"child1" jsonschema:"anyof_required=group1"` Child2 string `json:"child2" jsonschema:"anyof_required=group2"` Child3 any `json:"child3" jsonschema:"anyof_required=group2,oneof_type=string;array"` Child4 string `json:"child4" jsonschema:"anyof_required=group1"` } type Text string type TextNamed string type Outer struct { TextNamed Text `json:",omitempty"` Inner } type OuterNamed struct { Text `json:"text,omitempty"` Inner `json:"inner"` } type OuterPtr struct { *Inner Text `json:",omitempty"` } type Inner struct { Foo string `yaml:"foo"` } type MinValue struct { Value int `json:"value4" jsonschema_extras:"minimum=0"` } type Bytes []byte type TestNullable struct { Child1 string `json:"child1" jsonschema:"nullable"` } type CompactDate struct { Year int Month int } type UserWithAnchor struct { Name string `json:"name" jsonschema:"anchor=Name"` } func (CompactDate) JSONSchema() *Schema { return &Schema{ Type: "string", Title: "Compact Date", Description: "Short date that only includes year and month", Pattern: "^[0-9]{4}-[0-1][0-9]$", } } type TestDescriptionOverride struct { FirstName string `json:"FirstName"` LastName string `json:"LastName"` Age uint `json:"age"` MiddleName string `json:"middle_name,omitempty"` } func (TestDescriptionOverride) GetFieldDocString(fieldName string) string { switch fieldName { case "FirstName": return "test2" case "LastName": return "test3" case "Age": return "test4" case "MiddleName": return "test5" default: return "" } } type LookupName struct { Given string `json:"first"` Surname string `json:"surname"` } type LookupUser struct { Name *LookupName `json:"name"` Alias string `json:"alias,omitempty"` } type CustomSliceOuter struct { Slice CustomSliceType `json:"slice"` } type CustomSliceType []string func (CustomSliceType) JSONSchema() *Schema { return &Schema{ OneOf: []*Schema{{ Type: "string", }, { Type: "array", Items: &Schema{ Type: "string", }, }}, } } type CustomMapType map[string]string func (CustomMapType) JSONSchema() *Schema { properties := orderedmap.New() properties.Set("key", &Schema{ Type: "string", }) properties.Set("value", &Schema{ Type: "string", }) return &Schema{ Type: "array", Items: &Schema{ Type: "object", Properties: properties, Required: []string{"key", "value"}, }, } } type CustomMapOuter struct { MyMap CustomMapType `json:"my_map"` } type PatternTest struct { WithPattern string `json:"with_pattern" jsonschema:"minLength=1,pattern=[0-9]{1\\,4},maxLength=50"` } type RecursiveExample struct { Text string `json:"text"` Child []*RecursiveExample `json:"children,omitempty"` } type KeyNamedNested struct { NestedNotRenamedProperty string NotRenamed string } type KeyNamed struct { ThisWasLeftAsIs string NotComingFromJSON bool `json:"coming_from_json_tag_not_renamed"` NestedNotRenamed KeyNamedNested `json:"nested_not_renamed"` UnicodeShenanigans string RenamedByComputation int `jsonschema_description:"Description was preserved"` } type SchemaExtendTestBase struct { FirstName string `json:"FirstName"` LastName string `json:"LastName"` Age uint `json:"age"` MiddleName string `json:"middle_name,omitempty"` } type SchemaExtendTest struct { SchemaExtendTestBase `json:",inline"` } func (SchemaExtendTest) JSONSchemaExtend(base *Schema) { base.Properties.Delete("FirstName") base.Properties.Delete("age") val, _ := base.Properties.Get("LastName") (val).(*Schema).Description = "some extra words" base.Required = []string{"LastName"} } type Expression struct { Value int `json:"value" jsonschema_extras:"foo=bar=='baz'"` } func TestReflector(t *testing.T) { r := new(Reflector) s := "http://example.com/schema" r.SetBaseSchemaID(s) assert.EqualValues(t, s, r.BaseSchemaID) } func TestReflectFromType(t *testing.T) { r := new(Reflector) tu := new(TestUser) typ := reflect.TypeOf(tu) s := r.ReflectFromType(typ) assert.EqualValues(t, "https://github.com/authelia/jsonschema/test-user", s.ID) x := struct { Test string }{ Test: "foo", } typ = reflect.TypeOf(x) s = r.Reflect(typ) assert.Empty(t, s.ID) } func TestSchemaGeneration(t *testing.T) { tests := []struct { typ any reflector *Reflector fixture string }{ {&TestUser{}, &Reflector{}, "fixtures/test_user.json"}, {&UserWithAnchor{}, &Reflector{}, "fixtures/user_with_anchor.json"}, {&TestUser{}, &Reflector{AssignAnchor: true}, "fixtures/test_user_assign_anchor.json"}, {&TestUser{}, &Reflector{AllowAdditionalProperties: true}, "fixtures/allow_additional_props.json"}, {&TestUser{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/required_from_jsontags.json"}, {&TestUser{}, &Reflector{ExpandedStruct: true}, "fixtures/defaults_expanded_toplevel.json"}, {&TestUser{}, &Reflector{IgnoredTypes: []any{GrandfatherType{}}}, "fixtures/ignore_type.json"}, {&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"}, {&RootAnyOf{}, &Reflector{RequiredFromJSONSchemaTags: true}, "fixtures/anyof.json"}, {&CustomTypeField{}, &Reflector{ Mapper: func(i reflect.Type) *Schema { if i == reflect.TypeOf(CustomTime{}) { return &Schema{ Type: "string", Format: "date-time", } } return nil }, }, "fixtures/custom_type.json"}, {LookupUser{}, &Reflector{BaseSchemaID: "https://example.com/schemas"}, "fixtures/base_schema_id.json"}, {LookupUser{}, &Reflector{ Lookup: func(i reflect.Type) ID { switch i { case reflect.TypeOf(LookupUser{}): return ID("https://example.com/schemas/lookup-user") case reflect.TypeOf(LookupName{}): return ID("https://example.com/schemas/lookup-name") } return EmptyID }, }, "fixtures/lookup.json"}, {&LookupUser{}, &Reflector{ BaseSchemaID: "https://example.com/schemas", ExpandedStruct: true, AssignAnchor: true, Lookup: func(i reflect.Type) ID { switch i { case reflect.TypeOf(LookupUser{}): return ID("https://example.com/schemas/lookup-user") case reflect.TypeOf(LookupName{}): return ID("https://example.com/schemas/lookup-name") } return EmptyID }, }, "fixtures/lookup_expanded.json"}, {&Outer{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_inheritance.json"}, {&OuterNamed{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_embedded.json"}, {&OuterNamed{}, &Reflector{ExpandedStruct: true, AssignAnchor: true}, "fixtures/inlining_embedded_anchored.json"}, {&OuterPtr{}, &Reflector{ExpandedStruct: true}, "fixtures/inlining_ptr.json"}, {&MinValue{}, &Reflector{}, "fixtures/schema_with_minimum.json"}, {&TestNullable{}, &Reflector{}, "fixtures/nullable.json"}, {&GrandfatherType{}, &Reflector{ AdditionalFields: func(r reflect.Type) []reflect.StructField { return []reflect.StructField{ { Name: "Addr", Type: reflect.TypeOf((*net.IP)(nil)).Elem(), Tag: "json:\"ip_addr\"", Anonymous: false, }, } }, }, "fixtures/custom_additional.json"}, {&TestDescriptionOverride{}, &Reflector{}, "fixtures/test_description_override.json"}, {&CompactDate{}, &Reflector{}, "fixtures/compact_date.json"}, {&CustomSliceOuter{}, &Reflector{}, "fixtures/custom_slice_type.json"}, {&CustomMapOuter{}, &Reflector{}, "fixtures/custom_map_type.json"}, {&CustomTypeFieldWithInterface{}, &Reflector{}, "fixtures/custom_type_with_interface.json"}, {&PatternTest{}, &Reflector{}, "fixtures/commas_in_pattern.json"}, {&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"}, {&RecursiveExample{}, &Reflector{}, "fixtures/recursive.json"}, {&KeyNamed{}, &Reflector{ KeyNamer: func(s string) string { switch s { case "ThisWasLeftAsIs": fallthrough case "NotRenamed": fallthrough case "nested_not_renamed": return s case "coming_from_json_tag_not_renamed": return "coming_from_json_tag" case "NestedNotRenamed": return "nested-renamed" case "NestedNotRenamedProperty": return "nested-renamed-property" case "UnicodeShenanigans": return "✨unicode✨ s̸̥͝h̷̳͒e̴̜̽n̸̡̿a̷̘̔n̷̘͐i̶̫̐ǵ̶̯a̵̘͒n̷̮̾s̸̟̓" case "RenamedByComputation": return fmt.Sprintf("%.2f", float64(len(s))+1/137.0) } return "unknown case" }, }, "fixtures/keynamed.json"}, {MapType{}, &Reflector{}, "fixtures/map_type.json"}, {ArrayType{}, &Reflector{}, "fixtures/array_type.json"}, {SchemaExtendTest{}, &Reflector{}, "fixtures/custom_type_extend.json"}, {Expression{}, &Reflector{}, "fixtures/schema_with_expression.json"}, } for _, tt := range tests { name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json") t.Run(name, func(t *testing.T) { compareSchemaOutput(t, tt.fixture, tt.reflector, tt.typ, ) }) } } func prepareCommentReflector(t *testing.T) *Reflector { t.Helper() r := new(Reflector) err := r.AddGoComments("github.com/authelia/jsonschema", "./examples") require.NoError(t, err, "did not expect error while adding comments") return r } func TestBaselineUnmarshal(t *testing.T) { r := &Reflector{} compareSchemaOutput(t, "fixtures/test_user.json", r, &TestUser{}) } func compareSchemaOutput(t *testing.T, f string, r *Reflector, obj any) { t.Helper() expectedJSON, err := os.ReadFile(f) require.NoError(t, err) actualSchema := r.Reflect(obj) actualJSON, _ := json.MarshalIndent(actualSchema, "", " ") //nolint:errchkjson if *updateFixtures { _ = os.WriteFile(f, actualJSON, 0600) } if !assert.JSONEq(t, string(expectedJSON), string(actualJSON)) { if *compareFixtures { _ = os.WriteFile(strings.TrimSuffix(f, ".json")+".out.json", actualJSON, 0600) } } } func TestSplitOnUnescapedCommas(t *testing.T) { tests := []struct { strToSplit string expected []string }{ {`Hello,this,is\,a\,string,haha`, []string{`Hello`, `this`, `is,a,string`, `haha`}}, {`hello,no\\,split`, []string{`hello`, `no\,split`}}, {`string without commas`, []string{`string without commas`}}, {`ünicode,𐂄,Ж\,П,ᠳ`, []string{`ünicode`, `𐂄`, `Ж,П`, `ᠳ`}}, {`empty,,tag`, []string{`empty`, ``, `tag`}}, } for _, test := range tests { actual := splitOnUnescapedCommas(test.strToSplit) require.Equal(t, test.expected, actual) } } func TestArrayFormat(t *testing.T) { type URIArray struct { TestURIs []string `jsonschema:"type=array,format=uri"` } r := new(Reflector) schema := r.Reflect(&URIArray{}) d := schema.Definitions["URIArray"] require.NotNil(t, d) props := d.Properties require.NotNil(t, props) i, found := props.Get("TestURIs") require.True(t, found) p := i.(*Schema) pt := p.Items.Format require.Equal(t, pt, "uri") } jsonschema-0.1.7/utils.go000066400000000000000000000011731447504341700153530ustar00rootroot00000000000000package jsonschema import ( "regexp" "strings" ) var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") // ToSnakeCase converts the provided string into snake case using dashes. // This is useful for Schema IDs and definitions to be coherent with // common JSON Schema examples. func ToSnakeCase(str string) string { snake := matchFirstCap.ReplaceAllString(str, "${1}-${2}") snake = matchAllCap.ReplaceAllString(snake, "${1}-${2}") return strings.ToLower(snake) } func isDeprecatedComment(comment string) bool { return strings.HasPrefix(comment, "Deprecated:") }