Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactor type handling for maps and types, removing YAML #23

Merged
merged 4 commits into from Jul 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Expand Up @@ -18,4 +18,4 @@ jobs:
- name: Lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.42
version: v1.45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.46 just came out yesterday, I think :)

1 change: 1 addition & 0 deletions .golangci.yml
Expand Up @@ -51,6 +51,7 @@ linters:
- structcheck
- stylecheck
- exhaustive
- varnamelen

linters-settings:
govet:
Expand Down
75 changes: 13 additions & 62 deletions README.md
Expand Up @@ -18,6 +18,11 @@ This repository is a fork of the original [jsonschema](https://github.com/alecth
- 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/invopop/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

Expand Down Expand Up @@ -111,6 +116,12 @@ jsonschema.Reflect(&TestUser{})
}
```

## 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/invopop/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`
Expand Down Expand Up @@ -175,65 +186,6 @@ will output:
}
```

### PreferYAMLSchema

JSON schemas can also be used to validate YAML, however YAML frequently uses
different identifiers to JSON indicated by the `yaml:` tag. The `Reflector` will
by default prefer `json:` tags over `yaml:` tags (and only use the latter if the
former are not present). This behavior can be changed via the `PreferYAMLSchema`
flag, that will switch this behavior: `yaml:` tags will be preferred over
`json:` tags.

With `PreferYAMLSchema: true`, the following struct:

```go
type Person struct {
FirstName string `json:"FirstName" yaml:"first_name"`
}
```

would result in this schema:

```json
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/TestYamlAndJson",
"$defs": {
"Person": {
"required": ["first_name"],
"properties": {
"first_name": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
}
}
}
```

whereas without the flag one obtains:

```json
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$ref": "#/$defs/TestYamlAndJson",
"$defs": {
"Person": {
"required": ["FirstName"],
"properties": {
"first_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.
Expand Down Expand Up @@ -296,7 +248,7 @@ In some situations, the keys actually used to write files are different from Go

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 aforementionned transformations, without having to specify `json:"..."` on every struct field.
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

Expand Down Expand Up @@ -343,8 +295,7 @@ Will yield
}
```

As you can see, if a field name has a `json:""` or `yaml:""` tag set, the `key` argument to `KeyNamer` will have the value of that tag (if a field name has both, the value of `key` will respect [`PreferYAMLSchema`](#preferyamlschema)).

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

Expand Down
6 changes: 6 additions & 0 deletions examples/nested/nested.go
Expand Up @@ -6,6 +6,12 @@ type Pet struct {
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.
Expand Down
7 changes: 5 additions & 2 deletions examples/user.go
Expand Up @@ -15,8 +15,11 @@ type User struct {
Tags map[string]interface{} `json:"tags,omitempty"`

// An array of pets the user cares for.
Pets []*nested.Pet `json:"pets"`
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=Pants"`
Plants []*nested.Plant `json:"plants" jsonschema:"title=Plants"`
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pants was so much better... 🤣

}
5 changes: 4 additions & 1 deletion examples_test.go
Expand Up @@ -21,7 +21,10 @@ type SampleUser struct {

func ExampleReflect() {
s := jsonschema.Reflect(&SampleUser{})
data, _ := json.MarshalIndent(s, "", " ")
data, err := json.MarshalIndent(s, "", " ")
if err != nil {
panic(err.Error())
}
fmt.Println(string(data))
// Output:
// {
Expand Down
18 changes: 12 additions & 6 deletions fixtures/allow_additional_props.json
Expand Up @@ -3,6 +3,10 @@
"$id": "https://github.com/invopop/jsonschema/test-user",
"$ref": "#/$defs/TestUser",
"$defs": {
"Bytes": {
"type": "string",
"contentEncoding": "base64"
},
"GrandfatherType": {
"properties": {
"family_name": {
Expand All @@ -14,14 +18,14 @@
"family_name"
]
},
"MapType": {
"type": "object"
},
"TestUser": {
"properties": {
"some_base_property": {
"type": "integer"
},
"some_base_property_yaml": {
"type": "integer"
},
"grand": {
"$ref": "#/$defs/GrandfatherType"
},
Expand All @@ -31,6 +35,9 @@
"PublicNonExported": {
"type": "integer"
},
"MapType": {
"$ref": "#/$defs/MapType"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -98,8 +105,7 @@
"contentEncoding": "base64"
},
"photo2": {
"type": "string",
"contentEncoding": "base64"
"$ref": "#/$defs/Bytes"
},
"feeling": {
"oneOf": [
Expand Down Expand Up @@ -197,10 +203,10 @@
"type": "object",
"required": [
"some_base_property",
"some_base_property_yaml",
"grand",
"SomeUntaggedBaseProperty",
"PublicNonExported",
"MapType",
"id",
"name",
"password",
Expand Down
13 changes: 13 additions & 0 deletions fixtures/array_type.json
@@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/array-type",
"$ref": "#/$defs/ArrayType",
"$defs": {
"ArrayType": {
"items": {
"type": "string"
},
"type": "array"
}
}
}
16 changes: 8 additions & 8 deletions fixtures/commas_in_pattern.json
@@ -1,22 +1,22 @@
{
{
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/pattern-test",
"$ref": "#/$defs/PatternTest",
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$defs": {
"PatternTest": {
"required": [
"with_pattern"
],
"properties": {
"with_pattern": {
"type": "string",
"maxLength": 50,
"minLength": 1,
"pattern": "[0-9]{1,4}",
"type": "string"
"pattern": "[0-9]{1,4}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not making sense of this spec.

Is pattern anchored to the beginning/end of the string?

If so, isn't the maxLength effectively 4?

And if not, isn't this the same as "pattern": "[0-9]"?

That is to say: If the pattern isn't anchored, then in effect, we're just looking for a single digit in the string? And if it is anchored, then we'll never have a lenght beyond 4...

But maybe I'm misunderstanding.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct, but this is just a test for the examples, the content isn't that meaningful :-)

}
},
"additionalProperties": false,
"type": "object"
"type": "object",
"required": [
"with_pattern"
]
}
}
}
18 changes: 12 additions & 6 deletions fixtures/defaults_expanded_toplevel.json
Expand Up @@ -2,6 +2,10 @@
"$schema": "http://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/test-user",
"$defs": {
"Bytes": {
"type": "string",
"contentEncoding": "base64"
},
"GrandfatherType": {
"properties": {
"family_name": {
Expand All @@ -13,15 +17,15 @@
"required": [
"family_name"
]
},
"MapType": {
"type": "object"
}
},
"properties": {
"some_base_property": {
"type": "integer"
},
"some_base_property_yaml": {
"type": "integer"
},
"grand": {
"$ref": "#/$defs/GrandfatherType"
},
Expand All @@ -31,6 +35,9 @@
"PublicNonExported": {
"type": "integer"
},
"MapType": {
"$ref": "#/$defs/MapType"
},
"id": {
"type": "integer"
},
Expand Down Expand Up @@ -98,8 +105,7 @@
"contentEncoding": "base64"
},
"photo2": {
"type": "string",
"contentEncoding": "base64"
"$ref": "#/$defs/Bytes"
},
"feeling": {
"oneOf": [
Expand Down Expand Up @@ -198,10 +204,10 @@
"type": "object",
"required": [
"some_base_property",
"some_base_property_yaml",
"grand",
"SomeUntaggedBaseProperty",
"PublicNonExported",
"MapType",
"id",
"name",
"password",
Expand Down
28 changes: 23 additions & 5 deletions fixtures/go_comments.json
Expand Up @@ -3,6 +3,15 @@
"$id": "https://github.com/invopop/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": {
Expand All @@ -18,6 +27,13 @@
],
"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": {
Expand Down Expand Up @@ -62,18 +78,19 @@
"type": "object"
},
"pets": {
"items": {
"$ref": "#/$defs/Pet"
},
"type": "array",
"$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": "Pants",
"title": "Plants",
"description": "Set of plants that the user likes"
}
},
Expand All @@ -83,6 +100,7 @@
"id",
"name",
"pets",
"named_pets",
"plants"
],
"description": "User is used as a base to provide tests for comments."
Expand Down