Skip to content

Commit f6be1d0

Browse files
authored
Do not generate new models for readonly schema references (#13303)
* Add example of nested schema issue * Add failing test case * Special case properties with a single allOf and readonly * Remove rogue file from FILES
1 parent 2a007b3 commit f6be1d0

File tree

14 files changed

+715
-0
lines changed

14 files changed

+715
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
generatorName: typescript-fetch
2+
outputDir: samples/client/petstore/typescript-fetch/builds/allOf-readonly
3+
inputSpec: modules/openapi-generator/src/test/resources/3_0/allOf-readonly.yaml
4+
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch

modules/openapi-generator/src/main/java/org/openapitools/codegen/InlineModelResolver.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ private boolean isModelNeeded(Schema schema, Set<Schema> visitedSchemas) {
189189
if (schema instanceof ComposedSchema) {
190190
// allOf, anyOf, oneOf
191191
ComposedSchema m = (ComposedSchema) schema;
192+
193+
if (m.getAllOf() != null && m.getAllOf().size() == 1 && m.getReadOnly() != null && m.getReadOnly()) {
194+
// Check if this composed schema only contains an allOf and a readOnly.
195+
ComposedSchema c = new ComposedSchema();
196+
c.setAllOf(m.getAllOf());
197+
c.setReadOnly(true);
198+
if (m.equals(c)) {
199+
return isModelNeeded(m.getAllOf().get(0), visitedSchemas);
200+
}
201+
}
202+
192203
if (m.getAllOf() != null && !m.getAllOf().isEmpty()) {
193204
// check to ensure at least of the allOf item is model
194205
for (Schema inner : m.getAllOf()) {

modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchModelTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@
3636
import java.time.OffsetDateTime;
3737
import java.time.ZoneOffset;
3838
import java.util.Arrays;
39+
import java.util.Collections;
3940
import java.util.Date;
4041
import java.util.HashMap;
4142
import java.util.Locale;
43+
import java.util.Map;
4244

4345
/*
4446
import static io.swagger.codegen.CodegenConstants.IS_ENUM_EXT_NAME;
@@ -458,4 +460,14 @@ public void testWithoutNullSafeAdditionalProps() {
458460

459461
Assert.assertEquals(codegen.getTypeDeclaration(model), "{ [key: string]: string; }");
460462
}
463+
464+
@Test(description = "Don't generate new schemas for readonly references")
465+
public void testNestedReadonlySchemas() {
466+
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf-readonly.yaml");
467+
final DefaultCodegen codegen = new TypeScriptFetchClientCodegen();
468+
codegen.processOpts();
469+
codegen.setOpenAPI(openAPI);
470+
final Map<String, Schema> schemaBefore = openAPI.getComponents().getSchemas();
471+
Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
472+
}
461473
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
openapi: 3.0.1
2+
info:
3+
version: 1.0.0
4+
title: Example
5+
license:
6+
name: MIT
7+
servers:
8+
- url: http://api.example.xyz/v1
9+
paths:
10+
/person/display/{personId}:
11+
get:
12+
parameters:
13+
- name: personId
14+
in: path
15+
required: true
16+
description: The id of the person to retrieve
17+
schema:
18+
type: string
19+
operationId: list
20+
responses:
21+
'200':
22+
description: OK
23+
content:
24+
application/json:
25+
schema:
26+
$ref: "#/components/schemas/club"
27+
components:
28+
schemas:
29+
club:
30+
properties:
31+
owner:
32+
allOf:
33+
- $ref: '#/components/schemas/owner'
34+
readOnly: true
35+
36+
owner:
37+
properties:
38+
name:
39+
type: string
40+
maxLength: 255
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# OpenAPI Generator Ignore
2+
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
3+
4+
# Use this file to prevent files from being overwritten by the generator.
5+
# The patterns follow closely to .gitignore or .dockerignore.
6+
7+
# As an example, the C# client generator defines ApiClient.cs.
8+
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
9+
#ApiClient.cs
10+
11+
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
12+
#foo/*/qux
13+
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
14+
15+
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
16+
#foo/**/qux
17+
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
18+
19+
# You can also negate patterns with an exclamation (!).
20+
# For example, you can ignore all files in a docs folder with the file extension .md:
21+
#docs/*.md
22+
# Then explicitly reverse the ignore rule for a single file:
23+
#!docs/README.md
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
apis/DefaultApi.ts
2+
apis/index.ts
3+
index.ts
4+
models/Club.ts
5+
models/Owner.ts
6+
models/index.ts
7+
runtime.ts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6.1.0-SNAPSHOT
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
/**
4+
* Example
5+
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
6+
*
7+
* The version of the OpenAPI document: 1.0.0
8+
*
9+
*
10+
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
11+
* https://openapi-generator.tech
12+
* Do not edit the class manually.
13+
*/
14+
15+
16+
import * as runtime from '../runtime';
17+
import type {
18+
Club,
19+
} from '../models';
20+
import {
21+
ClubFromJSON,
22+
ClubToJSON,
23+
} from '../models';
24+
25+
export interface ListRequest {
26+
personId: string;
27+
}
28+
29+
/**
30+
*
31+
*/
32+
export class DefaultApi extends runtime.BaseAPI {
33+
34+
/**
35+
*/
36+
async listRaw(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Club>> {
37+
if (requestParameters.personId === null || requestParameters.personId === undefined) {
38+
throw new runtime.RequiredError('personId','Required parameter requestParameters.personId was null or undefined when calling list.');
39+
}
40+
41+
const queryParameters: any = {};
42+
43+
const headerParameters: runtime.HTTPHeaders = {};
44+
45+
const response = await this.request({
46+
path: `/person/display/{personId}`.replace(`{${"personId"}}`, encodeURIComponent(String(requestParameters.personId))),
47+
method: 'GET',
48+
headers: headerParameters,
49+
query: queryParameters,
50+
}, initOverrides);
51+
52+
return new runtime.JSONApiResponse(response, (jsonValue) => ClubFromJSON(jsonValue));
53+
}
54+
55+
/**
56+
*/
57+
async list(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Club> {
58+
const response = await this.listRaw(requestParameters, initOverrides);
59+
return await response.value();
60+
}
61+
62+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export * from './DefaultApi';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export * from './runtime';
4+
export * from './apis';
5+
export * from './models';

0 commit comments

Comments
 (0)