[CodegenResponse] various enhancements and bug fixes (#10984)

* add more response tests, fix simpleType

* samples update

* replace simpleType with containerType

* update doc template

* fix typo in mustache tag
This commit is contained in:
William Cheng 2021-12-05 17:43:17 +08:00 committed by GitHub
parent 186842ea19
commit 5451b77d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 643 additions and 12 deletions

View File

@ -29,7 +29,8 @@ public class CodegenOperation {
isArray, isMultipart, isArray, isMultipart,
isResponseBinary = false, isResponseFile = false, hasReference = false, isResponseBinary = false, isResponseFile = false, hasReference = false,
isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy, isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy,
isRestful, isDeprecated, isCallbackRequest, uniqueItems, hasDefaultResponse = false; isRestful, isDeprecated, isCallbackRequest, uniqueItems, hasDefaultResponse = false,
hasErrorResponseObject; // if 4xx, 5xx repsonses have at least one error object defined
public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType, public String path, operationId, returnType, returnFormat, httpMethod, returnBaseType,
returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse; returnContainer, summary, unescapedNotes, notes, baseName, defaultResponse;
public CodegenDiscriminator discriminator; public CodegenDiscriminator discriminator;
@ -297,6 +298,7 @@ public class CodegenOperation {
sb.append(", isResponseFile=").append(isResponseFile); sb.append(", isResponseFile=").append(isResponseFile);
sb.append(", hasReference=").append(hasReference); sb.append(", hasReference=").append(hasReference);
sb.append(", hasDefaultResponse=").append(hasDefaultResponse); sb.append(", hasDefaultResponse=").append(hasDefaultResponse);
sb.append(", hasErrorResponseObject=").append(hasErrorResponseObject);
sb.append(", isRestfulIndex=").append(isRestfulIndex); sb.append(", isRestfulIndex=").append(isRestfulIndex);
sb.append(", isRestfulShow=").append(isRestfulShow); sb.append(", isRestfulShow=").append(isRestfulShow);
sb.append(", isRestfulCreate=").append(isRestfulCreate); sb.append(", isRestfulCreate=").append(isRestfulCreate);
@ -371,6 +373,7 @@ public class CodegenOperation {
isResponseFile == that.isResponseFile && isResponseFile == that.isResponseFile &&
hasReference == that.hasReference && hasReference == that.hasReference &&
hasDefaultResponse == that.hasDefaultResponse && hasDefaultResponse == that.hasDefaultResponse &&
hasErrorResponseObject == that.hasErrorResponseObject &&
isRestfulIndex == that.isRestfulIndex && isRestfulIndex == that.isRestfulIndex &&
isRestfulShow == that.isRestfulShow && isRestfulShow == that.isRestfulShow &&
isRestfulCreate == that.isRestfulCreate && isRestfulCreate == that.isRestfulCreate &&
@ -435,6 +438,7 @@ public class CodegenOperation {
produces, prioritizedContentTypes, servers, bodyParam, allParams, bodyParams, pathParams, queryParams, produces, prioritizedContentTypes, servers, bodyParam, allParams, bodyParams, pathParams, queryParams,
headerParams, formParams, cookieParams, requiredParams, optionalParams, authMethods, tags, headerParams, formParams, cookieParams, requiredParams, optionalParams, authMethods, tags,
responses, callbacks, imports, examples, requestBodyExamples, externalDocs, vendorExtensions, responses, callbacks, imports, examples, requestBodyExamples, externalDocs, vendorExtensions,
nickname, operationIdOriginal, operationIdLowerCase, operationIdCamelCase, operationIdSnakeCase); nickname, operationIdOriginal, operationIdLowerCase, operationIdCamelCase, operationIdSnakeCase,
hasErrorResponseObject);
} }
} }

View File

