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

feat: Add NullableFromType option #110

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

candiduslynx
Copy link
Contributor

Instead of #106.

Allows to define property nullability based on the field type instead of the jsonschema:"nullable" tag.
Useful when the types included are referenced from 3rd party packages.

Consider the following example:

type TestNullableField struct {
	A *string `json:"a"`
}

The following JSON is a perfectly valid value for Go:

{
  "a": null
}

The schemas produced for the TestNullableField struct are:

Old behavior (`NullableFromType:false`)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/invopop/jsonschema/test-nullable-field",
  "$ref": "#/$defs/TestNullableField",
  "$defs": {
    "TestNullableField": {
      "properties": {
        "a": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "a"
      ]
    }
  }
}

New behavior (`NullableFromType:true`)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://github.com/invopop/jsonschema/test-nullable-field",
  "$ref": "#/$defs/TestNullableField",
  "$defs": {
    "TestNullableField": {
      "properties": {
        "a": {
          "oneOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "additionalProperties": false,
      "type": "object",
      "required": [
        "a"
      ]
    }
  }
}

In both cases a is required, but the old behavior will fail for {"a":null} whereas the new one will succeed.

@candiduslynx
Copy link
Contributor Author

@samlown this is a less intrusive implementation for #106.
It will kick in the new logic only if the NullableFromType option is used.

@candiduslynx candiduslynx changed the title feat: Add Reflector.NullableFromType option feat: Add NullableFromType option Oct 6, 2023
kodiakhq bot pushed a commit to cloudquery/codegen that referenced this pull request Oct 6, 2023
kodiakhq bot pushed a commit to cloudquery/cloudquery that referenced this pull request Oct 6, 2023
Closes #14027

Blocked by:
* cloudquery/codegen#39
* invopop/jsonschema#109 – merged to `cloudquery/jsonschema@cqmain`
* invopop/jsonschema#110 – merged to `cloudquery/jsonschema@cqmain`

I propose reviewing the annotations along with tests, as the JSON schemas generated are just too long to grasp visually.
@candiduslynx
Copy link
Contributor Author

candiduslynx commented Oct 9, 2023

@samlown I hope you're doing well in these uneasy days.

I've implemented the changes in #109 & #110 & tested them via a fork at github.com/cloudquery/jsonschema@cqmain.

The results of using the updated code along with NullableFromType: true, RequiredFromJSONSchemaTags: true is something I'm quite satisfied with.
You can find an example in schema.json file. It includes both the nullable properties based on the field type & the clashing types (you can easily find them searching for -1" suffix)).

Could you please give an estimate when these PRs (#109 & #110) could be merged? It'll be far easier to pin an upstream version instead of maintaining a fork for these 2 changes.

hydratim pushed a commit to hydratim/cloudquery that referenced this pull request Oct 20, 2023
Closes cloudquery#14027

Blocked by:
* cloudquery/codegen#39
* invopop/jsonschema#109 – merged to `cloudquery/jsonschema@cqmain`
* invopop/jsonschema#110 – merged to `cloudquery/jsonschema@cqmain`

I propose reviewing the annotations along with tests, as the JSON schemas generated are just too long to grasp visually.
@kop
Copy link

kop commented Dec 5, 2023

Highly interested in this PR as well. Is there anything I can help with to get this merged?

@candiduslynx
Copy link
Contributor Author

candiduslynx commented Dec 5, 2023

Highly interested in this PR as well. Is there anything I can help with to get this merged?

@kop I really hope @samlown has time to review/merge that.
In the meantime, we use a fork at github.com/cloudquery/jsonschema. It has cqmain default branch, that has the following PRs merged:

Copy link
Contributor

@samlown samlown left a comment

Choose a reason for hiding this comment

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

Sorry for the delay there! This looks good to me, thanks! I do think there should be some extra tests for those non-pointer reflection types though.

Comment on lines +986 to +991
case reflect.Pointer,
reflect.UnsafePointer,
reflect.Map,
reflect.Slice,
reflect.Interface:
return true
Copy link
Contributor

Choose a reason for hiding this comment

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

I do think there should be specific tests for these types. I also wonder if Interface and UnsafePointer are really necessary, if so, there should be a test to show it ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@samlown I've added the tests in be77f3d

I can see the appeal to remove both Interface & UnsafePointer as well, so I can update the code accordingly, if you insist.

I also updated the code to actually handle a more varied cases like map[string]*int or *[]*int & included that in tests.

@candiduslynx
Copy link
Contributor Author

@samlown thanks for the review!

I have an additional question here: currently the library uses oneOf to make schema nullable.
In my opinion, a better way would be anyOf:
If the schema is already nullable (e.g., it already is {"oneOf":[{...}, {"type":"null"}]}) then adding another oneOf layer will effectively make null value to fail validation, as the oneOf requires one & only one variant to be valid:

{
  "oneOf": [
    {
      "oneOf": [
        {},
        {
          "type": "null"
        }
      ]
    },
    {
      "type": "null"
    }
  ]
}

If we use anyOf this is lifted to at least one of the variants.

I can raise this as a separate issue & address it in a follow-up PR.
WDYT?

@elise-prequel
Copy link

Hi! Echoing interest in this PR -- happy to help if there's anything outstanding to get it in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants