Skip to content

[crystal-lang] Various fixes for Crystal client #21011

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ module {{moduleName}}
{{/maxLength}}
{{#minLength}}
if @api_client.config.client_side_validation && {{^required}}!{{{paramName}}}.nil? && {{/required}}{{{paramName}}}.to_s.size < {{{minLength}}}
raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, the character length must be great than or equal to {{{minLength}}}.")
raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, the character length must be greater than or equal to {{{minLength}}}.")
end

{{/minLength}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}

require "../spec_helper"
require "json"
require "time"

# Unit tests for {{moduleName}}::{{classname}}
# Automatically generated by openapi-generator (https://openapi-generator.tech)
# Please update as you see appropriate
{{#operations}}describe "{{classname}}" do
{{#operations}}
Spectator.describe "{{classname}}" do
describe "test an instance of {{classname}}" do
it "should create an instance of {{classname}}" do
api_instance = {{moduleName}}::{{classname}}.new
# TODO expect(api_instance).to be_instance_of({{moduleName}}::{{classname}})
expect(api_instance).to be_instance_of({{moduleName}}::{{classname}})
end
end

Expand All @@ -28,7 +27,7 @@ require "time"
{{#allParams}}{{^required}} # @option opts [{{{dataType}}}] :{{paramName}} {{description}}
{{/required}}{{/allParams}} # @return [{{{returnType}}}{{^returnType}}nil{{/returnType}}]
describe "{{operationId}} test" do
it "should work" do
skip "should work" do
# assertion here. ref: https://crystal-lang.org/reference/guides/testing.html
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ module {{moduleName}}
{{#isApiKey}}
"{{name}}" => {
type: "api_key",
in: {{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}},
in: {{#isKeyInCookie}}"cookie"{{/isKeyInCookie}}{{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}},
key: "{{keyParamName}}",
value: api_key_with_prefix(:{{keyParamName}})
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

require 'spec_helper'

describe {{moduleName}}::Configuration do
Spectator.describe {{moduleName}}::Configuration do
let(:config) { {{moduleName}}::Configuration.default }

before(:each) do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}

require "../spec_helper"
require "json"
require "time"

# Unit tests for {{moduleName}}::{{classname}}
# Automatically generated by openapi-generator (https://openapi-generator.tech)
# Please update as you see appropriate
{{#models}}
{{#model}}
describe {{moduleName}}::{{classname}} do
Spectator.describe {{moduleName}}::{{classname}} do
{{^oneOf}}

describe "test an instance of {{classname}}" do
it "should create an instance of {{classname}}" do
skip "should create an instance of {{classname}}" do
#instance = {{moduleName}}::{{classname}}.new
#expect(instance).to be_instance_of({{moduleName}}::{{classname}})
end
end
{{#vars}}
describe "test attribute '{{{name}}}'" do
it "should work" do
skip "should work" do
{{#isEnum}}
# assertion here. ref: https://crystal-lang.org/reference/guides/testing.html
# validator = Petstore::EnumTest::EnumAttributeValidator.new("{{{dataType}}}", [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}])
Expand Down Expand Up @@ -65,7 +63,7 @@ describe {{moduleName}}::{{classname}} do
{{/mappedModels}}
{{/discriminator}}
describe ".build" do
it "returns the correct model" do
skip "returns the correct model" do
end
end
{{/-first}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
{{/maxLength}}
{{#minLength}}
if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.size < {{{minLength}}}
invalid_properties.push("invalid value for \"{{{name}}}\", the character length must be great than or equal to {{{minLength}}}.")
invalid_properties.push("invalid value for \"{{{name}}}\", the character length must be greater than or equal to {{{minLength}}}.")
end

{{/minLength}}
Expand Down Expand Up @@ -226,7 +226,7 @@
{{/maxLength}}
{{#minLength}}
if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.to_s.size < {{{minLength}}}
raise ArgumentError.new("invalid value for \"{{{name}}}\", the character length must be great than or equal to {{{minLength}}}.")
raise ArgumentError.new("invalid value for \"{{{name}}}\", the character length must be greater than or equal to {{{minLength}}}.")
end

{{/minLength}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,104 @@
# {{{.}}}
{{/description}}
module {{classname}}
class << self
{{#oneOf}}
{{#-first}}
# List of class defined in oneOf (OpenAPI v3)
def openapi_one_of
[
{{/-first}}
:'{{{.}}}'{{^-last}},{{/-last}}
{{#-last}}
]
end
{{#oneOf}}
{{#-first}}
# List of class defined in oneOf (OpenAPI v3)
def self.openapi_one_of
[
{{/-first}}
:"{{{.}}}"{{^-last}},{{/-last}}
{{#-last}}
]
end

{{/-last}}
{{/oneOf}}
{{#discriminator}}
{{#propertyName}}
# Discriminator's property name (OpenAPI v3)
def openapi_discriminator_name
:'{{{.}}}'
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 openapi_discriminator_mapping
{
{{/-first}}
:'{{{mappingName}}}' => :'{{{modelName}}}'{{^-last}},{{/-last}}
{{#-last}}
}
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 build(data)
{{#discriminator}}
discriminator_value = data[openapi_discriminator_name]
return nil unless discriminator_value
{{#mappedModels}}
{{#-first}}
{{/-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
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}}
{{/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.
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
end
{{moduleName}}.const_get(klass).build_from_hash(data)
{{/-first}}
{{/mappedModels}}
{{^mappedModels}}
{{moduleName}}.const_get(discriminator_value).build_from_hash(data)
{{/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.
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
end

openapi_one_of.includes?(:AnyType) ? data : nil
{{/discriminator}}
end
{{^discriminator}}

private
openapi_one_of.includes?(:AnyType) ? data : nil
{{/discriminator}}
end
{{^discriminator}}

SchemaMismatchError = Class.new(StandardError)
SchemaMismatchError = Class.new(StandardError)

# Note: 'File' is missing here because in the regular case we get the data _after_ a call to JSON.parse.
def find_and_cast_into_type(klass, data)
return if data.nil?
# 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)
return if data.nil?

begin
case klass.to_s
when 'Boolean'
when "Boolean"
return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
when 'Float'
when "Float"
return data if data.instance_of?(Float)
when 'Integer'
when "Integer"
return data if data.instance_of?(Integer)
when 'Time'
when "Time"
return Time.parse(data)
when 'Date'
when "Date"
return Date.parse(data)
when 'String'
when "String"
return data if data.instance_of?(String)
when 'Object' # "type: object"
when "Object" # "type: object"
return data if data.instance_of?(Hash)
when /\AArray<(?<sub_type>.+)>\z/ # "type: array"
if data.instance_of?(Array)
Expand All @@ -111,7 +109,7 @@
when /\AHash<String, (?<sub_type>.+)>\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({}) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) }
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)
Expand All @@ -132,6 +130,6 @@
rescue
raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
end
{{/discriminator}}
end
{{/discriminator}}
end
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ development_dependencies:
version: ~>1.5.0
ameba:
github: crystal-ameba/ameba
spectator:
gitlab: arctic-fox/spectator
version: ~> 0.12.0

license: {{{shardLicense}}}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}

# load modules
require "spec"
require "spectator"
require "json"
require "time"
require "../src/{{{shardName}}}"

def assert_compilation_error(path : String, message : String) : Nil
Expand Down
1 change: 0 additions & 1 deletion samples/client/petstore/crystal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
<arguments>
<argument>install</argument>
<argument>--ignore-crystal-version</argument>
<argument>--without-development</argument>
</arguments>
</configuration>
</execution>
Expand Down
24 changes: 24 additions & 0 deletions samples/client/petstore/crystal/shard.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
version: 2.0
shards:
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 1.6.4

backtracer:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.4

crest:
git: https://github.com/mamantoha/crest.git
version: 1.3.13

exception_page:
git: https://github.com/crystal-loot/exception_page.git
version: 0.4.1

http-client-digest_auth:
git: https://github.com/mamantoha/http-client-digest_auth.git
version: 0.6.0
Expand All @@ -12,3 +24,15 @@ shards:
git: https://github.com/mamantoha/http_proxy.git
version: 0.10.3

kemal:
git: https://github.com/kemalcr/kemal.git
version: 1.5.0

radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1

spectator:
git: https://gitlab.com/arctic-fox/spectator.git
version: 0.12.1

3 changes: 3 additions & 0 deletions samples/client/petstore/crystal/shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@ development_dependencies:
version: ~>1.5.0
ameba:
github: crystal-ameba/ameba
spectator:
gitlab: arctic-fox/spectator
version: ~> 0.12.0

license:
Loading
Loading