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
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

+11
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

+12
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
}
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
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
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
6.1.0-SNAPSHOT
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+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export * from './DefaultApi';
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';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
import { exists, mapValues } from '../runtime';
16+
import type { Owner } from './Owner';
17+
import {
18+
OwnerFromJSON,
19+
OwnerFromJSONTyped,
20+
OwnerToJSON,
21+
} from './Owner';
22+
23+
/**
24+
*
25+
* @export
26+
* @interface Club
27+
*/
28+
export interface Club {
29+
/**
30+
*
31+
* @type {Owner}
32+
* @memberof Club
33+
*/
34+
readonly owner?: Owner;
35+
}
36+
37+
/**
38+
* Check if a given object implements the Club interface.
39+
*/
40+
export function instanceOfClub(value: object): boolean {
41+
let isInstance = true;
42+
43+
return isInstance;
44+
}
45+
46+
export function ClubFromJSON(json: any): Club {
47+
return ClubFromJSONTyped(json, false);
48+
}
49+
50+
export function ClubFromJSONTyped(json: any, ignoreDiscriminator: boolean): Club {
51+
if ((json === undefined) || (json === null)) {
52+
return json;
53+
}
54+
return {
55+
56+
'owner': !exists(json, 'owner') ? undefined : OwnerFromJSON(json['owner']),
57+
};
58+
}
59+
60+
export function ClubToJSON(value?: Club | null): any {
61+
if (value === undefined) {
62+
return undefined;
63+
}
64+
if (value === null) {
65+
return null;
66+
}
67+
return {
68+
69+
};
70+
}
71+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
import { exists, mapValues } from '../runtime';
16+
/**
17+
*
18+
* @export
19+
* @interface Owner
20+
*/
21+
export interface Owner {
22+
/**
23+
*
24+
* @type {string}
25+
* @memberof Owner
26+
*/
27+
name?: string;
28+
}
29+
30+
/**
31+
* Check if a given object implements the Owner interface.
32+
*/
33+
export function instanceOfOwner(value: object): boolean {
34+
let isInstance = true;
35+
36+
return isInstance;
37+
}
38+
39+
export function OwnerFromJSON(json: any): Owner {
40+
return OwnerFromJSONTyped(json, false);
41+
}
42+
43+
export function OwnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): Owner {
44+
if ((json === undefined) || (json === null)) {
45+
return json;
46+
}
47+
return {
48+
49+
'name': !exists(json, 'name') ? undefined : json['name'],
50+
};
51+
}
52+
53+
export function OwnerToJSON(value?: Owner | null): any {
54+
if (value === undefined) {
55+
return undefined;
56+
}
57+
if (value === null) {
58+
return null;
59+
}
60+
return {
61+
62+
'name': value.name,
63+
};
64+
}
65+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
export * from './Club';
4+
export * from './Owner';

0 commit comments

Comments
 (0)