Add x-nullable (OAS2) support (#889)

* add x-nullable oas2 support

* add test spec for testing x-nullable
This commit is contained in:
William Cheng 2018-08-25 18:31:23 +08:00 committed by GitHub
parent 6a00b2a802
commit 50c6ac34f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 792 additions and 4 deletions

View File

@ -41,6 +41,7 @@ public class CodegenParameter {
public CodegenProperty mostInnerItems;
public Map<String, Object> vendorExtensions = new HashMap<String, Object>();
public boolean hasValidation;
public boolean isNullable;
/**
* Determines whether this parameter is mandatory. If the parameter is in "path",
@ -150,6 +151,7 @@ public class CodegenParameter {
output.vendorExtensions = new HashMap<String, Object>(this.vendorExtensions);
}
output.hasValidation = this.hasValidation;
output.isNullable = this.isNullable;
output.isBinary = this.isBinary;
output.isByteArray = this.isByteArray;
output.isString = this.isString;
@ -269,6 +271,8 @@ public class CodegenParameter {
return false;
if (hasValidation != that.hasValidation)
return false;
if (isNullable != that.isNullable)
return false;
if (required != that.required)
return false;
if (maximum != null ? !maximum.equals(that.maximum) : that.maximum != null)
@ -344,6 +348,7 @@ public class CodegenParameter {
result = 31 * result + (mostInnerItems != null ? mostInnerItems.hashCode() : 0);
result = 31 * result + (vendorExtensions != null ? vendorExtensions.hashCode() : 0);
result = 31 * result + (hasValidation ? 13:31);
result = 31 * result + (isNullable ? 13:31);
result = 31 * result + (required ? 13:31);
result = 31 * result + (maximum != null ? maximum.hashCode() : 0);
result = 31 * result + (exclusiveMaximum ? 13:31);
@ -409,6 +414,7 @@ public class CodegenParameter {
", mostInnerItems=" + mostInnerItems +
", vendorExtensions=" + vendorExtensions +
", hasValidation=" + hasValidation +
", isNullable=" + isNullable +
", required=" + required +
", maximum='" + maximum + '\'' +
", exclusiveMaximum=" + exclusiveMaximum +

View File

@ -1789,7 +1789,11 @@ public class DefaultCodegen implements CodegenConfig {
if (p.getWriteOnly() != null) {
property.isWriteOnly = p.getWriteOnly();
}
if (p.getNullable() != null) {
// use x-nullable
if (p.getExtensions() != null && p.getExtensions().get("x-nullable") != null) {
property.isNullable = Boolean.valueOf(p.getExtensions().get("x-nullable").toString());
} else if (p.getNullable() != null) { // use nullable defined in OAS3
property.isNullable = p.getNullable();
}
@ -2649,6 +2653,14 @@ public class DefaultCodegen implements CodegenConfig {
LOGGER.warn("warning! Schema not found for parameter \"" + parameter.getName() + "\", using String");
parameterSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to missing type definition.");
}
// x-nullable extension in OAS2
if (parameter.getExtensions() != null && parameter.getExtensions().get("x-nullable") != null) {
codegenParameter.isNullable = Boolean.valueOf(parameter.getExtensions().get("x-nullable").toString());
} else if (Boolean.TRUE.equals(parameterSchema.getNullable())) { // use nullable defined in the spec
codegenParameter.isNullable = true;
}
// set default value
if (parameterSchema.getDefault() != null) {
codegenParameter.defaultValue = toDefaultValue(parameterSchema);
@ -4265,6 +4277,9 @@ public class DefaultCodegen implements CodegenConfig {
// default to csv:
codegenParameter.collectionFormat = StringUtils.isEmpty(collectionFormat) ? "csv" : collectionFormat;
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
// recursively add import
while (codegenProperty != null) {
imports.add(codegenProperty.baseType);
@ -4293,7 +4308,7 @@ public class DefaultCodegen implements CodegenConfig {
public CodegenParameter fromFormProperty(String name, Schema propertySchema, Set<String> imports) {
CodegenParameter codegenParameter = CodegenModelFactory.newInstance(CodegenModelType.PARAMETER);
LOGGER.debug("Debugging fromFormProperty: " + name);
LOGGER.debug("Debugging fromFormProperty {}: {}", name, propertySchema);
CodegenProperty codegenProperty = fromProperty(name, propertySchema);
codegenParameter.isFormParam = Boolean.TRUE;
@ -4307,7 +4322,6 @@ public class DefaultCodegen implements CodegenConfig {
codegenParameter.jsonSchema = Json.pretty(propertySchema);
codegenParameter.defaultValue = codegenProperty.getDefaultValue();
if (codegenProperty.getVendorExtensions() != null && !codegenProperty.getVendorExtensions().isEmpty()) {
codegenParameter.vendorExtensions = codegenProperty.getVendorExtensions();
}
@ -4366,6 +4380,8 @@ public class DefaultCodegen implements CodegenConfig {
setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty);
setParameterExampleValue(codegenParameter);
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
//TODO collectionFormat for form parameter not yet supported
//codegenParameter.collectionFormat = getCollectionFormat(propertySchema);
@ -4415,6 +4431,9 @@ public class DefaultCodegen implements CodegenConfig {
codegenParameter.isMapContainer = Boolean.TRUE;
setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty);
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
} else if (ModelUtils.isArraySchema(schema)) {
final ArraySchema arraySchema = (ArraySchema) schema;
Schema inner = arraySchema.getItems();
@ -4454,6 +4473,8 @@ public class DefaultCodegen implements CodegenConfig {
codegenParameter.isListContainer = Boolean.TRUE;
setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty);
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
while (codegenProperty != null) {
imports.add(codegenProperty.baseType);
@ -4516,6 +4537,8 @@ public class DefaultCodegen implements CodegenConfig {
}
}
setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty);
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
}
} else {
@ -4539,6 +4562,8 @@ public class DefaultCodegen implements CodegenConfig {
}
setParameterBooleanFlagWithCodegenProperty(codegenParameter, codegenProperty);
// set nullable
setParameterNullable(codegenParameter, codegenProperty);
}
// set the parameter's example value
@ -4636,4 +4661,12 @@ public class DefaultCodegen implements CodegenConfig {
}
return codegenServerVariables;
}
private void setParameterNullable(CodegenParameter parameter, CodegenProperty property) {
if (property.getVendorExtensions() != null && property.getVendorExtensions().get("x-nullable") != null) {
parameter.isNullable = Boolean.valueOf(property.getVendorExtensions().get("x-nullable").toString());
} else {
parameter.isNullable = property.isNullable;
}
}
}

View File

@ -172,7 +172,7 @@ public class RubyClientCodegenTest {
@Test(description = "test nullable for properties")
public void nullableTest() {
public void nullablePropertyTest() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/petstore_oas3_test.yaml", null, new ParseOptions()).getOpenAPI();
final RubyClientCodegen codegen = new RubyClientCodegen();
codegen.setModuleName("OnlinePetstore");
@ -227,4 +227,49 @@ public class RubyClientCodegenTest {
Assert.assertFalse(cp5.isNullable);
}
@Test(description = "test nullable for parameters (OAS3)")
public void nullableParameterOAS3Test() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/3_0/petstore_oas3_test.yaml", null, new ParseOptions()).getOpenAPI();
final RubyClientCodegen codegen = new RubyClientCodegen();
codegen.setModuleName("OnlinePetstore");
final String path = "/pet/{petId}";
final Operation p = openAPI.getPaths().get(path).getPost();
final CodegenOperation op = codegen.fromOperation(path, "post", p, openAPI.getComponents().getSchemas());
Assert.assertEquals(op.pathParams.size(), 1);
CodegenParameter pp = op.pathParams.get(0);
Assert.assertTrue(pp.isNullable);
Assert.assertEquals(op.formParams.size(), 2);
CodegenParameter name = op.formParams.get(0);
Assert.assertFalse(name.isNullable);
CodegenParameter status = op.formParams.get(1);
Assert.assertTrue(status.isNullable);
}
@Test(description = "test nullable for parameters (OAS2)")
public void nullableParameterOAS2Test() {
final OpenAPI openAPI = new OpenAPIParser().readLocation("src/test/resources/2_0/petstore-nullable.yaml", null, new ParseOptions()).getOpenAPI();
final RubyClientCodegen codegen = new RubyClientCodegen();
codegen.setModuleName("OnlinePetstore");
final String path = "/pet/{petId}";
final Operation p = openAPI.getPaths().get(path).getPost();
final CodegenOperation op = codegen.fromOperation(path, "post", p, openAPI.getComponents().getSchemas());
// path parameter x-nullable test
Assert.assertEquals(op.pathParams.size(), 1);
CodegenParameter pp = op.pathParams.get(0);
Assert.assertTrue(pp.isNullable);
// form parameter x-nullable test
Assert.assertEquals(op.formParams.size(), 2);
CodegenParameter name = op.formParams.get(0);
Assert.assertFalse(name.isNullable);
CodegenParameter status = op.formParams.get(1);
// TODO comment out the following as there seems to be an issue with swagger parser not brining over the
// vendor extensions of the form parameter when creating the schema
//Assert.assertTrue(status.isNullable);
}
}

View File

@ -0,0 +1,702 @@
swagger: '2.0'
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: 'http://www.apache.org/licenses/LICENSE-2.0.html'
host: petstore.swagger.io
basePath: /v2
tags:
- name: pet
description: Everything about your Pets
- name: store
description: Access to Petstore orders
- name: user
description: Operations about user
schemes:
- http
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
description: ''
operationId: addPet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
put:
tags:
- pet
summary: Update an existing pet
description: ''
operationId: updatePet
consumes:
- application/json
- application/xml
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Pet object that needs to be added to the store
required: true
schema:
$ref: '#/definitions/Pet'
responses:
'400':
description: Invalid ID supplied
'404':
description: Pet not found
'405':
description: Validation exception
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/pet/findByStatus:
get:
tags:
- pet
summary: Finds Pets by status
description: Multiple status values can be provided with comma separated strings
operationId: findPetsByStatus
produces:
- application/xml
- application/json
parameters:
- name: status
in: query
description: Status values that need to be considered for filter
required: true
type: array
items:
type: string
enum:
- available
- pending
- sold
default: available
collectionFormat: csv
responses:
'200':
description: successful operation
schema:
type: array
items:
$ref: '#/definitions/Pet'
'400':
description: Invalid status value
security:
- petstore_auth:
- 'write:pets'
- '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
produces:
- application/xml
- application/json
parameters:
- name: tags
in: query
description: Tags to filter by
required: true
type: array
items:
type: string
collectionFormat: csv
responses:
'200':
description: successful operation
schema:
type: array
items:
$ref: '#/definitions/Pet'
'400':
description: Invalid tag value
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
deprecated: true
'/pet/{petId}':
get:
tags:
- pet
summary: Find pet by ID
description: Returns a single pet
operationId: getPetById
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet to return
required: true
type: integer
format: int64
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Pet'
'400':
description: Invalid ID supplied
'404':
description: Pet not found
security:
- api_key: []
post:
tags:
- pet
summary: Updates a pet in the store with form data
description: ''
operationId: updatePetWithForm
consumes:
- application/x-www-form-urlencoded
produces:
- application/xml
- application/json
parameters:
- name: petId
in: path
description: ID of pet that needs to be updated
required: true
type: integer
format: int64
x-nullable: true
- name: name
in: formData
description: Updated name of the pet
required: false
type: string
- name: status
in: formData
description: Updated status of the pet
required: false
type: string
x-nullable: true
responses:
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
delete:
tags:
- pet
summary: Deletes a pet
description: ''
operationId: deletePet
produces:
- application/xml
- application/json
parameters:
- name: api_key
in: header
required: false
type: string
- name: petId
in: path
description: Pet id to delete
required: true
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
consumes:
- multipart/form-data
produces:
- application/json
parameters:
- name: petId
in: path
description: ID of pet to update
required: true
type: integer
format: int64
- name: additionalMetadata
in: formData
description: Additional data to pass to server
required: false
type: string
- name: file
in: formData
description: file to upload
required: false
type: file
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/ApiResponse'
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
/store/inventory:
get:
tags:
- store
summary: Returns pet inventories by status
description: Returns a map of status codes to quantities
operationId: getInventory
produces:
- application/json
parameters: []
responses:
'200':
description: successful operation
schema:
type: object
additionalProperties:
type: integer
format: int32
security:
- api_key: []
/store/order:
post:
tags:
- store
summary: Place an order for a pet
description: ''
operationId: placeOrder
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: order placed for purchasing the pet
required: true
schema:
$ref: '#/definitions/Order'
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Order'
'400':
description: Invalid Order
'/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
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of pet that needs to be fetched
required: true
type: integer
maximum: 5
minimum: 1
format: int64
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/Order'
'400':
description: Invalid ID supplied
'404':
description: Order not found
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
produces:
- application/xml
- application/json
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
type: string
responses:
'400':
description: Invalid ID supplied
'404':
description: Order not found
/user:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: Created user object
required: true
schema:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/createWithArray:
post:
tags:
- user
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithArrayInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/createWithList:
post:
tags:
- user
summary: Creates list of users with given input array
description: ''
operationId: createUsersWithListInput
produces:
- application/xml
- application/json
parameters:
- in: body
name: body
description: List of user object
required: true
schema:
type: array
items:
$ref: '#/definitions/User'
responses:
default:
description: successful operation
/user/login:
get:
tags:
- user
summary: Logs user into the system
description: ''
operationId: loginUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: query
description: The user name for login
required: true
type: string
- name: password
in: query
description: The password for login in clear text
required: true
type: string
responses:
'200':
description: successful operation
schema:
type: string
headers:
X-Rate-Limit:
type: integer
format: int32
description: calls per hour allowed by the user
X-Expires-After:
type: string
format: date-time
description: date in UTC when toekn expires
'400':
description: Invalid username/password supplied
/user/logout:
get:
tags:
- user
summary: Logs out current logged in user session
description: ''
operationId: logoutUser
produces:
- application/xml
- application/json
parameters: []
responses:
default:
description: successful operation
'/user/{username}':
get:
tags:
- user
summary: Get user by user name
description: ''
operationId: getUserByName
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: 'The name that needs to be fetched. Use user1 for testing.'
required: true
type: string
responses:
'200':
description: successful operation
schema:
$ref: '#/definitions/User'
'400':
description: Invalid username supplied
'404':
description: User not found
put:
tags:
- user
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: name that need to be deleted
required: true
type: string
- in: body
name: body
description: Updated user object
required: true
schema:
$ref: '#/definitions/User'
responses:
'400':
description: Invalid user supplied
'404':
description: User not found
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
produces:
- application/xml
- application/json
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
type: string
responses:
'400':
description: Invalid username supplied
'404':
description: User not found
securityDefinitions:
petstore_auth:
type: oauth2
authorizationUrl: 'http://petstore.swagger.io/api/oauth/dialog'
flow: implicit
scopes:
'write:pets': modify pets in your account
'read:pets': read your pets
api_key:
type: apiKey
name: api_key
in: header
definitions:
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
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
x-nullable: true
category:
$ref: '#/definitions/Category'
name:
type: string
example: doggie
x-nullable: true
photoUrls:
type: array
xml:
name: photoUrl
wrapped: true
items:
type: string
x-nullable: true
tags:
x-nullable: true
type: array
xml:
name: tag
wrapped: true
items:
$ref: '#/definitions/Tag'
status:
type: string
x-nullable: true
description: pet status in the store
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

View File

@ -181,6 +181,7 @@ paths:
schema:
type: integer
format: int64
nullable: true
responses:
'405':
description: Invalid input
@ -200,6 +201,7 @@ paths:
status:
description: Updated status of the pet
type: string
nullable: true
delete:
tags:
- pet