Skip to content

Commit

Permalink
Better support for $ref to schema's properties (#18233)
Browse files Browse the repository at this point in the history
* use once

* add support for reference to schema properties
  • Loading branch information
wing328 authored Mar 27, 2024
1 parent 309918d commit e78aeb6
Show file tree
Hide file tree
Showing 11 changed files with 763 additions and 16 deletions.
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

0 comments on commit e78aeb6

Please sign in to comment.