-
Notifications
You must be signed in to change notification settings - Fork 0
Support nullable #1
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
Conversation
- Add transform for nullable: true to include null as a possible type for any of the following schema types: const, enum, type, anyOf, and oneOf. - Log a warning when enum does not contain null but schema is marked nullable: true. - Add tests and update snapshot
tvharris
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdmower-csnw: Approved. This is going to be a huge help for us in multiple projects!
There are a few points to consider below. I reviewed with the mindset that the implementation should be consistent with AJV's because EOV uses AJV.
src/normalizer.ts
Outdated
| if (schema.const !== undefined) { | ||
| if (schema.const !== null) { | ||
| schema.enum = [schema.const, null] | ||
| delete schema.const | ||
| } | ||
| } else if (schema.enum) { | ||
| if (!schema.enum.includes(null)) { | ||
| schema.enum.push(null) | ||
| log('yellow', 'normalizer', 'enum should include null when schema is nullable', schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Should there be a warning for the
constcase, too? AJV docs say withnullable: true:
const would fail, unless it is "const": null
-
I wouldn't be opposed to throwing instead of the warnings, because it's critical that the schema is updated or the type will be at odds with how AJV validates.
-
What if
constandenumare both defined? Is that case caught earlier by validation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The const case is mostly moot because normalizer Transform const to singleton enum runs just before Transform nullable to null type:
rules.set('Transform const to singleton enum', schema => {
if (schema.const !== undefined) {
schema.enum = [schema.const]
delete schema.const
}
})I included const handling just in case normalizers are reordered in the future.
What if const and enum are both defined? Is that case caught earlier by validation?
schema.enum actually gets wiped out in this case due to the above normalizer. This is a great example of how this tool is not a validator. There's an expectation that the user has performed validation prior to using this tool.
Should there be a warning for the const case, too? AJV docs say with
nullable: true.
That's fine. Can do.
I wouldn't be opposed to throwing instead of the warnings, because it's critical that the schema is updated or the type will be at odds with how AJV validates.
Like mentioned before, this is not a validator. It's a tool that seldom throws.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opted to reorder transforms so that Transform nullable to null type runs before Transform const to singleton enum in a5db6da. Also created a warning() function that prints to the console even when VERBOSE is not used.
src/normalizer.ts
Outdated
| } else if (schema.anyOf) { | ||
| schema.anyOf.push(link({type: 'null'}, schema.anyOf)) | ||
| } else if (schema.oneOf) { | ||
| schema.oneOf.push(link({type: 'null'}, schema.oneOf)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdmower-csnw: Is this how AJV handles nullable: true for anyOf and oneOf? It doesn't say explicitly in the docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a rabbit hole.
The OpenAPI spec writers (i.e. not the full picture, just one example of a team defining a JSON schema) eventually decided to restrict use of nullable to only schemas that define type in 3.0.3 (see 3.0.3: Fields). In an early draft of 3.1, when nullable was planned for deprecation rather than removal, wording implied that nullable wasn't forbidden when paired with something like allOf, but that it could be overridden (see 3.1(draft): Fields). The start of the rabbit hole is here: OpenAPI-Specification: Reference objects don't combine well with “nullable”.
I approached this from the standpoint that nullable paired with anyOf and oneOf is unambiguous (but not allOf). After more reading, it looks like this was a bad assumption and there's good reason the OpenAPI team opted to limit use of nullable to schemas with type. The most helpful comments were this inqury: OAI/OpenAPI-Specification#1368 (comment), and this response: OAI/OpenAPI-Specification#1368 (comment) .
I lean towards leaving the const and enum pairings with nullable as is, i.e. allowed, but logged if null not in value(s) -- the intent is clear even if not 100% spec compliant. On the other hand, I no longer feel comfortable pairing nullable with anyOf, oneOf, and allOf. These will be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling of anyOf and oneOf removed in a5db6da.
| h: { | ||
| type: 'string', | ||
| const: null, | ||
| nullable: true, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdmower-csnw: This might not be directly related to the changes in this PR, but I was wondering if there's a warning for a schema like this where the type and const (or enum values) are inconsistent. I don't know how AJV handles such a schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is not a warning output by this tool. You can test this.
| "f": { | ||
| "type": ["null", "null"] | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mdmower-csnw: The output here and cases l, n, p don't seem ideal with the redundant null. I suppose it doesn't matter since the types are generated correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I don't think there's need for deduplication of types at this stage since the end user doesn't see it and the TypeScript conversion handles duplicates just fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deduplication was trivial so opted to handle it in a5db6da.
mdmower-csnw
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed with the mindset that the implementation should be consistent with AJV's because EOV uses AJV.
I wouldn't call that the guiding principle. This project is not affiliated with express-openapi-validator. Also, AJV is a great resource, but this tool is not a validator, so it doesn't have to be as strict. When I search the module for throw , it seems to me that the maintainer opts to work with what is available to the extent possible rather than throw because of minor validation issues. It's not my intention to start diverging significantly from upstream. If we can achieve our needs with a light touch, that's how I'd like to proceed.
src/normalizer.ts
Outdated
| if (schema.const !== undefined) { | ||
| if (schema.const !== null) { | ||
| schema.enum = [schema.const, null] | ||
| delete schema.const | ||
| } | ||
| } else if (schema.enum) { | ||
| if (!schema.enum.includes(null)) { | ||
| schema.enum.push(null) | ||
| log('yellow', 'normalizer', 'enum should include null when schema is nullable', schema) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The const case is mostly moot because normalizer Transform const to singleton enum runs just before Transform nullable to null type:
rules.set('Transform const to singleton enum', schema => {
if (schema.const !== undefined) {
schema.enum = [schema.const]
delete schema.const
}
})I included const handling just in case normalizers are reordered in the future.
What if const and enum are both defined? Is that case caught earlier by validation?
schema.enum actually gets wiped out in this case due to the above normalizer. This is a great example of how this tool is not a validator. There's an expectation that the user has performed validation prior to using this tool.
Should there be a warning for the const case, too? AJV docs say with
nullable: true.
That's fine. Can do.
I wouldn't be opposed to throwing instead of the warnings, because it's critical that the schema is updated or the type will be at odds with how AJV validates.
Like mentioned before, this is not a validator. It's a tool that seldom throws.
src/normalizer.ts
Outdated
| } else if (schema.anyOf) { | ||
| schema.anyOf.push(link({type: 'null'}, schema.anyOf)) | ||
| } else if (schema.oneOf) { | ||
| schema.oneOf.push(link({type: 'null'}, schema.oneOf)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is a rabbit hole.
The OpenAPI spec writers (i.e. not the full picture, just one example of a team defining a JSON schema) eventually decided to restrict use of nullable to only schemas that define type in 3.0.3 (see 3.0.3: Fields). In an early draft of 3.1, when nullable was planned for deprecation rather than removal, wording implied that nullable wasn't forbidden when paired with something like allOf, but that it could be overridden (see 3.1(draft): Fields). The start of the rabbit hole is here: OpenAPI-Specification: Reference objects don't combine well with “nullable”.
I approached this from the standpoint that nullable paired with anyOf and oneOf is unambiguous (but not allOf). After more reading, it looks like this was a bad assumption and there's good reason the OpenAPI team opted to limit use of nullable to schemas with type. The most helpful comments were this inqury: OAI/OpenAPI-Specification#1368 (comment), and this response: OAI/OpenAPI-Specification#1368 (comment) .
I lean towards leaving the const and enum pairings with nullable as is, i.e. allowed, but logged if null not in value(s) -- the intent is clear even if not 100% spec compliant. On the other hand, I no longer feel comfortable pairing nullable with anyOf, oneOf, and allOf. These will be removed.
| h: { | ||
| type: 'string', | ||
| const: null, | ||
| nullable: true, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is not a warning output by this tool. You can test this.
| "f": { | ||
| "type": ["null", "null"] | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. I don't think there's need for deduplication of types at this stage since the end user doesn't see it and the TypeScript conversion handles duplicates just fine.
- Introduce warning() to output console warnings - Add warning for nullable const, where const value is not null - Move nullable transform before const->enum transform so that potential warning about nullable const can display - Remove anyOf and oneOf handling since JSON spec should only constrain possibilities, not expand (allowance made for enum and const to expand types but they show warnings when this happens). - Avoid pushing 'null' onto available types if it already exists.
nullable: trueto includenullas a possible type for any of the following schema types:const,enum,type.constis not set tonullbut schema is markednullable: trueenumdoes not containnullbut schema is markednullable: trueReferences from upstream: