Skip to content

Commit

Permalink
Nullable subschemas (#13850)
Browse files Browse the repository at this point in the history
* Add failing example of nullable subschema

* Do not generate new subschemas when nullable

* Generate client

* Update go example schema/test
  • Loading branch information
mrginglymus authored Dec 22, 2022
1 parent 028b38d commit 71a7a82
Show file tree
Hide file tree
Showing 16 changed files with 715 additions and 9 deletions.
4 changes: 4 additions & 0 deletions bin/configs/typescript-fetch-allOf-nullable.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
generatorName: typescript-fetch
outputDir: samples/client/petstore/typescript-fetch/builds/allOf-nullable
inputSpec: modules/openapi-generator/src/test/resources/3_0/allOf-nullable.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript-fetch
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,16 @@ private boolean isModelNeeded(Schema schema, Set<Schema> visitedSchemas) {
// allOf, anyOf, oneOf
ComposedSchema m = (ComposedSchema) schema;

if (m.getAllOf() != null && m.getAllOf().size() == 1 && m.getReadOnly() != null && m.getReadOnly()) {
// Check if this composed schema only contains an allOf and a readOnly.
boolean isSingleAllOf = m.getAllOf() != null && m.getAllOf().size() == 1;
boolean isReadOnly = m.getReadOnly() != null && m.getReadOnly();
boolean isNullable = m.getNullable() != null && m.getNullable();

if (isSingleAllOf && (isReadOnly || isNullable)) {
// Check if this composed schema only contains an allOf and a readOnly or nullable.
ComposedSchema c = new ComposedSchema();
c.setAllOf(m.getAllOf());
c.setReadOnly(true);
c.setReadOnly(m.getReadOnly());
c.setNullable(m.getNullable());
if (m.equals(c)) {
return isModelNeeded(m.getAllOf().get(0), visitedSchemas);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ public void testNullableComposition() throws IOException {
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);

TestUtils.assertFileContains(Paths.get(output + "/model_example.go"), "Child NullableExampleChild");
TestUtils.assertFileContains(Paths.get(output + "/model_example.go"), "Child NullableChild");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -470,4 +470,14 @@ public void testNestedReadonlySchemas() {
final Map<String, Schema> schemaBefore = openAPI.getComponents().getSchemas();
Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
}

@Test(description = "Don't generate new schemas for nullable references")
public void testNestedNullableSchemas() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/allOf-nullable.yaml");
final DefaultCodegen codegen = new TypeScriptFetchClientCodegen();
codegen.processOpts();
codegen.setOpenAPI(openAPI);
final Map<String, Schema> schemaBefore = openAPI.getComponents().getSchemas();
Assert.assertEquals(schemaBefore.keySet(), Sets.newHashSet("club", "owner"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
openapi: 3.0.1
info:
version: 1.0.0
title: Example
license:
name: MIT
servers:
- url: http://api.example.xyz/v1
paths:
/person/display/{personId}:
get:
parameters:
- name: personId
in: path
required: true
description: The id of the person to retrieve
schema:
type: string
operationId: list
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/club"
components:
schemas:
club:
properties:
owner:
allOf:
- $ref: '#/components/schemas/owner'
nullable: true

owner:
properties:
name:
type: string
maxLength: 255
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apis/DefaultApi.ts
apis/index.ts
index.ts
models/Club.ts
models/Owner.ts
models/index.ts
runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6.2.1-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* tslint:disable */
/* eslint-disable */
/**
* Example
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/


import * as runtime from '../runtime';
import type {
Club,
} from '../models';
import {
ClubFromJSON,
ClubToJSON,
} from '../models';

export interface ListRequest {
personId: string;
}

/**
*
*/
export class DefaultApi extends runtime.BaseAPI {

/**
*/
async listRaw(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<Club>> {
if (requestParameters.personId === null || requestParameters.personId === undefined) {
throw new runtime.RequiredError('personId','Required parameter requestParameters.personId was null or undefined when calling list.');
}

const queryParameters: any = {};

const headerParameters: runtime.HTTPHeaders = {};

const response = await this.request({
path: `/person/display/{personId}`.replace(`{${"personId"}}`, encodeURIComponent(String(requestParameters.personId))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);

return new runtime.JSONApiResponse(response, (jsonValue) => ClubFromJSON(jsonValue));
}

/**
*/
async list(requestParameters: ListRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Club> {
const response = await this.listRaw(requestParameters, initOverrides);
return await response.value();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* tslint:disable */
/* eslint-disable */
export * from './DefaultApi';
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/* tslint:disable */
/* eslint-disable */
export * from './runtime';
export * from './apis';
export * from './models';
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* tslint:disable */
/* eslint-disable */
/**
* Example
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
import type { Owner } from './Owner';
import {
OwnerFromJSON,
OwnerFromJSONTyped,
OwnerToJSON,
} from './Owner';

/**
*
* @export
* @interface Club
*/
export interface Club {
/**
*
* @type {Owner}
* @memberof Club
*/
owner?: Owner | null;
}

/**
* Check if a given object implements the Club interface.
*/
export function instanceOfClub(value: object): boolean {
let isInstance = true;

return isInstance;
}

export function ClubFromJSON(json: any): Club {
return ClubFromJSONTyped(json, false);
}

export function ClubFromJSONTyped(json: any, ignoreDiscriminator: boolean): Club {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'owner': !exists(json, 'owner') ? undefined : OwnerFromJSON(json['owner']),
};
}

export function ClubToJSON(value?: Club | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'owner': OwnerToJSON(value.owner),
};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* tslint:disable */
/* eslint-disable */
/**
* Example
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface Owner
*/
export interface Owner {
/**
*
* @type {string}
* @memberof Owner
*/
name?: string;
}

/**
* Check if a given object implements the Owner interface.
*/
export function instanceOfOwner(value: object): boolean {
let isInstance = true;

return isInstance;
}

export function OwnerFromJSON(json: any): Owner {
return OwnerFromJSONTyped(json, false);
}

export function OwnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): Owner {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'name': !exists(json, 'name') ? undefined : json['name'],
};
}

export function OwnerToJSON(value?: Owner | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'name': value.name,
};
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/* tslint:disable */
/* eslint-disable */
export * from './Club';
export * from './Owner';
Loading

0 comments on commit 71a7a82

Please sign in to comment.