diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java index eb6522a9318..d290aa5c6ab 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenParameter.java @@ -41,6 +41,7 @@ public class CodegenParameter { public CodegenProperty mostInnerItems; public Map vendorExtensions = new HashMap(); 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(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 + diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 61d1ae83155..01017af3fad 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -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 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; + } + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java index a0c5a326ead..2e43257eaa2 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ruby/RubyClientCodegenTest.java @@ -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); + } } diff --git a/modules/openapi-generator/src/test/resources/2_0/petstore-nullable.yaml b/modules/openapi-generator/src/test/resources/2_0/petstore-nullable.yaml new file mode 100644 index 00000000000..cff13ed73ce --- /dev/null +++ b/modules/openapi-generator/src/test/resources/2_0/petstore-nullable.yaml @@ -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 diff --git a/modules/openapi-generator/src/test/resources/3_0/petstore_oas3_test.yaml b/modules/openapi-generator/src/test/resources/3_0/petstore_oas3_test.yaml index c9c83e09a6b..370ae3ee32f 100644 --- a/modules/openapi-generator/src/test/resources/3_0/petstore_oas3_test.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/petstore_oas3_test.yaml @@ -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