diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache index 4f2de178b0e..8e09ec2aba3 100644 --- a/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache @@ -4,14 +4,28 @@ class {{classname}}{{#parent}} < {{{.}}}{{/parent}} include JSON::Serializable - {{#vars}} + {{#hasRequired}} + # Required properties + {{/hasRequired}} + {{#requiredVars}} {{#description}} # {{{.}}} {{/description}} - @[JSON::Field(key: "{{{baseName}}}", type: {{{dataType}}}{{#defaultValue}}, default: {{{defaultValue}}}{{/defaultValue}}{{#isNullable}}, nillable: true, emit_null: true{{/isNullable}})] + @[JSON::Field(key: "{{{baseName}}}", type: {{{dataType}}}{{#defaultValue}}, default: {{{defaultValue}}}{{/defaultValue}}, nillable: false, emit_null: false)] property {{{name}}} : {{{dataType}}} - {{/vars}} + {{/requiredVars}} + {{#hasOptional}} + # Optional properties + {{/hasOptional}} + {{#optionalVars}} + {{#description}} + # {{{.}}} + {{/description}} + @[JSON::Field(key: "{{{baseName}}}", type: {{{dataType}}}?{{#defaultValue}}, default: {{{defaultValue}}}{{/defaultValue}}, nillable: true, emit_null: false)] + property {{{name}}} : {{{dataType}}}? + + {{/optionalVars}} {{#hasEnums}} class EnumAttributeValidator getter datatype : String @@ -74,7 +88,7 @@ {{/discriminator}} # Initializes the object # @param [Hash] attributes Model attributes in the form of hash - def initialize({{#vars}}@{{{name}}} : {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/vars}}) + def initialize({{#requiredVars}}@{{{name}}} : {{{dataType}}}{{^-last}}, {{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}}, {{/hasOptional}}{{/hasRequired}}{{#optionalVars}}@{{{name}}} : {{{dataType}}}?{{^-last}}, {{/-last}}{{/optionalVars}}) end # Show invalid properties with the reasons. Usually used together with valid? @@ -82,14 +96,6 @@ def list_invalid_properties invalid_properties = {{^parent}}Array(String).new{{/parent}}{{#parent}}super{{/parent}} {{#vars}} - {{^isNullable}} - {{#required}} - if @{{{name}}}.nil? - invalid_properties.push("invalid value for \"{{{name}}}\", {{{name}}} cannot be nil.") - end - - {{/required}} - {{/isNullable}} {{#hasValidation}} {{#maxLength}} if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.size > {{{maxLength}}} @@ -143,11 +149,6 @@ # @return true if the model is valid def valid? {{#vars}} - {{^isNullable}} - {{#required}} - return false if @{{{name}}}.nil? - {{/required}} - {{/isNullable}} {{#isEnum}} {{^isContainer}} {{{name}}}_validator = EnumAttributeValidator.new("{{{dataType}}}", [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]) @@ -217,14 +218,6 @@ # Custom attribute writer method with validation # @param [Object] {{{name}}} Value to be assigned def {{{name}}}=({{{name}}}) - {{^isNullable}} - {{#required}} - if {{{name}}}.nil? - raise ArgumentError.new("{{{name}}} cannot be nil") - end - - {{/required}} - {{/isNullable}} {{#maxLength}} if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.to_s.size > {{{maxLength}}} raise ArgumentError.new("invalid value for \"{{{name}}}\", the character length must be smaller than or equal to {{{maxLength}}}.") @@ -277,7 +270,7 @@ # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class{{#vars}} && {{name}} == o.{{name}}{{/vars}}{{#parent}} && super(o){{/parent}} end diff --git a/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache b/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache index 9facafa4a93..56675bb6876 100644 --- a/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache @@ -3,4 +3,12 @@ # load modules require "spec" require "json" -require "../src/{{{shardName}}}" \ No newline at end of file +require "../src/{{{shardName}}}" + +def assert_compilation_error(path : String, message : String) : Nil + buffer = IO::Memory.new + result = Process.run("crystal", ["run", "--no-color", "--no-codegen", path], error: buffer) + result.success?.should be_false + buffer.to_s.should contain message + buffer.close +end diff --git a/samples/client/petstore/crystal/pet_compilation_error_spec.cr b/samples/client/petstore/crystal/pet_compilation_error_spec.cr new file mode 100644 index 00000000000..519cb5b73e3 --- /dev/null +++ b/samples/client/petstore/crystal/pet_compilation_error_spec.cr @@ -0,0 +1,11 @@ +require "./spec/spec_helper" +require "json" +require "time" + +describe Petstore::Pet do + describe "test an instance of Pet" do + it "should fail to compile if any required properties is missing" do + pet = Petstore::Pet.new(id: nil, category: nil, name: nil, photo_urls: Array(String).new, tags: nil, status: nil) + end + end +end diff --git a/samples/client/petstore/crystal/spec/api/pet_api_spec.cr b/samples/client/petstore/crystal/spec/api/pet_api_spec.cr index 5c5f56295ac..5eb508ebd72 100644 --- a/samples/client/petstore/crystal/spec/api/pet_api_spec.cr +++ b/samples/client/petstore/crystal/spec/api/pet_api_spec.cr @@ -29,8 +29,28 @@ describe "PetApi" do # @param [Hash] opts the optional parameters # @return [Pet] describe "add_pet test" do - it "should work" do + it "should work with only required attributes" do # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html + + config = Petstore::Configuration.new + config.access_token = "yyy" + config.api_key[:api_key] = "xxx" + config.api_key_prefix[:api_key] = "Token" + + api_client = Petstore::ApiClient.new(config) + + api_instance = Petstore::PetApi.new(api_client) + + pet_name = "new pet" + new_pet = Petstore::Pet.new(id: nil, category: nil, name: pet_name, photo_urls: Array(String).new, tags: nil, status: nil) + + pet = api_instance.add_pet(new_pet) + pet.id.should_not be_nil + pet.category.should be_nil + pet.name.should eq pet_name + pet.photo_urls.should eq Array(String).new + pet.status.should be_nil + pet.tags.should eq Array(Petstore::Tag).new end end @@ -89,20 +109,17 @@ describe "PetApi" do api_instance = Petstore::PetApi.new(api_client) - # create a pet to start with - pet_id = Int64.new(91829) - pet = Petstore::Pet.new(id: pet_id, category: Petstore::Category.new(id: pet_id + 10, name: "crystal category"), name: "crystal", photo_urls: ["https://crystal-lang.org"], tags: [Petstore::Tag.new(id: pet_id + 100, name: "crystal tag")], status: "available") - - api_instance.add_pet(pet) + new_pet = Petstore::Pet.new(id: nil, category: nil, name: "crystal", photo_urls: Array(String).new, tags: nil, status: nil) + pet = api_instance.add_pet(new_pet) + pet_id = pet.id.not_nil! result = api_instance.get_pet_by_id(pet_id: pet_id) result.id.should eq pet_id - result.category.id.should eq pet_id + 10 - result.category.name.should eq "crystal category" + result.category.should be_nil result.name.should eq "crystal" - result.photo_urls.should eq ["https://crystal-lang.org"] - result.status.should eq "available" - result.tags[0].id.should eq pet_id + 100 + result.photo_urls.should eq Array(String).new + result.status.should be_nil + result.tags.should eq Array(Petstore::Tag).new end end diff --git a/samples/client/petstore/crystal/spec/models/pet_spec.cr b/samples/client/petstore/crystal/spec/models/pet_spec.cr index d3eaf60dfc1..16117449db5 100644 --- a/samples/client/petstore/crystal/spec/models/pet_spec.cr +++ b/samples/client/petstore/crystal/spec/models/pet_spec.cr @@ -18,11 +18,49 @@ require "time" describe Petstore::Pet do describe "test an instance of Pet" do - it "should create an instance of Pet" do - #instance = Petstore::Pet.new - #expect(instance).to be_instance_of(Petstore::Pet) + it "should fail to compile if any required properties is missing" do + assert_compilation_error(path: "./pet_compilation_error_spec.cr", message: "Error: no overload matches 'Petstore::Pet.new', id: Nil, category: Nil, name: Nil, photo_urls: Array(String), tags: Nil, status: Nil") + end + + it "should create an instance of Pet with only required properties" do + pet = Petstore::Pet.new(id: nil, category: nil, name: "new pet", photo_urls: Array(String).new, tags: nil, status: nil) + pet.should be_a(Petstore::Pet) + end + + it "should create an instance of Pet with all properties" do + pet_id = 12345_i64 + pet = Petstore::Pet.new(id: pet_id, category: Petstore::Category.new(id: pet_id + 10, name: "crystal category"), name: "crystal", photo_urls: ["https://crystal-lang.org"], tags: [Petstore::Tag.new(id: pet_id + 100, name: "crystal tag")], status: "available") + pet.should be_a(Petstore::Pet) end end + + describe "#from_json" do + it "should instantiate a new instance from json string with required properties" do + pet = Petstore::Pet.from_json("{\"name\": \"json pet\", \"photoUrls\": []}") + pet.should be_a(Petstore::Pet) + pet.name.should eq "json pet" + pet.photo_urls.should eq Array(String).new + end + + it "should raise error when instantiating a new instance from json string with missing required properties" do + expect_raises(JSON::SerializableError, "Missing JSON attribute: name") do + Petstore::Pet.from_json("{\"photoUrls\": []}") + end + expect_raises(JSON::SerializableError, "Missing JSON attribute: photoUrls") do + Petstore::Pet.from_json("{\"name\": \"json pet\"}") + end + end + + it "should raise error when instantiating a new instance from json string with required properties set to null value" do + expect_raises(JSON::SerializableError, "Expected String but was Null") do + Petstore::Pet.from_json("{\"name\": null, \"photoUrls\": []}") + end + expect_raises(JSON::SerializableError, "Expected BeginArray but was Null") do + Petstore::Pet.from_json("{\"name\": \"json pet\", \"photoUrls\": null}") + end + end + end + describe "test attribute 'id'" do it "should work" do # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html diff --git a/samples/client/petstore/crystal/spec/models/tag_spec.cr b/samples/client/petstore/crystal/spec/models/tag_spec.cr index f79e12657fc..64a05f292ce 100644 --- a/samples/client/petstore/crystal/spec/models/tag_spec.cr +++ b/samples/client/petstore/crystal/spec/models/tag_spec.cr @@ -23,6 +23,21 @@ describe Petstore::Tag do #expect(instance).to be_instance_of(Petstore::Tag) end end + + describe "test equality of Tag instances" do + it "should equal to itself" do + tag1 = Petstore::Tag.new(id: 0, name: "same") + tag2 = tag1 + (tag1 == tag2).should be_true + end + + it "should equal to another instance with same attributes" do + tag1 = Petstore::Tag.new(id: 0, name: "tag") + tag2 = Petstore::Tag.new(id: 0, name: "tag") + (tag1 == tag2).should be_true + end + end + describe "test attribute 'id'" do it "should work" do # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html diff --git a/samples/client/petstore/crystal/spec/spec_helper.cr b/samples/client/petstore/crystal/spec/spec_helper.cr index c6eb4e7a603..7349161df1e 100644 --- a/samples/client/petstore/crystal/spec/spec_helper.cr +++ b/samples/client/petstore/crystal/spec/spec_helper.cr @@ -11,4 +11,12 @@ # load modules require "spec" require "json" -require "../src/petstore" \ No newline at end of file +require "../src/petstore" + +def assert_compilation_error(path : String, message : String) : Nil + buffer = IO::Memory.new + result = Process.run("crystal", ["run", "--no-color", "--no-codegen", path], error: buffer) + result.success?.should be_false + buffer.to_s.should contain message + buffer.close +end diff --git a/samples/client/petstore/crystal/src/petstore/models/api_response.cr b/samples/client/petstore/crystal/src/petstore/models/api_response.cr index be551134907..7a0719b6615 100644 --- a/samples/client/petstore/crystal/src/petstore/models/api_response.cr +++ b/samples/client/petstore/crystal/src/petstore/models/api_response.cr @@ -16,14 +16,15 @@ module Petstore class ApiResponse include JSON::Serializable - @[JSON::Field(key: "code", type: Int32)] - property code : Int32 + # Optional properties + @[JSON::Field(key: "code", type: Int32?, nillable: true, emit_null: false)] + property code : Int32? - @[JSON::Field(key: "type", type: String)] - property _type : String + @[JSON::Field(key: "type", type: String?, nillable: true, emit_null: false)] + property _type : String? - @[JSON::Field(key: "message", type: String)] - property message : String + @[JSON::Field(key: "message", type: String?, nillable: true, emit_null: false)] + property message : String? # Initializes the object # @param [Hash] attributes Model attributes in the form of hash @@ -46,7 +47,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && code == o.code && _type == o._type && diff --git a/samples/client/petstore/crystal/src/petstore/models/category.cr b/samples/client/petstore/crystal/src/petstore/models/category.cr index 55c64154d97..65dd3f8cdd0 100644 --- a/samples/client/petstore/crystal/src/petstore/models/category.cr +++ b/samples/client/petstore/crystal/src/petstore/models/category.cr @@ -16,11 +16,12 @@ module Petstore class Category include JSON::Serializable - @[JSON::Field(key: "id", type: Int64)] - property id : Int64 + # Optional properties + @[JSON::Field(key: "id", type: Int64?, nillable: true, emit_null: false)] + property id : Int64? - @[JSON::Field(key: "name", type: String)] - property name : String + @[JSON::Field(key: "name", type: String?, nillable: true, emit_null: false)] + property name : String? # Initializes the object # @param [Hash] attributes Model attributes in the form of hash @@ -60,7 +61,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && id == o.id && name == o.name diff --git a/samples/client/petstore/crystal/src/petstore/models/order.cr b/samples/client/petstore/crystal/src/petstore/models/order.cr index 5d00ea6688f..0536a0c3de6 100644 --- a/samples/client/petstore/crystal/src/petstore/models/order.cr +++ b/samples/client/petstore/crystal/src/petstore/models/order.cr @@ -16,24 +16,25 @@ module Petstore class Order include JSON::Serializable - @[JSON::Field(key: "id", type: Int64)] - property id : Int64 + # Optional properties + @[JSON::Field(key: "id", type: Int64?, nillable: true, emit_null: false)] + property id : Int64? - @[JSON::Field(key: "petId", type: Int64)] - property pet_id : Int64 + @[JSON::Field(key: "petId", type: Int64?, nillable: true, emit_null: false)] + property pet_id : Int64? - @[JSON::Field(key: "quantity", type: Int32)] - property quantity : Int32 + @[JSON::Field(key: "quantity", type: Int32?, nillable: true, emit_null: false)] + property quantity : Int32? - @[JSON::Field(key: "shipDate", type: Time)] - property ship_date : Time + @[JSON::Field(key: "shipDate", type: Time?, nillable: true, emit_null: false)] + property ship_date : Time? # Order Status - @[JSON::Field(key: "status", type: String)] - property status : String + @[JSON::Field(key: "status", type: String?, nillable: true, emit_null: false)] + property status : String? - @[JSON::Field(key: "complete", type: Bool, default: false)] - property complete : Bool + @[JSON::Field(key: "complete", type: Bool?, default: false, nillable: true, emit_null: false)] + property complete : Bool? class EnumAttributeValidator getter datatype : String @@ -91,7 +92,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && id == o.id && pet_id == o.pet_id && diff --git a/samples/client/petstore/crystal/src/petstore/models/pet.cr b/samples/client/petstore/crystal/src/petstore/models/pet.cr index cdac65776d7..8edf5789c9a 100644 --- a/samples/client/petstore/crystal/src/petstore/models/pet.cr +++ b/samples/client/petstore/crystal/src/petstore/models/pet.cr @@ -16,24 +16,26 @@ module Petstore class Pet include JSON::Serializable - @[JSON::Field(key: "id", type: Int64)] - property id : Int64 - - @[JSON::Field(key: "category", type: Category)] - property category : Category - - @[JSON::Field(key: "name", type: String)] + # Required properties + @[JSON::Field(key: "name", type: String, nillable: false, emit_null: false)] property name : String - @[JSON::Field(key: "photoUrls", type: Array(String))] + @[JSON::Field(key: "photoUrls", type: Array(String), nillable: false, emit_null: false)] property photo_urls : Array(String) - @[JSON::Field(key: "tags", type: Array(Tag))] - property tags : Array(Tag) + # Optional properties + @[JSON::Field(key: "id", type: Int64?, nillable: true, emit_null: false)] + property id : Int64? + + @[JSON::Field(key: "category", type: Category?, nillable: true, emit_null: false)] + property category : Category? + + @[JSON::Field(key: "tags", type: Array(Tag)?, nillable: true, emit_null: false)] + property tags : Array(Tag)? # pet status in the store - @[JSON::Field(key: "status", type: String)] - property status : String + @[JSON::Field(key: "status", type: String?, nillable: true, emit_null: false)] + property status : String? class EnumAttributeValidator getter datatype : String @@ -60,29 +62,19 @@ module Petstore # Initializes the object # @param [Hash] attributes Model attributes in the form of hash - def initialize(@id : Int64?, @category : Category?, @name : String, @photo_urls : Array(String), @tags : Array(Tag)?, @status : String?) + def initialize(@name : String, @photo_urls : Array(String), @id : Int64?, @category : Category?, @tags : Array(Tag)?, @status : String?) end # Show invalid properties with the reasons. Usually used together with valid? # @return Array for valid properties with the reasons def list_invalid_properties invalid_properties = Array(String).new - if @name.nil? - invalid_properties.push("invalid value for \"name\", name cannot be nil.") - end - - if @photo_urls.nil? - invalid_properties.push("invalid value for \"photo_urls\", photo_urls cannot be nil.") - end - invalid_properties end # Check to see if the all the properties in the model are valid # @return true if the model is valid def valid? - return false if @name.nil? - return false if @photo_urls.nil? status_validator = EnumAttributeValidator.new("String", ["available", "pending", "sold"]) return false unless status_validator.valid?(@status) true @@ -101,7 +93,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && id == o.id && category == o.category && diff --git a/samples/client/petstore/crystal/src/petstore/models/tag.cr b/samples/client/petstore/crystal/src/petstore/models/tag.cr index bd293329185..cd5e7ed2989 100644 --- a/samples/client/petstore/crystal/src/petstore/models/tag.cr +++ b/samples/client/petstore/crystal/src/petstore/models/tag.cr @@ -16,11 +16,12 @@ module Petstore class Tag include JSON::Serializable - @[JSON::Field(key: "id", type: Int64)] - property id : Int64 + # Optional properties + @[JSON::Field(key: "id", type: Int64?, nillable: true, emit_null: false)] + property id : Int64? - @[JSON::Field(key: "name", type: String)] - property name : String + @[JSON::Field(key: "name", type: String?, nillable: true, emit_null: false)] + property name : String? # Initializes the object # @param [Hash] attributes Model attributes in the form of hash @@ -43,7 +44,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && id == o.id && name == o.name diff --git a/samples/client/petstore/crystal/src/petstore/models/user.cr b/samples/client/petstore/crystal/src/petstore/models/user.cr index 47430b4452a..a80b2574d2c 100644 --- a/samples/client/petstore/crystal/src/petstore/models/user.cr +++ b/samples/client/petstore/crystal/src/petstore/models/user.cr @@ -16,30 +16,31 @@ module Petstore class User include JSON::Serializable - @[JSON::Field(key: "id", type: Int64)] - property id : Int64 + # Optional properties + @[JSON::Field(key: "id", type: Int64?, nillable: true, emit_null: false)] + property id : Int64? - @[JSON::Field(key: "username", type: String)] - property username : String + @[JSON::Field(key: "username", type: String?, nillable: true, emit_null: false)] + property username : String? - @[JSON::Field(key: "firstName", type: String)] - property first_name : String + @[JSON::Field(key: "firstName", type: String?, nillable: true, emit_null: false)] + property first_name : String? - @[JSON::Field(key: "lastName", type: String)] - property last_name : String + @[JSON::Field(key: "lastName", type: String?, nillable: true, emit_null: false)] + property last_name : String? - @[JSON::Field(key: "email", type: String)] - property email : String + @[JSON::Field(key: "email", type: String?, nillable: true, emit_null: false)] + property email : String? - @[JSON::Field(key: "password", type: String)] - property password : String + @[JSON::Field(key: "password", type: String?, nillable: true, emit_null: false)] + property password : String? - @[JSON::Field(key: "phone", type: String)] - property phone : String + @[JSON::Field(key: "phone", type: String?, nillable: true, emit_null: false)] + property phone : String? # User Status - @[JSON::Field(key: "userStatus", type: Int32)] - property user_status : Int32 + @[JSON::Field(key: "userStatus", type: Int32?, nillable: true, emit_null: false)] + property user_status : Int32? # Initializes the object # @param [Hash] attributes Model attributes in the form of hash @@ -62,7 +63,7 @@ module Petstore # Checks equality by comparing each attribute. # @param [Object] Object to be compared def ==(o) - return true if self.equal?(o) + return true if self.same?(o) self.class == o.class && id == o.id && username == o.username &&