Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 2 additions & 0 deletions docs/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,8 @@ Another useful option is `inlineSchemaOptions`, which allows you to customize ho
- `REFACTOR_ALLOF_INLINE_SCHEMAS=true` will restore the 6.x (or below) behaviour to refactor allOf inline schemas into $ref. (v7.0.0 will skip the refactoring of these allOf inline schemas by default)
- `RESOLVE_INLINE_ENUMS=true` will refactor inline enum definitions into $ref. This must be activated to allow the renaming of inline enum definitions using `inlineSchemaNameMappings`.

Note: anonymous (untitled) inline schemas are **not** reused across different parent schemas by default (issue #18963). When two parent schemas define structurally identical inline objects without a `title`, each parent gets its own named model (e.g. `Item_user` and `Issue_user`). To have an inline schema reused wherever it appears, add an explicit `title` field — only titled inline schemas are eligible for reuse.

## OpenAPI Normalizer

OpenAPI Normalizer transforms the input OpenAPI doc/spec (which may not perfectly conform to the specification) to make it workable with OpenAPI Generator. A few rules are switched on by default since 7.0.0 release:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,15 @@ private String matchGenerated(Schema model) {
if (skipSchemaReuse) { // skip reusing schema
return null;
}
// Only titled schemas represent intentionally shared named types eligible for reuse.
// Schemas without a meaningful title have context-derived names (e.g. Item_user) that are
// arbitrary — reusing one parent's model for a different parent's structurally-identical
// inline schema forces a misleading name onto the other context. "Meaningful title" mirrors
// the same check in resolveModelName: non-null and not empty after sanitization.
String title = model.getTitle();
if (title == null || "".equals(sanitizeName(title).replace("_", ""))) {
return null;
}

try {
// Exact content match.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3522,22 +3522,24 @@ public void testVarsAndRequiredVarsPresent() {
assertEquals(co.bodyParams.get(0).vars, vars);
assertEquals(co.bodyParams.get(0).requiredVars, requiredVars);

// CodegenOperation puts the inline schema into schemas and refs it
// CodegenOperation puts the inline schema into schemas and refs it; the response gets its
// own named model since request and response are anonymous schemas in different contexts
assertTrue(co.responses.get(0).isModel);
assertEquals("objectWithOptionalAndRequiredProps_request", co.responses.get(0).baseType);
modelName = "objectWithOptionalAndRequiredProps_request";
assertEquals("objectWithOptionalAndRequiredProps_200_response", co.responses.get(0).baseType);
modelName = "objectWithOptionalAndRequiredProps_200_response";
sc = openAPI.getComponents().getSchemas().get(modelName);
cm = codegen.fromModel(modelName, sc);
assertEquals(cm.vars, vars);
assertEquals(cm.requiredVars, requiredVars);

// CodegenProperty puts the inline schema into schemas and refs it
// CodegenProperty puts the inline schema into schemas and refs it; anonymous component
// inline property gets its own named model derived from parent schema and property name
modelName = "ObjectPropContainsProps";
sc = openAPI.getComponents().getSchemas().get(modelName);
cm = codegen.fromModel(modelName, sc);
CodegenProperty cp = cm.getVars().get(0);
assertTrue(cp.isModel);
assertEquals("objectWithOptionalAndRequiredProps_request", cp.complexType);
assertEquals("ObjectPropContainsProps_a", cp.complexType);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1186,6 +1186,9 @@ public void testInlineSchemaOptions() {

@Test
public void testInlineSchemaSkipReuseSetToFalse() {
// meta_200_response and mega_200_response are anonymous (untitled) inline schemas under
// different operations. Anonymous inline schemas are not reused across parents, so both
// receive their own named model even though they are structurally identical.
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/inline_model_resolver.yaml");
InlineModelResolver resolver = new InlineModelResolver();
Map<String, String> inlineSchemaOptions = new HashMap<>();
Expand All @@ -1197,9 +1200,10 @@ public void testInlineSchemaSkipReuseSetToFalse() {
assertTrue(schema.getProperties().get("name") instanceof StringSchema);
assertTrue(schema.getProperties().get("id") instanceof IntegerSchema);

// mega_200_response is NOT created since meta_200_response is reused
Schema schema2 = openAPI.getComponents().getSchemas().get("mega_200_response");
assertNull(schema2);
assertNotNull(schema2);
assertTrue(schema2.getProperties().get("name") instanceof StringSchema);
assertTrue(schema2.getProperties().get("id") instanceof IntegerSchema);
}

@Test
Expand All @@ -1220,6 +1224,86 @@ public void testInlineSchemaSkipReuseSetToTrue() {
assertTrue(schema2.getProperties().get("id") instanceof IntegerSchema);
}

@Test
public void resolveAnonymousInlineSchemaNotReusedAcrossParents() {
// Two different component schemas (Item, Issue) each define an anonymous (untitled) inline
// `user` object with identical members (id, name). Each parent must produce its own named
// model; the name from one parent's context must not be imposed on the other.
OpenAPI openapi = new OpenAPI();
openapi.setComponents(new Components());

Schema userSchema = new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("name", new StringSchema());

Schema anotherUserSchema = new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("name", new StringSchema());

openapi.getComponents().addSchemas("Item", new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("user", userSchema));

openapi.getComponents().addSchemas("Issue", new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("user", anotherUserSchema));

new InlineModelResolver().flatten(openapi);

Map<String, Schema> schemas = openapi.getComponents().getSchemas();

// Both inline user schemas must exist as separate named models
assertNotNull("Item_user model must exist", schemas.get("Item_user"));
assertNotNull("Issue_user model must exist", schemas.get("Issue_user"));

// Item.user must reference its own model
Schema itemUser = (Schema) schemas.get("Item").getProperties().get("user");
assertEquals("#/components/schemas/Item_user", itemUser.get$ref());

// Issue.user must reference its own model, not Item_user
Schema issueUser = (Schema) schemas.get("Issue").getProperties().get("user");
assertEquals("#/components/schemas/Issue_user", issueUser.get$ref());
}

@Test
public void resolveTitledInlineSchemaIsReusedAcrossParents() {
// A titled inline schema represents a named type that is intentionally shared. When two
// different parent schemas each have a property whose inline schema carries the same title
// and identical structure, both properties must reference the same named model — only one
// entry is created in components, not a numbered variant.
OpenAPI openapi = new OpenAPI();
openapi.setComponents(new Components());

openapi.getComponents().addSchemas("Item", new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("user", new ObjectSchema()
.title("UserSummary")
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("name", new StringSchema())));

openapi.getComponents().addSchemas("Issue", new ObjectSchema()
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("user", new ObjectSchema()
.title("UserSummary")
.addProperty("id", new StringSchema().format("uuid"))
.addProperty("name", new StringSchema())));

new InlineModelResolver().flatten(openapi);

Map<String, Schema> schemas = openapi.getComponents().getSchemas();

// Titled inline schema must be deduplicated — no numbered variant
assertNotNull("UserSummary schema must exist", schemas.get("UserSummary"));
assertNull("Duplicate UserSummary_1 must not exist", schemas.get("UserSummary_1"));

// Both parents must reference the same shared model
Schema itemUser = (Schema) schemas.get("Item").getProperties().get("user");
assertEquals("#/components/schemas/UserSummary", itemUser.get$ref());

Schema issueUser = (Schema) schemas.get("Issue").getProperties().get("user");
assertEquals("#/components/schemas/UserSummary", issueUser.get$ref());
}

@Test
public void resolveInlineRequestBodyAllOf() {
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/inline_model_resolver.yaml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
.travis.yml
DESCRIPTION
NAMESPACE
R/add_pet_optional_request.R
R/allof_tag_api_response.R
R/animal.R
R/any_of_pig.R
Expand Down Expand Up @@ -40,6 +41,7 @@ R/user_api.R
R/whale.R
R/zebra.R
README.md
docs/AddPetOptionalRequest.md
docs/AllofTagApiResponse.md
docs/Animal.md
docs/AnyOfPig.md
Expand Down Expand Up @@ -72,3 +74,4 @@ docs/Whale.md
docs/Zebra.md
git_push.sh
tests/testthat.R
tests/testthat/test_add_pet_optional_request.R
1 change: 1 addition & 0 deletions samples/client/petstore/R-httr2-wrapper/NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export(ApiResponse)
export(ApiException)

# Models
export(AddPetOptionalRequest)
export(AllofTagApiResponse)
export(Animal)
export(AnyOfPig)
Expand Down
Loading