@ -3976,6 +3976,12 @@ public class DefaultCodegen implements CodegenConfig {
if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseFile)) { if (Boolean.TRUE.equals(r.isFile) && Boolean.TRUE.equals(r.is2xx) && Boolean.FALSE.equals(op.isResponseFile)) {
op.isResponseFile = Boolean.TRUE; op.isResponseFile = Boolean.TRUE;
} }
// check if any 4xx or 5xx reponse has an error response object defined
if ((Boolean.TRUE.equals(r.is4xx) || Boolean.TRUE.equals(r.is5xx)) &&
Boolean.FALSE.equals(r.primitiveType) && Boolean.FALSE.equals(r.simpleType)) {
op.hasErrorResponseObject = Boolean.TRUE;
}
} }
op.responses.sort((a, b) -> { op.responses.sort((a, b) -> {
int aScore = a.isWildcard() ? 2 : a.isRange() ? 1 : 0; int aScore = a.isWildcard() ? 2 : a.isRange() ? 1 : 0;
@ -4269,6 +4275,7 @@ public class DefaultCodegen implements CodegenConfig {
r.setComposedSchemas(getComposedSchemas(responseSchema)); r.setComposedSchemas(getComposedSchemas(responseSchema));
if (ModelUtils.isArraySchema(responseSchema)) { if (ModelUtils.isArraySchema(responseSchema)) {
r.simpleType = false; r.simpleType = false;
r.isArray = true;
r.containerType = cp.containerType; r.containerType = cp.containerType;
ArraySchema as = (ArraySchema) responseSchema; ArraySchema as = (ArraySchema) responseSchema;
CodegenProperty items = fromProperty("response", getSchemaItems(as)); CodegenProperty items = fromProperty("response", getSchemaItems(as));
@ -4324,6 +4331,8 @@ public class DefaultCodegen implements CodegenConfig {
} else if (ModelUtils.isTypeObjectSchema(responseSchema)) { } else if (ModelUtils.isTypeObjectSchema(responseSchema)) {
if (ModelUtils.isFreeFormObject(openAPI, responseSchema)) { if (ModelUtils.isFreeFormObject(openAPI, responseSchema)) {
r.isFreeFormObject = true; r.isFreeFormObject = true;
} else {
r.isModel = true;
} }
r.simpleType = false; r.simpleType = false;
r.containerType = cp.containerType; r.containerType = cp.containerType;
@ -4335,9 +4344,6 @@ public class DefaultCodegen implements CodegenConfig {
LOGGER.debug("Property type is not primitive: {}", cp.dataType); LOGGER.debug("Property type is not primitive: {}", cp.dataType);
} }
if (!r.isMap && !r.isArray) {
r.simpleType = true;
}
r.primitiveType = (r.baseType == null || languageSpecificPrimitives().contains(r.baseType)); r.primitiveType = (r.baseType == null || languageSpecificPrimitives().contains(r.baseType));
if (r.baseType == null) { if (r.baseType == null) {

View File

@ -613,7 +613,7 @@ public class ElixirClientCodegen extends DefaultCodegen implements CodegenConfig
return "%{}"; return "%{}";
} }
// Primitive return type, don't even try to decode // Primitive return type, don't even try to decode
if (baseType == null || (simpleType && primitiveType)) { if (baseType == null || (containerType == null && primitiveType)) {
return "false"; return "false";
} else if (isArray && languageSpecificPrimitives().contains(baseType)) { } else if (isArray && languageSpecificPrimitives().contains(baseType)) {
return "[]"; return "[]";
@ -733,16 +733,13 @@ public class ElixirClientCodegen extends DefaultCodegen implements CodegenConfig
StringBuilder returnEntry = new StringBuilder(); StringBuilder returnEntry = new StringBuilder();
if (exResponse.baseType == null) { if (exResponse.baseType == null) {
returnEntry.append("nil"); returnEntry.append("nil");
} else if (exResponse.simpleType) { } else if (exResponse.containerType == null) { // not container (array, map, set)
if (!exResponse.primitiveType) { if (!exResponse.primitiveType) {
returnEntry.append(moduleName); returnEntry.append(moduleName);
returnEntry.append(".Model."); returnEntry.append(".Model.");
} }
returnEntry.append(exResponse.baseType); returnEntry.append(exResponse.baseType);
returnEntry.append(".t"); returnEntry.append(".t");
} else if (exResponse.containerType == null) {
returnEntry.append(returnBaseType);
returnEntry.append(".t");
} else { } else {
if (exResponse.containerType.equals("array") || if (exResponse.containerType.equals("array") ||
exResponse.containerType.equals("set")) { exResponse.containerType.equals("set")) {

View File

@ -104,7 +104,7 @@ Operation Id:: {{nickname}}
| {{code}} | {{code}}
| {{message}} | {{message}}
| {{^simpleType}}{{dataType}}[<<{{baseType}}>>]{{/simpleType}} {{#simpleType}}<<{{dataType}}>>{{/simpleType}} | {{#containerType}}{{dataType}}[<<{{baseType}}>>]{{/containerType}} {{^containerType}}<<{{dataType}}>>{{/containerType}}
{{/responses}} {{/responses}}
|=== |===

View File

@ -134,7 +134,7 @@
{{#responses}} {{#responses}}
<h4 class="field-label">{{code}}</h4> <h4 class="field-label">{{code}}</h4>
{{message}} {{message}}
{{#simpleType}}<a href="#{{dataType}}">{{dataType}}</a>{{/simpleType}} {{^containerType}}<a href="#{{dataType}}">{{dataType}}</a>{{/containerType}}
{{#examples}} {{#examples}}
<h3 class="field-label">Example data</h3> <h3 class="field-label">Example data</h3>
<div class="example-data-content-type">Content-Type: {{{contentType}}}</div> <div class="example-data-content-type">Content-Type: {{{contentType}}}</div>

View File

@ -3877,6 +3877,59 @@ public class DefaultCodegenTest {
} }
@Test @Test
public void testResponses() {
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/response-tests.yaml");
final DefaultCodegen codegen = new DefaultCodegen();
codegen.setOpenAPI(openAPI);
codegen.setDisallowAdditionalPropertiesIfNotPresent(false);
String path;
Operation operation;
CodegenOperation co;
CodegenParameter cpa;
CodegenResponse cr;
path = "/pet/{petId}";
operation = openAPI.getPaths().get(path).getGet();
co = codegen.fromOperation(path, "GET", operation, null);
//assertTrue(co.hasErrorResponseObject);
cr = co.responses.get(0);
assertTrue(cr.is2xx);
assertFalse(cr.simpleType);
assertFalse(cr.primitiveType);
cr = co.responses.get(3);
assertTrue(cr.is5xx);
assertFalse(cr.simpleType);
assertFalse(cr.primitiveType);
path = "/pet";
operation = openAPI.getPaths().get(path).getPut();
co = codegen.fromOperation(path, "PUT", operation, null);
assertTrue(co.hasErrorResponseObject);
// 200 response
cr = co.responses.get(0);
assertTrue(cr.is2xx);
assertFalse(cr.simpleType);
assertFalse(cr.primitiveType);
// 400 response
cr = co.responses.get(1);
assertTrue(cr.is4xx);
assertEquals(cr.code, "400");
assertFalse(cr.simpleType);
assertFalse(cr.primitiveType);
path = "/pet/findByTags";
operation = openAPI.getPaths().get(path).getGet();
co = codegen.fromOperation(path, "GET", operation, null);
assertFalse(co.hasErrorResponseObject);
cr = co.responses.get(0);
assertTrue(cr.is2xx);
assertFalse(cr.simpleType);
assertFalse(cr.primitiveType);
}
public void testParameterContent() { public void testParameterContent() {
DefaultCodegen codegen = new DefaultCodegen(); DefaultCodegen codegen = new DefaultCodegen();
final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/content-data.yaml"); final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/content-data.yaml");

View File

@ -0,0 +1,571 @@
openapi: 3.0.0
servers:
- url: 'http://petstore.swagger.io/v2'
info:
description: >-
This is a sample server Petstore server. For this sample, you can use the api key
`special-key` to test the authorization filters.
version: 1.0.0
title: OpenAPI Petstore
license:
name: Apache-2.0
url: 'https://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: pet
description: Everything about your Pets
- name: store
description: Access to Petstore orders
- name: user
description: Operations about user
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
description: ''
operationId: addPet
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
$ref: '#/components/requestBodies/Pet'
put:
tags:
- pet
summary: Update an existing pet
description: ''
operationId: updatePet
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
'404':
description: Pet not found
'405':
description: Validation exception
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
$ref: '#/components/requestBodies/Pet'
/pet/findByStatus:
get:
tags:
- pet
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: true
style: form
explode: false
deprecated: true
schema:
type: array
items:
type: string
enum:
- available
- pending
- sold
default: available
responses:
'200':
description: successful operation
content:
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid status value
security:
- petstore_auth:
- 'read:pets'
/pet/findByTags:
get:
tags:
- pet
summary: Finds Pets by tags
description: >-
Multiple tags can be provided with comma separated strings. Use tag1,
tag2, tag3 for testing.
operationId: findPetsByTags
parameters:
- name: tags
in: query
description: Tags to filter by
required: true
style: form
explode: false
schema:
type: array
items:
type: string
responses:
'200':
description: successful operation
content:
application/xml:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid tag value
security:
- petstore_auth:
- 'read:pets'
deprecated: true
'/pet/{petId}':
get:
tags:
- pet
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'500':
description: Server error
content:
application/application:
schema:
$ref: '#/components/schemas/ApiResponse'
security:
- api_key: []
post:
tags:
- pet
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
schema:
type: integer
format: int64
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
name:
description: Updated name of the pet
type: string
status:
description: Updated status of the pet
type: string
delete:
tags:
- pet
summary: Deletes a pet
description: ''
operationId: deletePet
parameters:
- name: api_key
in: header
required: false
schema:
type: string
- name: petId
in: path
description: Pet id to delete
required: true
schema:
type: integer
format: int64
responses:
'400':
description: Invalid pet value
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
'/pet/{petId}/uploadImage':
post:
tags:
- pet
summary: uploads an image
description: ''
operationId: uploadFile
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
schema:
type: integer
format: int64
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/ApiResponse'
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
additionalMetadata:
description: Additional data to pass to server
type: string
file:
description: file to upload
type: string
format: binary
'/store/order/{orderId}':
get:
tags:
- store
summary: Find purchase order by ID
description: >-
For valid response try integer IDs with value <= 5 or > 10. Other values
will generated exceptions
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of pet that needs to be fetched
required: true
schema:
type: integer
format: int64
minimum: 1
maximum: 5
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Order'
application/json:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
'500':
description: Server error
delete:
tags:
- store
summary: Delete purchase order by ID
description: >-
For valid response try integer IDs with value < 1000. Anything above
1000 or nonintegers will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: string
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
/user/login:
get:
tags:
- user
summary: Logs user into the system
description: ''
operationId: loginUser
parameters:
- name: username
in: query
description: The user name for login
required: true
schema:
type: string
pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$'
- name: password
in: query
description: The password for login in clear text
required: true
schema:
type: string
responses:
'200':
description: successful operation
headers:
Set-Cookie:
description: >-
Cookie authentication key for use with the `api_key`
apiKey authentication.
schema:
type: string
example: AUTH_KEY=abcde12345; Path=/; HttpOnly
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when token expires
schema:
type: string
format: date-time
content:
application/xml:
schema:
type: string
application/json:
schema:
type: string
'400':
description: Invalid username/password supplied
externalDocs:
description: Find out more about Swagger
url: 'http://swagger.io'
components:
requestBodies:
UserArray:
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
description: List of user object
required: true
Pet:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
description: Pet object that needs to be added to the store
required: true
securitySchemes:
petstore_auth:
type: oauth2
flows:
implicit:
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
scopes:
'write:pets': modify pets in your account
'read:pets': read your pets
api_key:
type: apiKey
name: api_key
in: header
schemas:
Order:
title: Pet Order
description: An order for a pets from the pet store
type: object
properties:
id:
type: integer
format: int64
petId:
type: integer
format: int64
quantity:
type: integer
format: int32
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
enum:
- placed
- approved
- delivered
complete:
type: boolean
default: false
xml:
name: Order
Category:
title: Pet category
description: A category for a pet
type: object
properties:
id:
type: integer
format: int64
name:
type: string
pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$'
xml:
name: Category
User:
title: a User
description: A User who is purchasing from the pet store
type: object
properties:
id:
type: integer
format: int64
username:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
password:
type: string
phone:
type: string
userStatus:
type: integer
format: int32
description: User Status
xml:
name: User
Tag:
title: Pet Tag
description: A tag for a pet
type: object
properties:
id:
type: integer
format: int64
name:
type: string
xml:
name: Tag
Pet:
title: a Pet
description: A pet for sale in the pet store
type: object
required:
- name
- photoUrls
properties:
id:
type: integer
format: int64
category:
$ref: '#/components/schemas/Category'
name:
type: string
example: doggie
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string
tags:
type: array
xml:
name: tag
wrapped: true
items:
$ref: '#/components/schemas/Tag'
status:
type: string
description: pet status in the store
deprecated: true
enum:
- available
- pending
- sold
xml:
name: Pet
ApiResponse:
title: An uploaded response
description: Describes the result of uploading an image resource
type: object
properties:
code:
type: integer
format: int32
type:
type: string
message:
type: string