Skip to content
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

[crystal] Fix some issues in crystal client templates #10629

Merged
merged 22 commits into from
Oct 23, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
9291810
Fix some issues in crystal client templates using google drive v3 oas…
cyangle Oct 19, 2021
638b447
update crystal petstore sample code
cyangle Oct 19, 2021
961afec
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
cyangle Oct 19, 2021
28db021
Address PR comments
cyangle Oct 19, 2021
42d3658
Use size instead of length
cyangle Oct 19, 2021
15c8edc
Fix typo
cyangle Oct 20, 2021
6439537
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
cyangle Oct 20, 2021
e6c6666
Use default value instead of nil
cyangle Oct 21, 2021
b825728
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
cyangle Oct 21, 2021
4c9ec63
remove unused template file
cyangle Oct 21, 2021
00431a2
Use ::File instead of File as file type to avoid conflicts
cyangle Oct 21, 2021
ec60ecf
support file upload in multipart/form-data post body
cyangle Oct 21, 2021
d65cad2
Revert breaking changes in api template
cyangle Oct 22, 2021
2c17a6f
Use double quotes to quote string values
cyangle Oct 22, 2021
4825d2a
Merge branch 'crystal-multipart' into crystal
cyangle Oct 22, 2021
dce8bbb
Update api_client to use global ::File and update petstore samples
cyangle Oct 22, 2021
e8242dd
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
cyangle Oct 22, 2021
acf8050
JSON Annotation Field key's value should be double quoted
cyangle Oct 22, 2021
edd01d3
Handle nil values for form_params
cyangle Oct 22, 2021
0192e50
Remove default values from method definitions due to grammar error
cyangle Oct 22, 2021
210e149
Fix integration tests
cyangle Oct 22, 2021
e575bd2
Merge branch 'master' of https://github.com/OpenAPITools/openapi-gene…
cyangle Oct 22, 2021
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 @@ -23,7 +23,7 @@ module {{moduleName}}
{{/required}}
{{/allParams}}
# @return [{{{returnType}}}{{^returnType}}nil{{/returnType}}]
def {{operationId}}({{#allParams}}{{paramName}} : {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
def {{operationId}}({{#allParams}}{{paramName}} : {{{dataType}}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
{{#returnType}}data, _status_code, _headers = {{/returnType}}{{operationId}}_with_http_info({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}})
{{#returnType}}data{{/returnType}}{{^returnType}}nil{{/returnType}}
end
Expand Down Expand Up @@ -130,7 +130,7 @@ module {{moduleName}}
# query parameters
query_params = Hash(String, String).new
{{#queryParams}}
query_params["{{{baseName}}}"] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}{{/collectionFormat}}
query_params["{{{baseName}}}"] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}.to_s unless {{{paramName}}}.nil?{{/collectionFormat}}
{{/queryParams}}

# header parameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,105 +38,6 @@ module {{moduleName}}
(mime == "*/*") || !(mime =~ /Application\/.*json(?!p)(;.*)?/i).nil?
end

# Deserialize the response to the given return type.
#
# @param [Response] response HTTP response
# @param [String] return_type some examples: "User", "Array<User>", "Hash<String, Integer>"
def deserialize(response, return_type)
body = response.body

# handle file downloading - return the File instance processed in request callbacks
# note that response body is empty when the file is written in chunks in request on_body callback
if return_type == "File"
content_disposition = response.headers["Content-Disposition"].to_s
if content_disposition && content_disposition =~ /filename=/i
filename = content_disposition.match(/filename=[""]?([^""\s]+)[""]?/i).try &.[0]
prefix = sanitize_filename(filename)
else
prefix = "download-"
end
if !prefix.nil? && prefix.ends_with?("-")
prefix = prefix + "-"
end
encoding = response.headers["Content-Encoding"].to_s

# TODO add file support
raise ApiError.new(code: 0, message: "File response not yet supported in the client.") if return_type
return nil

#@tempfile = Tempfile.open(prefix, @config.temp_folder_path, encoding: encoding)
#@tempfile.write(@stream.join.force_encoding(encoding))
#@tempfile.close
#Log.info { "Temp file written to #{@tempfile.path}, please copy the file to a proper folder "\
# "with e.g. `FileUtils.cp(tempfile.path, \"/new/file/path\")` otherwise the temp file "\
# "will be deleted automatically with GC. It's also recommended to delete the temp file "\
# "explicitly with `tempfile.delete`" }
#return @tempfile
end

return nil if body.nil? || body.empty?

# return response body directly for String return type
return body if return_type == "String"

# ensuring a default content type
content_type = response.headers["Content-Type"] || "application/json"

raise ApiError.new(code: 0, message: "Content-Type is not supported: #{content_type}") unless json_mime?(content_type)

begin
data = JSON.parse("[#{body}]")[0]
rescue e : Exception
if %w(String Date Time).includes?(return_type)
data = body
else
raise e
end
end

convert_to_type data, return_type
end

# Convert data to the given return type.
# @param [Object] data Data to be converted
# @param [String] return_type Return type
# @return [Mixed] Data in a particular type
def convert_to_type(data, return_type)
return nil if data.nil?
case return_type
when "String"
data.to_s
when "Integer"
data.to_s.to_i
when "Float"
data.to_s.to_f
when "Boolean"
data == true
when "Time"
# parse date time (expecting ISO 8601 format)
Time.parse! data.to_s, "%Y-%m-%dT%H:%M:%S%Z"
when "Date"
# parse date (expecting ISO 8601 format)
Time.parse! data.to_s, "%Y-%m-%d"
when "Object"
# generic object (usually a Hash), return directly
data
when /\AArray<(.+)>\z/
# e.g. Array<Pet>
sub_type = $1
data.map { |item| convert_to_type(item, sub_type) }
when /\AHash\<String, (.+)\>\z/
# e.g. Hash<String, Integer>
sub_type = $1
({} of Symbol => String).tap do |hash|
data.each { |k, v| hash[k] = convert_to_type(v, sub_type) }
end
else
# models (e.g. Pet) or oneOf
klass = Petstore.const_get(return_type)
klass.respond_to?(:openapi_one_of) ? klass.build(data) : klass.build_from_hash(data)
end
end

# Sanitize filename by removing path.
# e.g. ../../sun.gif becomes sun.gif
Expand Down Expand Up @@ -245,18 +146,19 @@ module {{moduleName}}
when :pipes
param.join("|")
when :multi
# TODO: Need to fix this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about raise an error saying multi is not supported in the client?

# return the array directly as typhoeus will handle it as expected
param
else
fail "unknown collection format: #{collection_format.inspect}"
raise "unknown collection format: #{collection_format.inspect}"
end
end

# Call an API with given options.
#
# @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, form_params = {} of Symbol => String)
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, form_params = {} of Symbol => String)
#ssl_options = {
# :ca_file => @config.ssl_ca_file,
# :verify => @config.ssl_verify,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ module {{moduleName}}
{{#-first}}
variables: {
{{/-first}}
{{{name}}}: {
"{{{name}}}": {
description: "{{{description}}}{{^description}}No description provided{{/description}}",
default_value: "{{{defaultValue}}}",
{{#enumValues}}
Expand Down Expand Up @@ -302,7 +302,7 @@ module {{moduleName}}
{{#-first}}
variables: {
{{/-first}}
{{{name}}}: {
"{{{name}}}": {
description: "{{{description}}}{{^description}}No description provided{{/description}}",
default_value: "{{{defaultValue}}}",
{{#enumValues}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
class {{classname}}{{#allowableValues}}{{#enumVars}}
{{{name}}} = {{{value}}}.freeze{{/enumVars}}

{{/allowableValues}}
{{{name}}} = {{{value}}}
{{/enumVars}} {{/allowableValues}}
# Builds the enum from string
# @param [String] The enum value in the form of the string
# @return [String] The enum value
def self.build_from_hash(value)
def self.build_from_hash(value : String) : String
new.build_from_hash(value)
end

# Builds the enum from string
# @param [String] The enum value in the form of the string
# @return [String] The enum value
def build_from_hash(value)
constantValues = {{classname}}.constants.select { |c| {{classname}}::const_get(c) == value }
raise "Invalid ENUM value #{value} for class #{{{classname}}}" if constantValues.empty?
value
def build_from_hash(value : String) : String
case value
{{#allowableValues}}{{#enumVars}}
when {{{value}}}
{{{name}}}
{{/enumVars}} {{/allowableValues}}
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion to allow trailing spaces:

      {{#allowableValues}}
      {{#enumVars}}
      when {{{value}}}
        {{{name}}}
      {{/enumVars}}
      {{/allowableValues}}

raise "Invalid ENUM value #{value} for class #{{{classname}}}"
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
{{#description}}
# {{{.}}}
{{/description}}
@[JSON::Field(key: {{{baseName}}}, type: {{{dataType}}}{{#default}}, default: {{{.}}}{{/default}}{{#isNullable}}, nillable: true, emit_null: true{{/isNullable}})]
property {{{name}}} : {{{dataType}}}
@[JSON::Field(key: {{{baseName}}}, type: {{{dataType}}}{{^required}}?{{/required}}{{#default}}, default: {{{.}}}{{/default}}{{#isNullable}}, nillable: true, emit_null: true{{/isNullable}})]
property {{{name}}} : {{{dataType}}}{{^required}}?{{/required}}

{{/vars}}
{{#hasEnums}}
Expand Down Expand Up @@ -74,13 +74,13 @@
{{/discriminator}}
# Initializes the object
# @param [Hash] attributes Model attributes in the form of hash
def initialize({{#vars}}@{{{name}}} : {{{dataType}}}{{^required}} | Nil{{/required}}{{^-last}}, {{/-last}}{{/vars}})
def initialize({{#vars}}@{{{name}}} : {{{dataType}}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/vars}})
end

# Show invalid properties with the reasons. Usually used together with valid?
# @return Array for valid properties with the reasons
def list_invalid_properties
invalid_properties = {{^parent}}Array.new{{/parent}}{{#parent}}super{{/parent}}
invalid_properties = {{^parent}}Array(String).new{{/parent}}{{#parent}}super{{/parent}}
{{#vars}}
{{^isNullable}}
{{#required}}
Expand Down
6 changes: 3 additions & 3 deletions samples/client/petstore/crystal/src/petstore/api/pet_api.cr
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ module Petstore
# Deletes a pet
# @param pet_id [Int64] Pet id to delete
# @return [nil]
def delete_pet(pet_id : Int64, api_key : String?)
def delete_pet(pet_id : Int64, api_key : String? = nil)
delete_pet_with_http_info(pet_id, api_key)
nil
end
Expand Down Expand Up @@ -373,7 +373,7 @@ module Petstore
# Updates a pet in the store with form data
# @param pet_id [Int64] ID of pet that needs to be updated
# @return [nil]
def update_pet_with_form(pet_id : Int64, name : String?, status : String?)
def update_pet_with_form(pet_id : Int64, name : String? = nil, status : String? = nil)
update_pet_with_form_with_http_info(pet_id, name, status)
nil
end
Expand Down Expand Up @@ -432,7 +432,7 @@ module Petstore
# uploads an image
# @param pet_id [Int64] ID of pet to update
# @return [ApiResponse]
def upload_file(pet_id : Int64, additional_metadata : String?, file : File?)
def upload_file(pet_id : Int64, additional_metadata : String? = nil, file : File? = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cyangle Is this (default to nil) also breaking change when it comes to calling the operations? If yes, please revert the change for the time being?

Copy link
Contributor Author

@cyangle cyangle Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed it

data, _status_code, _headers = upload_file_with_http_info(pet_id, additional_metadata, file)
data
end
Expand Down
4 changes: 2 additions & 2 deletions samples/client/petstore/crystal/src/petstore/api/user_api.cr
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@ module Petstore

# query parameters
query_params = Hash(String, String).new
query_params["username"] = username
query_params["password"] = password
query_params["username"] = username.to_s unless username.nil?
query_params["password"] = password.to_s unless password.nil?

# header parameters
header_params = Hash(String, String).new
Expand Down
104 changes: 3 additions & 101 deletions samples/client/petstore/crystal/src/petstore/api_client.cr
Original file line number Diff line number Diff line change
Expand Up @@ -46,105 +46,6 @@ module Petstore
(mime == "*/*") || !(mime =~ /Application\/.*json(?!p)(;.*)?/i).nil?
end

# Deserialize the response to the given return type.
#
# @param [Response] response HTTP response
# @param [String] return_type some examples: "User", "Array<User>", "Hash<String, Integer>"
def deserialize(response, return_type)
body = response.body

# handle file downloading - return the File instance processed in request callbacks
# note that response body is empty when the file is written in chunks in request on_body callback
if return_type == "File"
content_disposition = response.headers["Content-Disposition"].to_s
if content_disposition && content_disposition =~ /filename=/i
filename = content_disposition.match(/filename=[""]?([^""\s]+)[""]?/i).try &.[0]
prefix = sanitize_filename(filename)
else
prefix = "download-"
end
if !prefix.nil? && prefix.ends_with?("-")
prefix = prefix + "-"
end
encoding = response.headers["Content-Encoding"].to_s

# TODO add file support
raise ApiError.new(code: 0, message: "File response not yet supported in the client.") if return_type
return nil

#@tempfile = Tempfile.open(prefix, @config.temp_folder_path, encoding: encoding)
#@tempfile.write(@stream.join.force_encoding(encoding))
#@tempfile.close
#Log.info { "Temp file written to #{@tempfile.path}, please copy the file to a proper folder "\
# "with e.g. `FileUtils.cp(tempfile.path, \"/new/file/path\")` otherwise the temp file "\
# "will be deleted automatically with GC. It's also recommended to delete the temp file "\
# "explicitly with `tempfile.delete`" }
#return @tempfile
end

return nil if body.nil? || body.empty?

# return response body directly for String return type
return body if return_type == "String"

# ensuring a default content type
content_type = response.headers["Content-Type"] || "application/json"

raise ApiError.new(code: 0, message: "Content-Type is not supported: #{content_type}") unless json_mime?(content_type)

begin
data = JSON.parse("[#{body}]")[0]
rescue e : Exception
if %w(String Date Time).includes?(return_type)
data = body
else
raise e
end
end

convert_to_type data, return_type
end

# Convert data to the given return type.
# @param [Object] data Data to be converted
# @param [String] return_type Return type
# @return [Mixed] Data in a particular type
def convert_to_type(data, return_type)
return nil if data.nil?
case return_type
when "String"
data.to_s
when "Integer"
data.to_s.to_i
when "Float"
data.to_s.to_f
when "Boolean"
data == true
when "Time"
# parse date time (expecting ISO 8601 format)
Time.parse! data.to_s, "%Y-%m-%dT%H:%M:%S%Z"
when "Date"
# parse date (expecting ISO 8601 format)
Time.parse! data.to_s, "%Y-%m-%d"
when "Object"
# generic object (usually a Hash), return directly
data
when /\AArray<(.+)>\z/
# e.g. Array<Pet>
sub_type = $1
data.map { |item| convert_to_type(item, sub_type) }
when /\AHash\<String, (.+)\>\z/
# e.g. Hash<String, Integer>
sub_type = $1
({} of Symbol => String).tap do |hash|
data.each { |k, v| hash[k] = convert_to_type(v, sub_type) }
end
else
# models (e.g. Pet) or oneOf
klass = Petstore.const_get(return_type)
klass.respond_to?(:openapi_one_of) ? klass.build(data) : klass.build_from_hash(data)
end
end

# Sanitize filename by removing path.
# e.g. ../../sun.gif becomes sun.gif
Expand Down Expand Up @@ -253,18 +154,19 @@ module Petstore
when :pipes
param.join("|")
when :multi
# TODO: Need to fix this
# return the array directly as typhoeus will handle it as expected
param
else
fail "unknown collection format: #{collection_format.inspect}"
raise "unknown collection format: #{collection_format.inspect}"
end
end

# Call an API with given options.
#
# @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, form_params = {} of Symbol => String)
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, form_params = {} of Symbol => String)
#ssl_options = {
# :ca_file => @config.ssl_ca_file,
# :verify => @config.ssl_verify,
Expand Down
Loading