From 51b7581ff47e3f53bd86b7b71c120035e680ad12 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Mon, 22 Dec 2025 17:13:14 +0100 Subject: [PATCH 01/22] style(crystal): fix coding style --- .../languages/CrystalClientCodegen.java | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index f8bddd40330d..a3ea841189ad 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -85,28 +85,32 @@ public CrystalClientCodegen() { SecurityFeature.BasicAuth, SecurityFeature.BearerToken, SecurityFeature.ApiKey, - SecurityFeature.OAuth2_Implicit)) + SecurityFeature.OAuth2_Implicit + )) .excludeGlobalFeatures( GlobalFeature.XMLStructureDefinitions, GlobalFeature.Callbacks, GlobalFeature.LinkObjects, GlobalFeature.ParameterStyling, GlobalFeature.ParameterizedServer, - GlobalFeature.MultiServer) + GlobalFeature.MultiServer + ) .includeSchemaSupportFeatures( - SchemaSupportFeature.Polymorphism) + SchemaSupportFeature.Polymorphism + ) .excludeParameterFeatures( - ParameterFeature.Cookie) + ParameterFeature.Cookie + ) .includeClientModificationFeatures( ClientModificationFeature.BasePath, - ClientModificationFeature.UserAgent)); - - generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata) - .stability(Stability.BETA) - .build(); + ClientModificationFeature.UserAgent + ) + ); supportsInheritance = true; + generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata).stability(Stability.BETA).build(); + // clear import mapping (from default generator) as crystal does not use it // at the moment importMapping.clear(); @@ -188,8 +192,7 @@ public CrystalClientCodegen() { primitiveTypes = new ArrayList(typeMapping.values()); // remove modelPackage and apiPackage added by default - cliOptions.removeIf(opt -> CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) || - CodegenConstants.API_PACKAGE.equals(opt.getOpt())); + cliOptions.removeIf(opt -> CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) || CodegenConstants.API_PACKAGE.equals(opt.getOpt())); cliOptions.add(new CliOption(SHARD_NAME, "shard name (e.g. twitter_client").defaultValue("openapi_client")); @@ -207,8 +210,7 @@ public CrystalClientCodegen() { cliOptions.add(new CliOption(SHARD_AUTHOR_EMAIL, "shard author email (only one is supported).")); - cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, - CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).defaultValue(Boolean.TRUE.toString())); + cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).defaultValue(Boolean.TRUE.toString())); cliOptions.add(new CliOption(PARAMS_ENCODER, "params_encoder setting (e.g. Crest::NestedParamsEncoder, Crest::EnumeratedFlatParamsEncoder, Crest::ZeroEnumeratedFlatParamsEncoder"). @@ -220,8 +222,7 @@ public void processOpts() { super.processOpts(); if (StringUtils.isEmpty(System.getenv("CRYSTAL_POST_PROCESS_FILE"))) { - LOGGER.info( - "Hint: Environment variable 'CRYSTAL_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export CRYSTAL_POST_PROCESS_FILE=\"/usr/local/bin/crystal tool format\"' (Linux/Mac)"); + LOGGER.info("Hint: Environment variable 'CRYSTAL_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export CRYSTAL_POST_PROCESS_FILE=\"/usr/local/bin/crystal tool format\"' (Linux/Mac)"); } else if (!this.isEnablePostProcessFile()) { LOGGER.info("Warning: Environment variable 'CRYSTAL_POST_PROCESS_FILE' is set but file post-processing is not enabled. To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI)."); } @@ -294,14 +295,12 @@ public void processOpts() { supportingFiles.add(new SupportingFile("shard.mustache", "", "shard.yml")); // crystal spec files - supportingFiles.add(new SupportingFile("spec_helper.mustache", specFolder, "spec_helper.cr") - .doNotOverwrite()); + supportingFiles.add(new SupportingFile("spec_helper.mustache", specFolder, "spec_helper.cr").doNotOverwrite()); // add lambda for mustache templates additionalProperties.put("lambdaPrefixWithHash", new PrefixWithHashLambda()); additionalProperties.put("lambdaUppercase", new UppercaseLambda()); additionalProperties.put("lambdaTitlecase", new TitlecaseLambda()); - } @Override @@ -407,9 +406,9 @@ public String toModelName(final String name) { // model name starts with number if (modelName.matches("^\\d.*")) { - LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", modelName, - camelize("model_" + modelName)); - modelName = "model_" + modelName; // e.g. 200Response => Model200Response (after camelize) + LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", modelName, camelize("model_" + modelName)); + // e.g. 200Response => Model200Response (after camelize) + modelName = "model_" + modelName; } // camelize the model name @@ -548,8 +547,7 @@ public String toOperationId(String operationId) { // operationId starts with a number if (operationId.matches("^\\d.*")) { - LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, - underscore(sanitizeName("call_" + operationId))); + LOGGER.warn("{} (starting with a number) cannot be used as method name. Renamed to {}", operationId, underscore(sanitizeName("call_" + operationId))); operationId = "call_" + operationId; } @@ -576,6 +574,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List modelMaps = ModelMap.toCodegenModelMap(allModels); HashMap processedModelMaps = new HashMap<>(); @@ -607,8 +606,7 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List modelMaps, - HashMap processedModelMap) { + private String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps, HashMap processedModelMap) { if (codegenParameter.isArray) { // array if (codegenParameter.items == null) { return "[]"; @@ -675,8 +673,7 @@ private String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps, - HashMap processedModelMap) { + private String constructExampleCode(CodegenProperty codegenProperty, HashMap modelMaps, HashMap processedModelMap) { if (codegenProperty.isArray) { // array return "[" + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "]"; } else if (codegenProperty.isMap) { @@ -743,8 +740,7 @@ private String constructExampleCode(CodegenProperty codegenProperty, HashMap modelMaps, - HashMap processedModelMap) { + private String constructExampleCode(CodegenModel codegenModel, HashMap modelMaps, HashMap processedModelMap) { // break infinite recursion. Return, in case a model is already processed in the // current context. String model = codegenModel.name; @@ -758,8 +754,7 @@ private String constructExampleCode(CodegenModel codegenModel, HashMap> enumVars = (List>) codegenModel.allowableValues - .get("enumVars"); + List> enumVars = (List>) codegenModel.allowableValues.get("enumVars"); return moduleName + "::" + codegenModel.classname + "::" + enumVars.get(0).get("name"); } else if (codegenModel.oneOf != null && !codegenModel.oneOf.isEmpty()) { String subModel = (String) codegenModel.oneOf.toArray()[0]; From e3c8012ed4b19642106cf07bc94640c8de8b7c5a Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Mon, 22 Dec 2025 17:22:27 +0100 Subject: [PATCH 02/22] fix(crystal): object_id is so central in Crystal that it should not be overridden by user code See: https://crystal-lang.org/api/1.18.2/Reference.html#object_id%3AUInt64-instance-method --- .../openapitools/codegen/languages/CrystalClientCodegen.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index a3ea841189ad..2f0fd13bc7d5 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -135,6 +135,7 @@ public CrystalClientCodegen() { // reserved word. Ref: // https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords + // https://crystal-lang.org/api/1.18.2/Reference.html reservedWords = new HashSet<>( Arrays.asList( "abstract", "annotation", "do", "if", "nil?", "select", "union", @@ -146,7 +147,7 @@ public CrystalClientCodegen() { "break", "extend", "macro", "require", "true", "with", "case", "false", "module", "rescue", "type", "yield", "class", "for", "next", "responds_to?", "typeof", - "def", "fun", "nil", "return", "uninitialized")); + "def", "fun", "nil", "return", "uninitialized", "object_id")); languageSpecificPrimitives.clear(); languageSpecificPrimitives.add("String"); From 89e8f3208e220085f9183189b4491c9cdce3b3d2 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 05:21:27 +0100 Subject: [PATCH 03/22] style(crystal): fix coding style --- .../src/main/resources/crystal/shard.mustache | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/shard.mustache b/modules/openapi-generator/src/main/resources/crystal/shard.mustache index 6a1c86894e47..cacf91c160ae 100644 --- a/modules/openapi-generator/src/main/resources/crystal/shard.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/shard.mustache @@ -1,10 +1,15 @@ name: {{{shardName}}} + version: {{{shardVersion}}} + authors: - {{{shardAuthors}}} + description: | - {{{ shardDescription}}} + crystal: ">= 0.35.1" + dependencies: any_hash: github: Sija/any_hash.cr @@ -13,11 +18,11 @@ dependencies: version: ~> 1.3.13 development_dependencies: - kemal: - github: kemalcr/kemal - version: ~>1.5.0 ameba: github: crystal-ameba/ameba + kemal: + github: kemalcr/kemal + version: ~> 1.5.0 spectator: gitlab: arctic-fox/spectator version: ~> 0.12.0 From ab689e006971b026066dfa4611cf1c5b44b662f2 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 05:25:04 +0100 Subject: [PATCH 04/22] fix(crystal): fix partial_oneof_module by using a class instead of a module Fix https://github.com/OpenAPITools/openapi-generator/issues/22563 --- .../crystal/partial_oneof_module.mustache | 171 +++++++----------- 1 file changed, 70 insertions(+), 101 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache index ffd0c190d313..fcd6d2237e5c 100644 --- a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache @@ -1,135 +1,104 @@ {{#description}} # {{{.}}} {{/description}} - module {{classname}} + class {{classname}} + include JSON::Serializable + include YAML::Serializable + + class SchemaMismatchError < Exception + end + {{#oneOf}} {{#-first}} # List of class defined in oneOf (OpenAPI v3) def self.openapi_one_of [ {{/-first}} - :"{{{.}}}"{{^-last}},{{/-last}} + {{{.}}}{{^-last}},{{/-last}} {{#-last}} ] end {{/-last}} {{/oneOf}} - {{#discriminator}} - {{#propertyName}} - # Discriminator's property name (OpenAPI v3) - def self.openapi_discriminator_name - :"{{{.}}}" - end - {{/propertyName}} - {{#mappedModels}} - {{#-first}} - # Discriminator's mapping (OpenAPI v3) - def self.openapi_discriminator_mapping - { - {{/-first}} - :"{{{mappingName}}}" => :"{{{modelName}}}"{{^-last}},{{/-last}} - {{#-last}} - } - end - - {{/-last}} - {{/mappedModels}} - {{/discriminator}} - # Builds the object - # @param [Mixed] Data to be matched against the list of oneOf items - # @return [Object] Returns the model or the data itself - def self.build(data) {{#discriminator}} - discriminator_value = data[openapi_discriminator_name] - return nil unless discriminator_value - {{#mappedModels}} - {{#-first}} - - klass = openapi_discriminator_mapping[discriminator_value.to_sym] - return nil unless klass - - {{moduleName}}.const_get(klass).build_from_hash(data) - {{/-first}} - {{/mappedModels}} - {{^mappedModels}} - {{moduleName}}.const_get(discriminator_value).build_from_hash(data) - {{/mappedModels}} + use_yaml_discriminator {{#propertyName}}"{{{.}}}"{{/propertyName}}, { + {{#mappedModels}}{{{mappingName}}}: {{{modelName}}}{{^-last}},{{/-last}}{{/mappedModels}} + } {{/discriminator}} - {{^discriminator}} - # Go through the list of oneOf items and attempt to identify the appropriate one. - # Note: - # - We do not attempt to check whether exactly one item matches. - # - No advanced validation of types in some cases (e.g. "x: { type: string }" will happily match { x: 123 }) - # due to the way the deserialization is made in the base_object template (it just casts without verifying). - # - TODO: scalar values are de facto behaving as if they were nullable. - # - TODO: logging when debugging is set. + + def self.build(data) openapi_one_of.each do |klass| begin - next if klass == :AnyType # "nullable: true" typed_data = find_and_cast_into_type(klass, data) return typed_data if typed_data - rescue # rescue all errors so we keep iterating even if the current item lookup raises + rescue ex + # rescue all errors so we keep iterating even if the current item lookup raises + Log.trace { ex.message } end end - openapi_one_of.includes?(:AnyType) ? data : nil - {{/discriminator}} + nil end - {{^discriminator}} - SchemaMismatchError = Class.new(StandardError) - - # Note: 'File' is missing here because in the regular case we get the data _after_ a call to JSON.parse. - private def self.find_and_cast_into_type(klass, data) + {{#oneOf}} + private def self.find_and_cast_into_type(klass : {{{.}}}.class, data) return if data.nil? - begin - case klass.to_s - when "Boolean" - return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass) - when "Float" - return data if data.instance_of?(Float) - when "Integer" - return data if data.instance_of?(Integer) - when "Time" - return Time.parse(data) - when "Date" - return Date.parse(data) - when "String" - return data if data.instance_of?(String) - when "Object" # "type: object" - return data if data.instance_of?(Hash) - when /\AArray<(?.+)>\z/ # "type: array" - if data.instance_of?(Array) - sub_type = Regexp.last_match[:sub_type] - return data.map { |item| find_and_cast_into_type(sub_type, item) } - end - when /\AHash.+)>\z/ # "type: object" with "additionalProperties: { ... }" - if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) } - sub_type = Regexp.last_match[:sub_type] - return data.each_with_object({} of String | Symbol => Bool | Float | Integer | Time | Date | String | Array | Hash) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) } - end - else # model - const = {{moduleName}}.const_get(klass) - if const - if const.respond_to?(:openapi_one_of) # nested oneOf model - model = const.build(data) - return model if model - else - # raise if data contains keys that are not known to the model - raise unless (data.keys - const.acceptable_attributes).empty? - model = const.build_from_hash(data) - return model if model && model.valid? - end - end + Log.trace { "INSPECTING DATA" } + Log.trace { data.inspect } + + case data + when NetboxClient::RecursiveHash + if (value = cast_value(array_data: false, array_class: array_class?(klass), klass: klass, data: data)) + return new(value) + end + when Array(NetboxClient::RecursiveHash) + if (value = cast_value(array_data: true, array_class: array_class?(klass), klass: klass, data: data)) + return new(value) end + else + raise SchemaMismatchError.new("#{data} doesn't match the #{klass} type") + end + end + {{/oneOf}} + + private def self.cast_value(array_data : Bool, array_class : Bool, klass, data) + if array_class == true && array_data == true + Log.debug { "Building array of classes: #{klass} / #{data}" } - raise # if no match by now, raise - rescue - raise SchemaMismatchError, "#{data} doesn't match the #{klass} type" + klass.from_json(data.to_json) + elsif array_class == false && array_data == false + Log.debug { "Building single class: #{klass} / #{data}" } + + klass.from_json(data.to_json) + end + end + + private def self.array_class?(klass) + klass.name.starts_with?("Array(") + end + + {{#oneOf}} + def initialize(@value : {{{.}}}) + end + + {{/oneOf}} + + delegate :to_yaml, to: @value + delegate :to_json, to: @value + + def to_any_h + {"value" => to_h} + end + + def to_h + val = @value + if val.is_a?(Int32) + val + else + val.to_h end end - {{/discriminator}} end From 840f7699a65198b89c92cf581895802557a256b5 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 05:47:07 +0100 Subject: [PATCH 05/22] fix(crystal): remove non-working code It's not working because it uses #send method which doesn't exist in Crystal See: https://crystal-lang.org/reference/1.18/crystal_for_rubyists/metaprogramming_help.html#differences-between-ruby-and-crystal --- .../resources/crystal/base_object.mustache | 75 ------------------- 1 file changed, 75 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/base_object.mustache b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache index 1256b0bfc53f..2debd0d669c7 100644 --- a/modules/openapi-generator/src/main/resources/crystal/base_object.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache @@ -1,78 +1,3 @@ - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - {{#parent}} - super(attributes) - {{/parent}} - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = {{moduleName}}.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s From 4f71fb02284535e46ca25892dab907efac07ac96 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 05:50:43 +0100 Subject: [PATCH 06/22] fix(crystal): update reserved words, separate reserved keywords from methods --- .../languages/CrystalClientCodegen.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 2f0fd13bc7d5..657bf2742d61 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -134,20 +134,24 @@ public CrystalClientCodegen() { hideGenerationTimestamp = Boolean.TRUE; // reserved word. Ref: - // https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords + // https://crystal-lang.org/reference/1.18/crystal_for_rubyists/index.html#available-keywords // https://crystal-lang.org/api/1.18.2/Reference.html reservedWords = new HashSet<>( Arrays.asList( - "abstract", "annotation", "do", "if", "nil?", "select", "union", - "alias", "else", "in", "of", "self", "unless", - "as", "elsif", "include", "out", "sizeof", "until", - "as?", "end", "instance", "sizeof", "pointerof", "struct", "verbatim", - "asm", "ensure", "is_a?", "private", "super", "when", - "begin", "enum", "lib", "protected", "then", "while", - "break", "extend", "macro", "require", "true", "with", - "case", "false", "module", "rescue", "type", "yield", - "class", "for", "next", "responds_to?", "typeof", - "def", "fun", "nil", "return", "uninitialized", "object_id")); + // language reserved words (keywords) + "abstract", "do", "if", "nil?", "return", "uninitialized", + "alias", "else", "in", "of", "select", "union", + "as", "elsif", "include", "out", "self", "unless", + "as?", "end", "instance_sizeof", "pointerof", "sizeof", "until", + "asm", "ensure", "is_a?", "previous_def", "struct", "verbatim", + "begin", "enum", "lib", "private", "super", "when", + "break", "extend", "macro", "protected", "then", "while", + "case", "false", "module", "require", "true", "with", + "class", "for", "next", "rescue", "type", "yield", + "def", "fun", "nil", "responds_to?", "typeof", + // additional reserved words (methods) + "annotation", "object_id" + )); languageSpecificPrimitives.clear(); languageSpecificPrimitives.add("String"); From c54d72ae9b04ec7017e21fbfd85663643aba20d0 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 05:53:53 +0100 Subject: [PATCH 07/22] fix(crystal): update samples --- samples/client/petstore/crystal/shard.yml | 11 ++- .../models/another_property_name_mapping.cr | 72 ------------------- .../src/petstore/models/api_response.cr | 72 ------------------- .../crystal/src/petstore/models/category.cr | 72 ------------------- .../src/petstore/models/format_test.cr | 72 ------------------- .../crystal/src/petstore/models/order.cr | 72 ------------------- .../crystal/src/petstore/models/pet.cr | 72 ------------------- .../crystal/src/petstore/models/tag.cr | 72 ------------------- .../crystal/src/petstore/models/user.cr | 72 ------------------- 9 files changed, 8 insertions(+), 579 deletions(-) diff --git a/samples/client/petstore/crystal/shard.yml b/samples/client/petstore/crystal/shard.yml index 583b30f688ab..435b47ff5a67 100644 --- a/samples/client/petstore/crystal/shard.yml +++ b/samples/client/petstore/crystal/shard.yml @@ -1,10 +1,15 @@ name: petstore + version: 1.0.0 + authors: - + description: | - + crystal: ">= 0.35.1" + dependencies: any_hash: github: Sija/any_hash.cr @@ -13,11 +18,11 @@ dependencies: version: ~> 1.3.13 development_dependencies: - kemal: - github: kemalcr/kemal - version: ~>1.5.0 ameba: github: crystal-ameba/ameba + kemal: + github: kemalcr/kemal + version: ~> 1.5.0 spectator: gitlab: arctic-fox/spectator version: ~> 0.12.0 diff --git a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr index d670a1266a57..d21008f2e39e 100644 --- a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr +++ b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr @@ -72,78 +72,6 @@ module Petstore [http_debug_operation, underscore_type, _type, type_with_underscore].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s 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 2ae4be110740..cccbcbc83ffc 100644 --- a/samples/client/petstore/crystal/src/petstore/models/api_response.cr +++ b/samples/client/petstore/crystal/src/petstore/models/api_response.cr @@ -69,78 +69,6 @@ module Petstore [code, _type, message].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/category.cr b/samples/client/petstore/crystal/src/petstore/models/category.cr index fefff9d8fed4..43ab2668c4de 100644 --- a/samples/client/petstore/crystal/src/petstore/models/category.cr +++ b/samples/client/petstore/crystal/src/petstore/models/category.cr @@ -82,78 +82,6 @@ module Petstore [id, name].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/format_test.cr b/samples/client/petstore/crystal/src/petstore/models/format_test.cr index 3e36ff41c4ef..a7f6b3a803b1 100644 --- a/samples/client/petstore/crystal/src/petstore/models/format_test.cr +++ b/samples/client/petstore/crystal/src/petstore/models/format_test.cr @@ -318,78 +318,6 @@ module Petstore [integer, int32, int64, number, float, double, decimal, string, byte, binary, date, date_time, uuid, password, pattern_with_digits, pattern_with_digits_and_delimiter].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/order.cr b/samples/client/petstore/crystal/src/petstore/models/order.cr index bbb55de5e90e..0a8e34a47226 100644 --- a/samples/client/petstore/crystal/src/petstore/models/order.cr +++ b/samples/client/petstore/crystal/src/petstore/models/order.cr @@ -136,78 +136,6 @@ module Petstore [id, pet_id, quantity, ship_date, status, complete].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/pet.cr b/samples/client/petstore/crystal/src/petstore/models/pet.cr index d08a04fe5f22..98710693ea0d 100644 --- a/samples/client/petstore/crystal/src/petstore/models/pet.cr +++ b/samples/client/petstore/crystal/src/petstore/models/pet.cr @@ -137,78 +137,6 @@ module Petstore [id, category, name, photo_urls, tags, status].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/tag.cr b/samples/client/petstore/crystal/src/petstore/models/tag.cr index 5dc2ce4ffeb2..63d50aec73ed 100644 --- a/samples/client/petstore/crystal/src/petstore/models/tag.cr +++ b/samples/client/petstore/crystal/src/petstore/models/tag.cr @@ -65,78 +65,6 @@ module Petstore [id, name].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s diff --git a/samples/client/petstore/crystal/src/petstore/models/user.cr b/samples/client/petstore/crystal/src/petstore/models/user.cr index 8a56a5844875..8d47fc01741a 100644 --- a/samples/client/petstore/crystal/src/petstore/models/user.cr +++ b/samples/client/petstore/crystal/src/petstore/models/user.cr @@ -90,78 +90,6 @@ module Petstore [id, username, first_name, last_name, email, password, phone, user_status].hash end - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def self.build_from_hash(attributes) - new.build_from_hash(attributes) - end - - # Builds the object from hash - # @param [Hash] attributes Model attributes in the form of hash - # @return [Object] Returns the model itself - def build_from_hash(attributes) - return nil unless attributes.is_a?(Hash) - self.class.openapi_types.each_pair do |key, type| - if !attributes[self.class.attribute_map[key]]? && self.class.openapi_nullable.includes?(key) - self.send("#{key}=", nil) - elsif type =~ /\AArray<(.*)>/i - # check to ensure the input is an array given that the attribute - # is documented as an array but the input is not - if attributes[self.class.attribute_map[key]].is_a?(Array) - self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) }) - end - elsif !attributes[self.class.attribute_map[key]].nil? - self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]])) - end - end - - self - end - - # Deserializes the data based on type - # @param string type Data type - # @param string value Value to be deserialized - # @return [Object] Deserialized data - def _deserialize(type, value) - case type.to_sym - when :Time - Time.parse(value) - when :Date - Date.parse(value) - when :String - value.to_s - when :Integer - value.to_i - when :Float - value.to_f - when :Boolean - if value.to_s =~ /\A(true|t|yes|y|1)\z/i - true - else - false - end - when :Object - # generic object (usually a Hash), return directly - value - when /\AArray<(?.+)>\z/ - inner_type = Regexp.last_match[:inner_type] - value.map { |v| _deserialize(inner_type, v) } - when /\AHash<(?.+?), (?.+)>\z/ - k_type = Regexp.last_match[:k_type] - v_type = Regexp.last_match[:v_type] - ({} of Symbol => String).tap do |hash| - value.each do |k, v| - hash[_deserialize(k_type, k)] = _deserialize(v_type, v) - end - end - else # model - # models (e.g. Pet) or oneOf - klass = Petstore.const_get(type) - klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value) - end - end - # Returns the string representation of the object # @return [String] String presentation of the object def to_s From 9d4abe3d5a64880adcda330d76f95480b3cfa499 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 06:02:10 +0100 Subject: [PATCH 08/22] fix(crystal): set default values when options are not passed --- .../languages/CrystalClientCodegen.java | 19 ++++++++++++++++--- samples/client/petstore/crystal/shard.yml | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 657bf2742d61..d325f127313c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -234,43 +234,56 @@ public void processOpts() { if (additionalProperties.containsKey(SHARD_NAME)) { setShardName((String) additionalProperties.get(SHARD_NAME)); + } else { + additionalProperties.put(SHARD_NAME, shardName); } - additionalProperties.put(SHARD_NAME, shardName); if (additionalProperties.containsKey(MODULE_NAME)) { setModuleName((String) additionalProperties.get(MODULE_NAME)); + } else { + additionalProperties.put(MODULE_NAME, moduleName); } - additionalProperties.put(MODULE_NAME, moduleName); if (additionalProperties.containsKey(SHARD_VERSION)) { setShardVersion((String) additionalProperties.get(SHARD_VERSION)); } else { - // not set, pass the default value to template additionalProperties.put(SHARD_VERSION, shardVersion); } if (additionalProperties.containsKey(SHARD_LICENSE)) { setShardLicense((String) additionalProperties.get(SHARD_LICENSE)); + } else { + additionalProperties.put(SHARD_LICENSE, shardLicense); } if (additionalProperties.containsKey(SHARD_HOMEPAGE)) { setShardHomepage((String) additionalProperties.get(SHARD_HOMEPAGE)); + } else { + additionalProperties.put(SHARD_HOMEPAGE, shardHomepage); } if (additionalProperties.containsKey(SHARD_SUMMARY)) { setShardSummary((String) additionalProperties.get(SHARD_SUMMARY)); + } else { + additionalProperties.put(SHARD_SUMMARY, shardSummary); } if (additionalProperties.containsKey(SHARD_DESCRIPTION)) { setShardDescription((String) additionalProperties.get(SHARD_DESCRIPTION)); + } else { + additionalProperties.put(SHARD_DESCRIPTION, shardDescription); } if (additionalProperties.containsKey(SHARD_AUTHOR)) { setShardAuthor((String) additionalProperties.get(SHARD_AUTHOR)); + } else { + additionalProperties.put(SHARD_AUTHOR, shardAuthor); } if (additionalProperties.containsKey(SHARD_AUTHOR_EMAIL)) { setShardAuthorEmail((String) additionalProperties.get(SHARD_AUTHOR_EMAIL)); + } else { + additionalProperties.put(SHARD_AUTHOR_EMAIL, shardAuthorEmail); } if (additionalProperties.containsKey(PARAMS_ENCODER)) { diff --git a/samples/client/petstore/crystal/shard.yml b/samples/client/petstore/crystal/shard.yml index 435b47ff5a67..5240d5459800 100644 --- a/samples/client/petstore/crystal/shard.yml +++ b/samples/client/petstore/crystal/shard.yml @@ -6,7 +6,7 @@ authors: - description: | - - + - This shard maps to a REST API crystal: ">= 0.35.1" @@ -27,4 +27,4 @@ development_dependencies: gitlab: arctic-fox/spectator version: ~> 0.12.0 -license: +license: unlicense From dad9b16398e5d05a67052b7da6cde858a3da3595 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 06:10:48 +0100 Subject: [PATCH 09/22] fix(crystal): follow up https://github.com/OpenAPITools/openapi-generator/pull/22545 --- .../codegen/languages/CrystalClientCodegen.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index d325f127313c..970416226b56 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -777,9 +777,19 @@ private String constructExampleCode(CodegenModel codegenModel, HashMap Date: Tue, 23 Dec 2025 06:17:06 +0100 Subject: [PATCH 10/22] fix(crystal): remove travis.yml file --- .../codegen/languages/CrystalClientCodegen.java | 1 - .../src/main/resources/crystal/travis.mustache | 8 -------- samples/client/petstore/crystal/.openapi-generator/FILES | 1 - 3 files changed, 10 deletions(-) delete mode 100644 modules/openapi-generator/src/main/resources/crystal/travis.mustache diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 970416226b56..318074d4787d 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -309,7 +309,6 @@ public void processOpts() { supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")); supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh")); supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore")); - supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml")); supportingFiles.add(new SupportingFile("shard.mustache", "", "shard.yml")); // crystal spec files diff --git a/modules/openapi-generator/src/main/resources/crystal/travis.mustache b/modules/openapi-generator/src/main/resources/crystal/travis.mustache deleted file mode 100644 index 21509cfe82ae..000000000000 --- a/modules/openapi-generator/src/main/resources/crystal/travis.mustache +++ /dev/null @@ -1,8 +0,0 @@ -# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}} - -language: crystal - -script: - - crystal spec -# uncomment below to check the code format -# - crystal tool format --check diff --git a/samples/client/petstore/crystal/.openapi-generator/FILES b/samples/client/petstore/crystal/.openapi-generator/FILES index 30a8f1ce4b53..27f0b76ec782 100644 --- a/samples/client/petstore/crystal/.openapi-generator/FILES +++ b/samples/client/petstore/crystal/.openapi-generator/FILES @@ -1,5 +1,4 @@ .gitignore -.travis.yml README.md git_push.sh shard.yml From 221f86224f9d758938e7c8dc9bd5d280fa238eec Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 06:20:39 +0100 Subject: [PATCH 11/22] style(crystal): fix coding style --- .../languages/CrystalClientCodegen.java | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 318074d4787d..977a49ebffdc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -200,26 +200,15 @@ public CrystalClientCodegen() { cliOptions.removeIf(opt -> CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) || CodegenConstants.API_PACKAGE.equals(opt.getOpt())); cliOptions.add(new CliOption(SHARD_NAME, "shard name (e.g. twitter_client").defaultValue("openapi_client")); - cliOptions.add(new CliOption(MODULE_NAME, "module name (e.g. TwitterClient").defaultValue("OpenAPIClient")); - cliOptions.add(new CliOption(SHARD_VERSION, "shard version.").defaultValue("1.0.0")); - cliOptions.add(new CliOption(SHARD_LICENSE, "shard license.").defaultValue("unlicense")); - cliOptions.add(new CliOption(SHARD_HOMEPAGE, "shard homepage.").defaultValue("http://org.openapitools")); - cliOptions.add(new CliOption(SHARD_DESCRIPTION, "shard description.").defaultValue("This shard maps to a REST API")); - cliOptions.add(new CliOption(SHARD_AUTHOR, "shard author (only one is supported).")); - cliOptions.add(new CliOption(SHARD_AUTHOR_EMAIL, "shard author email (only one is supported).")); - cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).defaultValue(Boolean.TRUE.toString())); - - cliOptions.add(new CliOption(PARAMS_ENCODER, - "params_encoder setting (e.g. Crest::NestedParamsEncoder, Crest::EnumeratedFlatParamsEncoder, Crest::ZeroEnumeratedFlatParamsEncoder"). - defaultValue("Crest::NestedParamsEncoder")); + cliOptions.add(new CliOption(PARAMS_ENCODER, "params_encoder setting (e.g. Crest::NestedParamsEncoder, Crest::EnumeratedFlatParamsEncoder, Crest::ZeroEnumeratedFlatParamsEncoder").defaultValue("Crest::NestedParamsEncoder")); } @Override @@ -337,14 +326,12 @@ public String getName() { @Override public String apiFileFolder() { - return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator - + apiPackage.replace("/", File.separator); + return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + apiPackage.replace("/", File.separator); } @Override public String modelFileFolder() { - return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator - + modelPackage.replace("/", File.separator); + return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + modelPackage.replace("/", File.separator); } @Override From 66a4ee20a6ed79beede4eb2b25866d0bdde1b4df Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 16:18:14 +0100 Subject: [PATCH 12/22] style(crystal): use kwargs when calling @api_client --- .../src/main/resources/crystal/api.mustache | 24 ++- .../crystal/src/petstore/api/fake_api.cr | 24 ++- .../crystal/src/petstore/api/pet_api.cr | 192 ++++++++++-------- .../crystal/src/petstore/api/store_api.cr | 96 +++++---- .../crystal/src/petstore/api/user_api.cr | 192 ++++++++++-------- 5 files changed, 308 insertions(+), 220 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/api.mustache b/modules/openapi-generator/src/main/resources/crystal/api.mustache index 01a51a2d028f..7a2ae7c9ffa5 100644 --- a/modules/openapi-generator/src/main/resources/crystal/api.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/api.mustache @@ -165,19 +165,23 @@ module {{moduleName}} # auth_names auth_names = {{#authMethods}}{{#-first}}[{{/-first}}"{{name}}"{{^-last}}, {{/-last}}{{#-last}}]{{/-last}}{{/authMethods}}{{^authMethods}}[] of String{{/authMethods}} - data, status_code, headers = @api_client.call_api(:{{httpMethod}}, - local_var_path, - :"{{classname}}.{{operationId}}", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :{{httpMethod}}, + path: local_var_path, + operation: :"{{classname}}.{{operationId}}", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: {{classname}}#{{operationId}}\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return {{#returnType}}{{{.}}}.from_json(data){{/returnType}}{{^returnType}}nil{{/returnType}}, status_code, headers end {{^-last}} diff --git a/samples/client/petstore/crystal/src/petstore/api/fake_api.cr b/samples/client/petstore/crystal/src/petstore/api/fake_api.cr index aa1bf1bd4d3e..08e6dceb4f8b 100644 --- a/samples/client/petstore/crystal/src/petstore/api/fake_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/fake_api.cr @@ -89,19 +89,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"FakeApi.get_parameter_name_mapping", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"FakeApi.get_parameter_name_mapping", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: FakeApi#get_parameter_name_mapping\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end end diff --git a/samples/client/petstore/crystal/src/petstore/api/pet_api.cr b/samples/client/petstore/crystal/src/petstore/api/pet_api.cr index fa2be55965f9..4cb18742d0c2 100644 --- a/samples/client/petstore/crystal/src/petstore/api/pet_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/pet_api.cr @@ -66,19 +66,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"PetApi.add_pet", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"PetApi.add_pet", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#add_pet\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Pet.from_json(data), status_code, headers end @@ -128,19 +132,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:DELETE, - local_var_path, - :"PetApi.delete_pet", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :DELETE, + path: local_var_path, + operation: :"PetApi.delete_pet", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#delete_pet\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -192,19 +200,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"PetApi.find_pets_by_status", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"PetApi.find_pets_by_status", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#find_pets_by_status\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Array(Pet).from_json(data), status_code, headers end @@ -256,19 +268,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"PetApi.find_pets_by_tags", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"PetApi.find_pets_by_tags", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#find_pets_by_tags\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Array(Pet).from_json(data), status_code, headers end @@ -319,19 +335,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"PetApi.get_pet_by_id", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"PetApi.get_pet_by_id", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#get_pet_by_id\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Pet.from_json(data), status_code, headers end @@ -384,19 +404,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:PUT, - local_var_path, - :"PetApi.update_pet", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :PUT, + path: local_var_path, + operation: :"PetApi.update_pet", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#update_pet\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Pet.from_json(data), status_code, headers end @@ -449,19 +473,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"PetApi.update_pet_with_form", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"PetApi.update_pet_with_form", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#update_pet_with_form\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -516,19 +544,23 @@ module Petstore # auth_names auth_names = ["petstore_auth"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"PetApi.upload_file", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"PetApi.upload_file", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: PetApi#upload_file\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return ApiResponse.from_json(data), status_code, headers end end diff --git a/samples/client/petstore/crystal/src/petstore/api/store_api.cr b/samples/client/petstore/crystal/src/petstore/api/store_api.cr index 3ece615e869f..09bf1b391462 100644 --- a/samples/client/petstore/crystal/src/petstore/api/store_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/store_api.cr @@ -62,19 +62,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:DELETE, - local_var_path, - :"StoreApi.delete_order", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :DELETE, + path: local_var_path, + operation: :"StoreApi.delete_order", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: StoreApi#delete_order\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -119,19 +123,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"StoreApi.get_inventory", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"StoreApi.get_inventory", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: StoreApi#get_inventory\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Hash(String, Int32).from_json(data), status_code, headers end @@ -190,19 +198,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"StoreApi.get_order_by_id", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"StoreApi.get_order_by_id", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: StoreApi#get_order_by_id\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Order.from_json(data), status_code, headers end @@ -255,19 +267,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"StoreApi.place_order", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"StoreApi.place_order", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: StoreApi#place_order\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return Order.from_json(data), status_code, headers end end diff --git a/samples/client/petstore/crystal/src/petstore/api/user_api.cr b/samples/client/petstore/crystal/src/petstore/api/user_api.cr index 2f349cac381b..fed8d527e38c 100644 --- a/samples/client/petstore/crystal/src/petstore/api/user_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/user_api.cr @@ -64,19 +64,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"UserApi.create_user", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"UserApi.create_user", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#create_user\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -127,19 +131,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"UserApi.create_users_with_array_input", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"UserApi.create_users_with_array_input", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#create_users_with_array_input\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -190,19 +198,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:POST, - local_var_path, - :"UserApi.create_users_with_list_input", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :POST, + path: local_var_path, + operation: :"UserApi.create_users_with_list_input", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#create_users_with_list_input\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -251,19 +263,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:DELETE, - local_var_path, - :"UserApi.delete_user", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :DELETE, + path: local_var_path, + operation: :"UserApi.delete_user", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#delete_user\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -314,19 +330,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"UserApi.get_user_by_name", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"UserApi.get_user_by_name", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#get_user_by_name\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return User.from_json(data), status_code, headers end @@ -390,19 +410,23 @@ module Petstore # auth_names auth_names = [] of String - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"UserApi.login_user", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"UserApi.login_user", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#login_user\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return String.from_json(data), status_code, headers end @@ -445,19 +469,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:GET, - local_var_path, - :"UserApi.logout_user", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :GET, + path: local_var_path, + operation: :"UserApi.logout_user", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#logout_user\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end @@ -514,19 +542,23 @@ module Petstore # auth_names auth_names = ["api_key"] - data, status_code, headers = @api_client.call_api(:PUT, - local_var_path, - :"UserApi.update_user", - return_type, - post_body, - auth_names, - header_params, - query_params, - cookie_params, - form_params) + data, status_code, headers = @api_client.call_api( + http_method: :PUT, + path: local_var_path, + operation: :"UserApi.update_user", + return_type: return_type, + post_body: post_body, + auth_names: auth_names, + header_params: header_params, + query_params: query_params, + cookie_params: cookie_params, + form_params: form_params + ) + if @api_client.config.debugging Log.debug {"API called: UserApi#update_user\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"} end + return nil, status_code, headers end end From 7638e61e559b9bf62d5c184dbda708751f0f9d88 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 16:37:09 +0100 Subject: [PATCH 13/22] style(crystal): use kwargs when calling Crest::Request.new --- .../src/main/resources/crystal/api_client.mustache | 5 +++-- samples/client/petstore/crystal/src/petstore/api_client.cr | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/api_client.mustache b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache index 3eff533c447a..5000d3809c89 100644 --- a/modules/openapi-generator/src/main/resources/crystal/api_client.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache @@ -139,8 +139,9 @@ module {{moduleName}} form_or_body = form_params end - request = Crest::Request.new(http_method, - build_request_url(path, operation), + request = Crest::Request.new( + method: http_method, + url: build_request_url(path, operation), params: query_params, headers: header_params, cookies: cookie_params, diff --git a/samples/client/petstore/crystal/src/petstore/api_client.cr b/samples/client/petstore/crystal/src/petstore/api_client.cr index 8f842e530009..0a0632e32407 100644 --- a/samples/client/petstore/crystal/src/petstore/api_client.cr +++ b/samples/client/petstore/crystal/src/petstore/api_client.cr @@ -147,8 +147,9 @@ module Petstore form_or_body = form_params end - request = Crest::Request.new(http_method, - build_request_url(path, operation), + request = Crest::Request.new( + method: http_method, + url: build_request_url(path, operation), params: query_params, headers: header_params, cookies: cookie_params, From 6f01c8b4fe0378f3c7a64ecae53a02e4e4acc0ab Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Tue, 23 Dec 2025 16:42:37 +0100 Subject: [PATCH 14/22] fix(crystal): remove useless return_type argument --- .../src/main/resources/crystal/api.mustache | 4 --- .../resources/crystal/api_client.mustache | 2 +- .../crystal/src/petstore/api/fake_api.cr | 4 --- .../crystal/src/petstore/api/pet_api.cr | 32 ------------------- .../crystal/src/petstore/api/store_api.cr | 16 ---------- .../crystal/src/petstore/api/user_api.cr | 32 ------------------- .../crystal/src/petstore/api_client.cr | 2 +- 7 files changed, 2 insertions(+), 90 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/api.mustache b/modules/openapi-generator/src/main/resources/crystal/api.mustache index 7a2ae7c9ffa5..fe6b5857f8d2 100644 --- a/modules/openapi-generator/src/main/resources/crystal/api.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/api.mustache @@ -159,9 +159,6 @@ module {{moduleName}} # http body (model) post_body = {{#bodyParam}}{{{paramName}}}.to_json{{/bodyParam}}{{^bodyParam}}nil{{/bodyParam}} - # return_type - return_type = {{#returnType}}"{{{.}}}"{{/returnType}}{{^returnType}}nil{{/returnType}} - # auth_names auth_names = {{#authMethods}}{{#-first}}[{{/-first}}"{{name}}"{{^-last}}, {{/-last}}{{#-last}}]{{/-last}}{{/authMethods}}{{^authMethods}}[] of String{{/authMethods}} @@ -169,7 +166,6 @@ module {{moduleName}} http_method: :{{httpMethod}}, path: local_var_path, operation: :"{{classname}}.{{operationId}}", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, diff --git a/modules/openapi-generator/src/main/resources/crystal/api_client.mustache b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache index 5000d3809c89..dc3cb6872de3 100644 --- a/modules/openapi-generator/src/main/resources/crystal/api_client.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache @@ -119,7 +119,7 @@ module {{moduleName}} # # @return [Array<(Object, Integer, Hash)>] an array of 3 elements: # the data deserialized from response body (could be nil), response status code and response headers. - def call_api(http_method : Symbol, path : String, operation : Symbol, return_type : String?, post_body : String?, auth_names = [] of String, header_params = {} of String => String, query_params = {} of String => String, cookie_params = {} of String => String, form_params = {} of Symbol => (String | ::File)) + def call_api(http_method : Symbol, path : String, operation : Symbol, post_body : String?, auth_names = [] of String, header_params = {} of String => String, query_params = {} of String => String, cookie_params = {} of String => String, form_params = {} of Symbol => (String | ::File)) #ssl_options = { # :ca_file => @config.ssl_ca_file, # :verify => @config.ssl_verify, diff --git a/samples/client/petstore/crystal/src/petstore/api/fake_api.cr b/samples/client/petstore/crystal/src/petstore/api/fake_api.cr index 08e6dceb4f8b..b6b6a2cf6306 100644 --- a/samples/client/petstore/crystal/src/petstore/api/fake_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/fake_api.cr @@ -83,9 +83,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = [] of String @@ -93,7 +90,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"FakeApi.get_parameter_name_mapping", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, diff --git a/samples/client/petstore/crystal/src/petstore/api/pet_api.cr b/samples/client/petstore/crystal/src/petstore/api/pet_api.cr index 4cb18742d0c2..07d5dd377865 100644 --- a/samples/client/petstore/crystal/src/petstore/api/pet_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/pet_api.cr @@ -60,9 +60,6 @@ module Petstore # http body (model) post_body = pet.to_json - # return_type - return_type = "Pet" - # auth_names auth_names = ["petstore_auth"] @@ -70,7 +67,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"PetApi.add_pet", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -126,9 +122,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = ["petstore_auth"] @@ -136,7 +129,6 @@ module Petstore http_method: :DELETE, path: local_var_path, operation: :"PetApi.delete_pet", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -194,9 +186,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "Array(Pet)" - # auth_names auth_names = ["petstore_auth"] @@ -204,7 +193,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"PetApi.find_pets_by_status", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -262,9 +250,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "Array(Pet)" - # auth_names auth_names = ["petstore_auth"] @@ -272,7 +257,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"PetApi.find_pets_by_tags", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -329,9 +313,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "Pet" - # auth_names auth_names = ["api_key"] @@ -339,7 +320,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"PetApi.get_pet_by_id", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -398,9 +378,6 @@ module Petstore # http body (model) post_body = pet.to_json - # return_type - return_type = "Pet" - # auth_names auth_names = ["petstore_auth"] @@ -408,7 +385,6 @@ module Petstore http_method: :PUT, path: local_var_path, operation: :"PetApi.update_pet", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -467,9 +443,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = ["petstore_auth"] @@ -477,7 +450,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"PetApi.update_pet_with_form", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -538,9 +510,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "ApiResponse" - # auth_names auth_names = ["petstore_auth"] @@ -548,7 +517,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"PetApi.upload_file", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, diff --git a/samples/client/petstore/crystal/src/petstore/api/store_api.cr b/samples/client/petstore/crystal/src/petstore/api/store_api.cr index 09bf1b391462..97343d84835f 100644 --- a/samples/client/petstore/crystal/src/petstore/api/store_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/store_api.cr @@ -56,9 +56,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = [] of String @@ -66,7 +63,6 @@ module Petstore http_method: :DELETE, path: local_var_path, operation: :"StoreApi.delete_order", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -117,9 +113,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "Hash(String, Int32)" - # auth_names auth_names = ["api_key"] @@ -127,7 +120,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"StoreApi.get_inventory", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -192,9 +184,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "Order" - # auth_names auth_names = [] of String @@ -202,7 +191,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"StoreApi.get_order_by_id", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -261,9 +249,6 @@ module Petstore # http body (model) post_body = order.to_json - # return_type - return_type = "Order" - # auth_names auth_names = [] of String @@ -271,7 +256,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"StoreApi.place_order", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, diff --git a/samples/client/petstore/crystal/src/petstore/api/user_api.cr b/samples/client/petstore/crystal/src/petstore/api/user_api.cr index fed8d527e38c..6eac08612c11 100644 --- a/samples/client/petstore/crystal/src/petstore/api/user_api.cr +++ b/samples/client/petstore/crystal/src/petstore/api/user_api.cr @@ -58,9 +58,6 @@ module Petstore # http body (model) post_body = user.to_json - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -68,7 +65,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"UserApi.create_user", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -125,9 +121,6 @@ module Petstore # http body (model) post_body = user.to_json - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -135,7 +128,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"UserApi.create_users_with_array_input", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -192,9 +184,6 @@ module Petstore # http body (model) post_body = user.to_json - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -202,7 +191,6 @@ module Petstore http_method: :POST, path: local_var_path, operation: :"UserApi.create_users_with_list_input", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -257,9 +245,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -267,7 +252,6 @@ module Petstore http_method: :DELETE, path: local_var_path, operation: :"UserApi.delete_user", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -324,9 +308,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "User" - # auth_names auth_names = [] of String @@ -334,7 +315,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"UserApi.get_user_by_name", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -404,9 +384,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = "String" - # auth_names auth_names = [] of String @@ -414,7 +391,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"UserApi.login_user", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -463,9 +439,6 @@ module Petstore # http body (model) post_body = nil - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -473,7 +446,6 @@ module Petstore http_method: :GET, path: local_var_path, operation: :"UserApi.logout_user", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, @@ -536,9 +508,6 @@ module Petstore # http body (model) post_body = user.to_json - # return_type - return_type = nil - # auth_names auth_names = ["api_key"] @@ -546,7 +515,6 @@ module Petstore http_method: :PUT, path: local_var_path, operation: :"UserApi.update_user", - return_type: return_type, post_body: post_body, auth_names: auth_names, header_params: header_params, diff --git a/samples/client/petstore/crystal/src/petstore/api_client.cr b/samples/client/petstore/crystal/src/petstore/api_client.cr index 0a0632e32407..3e62e6fea087 100644 --- a/samples/client/petstore/crystal/src/petstore/api_client.cr +++ b/samples/client/petstore/crystal/src/petstore/api_client.cr @@ -127,7 +127,7 @@ module Petstore # # @return [Array<(Object, Integer, Hash)>] an array of 3 elements: # the data deserialized from response body (could be nil), response status code and response headers. - def call_api(http_method : Symbol, path : String, operation : Symbol, return_type : String?, post_body : String?, auth_names = [] of String, header_params = {} of String => String, query_params = {} of String => String, cookie_params = {} of String => String, form_params = {} of Symbol => (String | ::File)) + def call_api(http_method : Symbol, path : String, operation : Symbol, post_body : String?, auth_names = [] of String, header_params = {} of String => String, query_params = {} of String => String, cookie_params = {} of String => String, form_params = {} of Symbol => (String | ::File)) #ssl_options = { # :ca_file => @config.ssl_ca_file, # :verify => @config.ssl_verify, From 0102edf5caac90b6bd3254d1c99648354163578f Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Wed, 24 Dec 2025 17:13:41 +0100 Subject: [PATCH 15/22] fix(crystal): map object type to JSON::Any type --- .../openapitools/codegen/languages/CrystalClientCodegen.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index 977a49ebffdc..a8bad90da84e 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -183,8 +183,8 @@ public CrystalClientCodegen() { typeMapping.put("List", "Array"); typeMapping.put("set", "Set"); typeMapping.put("map", "Hash"); - typeMapping.put("object", "Object"); - typeMapping.put("AnyType", "Object"); + typeMapping.put("object", "JSON::Any"); + typeMapping.put("AnyType", "JSON::Any"); typeMapping.put("file", "::File"); typeMapping.put("binary", "String"); typeMapping.put("ByteArray", "String"); From 594c6043162a5a94019d7f10ed332a1b53e69c07 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Wed, 24 Dec 2025 17:14:52 +0100 Subject: [PATCH 16/22] fix(crystal): reduce use of require --- .../src/main/resources/crystal/model.mustache | 5 ----- .../src/main/resources/crystal/shard_name.mustache | 10 ++++++++-- samples/client/petstore/crystal/src/petstore.cr | 10 ++++++++-- .../petstore/models/another_property_name_mapping.cr | 5 ----- .../crystal/src/petstore/models/api_response.cr | 5 ----- .../petstore/crystal/src/petstore/models/category.cr | 5 ----- .../crystal/src/petstore/models/format_test.cr | 5 ----- .../petstore/crystal/src/petstore/models/order.cr | 5 ----- .../client/petstore/crystal/src/petstore/models/pet.cr | 5 ----- .../client/petstore/crystal/src/petstore/models/tag.cr | 5 ----- .../petstore/crystal/src/petstore/models/user.cr | 5 ----- 11 files changed, 16 insertions(+), 49 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/model.mustache b/modules/openapi-generator/src/main/resources/crystal/model.mustache index 31cd656198f8..2de5ed1b7780 100644 --- a/modules/openapi-generator/src/main/resources/crystal/model.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/model.mustache @@ -1,10 +1,5 @@ # {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}} -require "big" -require "json" -require "yaml" -require "time" - module {{moduleName}} {{#models}} {{#model}} diff --git a/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache b/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache index 16e645c5e66c..85e39816da44 100644 --- a/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache @@ -1,9 +1,15 @@ # {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}} -# Dependencies +# Stdlib dependencies +require "big" +require "json" +require "log" +require "time" +require "yaml" + +# External dependencies require "any_hash" require "crest" -require "log" module {{moduleName}} Log = ::Log.for("{{moduleName}}") # => Log for {{moduleName}} source diff --git a/samples/client/petstore/crystal/src/petstore.cr b/samples/client/petstore/crystal/src/petstore.cr index 50599155e969..8c4accd94d4c 100644 --- a/samples/client/petstore/crystal/src/petstore.cr +++ b/samples/client/petstore/crystal/src/petstore.cr @@ -8,10 +8,16 @@ #Generator version: 7.19.0-SNAPSHOT # -# Dependencies +# Stdlib dependencies +require "big" +require "json" +require "log" +require "time" +require "yaml" + +# External dependencies require "any_hash" require "crest" -require "log" module Petstore Log = ::Log.for("Petstore") # => Log for Petstore source diff --git a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr index d21008f2e39e..c33ca4aa92a9 100644 --- a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr +++ b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore class AnotherPropertyNameMapping include JSON::Serializable 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 cccbcbc83ffc..3bb4e88e0958 100644 --- a/samples/client/petstore/crystal/src/petstore/models/api_response.cr +++ b/samples/client/petstore/crystal/src/petstore/models/api_response.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # Describes the result of uploading an image resource class ApiResponse diff --git a/samples/client/petstore/crystal/src/petstore/models/category.cr b/samples/client/petstore/crystal/src/petstore/models/category.cr index 43ab2668c4de..26bbf66cc49a 100644 --- a/samples/client/petstore/crystal/src/petstore/models/category.cr +++ b/samples/client/petstore/crystal/src/petstore/models/category.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # A category for a pet class Category diff --git a/samples/client/petstore/crystal/src/petstore/models/format_test.cr b/samples/client/petstore/crystal/src/petstore/models/format_test.cr index a7f6b3a803b1..ebc1cf4a16f7 100644 --- a/samples/client/petstore/crystal/src/petstore/models/format_test.cr +++ b/samples/client/petstore/crystal/src/petstore/models/format_test.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore class FormatTest include JSON::Serializable diff --git a/samples/client/petstore/crystal/src/petstore/models/order.cr b/samples/client/petstore/crystal/src/petstore/models/order.cr index 0a8e34a47226..cd1b87abdf78 100644 --- a/samples/client/petstore/crystal/src/petstore/models/order.cr +++ b/samples/client/petstore/crystal/src/petstore/models/order.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # An order for a pets from the pet store class Order diff --git a/samples/client/petstore/crystal/src/petstore/models/pet.cr b/samples/client/petstore/crystal/src/petstore/models/pet.cr index 98710693ea0d..0d7b40df486c 100644 --- a/samples/client/petstore/crystal/src/petstore/models/pet.cr +++ b/samples/client/petstore/crystal/src/petstore/models/pet.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # A pet for sale in the pet store class Pet diff --git a/samples/client/petstore/crystal/src/petstore/models/tag.cr b/samples/client/petstore/crystal/src/petstore/models/tag.cr index 63d50aec73ed..4718eef8d790 100644 --- a/samples/client/petstore/crystal/src/petstore/models/tag.cr +++ b/samples/client/petstore/crystal/src/petstore/models/tag.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # A tag for a pet class Tag diff --git a/samples/client/petstore/crystal/src/petstore/models/user.cr b/samples/client/petstore/crystal/src/petstore/models/user.cr index 8d47fc01741a..71e5868103a1 100644 --- a/samples/client/petstore/crystal/src/petstore/models/user.cr +++ b/samples/client/petstore/crystal/src/petstore/models/user.cr @@ -8,11 +8,6 @@ #Generator version: 7.19.0-SNAPSHOT # -require "big" -require "json" -require "yaml" -require "time" - module Petstore # A User who is purchasing from the pet store class User From 7656747099e8c373182b781077c97e0c34f931a4 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Wed, 24 Dec 2025 18:09:38 +0100 Subject: [PATCH 17/22] fix(crystal): return early if value is nil --- .../src/main/resources/crystal/base_object.mustache | 2 ++ .../src/petstore/models/another_property_name_mapping.cr | 2 ++ .../client/petstore/crystal/src/petstore/models/api_response.cr | 2 ++ samples/client/petstore/crystal/src/petstore/models/category.cr | 2 ++ .../client/petstore/crystal/src/petstore/models/format_test.cr | 2 ++ samples/client/petstore/crystal/src/petstore/models/order.cr | 2 ++ samples/client/petstore/crystal/src/petstore/models/pet.cr | 2 ++ samples/client/petstore/crystal/src/petstore/models/tag.cr | 2 ++ samples/client/petstore/crystal/src/petstore/models/user.cr | 2 ++ 9 files changed, 18 insertions(+) diff --git a/modules/openapi-generator/src/main/resources/crystal/base_object.mustache b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache index 2debd0d669c7..4ee4a276ca0f 100644 --- a/modules/openapi-generator/src/main/resources/crystal/base_object.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache @@ -25,6 +25,8 @@ # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr index c33ca4aa92a9..da09d66dcd7e 100644 --- a/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr +++ b/samples/client/petstore/crystal/src/petstore/models/another_property_name_mapping.cr @@ -95,6 +95,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } 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 3bb4e88e0958..e5235921eb68 100644 --- a/samples/client/petstore/crystal/src/petstore/models/api_response.cr +++ b/samples/client/petstore/crystal/src/petstore/models/api_response.cr @@ -91,6 +91,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/category.cr b/samples/client/petstore/crystal/src/petstore/models/category.cr index 26bbf66cc49a..7618edd3b95f 100644 --- a/samples/client/petstore/crystal/src/petstore/models/category.cr +++ b/samples/client/petstore/crystal/src/petstore/models/category.cr @@ -103,6 +103,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/format_test.cr b/samples/client/petstore/crystal/src/petstore/models/format_test.cr index ebc1cf4a16f7..1e5736eef5a9 100644 --- a/samples/client/petstore/crystal/src/petstore/models/format_test.cr +++ b/samples/client/petstore/crystal/src/petstore/models/format_test.cr @@ -353,6 +353,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/order.cr b/samples/client/petstore/crystal/src/petstore/models/order.cr index cd1b87abdf78..faa2ad6958ef 100644 --- a/samples/client/petstore/crystal/src/petstore/models/order.cr +++ b/samples/client/petstore/crystal/src/petstore/models/order.cr @@ -161,6 +161,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/pet.cr b/samples/client/petstore/crystal/src/petstore/models/pet.cr index 0d7b40df486c..d4f6f75d1778 100644 --- a/samples/client/petstore/crystal/src/petstore/models/pet.cr +++ b/samples/client/petstore/crystal/src/petstore/models/pet.cr @@ -162,6 +162,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/tag.cr b/samples/client/petstore/crystal/src/petstore/models/tag.cr index 4718eef8d790..e203f1aca442 100644 --- a/samples/client/petstore/crystal/src/petstore/models/tag.cr +++ b/samples/client/petstore/crystal/src/petstore/models/tag.cr @@ -86,6 +86,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } diff --git a/samples/client/petstore/crystal/src/petstore/models/user.cr b/samples/client/petstore/crystal/src/petstore/models/user.cr index 71e5868103a1..a1a7a72530d1 100644 --- a/samples/client/petstore/crystal/src/petstore/models/user.cr +++ b/samples/client/petstore/crystal/src/petstore/models/user.cr @@ -117,6 +117,8 @@ module Petstore # @param [Object] value Any valid value # @return [Hash] Returns the value in the form of hash private def _to_h(value) + return nil if value.nil? + if value.is_a?(Hash) hash = NetboxClient::RecursiveHash.new value.each { |k, v| hash[k] = _to_h(v) } From b72b9572de07d6dbee6d7b4ba8c1ed5ffefa9a89 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Wed, 24 Dec 2025 18:37:36 +0100 Subject: [PATCH 18/22] fix(crystal): update doc --- docs/generators/crystal.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/generators/crystal.md b/docs/generators/crystal.md index ba481b67e4da..ea2b3cc400db 100644 --- a/docs/generators/crystal.md +++ b/docs/generators/crystal.md @@ -94,7 +94,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
  • if
  • in
  • include
  • -
  • instance
  • +
  • instance_sizeof
  • is_a?
  • lib
  • macro
  • @@ -102,9 +102,11 @@ These options may be applied as additional-properties (cli) or configOptions (pl
  • next
  • nil
  • nil?
  • +
  • object_id
  • of
  • out
  • pointerof
  • +
  • previous_def
  • private
  • protected
  • require
  • From 024d3f37be25103129656861a56e3498e0f0010d Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Thu, 25 Dec 2025 03:16:19 +0100 Subject: [PATCH 19/22] fix(crystal): remove dead code --- .../src/main/resources/crystal/spec_helper.mustache | 10 ---------- samples/client/petstore/crystal/spec/spec_helper.cr | 10 ---------- 2 files changed, 20 deletions(-) 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 98890dcc2462..2f89b5a69715 100644 --- a/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache @@ -2,14 +2,4 @@ # load modules require "spectator" -require "json" -require "time" 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/spec/spec_helper.cr b/samples/client/petstore/crystal/spec/spec_helper.cr index d1cb88f5a39a..6e10fb2392a0 100644 --- a/samples/client/petstore/crystal/spec/spec_helper.cr +++ b/samples/client/petstore/crystal/spec/spec_helper.cr @@ -10,14 +10,4 @@ # load modules require "spectator" -require "json" -require "time" 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 From d4a4d8f986a36b6bcc28550b75ba37274c2993d3 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Thu, 25 Dec 2025 03:20:22 +0100 Subject: [PATCH 20/22] fix(crystal): kemal is not used in specs --- .../src/main/resources/crystal/shard.mustache | 3 --- samples/client/petstore/crystal/shard.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/shard.mustache b/modules/openapi-generator/src/main/resources/crystal/shard.mustache index cacf91c160ae..08a392a1a491 100644 --- a/modules/openapi-generator/src/main/resources/crystal/shard.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/shard.mustache @@ -20,9 +20,6 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - kemal: - github: kemalcr/kemal - version: ~> 1.5.0 spectator: gitlab: arctic-fox/spectator version: ~> 0.12.0 diff --git a/samples/client/petstore/crystal/shard.yml b/samples/client/petstore/crystal/shard.yml index 5240d5459800..966c45d917fd 100644 --- a/samples/client/petstore/crystal/shard.yml +++ b/samples/client/petstore/crystal/shard.yml @@ -20,9 +20,6 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - kemal: - github: kemalcr/kemal - version: ~> 1.5.0 spectator: gitlab: arctic-fox/spectator version: ~> 0.12.0 From c5e8303f03eb89dcdc37c7d87b40460def6a375e Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Thu, 25 Dec 2025 03:46:26 +0100 Subject: [PATCH 21/22] fix(crystal): class name should be in PascalCase Fix: - class EnumAttributeValidatorFor_type < EnumAttributeValidator + class EnumAttributeValidatorForType < EnumAttributeValidator --- .../languages/CrystalClientCodegen.java | 6 +-- .../templating/mustache/PascalCaseLambda.java | 51 +++++++++++++++++++ .../crystal/partial_model_generic.mustache | 8 +-- 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PascalCaseLambda.java diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java index a8bad90da84e..29bb9e09ab95 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java @@ -29,8 +29,7 @@ import org.openapitools.codegen.model.OperationMap; import org.openapitools.codegen.model.OperationsMap; import org.openapitools.codegen.templating.mustache.PrefixWithHashLambda; -import org.openapitools.codegen.templating.mustache.UppercaseLambda; -import org.openapitools.codegen.templating.mustache.TitlecaseLambda; +import org.openapitools.codegen.templating.mustache.PascalCaseLambda; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -305,8 +304,7 @@ public void processOpts() { // add lambda for mustache templates additionalProperties.put("lambdaPrefixWithHash", new PrefixWithHashLambda()); - additionalProperties.put("lambdaUppercase", new UppercaseLambda()); - additionalProperties.put("lambdaTitlecase", new TitlecaseLambda()); + additionalProperties.put("lambdaPascalcase", new PascalCaseLambda()); } @Override diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PascalCaseLambda.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PascalCaseLambda.java new file mode 100644 index 000000000000..3bc1d4f338f8 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PascalCaseLambda.java @@ -0,0 +1,51 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.templating.mustache; + +import com.samskivert.mustache.Mustache; +import com.samskivert.mustache.Template; +import org.openapitools.codegen.utils.CamelizeOption; + +import java.io.IOException; +import java.io.Writer; + +import static org.openapitools.codegen.utils.StringUtils.camelize; + +/** + * Converts text in a fragment to PascalCase. + *

    + * Register: + *

    + * additionalProperties.put("pascalcase", new PascalCaseLambda());
    + * 
    + *

    + * Use: + *

    + * {{#pascalcase}}{{name}}{{/pascalcase}}
    + * 
    + */ +public class PascalCaseLambda implements Mustache.Lambda { + public PascalCaseLambda() { + } + + @Override + public void execute(Template.Fragment fragment, Writer writer) throws IOException { + String text = fragment.execute(); + text = camelize(text); + writer.write(text); + } +} 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 791a2084ddda..8d0ded604024 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 @@ -56,7 +56,7 @@ {{#vars}} {{#isEnum}} {{^isContainer}} - class EnumAttributeValidatorFor{{#lambdaTitlecase}}{{{name}}}{{/lambdaTitlecase}} < EnumAttributeValidator + class EnumAttributeValidatorFor{{#lambdaPascalcase}}{{{name}}}{{/lambdaPascalcase}} < EnumAttributeValidator @attribute : String @allowable_values : Array(Int32 | Int64 | Float32 | Float64 | String) @@ -118,7 +118,7 @@ {{#vars}} {{#isEnum}} {{^isContainer}} - {{{name}}}_validator = EnumAttributeValidatorFor{{#lambdaTitlecase}}{{{name}}}{{/lambdaTitlecase}}.new + {{{name}}}_validator = EnumAttributeValidatorFor{{#lambdaPascalcase}}{{{name}}}{{/lambdaPascalcase}}.new if !{{{name}}}_validator.valid?(@{{{name}}}) message = {{{name}}}_validator.message invalid_properties.push(message) @@ -181,7 +181,7 @@ {{#vars}} {{#isEnum}} {{^isContainer}} - {{{name}}}_validator = EnumAttributeValidatorFor{{#lambdaTitlecase}}{{{name}}}{{/lambdaTitlecase}}.new + {{{name}}}_validator = EnumAttributeValidatorFor{{#lambdaPascalcase}}{{{name}}}{{/lambdaPascalcase}}.new return false unless {{{name}}}_validator.valid?(@{{{name}}}) {{/isContainer}} {{/isEnum}} @@ -234,7 +234,7 @@ # Custom attribute writer method checking allowed values (enum). # @param [Object] {{{name}}} Object to be assigned def {{{name}}}=({{{name}}}) - validator = EnumAttributeValidatorFor{{#lambdaTitlecase}}{{{name}}}{{/lambdaTitlecase}}.new + validator = EnumAttributeValidatorFor{{#lambdaPascalcase}}{{{name}}}{{/lambdaPascalcase}}.new unless validator.valid?({{{name}}}) raise ArgumentError.new(validator.message) end From 5ad006ed9dbbfe27674897a3627258e5085e7642 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Thu, 25 Dec 2025 03:56:13 +0100 Subject: [PATCH 22/22] fix(crystal): fix ameba warnings --- .../src/main/resources/crystal/partial_oneof_module.mustache | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache index fcd6d2237e5c..f18cd6f6d9cd 100644 --- a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache +++ b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache @@ -51,11 +51,11 @@ case data when NetboxClient::RecursiveHash - if (value = cast_value(array_data: false, array_class: array_class?(klass), klass: klass, data: data)) + if value = cast_value(array_data: false, array_class: array_class?(klass), klass: klass, data: data) return new(value) end when Array(NetboxClient::RecursiveHash) - if (value = cast_value(array_data: true, array_class: array_class?(klass), klass: klass, data: data)) + if value = cast_value(array_data: true, array_class: array_class?(klass), klass: klass, data: data) return new(value) end else