-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
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
[core] Add x-is-free-form vendor extension #6849
Conversation
This adds an x-is-free-form vendor extension to allow users to skip our "free-form" logic which would previously prevent object schemas with no properties to be considered "free-form". The previous behavior was due in part to Swagger Parser not exposing `additionalProperties: false` to us (which should be similar behavior to this extension). A free-form object is considered a dynamic object with any number of properties/types. DefaultGenerator does not allow for generation of models considered free-form. However, a base type with no properties and no additional properties is allowed by OpenAPI Specification and is meaningful in many languages (e.g. "marker interfaces" or abstract closed types).
👍 Thanks for opening this issue! The team will review the labels and make any necessary changes. |
I think the definition of a free form object should be reconsidered here. Here is the definition from above:
I have a case where object has no properties (and therefore no discriminator property), but is referenced from another model's But perhaps I don't have the complete picture. Here is a code snippet that I have in my branch - ModelUtils#isFreeFormObject(). I added the section with the comments "also check to see if this object is referenced from another allOf, anyOf, oneOf" public static boolean isFreeFormObject(OpenAPI openAPI, Schema schema) {
if (schema == null) {
// TODO: Is this message necessary? A null schema is not a free-form object, so the result is correct.
once(LOGGER).error("Schema cannot be null in isFreeFormObject check");
return false;
}
// not free-form if allOf, anyOf, oneOf is not empty
if (schema instanceof ComposedSchema) {
ComposedSchema cs = (ComposedSchema) schema;
List<Schema> interfaces = ModelUtils.getInterfaces(cs);
if (interfaces != null && !interfaces.isEmpty()) {
return false;
}
}
// also check to see if this object is referenced from another allOf, anyOf, oneOf
Map<String, Schema> schemas = getSchemas(openAPI);
if (schemas != null && !schemas.isEmpty()) {
for (Schema s : schemas.values()) {
if (!s.equals(schema)) {
if (s instanceof ComposedSchema) {
ComposedSchema cs = (ComposedSchema) s;
List<Schema> interfaces = ModelUtils.getInterfaces(cs);
if (isReferenceToSchema(openAPI,interfaces, schema)) {
break;
}
}
}
}
}
// has at least one property
if ("object".equals(schema.getType())) {
// no properties
if ((schema.getProperties() == null || schema.getProperties().isEmpty())) {
Schema addlProps = getAdditionalProperties(openAPI, schema);
// additionalProperties not defined
if (addlProps == null) {
return true;
} else {
if (addlProps instanceof ObjectSchema) {
ObjectSchema objSchema = (ObjectSchema) addlProps;
// additionalProperties defined as {}
if (objSchema.getProperties() == null || objSchema.getProperties().isEmpty()) {
return true;
}
} else if (addlProps instanceof Schema) {
// additionalProperties defined as {}
if (addlProps.getType() == null && (addlProps.getProperties() == null || addlProps.getProperties().isEmpty())) {
return true;
}
}
}
}
}
return false;
}
private static boolean isReferenceToSchema(OpenAPI openAPI, List<Schema> schemas, Schema schema) {
if (schemas != null) {
for (Schema s : schemas) {
String ref = s.get$ref();
if (StringUtils.isNotEmpty(ref)) {
Schema referencedSchema = getSchema(openAPI, getSimpleRef(ref));
if (schema.equals(referencedSchema)) {
return true;
}
}
}
}
return false;
} Does this violate the semantics of a "free-form object"? |
@jeff9finger I should be at least partially addressing your concern about free-form definition in this PR by removing the requirement that a model be considered a model if it has at least one property. I like the suggestion to also consider if the schema is referenced in a composed schema, but I'll need to look at it more to understand if there are any hidden scenarios. |
@wing328 any idea how to tackle this efficiently? I think we could disallow "Free-form" objects as being allowed in oneOf/anyOf/allOf because we don't generate free-form objects. That proposed solution could only help. I still think we should have the vendor extension because as a user I'd rather have an empty model created than a compilation error for a valid OpenAPI document. The issue that I have is that all of these result in properties=null and additionalProperties=null:
imho the 3 schemas above should not be considered free-form objects, but we currently can't ensure this because swagger-parser doesn't properly handle the false condition and returns nulls when the schemas have no definitions. |
@@ -65,6 +65,8 @@ | |||
// A vendor extension to track the value of the 'disallowAdditionalPropertiesIfNotPresent' CLI | |||
private static final String disallowAdditionalPropertiesIfNotPresent = "x-disallow-additional-properties-if-not-present"; | |||
|
|||
private static final String freeFormExplicit = "x-is-free-form"; |
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.
Use FREE_FORM_EXPLICIT to match Java style for constants?
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 would like to do this whenever we get more caught up on pull requests, but matched other constant styles in the file to avoid potential merge conflicts elsewhere.
We do track Sonar and this is a rule that's evaluated.
@jimschubert Thanks for the analysis.
So, only schemas that don't define |
@jeff9finger for clarification, here are the rules I think would make sense:
The issue I'm seeing is that swagger-parser isn't giving us that empty object state, and it's not handling additionalProperties=false. I'll need to dig into it a little more to see if there's a workaround. |
pinging @OpenAPITools/generator-core-team again |
So I disagree with the description that this schema is not free form:
You mention that our codegen incorrectly sets properties = null and additionalProperties = null for this case. Free form objects could be generated and used as marker interfaces/abstract closed types.
This makes it so:
I also like the Idea of seeing if a free-form schema is used in another schema, and if it is, generating it based upon that usage. Shoot, do we not have a pre-processing step before specs are parsed? @sebastien-rosset do you have any thoughts on this issue? |
@jeff9finger , there is also a "isAnyType" method, would this help for your use case? |
I'm slowly catching up with this thread, @spacether I agree with you the above is a free-form object (without having read the entire thread). That's because 1) the type is |
FYI, I just submitted a PR to fix additionalProperties handling in v2 specs in swagger-parser at: swagger-api/swagger-parser#1414
If merged swagger-api/swagger-parser#1414 will mean that additionalProperties will be handed the same in v2 and v3 specs in our project. |
@spacether did you test this branch with that proposed change? I think this PR might still be necessary for example where specs are generated by tooling and that tooling is based on older versions of swagger which don't properly handle |
* master: (121 commits) [PowerShell] better publishing workflow (#7114) [aspnetcore] Typo issues in docs and generated code (#7094) fix http signaure auth in build.sbt (#7110) fix for the issue facing spec invlolving arrayschema structure with ref (#6310) [csharp-netcore] added cancellation tokens to async calls (#7077) [PS] Allow CI to publish the module (#7091) [Dart] Treat float as double (#6924) [Java][jersey2]Fix gradle HttpSignatureAuth dependencies (#7096) move maven,gradle tests to shipppable ci (#7108) [MARKDOWN] Fix issue 6089 with property and parameter names (#6105) [BUG] Multi-Part content type in response ignores properties of composed schema (allOf/oneOf) (#6073) [online] Fix for version conflicts with springfox/boot (#7102) skip some installations to shorten build time [Go][Exp] better code format (#7088) Fix Shippable CI (#7097) typescript-node: clean up require and import (#6947) commented out perl, bash tests to reduce build time Add a link to conference paper (#7086) Add a link to the blog post at qiita (#7084) migrate typescript.sh to new config format (#7078) ...
No I haven't tested this branch with my proposed change yet. Are these PRs coupled? Is this PR functionally equivalent to turning on generateAliasAsModel? |
To clarify the issues:
So, I think a vendor extension is the cleanest approach. |
Let's merge this and revisit when Justin's PR in swagger-parser gets merged. |
* master: (355 commits) [php-slim4] Move config to a separate file (OpenAPITools#6971) [C][Client][Clang Static Analyzer] Remove the useless free operation for (OpenAPITools#7309) Fix typescript-node generation when only models are generated (OpenAPITools#7127) update spring config to use java8 (OpenAPITools#7308) [C][Client][Clang Static Analyzer] Fix uninitialized argument value (OpenAPITools#7305) [Java] remove deprecated jackson classes (OpenAPITools#7304) Adds generator unaliasSchema method, uses it to refactor python-experimental (OpenAPITools#7274) [Rust][reqwest] prefix local variables with "local_var" (OpenAPITools#7299) [Java][jersey2]Support enum discriminator value in child objects (OpenAPITools#7267) [C][Client][Clang Static Analyzer] Fix memory leak before function returnning (OpenAPITools#7302) add extension for hashable in swift5 (OpenAPITools#7300) [kotlin][client] fix warning Extension is shadowed by a member (OpenAPITools#7286) Add wbt-solutions logo (OpenAPITools#7298) [c-sharp] Fix default values with discriminator (OpenAPITools#7296) Add x-is-json to csharp generators (OpenAPITools#7293) Add raksul (OpenAPITools#7295) Add wbt-solutions as using company (OpenAPITools#7292) [C][Client][Clang Static Analyzer] Fix memory leak in apiClient_invoke (OpenAPITools#7285) [Java][clients] remove java.lang prefix from Object (OpenAPITools#6806) [core] Add x-is-free-form vendor extension (OpenAPITools#6849) ...
This adds an x-is-free-form vendor extension to allow users to skip our
"free-form" logic which would previously prevent object schemas with no
properties to be considered "free-form". The previous behavior was due
in part to Swagger Parser not exposing
additionalProperties: false
tous (which should be similar behavior to this extension).
A free-form object is considered a dynamic object with any number of
properties/types. DefaultGenerator does not allow for generation of
models considered free-form. However, a base type with no properties and
no additional properties is allowed by OpenAPI Specification and is
meaningful in many languages (e.g. "marker interfaces" or abstract
closed types).
PR checklist
./bin/generate-samples.sh
to update all Petstore samples related to your fix. This is important, as CI jobs will verify all generator outputs of your HEAD commit as it would merge with master. These must match the expectations made by your contribution. You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example./bin/generate-samples.sh bin/config/java*
. For Windows users, please run the script in Git BASH.master
cc @OpenAPITools/generator-core-team