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

Better support for $ref to schema's properties #18233

Merged
merged 2 commits into from
Mar 27, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2464,6 +2464,14 @@ public Schema unaliasSchema(Schema schema) {
protected String getSingleSchemaType(Schema schema) {
Schema unaliasSchema = unaliasSchema(schema);

if (ModelUtils.isRefToSchemaWithProperties(unaliasSchema.get$ref())) {
// ref to schema's properties, e.g. #/components/schemas/Pet/properties/category
Schema refSchema = ModelUtils.getReferencedSchema(openAPI, unaliasSchema);
if (refSchema != null) {
return getSingleSchemaType(refSchema);
}
}

if (StringUtils.isNotBlank(unaliasSchema.get$ref())) { // reference to another definition/schema
// get the schema/model name from $ref
String schemaName = ModelUtils.getSimpleRef(unaliasSchema.get$ref());
Expand Down Expand Up @@ -3354,7 +3362,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
String modelName = ModelUtils.getSimpleRef(((Schema) oneOf).get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) oneOf, discPropName, visitedSchemas);
if (thisCp == null) {
LOGGER.warn(
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the referenced OneOf schema '{}' is missing {}",
composedSchemaName, discPropName, modelName, discPropName);
}
Expand All @@ -3363,7 +3371,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
continue;
}
if (cp != thisCp) {
LOGGER.warn(
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the OneOf schema '{}' has a different {} definition than the prior OneOf schema's. Make sure the {} type and required values are the same",
composedSchemaName, discPropName, modelName, discPropName, discPropName);
}
Expand All @@ -3377,7 +3385,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
String modelName = ModelUtils.getSimpleRef(((Schema) anyOf).get$ref());
CodegenProperty thisCp = discriminatorFound(composedSchemaName, (Schema) anyOf, discPropName, visitedSchemas);
if (thisCp == null) {
LOGGER.warn(
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the referenced AnyOf schema '{}' is missing {}",
composedSchemaName, discPropName, modelName, discPropName);
}
Expand All @@ -3386,7 +3394,7 @@ private CodegenProperty discriminatorFound(String composedSchemaName, Schema sc,
continue;
}
if (cp != thisCp) {
LOGGER.warn(
once(LOGGER).warn(
"'{}' defines discriminator '{}', but the AnyOf schema '{}' has a different {} definition than the prior AnyOf schema's. Make sure the {} type and required values are the same",
composedSchemaName, discPropName, modelName, discPropName, discPropName);
}
Expand Down Expand Up @@ -3454,7 +3462,7 @@ private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList<Schema> vis
}
}
if (discriminatorsPropNames.size() > 1) {
LOGGER.warn("The oneOf schemas have conflicting discriminator property names. " +
once(LOGGER).warn("The oneOf schemas have conflicting discriminator property names. " +
"oneOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
}
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getOneOf().size() && discriminatorsPropNames.size() == 1) {
Expand Down Expand Up @@ -3483,7 +3491,7 @@ private Discriminator recursiveGetDiscriminator(Schema sc, ArrayList<Schema> vis
}
}
if (discriminatorsPropNames.size() > 1) {
LOGGER.warn("The anyOf schemas have conflicting discriminator property names. " +
once(LOGGER).warn("The anyOf schemas have conflicting discriminator property names. " +
"anyOf schemas must have the same property name, but found " + String.join(", ", discriminatorsPropNames));
}
if (foundDisc != null && (hasDiscriminatorCnt + hasNullTypeCnt) == composedSchema.getAnyOf().size() && discriminatorsPropNames.size() == 1) {
Expand Down Expand Up @@ -3532,7 +3540,7 @@ protected List<MappedModel> getOneOfAnyOfDescendants(String composedSchemaName,
// schemas also has inline composed schemas
// Note: if it is only inline one level, then the inline model resolver will move it into its own
// schema and make it a $ref schema in the oneOf/anyOf location
LOGGER.warn(
once(LOGGER).warn(
"Invalid inline schema defined in oneOf/anyOf in '{}'. Per the OpenApi spec, for this case when a composed schema defines a discriminator, the oneOf/anyOf schemas must use $ref. Change this inline definition to a $ref definition",
composedSchemaName);
}
Expand All @@ -3554,14 +3562,14 @@ protected List<MappedModel> getOneOfAnyOfDescendants(String composedSchemaName,
msgSuffix += spacer + "invalid optional definition of " + discPropName + ", include it in required";
}
}
LOGGER.warn("'{}' defines discriminator '{}', but the referenced schema '{}' is incorrect. {}",
once(LOGGER).warn("'{}' defines discriminator '{}', but the referenced schema '{}' is incorrect. {}",
composedSchemaName, discPropName, modelName, msgSuffix);
}
MappedModel mm = new MappedModel(modelName, toModelName(modelName));
descendentSchemas.add(mm);
Schema cs = ModelUtils.getSchema(openAPI, modelName);
if (cs == null) { // cannot lookup the model based on the name
LOGGER.error("Failed to lookup the schema '{}' when processing oneOf/anyOf. Please check to ensure it's defined properly.", modelName);
once(LOGGER).error("Failed to lookup the schema '{}' when processing oneOf/anyOf. Please check to ensure it's defined properly.", modelName);
} else {
Map<String, Object> vendorExtensions = cs.getExtensions();
if (vendorExtensions != null && !vendorExtensions.isEmpty() && vendorExtensions.containsKey("x-discriminator-value")) {
Expand Down Expand Up @@ -3674,7 +3682,7 @@ protected CodegenDiscriminator createDiscriminator(String schemaName, Schema sch
if (e.getValue().indexOf('/') >= 0) {
name = ModelUtils.getSimpleRef(e.getValue());
if (ModelUtils.getSchema(openAPI, name) == null) {
LOGGER.error("Failed to lookup the schema '{}' when processing the discriminator mapping of oneOf/anyOf. Please check to ensure it's defined properly.", name);
once(LOGGER).error("Failed to lookup the schema '{}' when processing the discriminator mapping of oneOf/anyOf. Please check to ensure it's defined properly.", name);
}
} else {
name = e.getValue();
Expand Down Expand Up @@ -3983,6 +3991,13 @@ public CodegenProperty fromProperty(String name, Schema p, boolean required, boo
return cpc;
}

// if it's ref to schema's properties, get the actual schema defined in the properties
Schema refToPropertiesSchema = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, p.get$ref());
if (refToPropertiesSchema != null) {
p = refToPropertiesSchema;
return fromProperty(name, refToPropertiesSchema, required, schemaIsFromAdditionalProperties);
}

Schema original = null;
// check if it's allOf (only 1 sub schema) with or without default/nullable/etc set in the top level
if (ModelUtils.isAllOf(p) && p.getAllOf().size() == 1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,16 +929,76 @@ public static boolean shouldGenerateArrayModel(Schema schema) {
* @return schema without '$ref'
*/
public static Schema getReferencedSchema(OpenAPI openAPI, Schema schema) {
if (schema != null && StringUtils.isNotEmpty(schema.get$ref())) {
String name = getSimpleRef(schema.get$ref());
Schema referencedSchema = getSchema(openAPI, name);
if (referencedSchema != null) {
return referencedSchema;
if (schema == null) {
return null;
}

if (StringUtils.isEmpty(schema.get$ref())) {
return schema;
}

try {
Schema refSchema = getSchemaFromRefToSchemaWithProperties(openAPI, schema.get$ref());
if (refSchema != null) {
// it's ref to schema's properties, #/components/schemas/Pet/properties/category for example
return refSchema;
}
} catch (Exception e) {
LOGGER.warn("Failed to parse $ref {}. Please report the issue to openapi-generator GitHub repo.", schema.get$ref());
}

// a simple ref, e.g. #/components/schemas/Pet
String name = getSimpleRef(schema.get$ref());
Schema referencedSchema = getSchema(openAPI, name);
if (referencedSchema != null) {
return referencedSchema;
}

return schema;
}

/**
* Get the schema referenced by $ref to schema's properties, e.g. #/components/schemas/Pet/properties/category.
*
* @param openAPI specification being checked
* @param refString schema reference
* @return schema
*/
public static Schema getSchemaFromRefToSchemaWithProperties(OpenAPI openAPI, String refString) {
if (refString == null) {
return null;
}

String[] parts = refString.split("/");
// #/components/schemas/Pet/properties/category
if (parts.length == 6 && "properties".equals(parts[4])) {
Schema referencedSchema = getSchema(openAPI, parts[3]); // parts[3] is Pet
return (Schema) referencedSchema.getProperties().get(parts[5]); // parts[5] is category
} else {
return null;
}
}

/**
* Returns true if $ref to a reference to schema's properties, e.g. #/components/schemas/Pet/properties/category.
*
* @param refString schema reference
* @return true if $ref to a reference to schema's properties
*/
public static boolean isRefToSchemaWithProperties(String refString) {
if (refString == null) {
return false;
}

String[] parts = refString.split("/");
// #/components/schemas/Pet/properties/category
if (parts.length == 6 && "properties".equals(parts[4])) {
return true;
} else {
return false;
}
}

public static Schema getSchema(OpenAPI openAPI, String name) {
if (name == null) {
return null;
Expand Down Expand Up @@ -1272,7 +1332,9 @@ public static Schema unaliasSchema(OpenAPI openAPI,
}
Schema ref = allSchemas.get(simpleRef);
if (ref == null) {
once(LOGGER).warn("{} is not defined", schema.get$ref());
if (!isRefToSchemaWithProperties(schema.get$ref())) {
once(LOGGER).warn("{} is not defined", schema.get$ref());
}
return schema;
} else if (ref.getEnum() != null && !ref.getEnum().isEmpty()) {
// top-level enum class
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,25 @@ public void testSimpleRefDecoding() {
Assert.assertEquals(decoded, "~1 Hallo/Welt");
}

@Test
public void testRefToSchemaProperties() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore.yaml");

Schema category = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet/properties/category");
Assert.assertEquals(category.get$ref(), "#/components/schemas/Category");

Schema name = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet/properties/name");
Assert.assertEquals(name.getType(), "string");

Schema id = ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet/properties/id");
Assert.assertEquals(id.getType(), "integer");
Assert.assertEquals(id.getFormat(), "int64");

Assert.assertEquals(null, ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet/prop/category"));
Assert.assertEquals(null, ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet/properties/categoryyyy"));
Assert.assertEquals(null, ModelUtils.getSchemaFromRefToSchemaWithProperties(openAPI, "#/components/schemas/Pet"));
}


// 3.0 spec tests
@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2655,3 +2655,23 @@ components:
PetComposition:
allOf:
- $ref: '#/components/schemas/Pet'
PetRef:
type: object
required:
- name
- photoUrls
properties:
id:
$ref: '#/components/schemas/Pet/properties/id'
category:
$ref: '#/components/schemas/Pet/properties/category'
name:
$ref: '#/components/schemas/Pet/properties/name'
photoUrls:
$ref: '#/components/schemas/Pet/properties/photoUrls'
tags:
$ref: '#/components/schemas/Pet/properties/tags'
status:
$ref: '#/components/schemas/Pet/properties/status'
xml:
name: Pet
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ docs/ParentPet.md
docs/Pet.md
docs/PetApi.md
docs/PetComposition.md
docs/PetRef.md
docs/PetUsingAllOf.md
docs/PetWithRequiredTags.md
docs/Pig.md
Expand Down Expand Up @@ -213,6 +214,7 @@ src/main/java/org/openapitools/client/model/OuterEnumIntegerDefaultValue.java
src/main/java/org/openapitools/client/model/ParentPet.java
src/main/java/org/openapitools/client/model/Pet.java
src/main/java/org/openapitools/client/model/PetComposition.java
src/main/java/org/openapitools/client/model/PetRef.java
src/main/java/org/openapitools/client/model/PetUsingAllOf.java
src/main/java/org/openapitools/client/model/PetWithRequiredTags.java
src/main/java/org/openapitools/client/model/Pig.java
Expand Down
1 change: 1 addition & 0 deletions samples/client/petstore/java/okhttp-gson/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ Class | Method | HTTP request | Description
- [ParentPet](docs/ParentPet.md)
- [Pet](docs/Pet.md)
- [PetComposition](docs/PetComposition.md)
- [PetRef](docs/PetRef.md)
- [PetUsingAllOf](docs/PetUsingAllOf.md)
- [PetWithRequiredTags](docs/PetWithRequiredTags.md)
- [Pig](docs/Pig.md)
Expand Down
20 changes: 20 additions & 0 deletions samples/client/petstore/java/okhttp-gson/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2691,6 +2691,26 @@ components:
PetComposition:
allOf:
- $ref: '#/components/schemas/Pet'
PetRef:
properties:
id:
$ref: '#/components/schemas/Pet/properties/id'
category:
$ref: '#/components/schemas/Pet/properties/category'
name:
$ref: '#/components/schemas/Pet/properties/name'
photoUrls:
$ref: '#/components/schemas/Pet/properties/photoUrls'
tags:
$ref: '#/components/schemas/Pet/properties/tags'
status:
$ref: '#/components/schemas/Pet/properties/status'
required:
- name
- photoUrls
type: object
xml:
name: Pet
_foo_get_default_response:
example:
string:
Expand Down
28 changes: 28 additions & 0 deletions samples/client/petstore/java/okhttp-gson/docs/PetRef.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@


# PetRef


## Properties

| Name | Type | Description | Notes |
|------------ | ------------- | ------------- | -------------|
|**id** | **Long** | | [optional] |
|**category** | [**Category**](Category.md) | | [optional] |
|**name** | **String** | | |
|**photoUrls** | **List&lt;String&gt;** | | |
|**tags** | [**List&lt;Tag&gt;**](Tag.md) | | [optional] |
|**status** | [**StatusEnum**](#StatusEnum) | pet status in the store | [optional] |



## Enum: StatusEnum

| Name | Value |
|---- | -----|
| AVAILABLE | &quot;available&quot; |
| PENDING | &quot;pending&quot; |
| SOLD | &quot;sold&quot; |



Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ private static Class getClassByDiscriminator(Map classByDiscriminatorValue, Stri
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.ParentPet.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.Pet.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.PetComposition.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.PetRef.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.PetUsingAllOf.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.PetWithRequiredTags.CustomTypeAdapterFactory());
gsonBuilder.registerTypeAdapterFactory(new org.openapitools.client.model.Pig.CustomTypeAdapterFactory());
Expand Down
Loading
Loading