-
Notifications
You must be signed in to change notification settings - Fork 91
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
OAS 3.1 changes for schema model #1793
Conversation
Build failures are expected because this depends on spec changes. Locally I have tests passing up to this point:
With the failure:
which looks like this part of the build is running with the built smallrye-open-api, but using an older version of the spec APIs without |
Found the issue and updated the dependency for the testdata project and now my local build runs cleanly. |
a523705
to
5556253
Compare
protected <T> void addToMapProperty(String propertyName, String key, T value) { | ||
if (value != null) { | ||
Map<String, T> map = (Map<String, T>) data.get(propertyName); |
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 add and remove methods for maps and lists risk a ClassCastException
if the data stored in the property is not actually a map or a list.
We may want to improve this by:
- providing a better exception
- avoiding an exception
- for add: overwriting the value with a new list or map
- for remove: doing nothing
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've implemented the second option.
887cef7
to
504806b
Compare
public static SchemaImpl copyOf(Schema other) { | ||
SchemaImpl clone = (SchemaImpl) MergeUtil.mergeObjects(new SchemaImpl(), other); | ||
clone.required = copy(clone.required, () -> new ArrayList<>(clone.required)); | ||
clone.enumeration = copy(clone.enumeration, () -> new ArrayList<>(clone.enumeration)); | ||
clone.items = copy(clone.items, () -> copyOf(clone.items)); | ||
|
||
clone.allOf = copy(clone.allOf, () -> clone.allOf | ||
.stream() | ||
.map(SchemaImpl::copyOf) | ||
.collect(Collectors.toList())); | ||
|
||
clone.properties = copy(clone.properties, () -> { | ||
Map<String, Schema> copiedProperties = new LinkedHashMap<>(clone.properties.size()); | ||
clone.properties.forEach((k, v) -> copiedProperties.put(k, copyOf(v))); | ||
return copiedProperties; | ||
}); | ||
|
||
clone.additionalPropertiesSchema = copy(clone.additionalPropertiesSchema, | ||
() -> copyOf(clone.additionalPropertiesSchema)); | ||
|
||
clone.xml = copy(clone.xml, () -> MergeUtil.mergeObjects(new XMLImpl(), clone.xml)); | ||
clone.externalDocs = copy(clone.externalDocs, | ||
() -> MergeUtil.mergeObjects(new ExternalDocumentationImpl(), clone.externalDocs)); | ||
|
||
clone.oneOf = copy(clone.oneOf, () -> clone.oneOf | ||
.stream() | ||
.map(SchemaImpl::copyOf) | ||
.collect(Collectors.toList())); | ||
if (other == null) { | ||
return new SchemaImpl(); | ||
} |
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 logic of copyOf
is very similar to MapBasedModelImpl.mergeFrom
, we may be able to remove one of them.
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.
It's slightly different. When using mergeFrom
, if from
has a particular property but to
does not, the value from from
is used directly. When the method returns, both from
and to
reference the same object for that property. This matches the behaviour of MergeUtil.mergeObjects
and means mergeFrom
is only really suitable if you're going to throw both of the source objects away and just use the result object.
For this reason I recommend keeping both, even though the logic is similar.
@Azquelt I've updated the target branch to |
504806b
to
38e9254
Compare
@Azquelt , the target branch did not have CI enabled, so I've added it. We should get the test jobs running against the SNAPSHOT MP OpenAPI 4.0 build on your next rebase/push. |
a2a678f
to
2769a35
Compare
I've updated the serialization code so that all the "xxxIO" objects are created and owned by Unfortunately I found some mistakes in the tests in this area which are fixed by microprofile/microprofile-open-api#602. The code in this PR should pass all tests once that PR is merged. |
6520570
to
f3619a9
Compare
Schema::getExtensions, | ||
// Schema::getExtensions, |
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 this be reverted?
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 think it should be, but uncommenting this line alone causes test failures in the TCK because getExtensions()
doesn't currently behave like other getter methods. In particular it will never return null
and the code that uses this list of getters is explicitly checking for whether they return null
.
I've fixed this with e04261e. I had to update the extensions methods on SchemaImpl
to behave like other getter/setter sets and differentiate between being null
and being empty. ModelConstructionTest.processExtensible
from the TCK also expects this behaviour, but the exact way it was testing it wasn't tripped up by our current implementation.
if (schema != null && schema.getNullable() == null && TypeUtil.isOptional(returnType)) { | ||
schema.setNullable(Boolean.TRUE); | ||
if (schema != null && SchemaImpl.getNullable(schema) == null && TypeUtil.isOptional(returnType)) { | ||
if (schema.getType() != null) { | ||
schema.addType(Schema.SchemaType.NULL); | ||
} | ||
if (schema.getRef() != null) { | ||
Schema nullSchema = new SchemaImpl().type(singletonList(Schema.SchemaType.NULL)); | ||
// Move reference to type into its own subschema | ||
Schema refSchema = new SchemaImpl().ref(schema.getRef()); | ||
schema.setRef(null); | ||
if (schema.getAnyOf() == null) { | ||
schema.addAnyOf(refSchema).addAnyOf(nullSchema); | ||
} else { | ||
Schema anyOfSchema = new SchemaImpl().addAnyOf(refSchema).addAnyOf(nullSchema); | ||
schema.addAllOf(anyOfSchema); | ||
} | ||
} | ||
} |
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.
@MikeEdgar you were correct. The old code here did not consider that schema
might have $ref
set. If $ref
was set, then the value for nullable
set here got discarded when the schema was serialized.
It looked like the intent of the old code was to add nullable = true
when a resource method returned Optional<T>
so I made the new code handle this case whether or not the response schema is a reference.
@Azquelt I just fast-forwarded |
@MikeEdgar Thanks, I'll have a look |
This test took a noticable amount of time and more than 50% was string contatentation building the test file.
For consistency with other model setters.
7ef458c
to
2ad30f4
Compare
@MikeEdgar I've rebased this on the updated |
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.
This looks good to me, as expected. I left a few comments, but nothing major.
@phillip-kruger, this is for the MP OpenAPI 4.0 spec update. Please take a pass through if you have a chance.
&& param.getSchema().getType() != null | ||
&& param.getSchema().getType().contains(SchemaType.STRING) |
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.
Edge case, but I wonder if this method should only return true
if the type
array has "string"
and size == 1. No need to change, just making a mental bookmark.
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.
Possibly, though I note that only string instances are constrained by pattern
. If pattern
was set and type was ["number", "string"]
, any numeric value would be valid, but a string value would only be valid if it conformed to pattern
.
@Override | ||
public Schema addExtension(String name, Object value) { | ||
setProperty(name, value); | ||
explicitSetExtensions = true; | ||
return this; | ||
} |
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 new mutator methods are missing calls to incrementModCount
. I wonder if it would be better to move modCount
up to MapBasedModelImpl
as protected and consolidate usage of incrementModCount
. Either way is fine with me.
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.
Good spot, I've pushed up changes moving the incrementModCount()
calls into the setProperty
methods on SchemaImpl
.
I've kept it all in SchemaImpl
for the moment since we already have additional logic we do before getting and setting properties on a schema.
Let go, we can roll forward. Would this cause a breaking change in Quarkus, or is it backwards compatible ? |
This and other MP OpenAPI changes are going into the main-4.0 branch for now, so we won't release that until everything is completed and we'll bump the major version at that point. |
Just branch of current main into a tag for in case we need to do work on that branch. |
- Change schema to hold a freeform map of objects - New getters and setters added - Type now holds an array, but is output as a single string if it only contains one item - $ref is now valid with other fields so we cannot remove other fields at serialization time. - When merging objects, some array properties should be merged, but others (e.g. examples) shouldn't. - Old behaviour of setType and setNullable is maintained, as we rely on it fairly heavily to build up schema data from multiple sources
Make a copy of a ref schema before returning it to the caller after registering a new type to prevent the caller from corrupting the registry entry. This is more likely now that $ref is valid alongside other properties. We already did a copy when a ref was retrieved from the registry, but not when it was first registered.
- Type can now be an array, but will still be output as a string if it only has one element - The schema construction for "this object, but also allow null" is different. Previously we could combine a $ref with nullable = true, but nullable has been removed and we need to instead state that null is allowed as a type. This requires us to use anyOf instead. - When generating from annotations, examples is used instead of example
Existing code relying on the old semantics of these methods was changed to use static methods on SchemaImpl which preserves the old behavior.
Make the IOContext class the creator of all IO objects. This makes it possible for the SchemaIO class to get hold of whichever IO objects it needs to serialize generic Constructible objects.
When adding or removing an item from a schema property that is expected to be a collection or map, ensure that the property actually contains a collection or map before attempting to modify it.
This means schema extensions should be maintained when a schema references another schema but has different extensions set. To make this work, we need to maintain the old semantics of getExtensions and setExtensions which differentiated between returning null and returning an empty map.
New field in 4.0, same purpose as example which was already in the list.
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.
LGTM, merging to main-4.0
Implements the spec changes in microprofile/microprofile-open-api#598