diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18270ce..b64f467 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: - uses: ruby/setup-ruby@v1 with: - ruby-version: "2.6" + ruby-version: "2.7" - name: Install httpie run: | diff --git a/gen-client.sh b/gen-client.sh index 6e4cd72..a82a43f 100755 --- a/gen-client.sh +++ b/gen-client.sh @@ -21,7 +21,7 @@ VERSION="$(python3 -c "from packaging.version import Version; print(Version('${V echo "Version: ${VERSION}" OPENAPI_PYTHON_IMAGE="${OPENAPI_PYTHON_IMAGE:-docker.io/openapitools/openapi-generator-cli:v4.3.1}" -OPENAPI_RUBY_IMAGE="${OPENAPI_RUBY_IMAGE:-docker.io/openapitools/openapi-generator-cli:v4.3.1}" +OPENAPI_RUBY_IMAGE="${OPENAPI_RUBY_IMAGE:-docker.io/openapitools/openapi-generator-cli:v7.6.0}" OPENAPI_TYPESCRIPT_IMAGE="${OPENAPI_TYPESCRIPT_IMAGE:-docker.io/openapitools/openapi-generator-cli:v5.2.1}" if command -v podman > /dev/null @@ -83,6 +83,7 @@ fi if [ "$LANGUAGE" = "ruby" ] then # https://github.com/OpenAPITools/openapi-generator/wiki/FAQ#how-to-skip-certain-files-during-code-generation + # useAutoload fix: https://github.com/OpenAPITools/openapi-generator/issues/12854 mkdir -p "${PACKAGE}-client" echo git_push.sh > "${PACKAGE}-client/.openapi-generator-ignore" @@ -96,7 +97,7 @@ then -i "${VOLUME_DIR}/${API_SPEC}" \ -g ruby \ -o "${VOLUME_DIR}/${PACKAGE}-client" \ - "--additional-properties=gemName=${PACKAGE}_client,gemLicense="GPLv2+",gemVersion=${VERSION},gemHomepage=https://github.com/pulp/${PACKAGE}" \ + "--additional-properties=gemName=${PACKAGE}_client,gemLicense="GPLv2+",gemVersion=${VERSION},gemHomepage=https://github.com/pulp/${PACKAGE},useAutoload=true" \ --library=faraday \ -t "${VOLUME_DIR}/templates/ruby" \ --skip-validate-spec \ diff --git a/templates/ruby/api_client_faraday_partial.mustache b/templates/ruby/api_client_faraday_partial.mustache deleted file mode 100644 index c21a713..0000000 --- a/templates/ruby/api_client_faraday_partial.mustache +++ /dev/null @@ -1,128 +0,0 @@ - # 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, path, opts = {}) - ssl_options = { - :ca_file => @config.ssl_ca_file, - :verify => @config.ssl_verify, - :verify_mode => @config.ssl_verify_mode, - :client_cert => @config.ssl_client_cert, - :client_key => @config.ssl_client_key - } - request_options = { - :params_encoder => @config.params_encoder - } - connection = Faraday.new(:url => config.base_url, :ssl => ssl_options, :request => request_options) do |conn| - conn.basic_auth(config.username, config.password) - if opts[:header_params]["Content-Type"] == "multipart/form-data" - conn.request :multipart - conn.request :url_encoded - end - conn.adapter(Faraday.default_adapter) - end - - begin - response = connection.public_send(http_method.to_sym.downcase) do |req| - build_request(http_method, path, req, opts) - end - - if @config.debugging - @config.logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n" - end - - unless response.success? - if response.status == 0 - # Errors from libcurl will be made visible here - fail ApiError.new(:code => 0, - :message => response.return_message) - else - fail ApiError.new(:code => response.status, - :response_headers => response.headers, - :response_body => response.body), - response.reason_phrase - end - end - rescue Faraday::TimeoutError - fail ApiError.new('Connection timed out') - end - - if opts[:return_type] - data = deserialize(response, opts[:return_type]) - else - data = nil - end - return data, response.status, response.headers - end - - # Builds the HTTP request - # - # @param [String] http_method HTTP method/verb (e.g. POST) - # @param [String] path URL path (e.g. /account/new) - # @option opts [Hash] :header_params Header parameters - # @option opts [Hash] :query_params Query parameters - # @option opts [Hash] :form_params Query parameters - # @option opts [Object] :body HTTP body (JSON/XML) - # @return [Typhoeus::Request] A Typhoeus Request - def build_request(http_method, path, request, opts = {}) - url = build_request_url(path) - http_method = http_method.to_sym.downcase - - header_params = @default_headers.merge(opts[:header_params] || {}) - query_params = opts[:query_params] || {} - form_params = opts[:form_params] || {} - - update_params_for_auth! header_params, query_params, opts[:auth_names] - - req_opts = { - :params_encoding => @config.params_encoding, - :timeout => @config.timeout, - :verbose => @config.debugging - } - - if [:post, :patch, :put, :delete].include?(http_method) - req_body = build_request_body(header_params, form_params, opts[:body]) - if @config.debugging - @config.logger.debug "HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n" - end - end - request.headers = header_params - request.body = req_body - request.options = OpenStruct.new(req_opts) - request.url url - request.params = query_params - download_file(request) if opts[:return_type] == 'File' - request - end - - # Builds the HTTP request body - # - # @param [Hash] header_params Header parameters - # @param [Hash] form_params Query parameters - # @param [Object] body HTTP body (JSON/XML) - # @return [String] HTTP body data in the form of string - def build_request_body(header_params, form_params, body) - # http form - if header_params['Content-Type'] == 'application/x-www-form-urlencoded' - data = URI.encode_www_form(form_params) - elsif header_params['Content-Type'] == 'multipart/form-data' - data = {} - form_params.each do |key, value| - case value - when ::File, ::Tempfile - # TODO hardcode to application/octet-stream, need better way to detect content type - data[key] = Faraday::FilePart.new(value.path, 'application/octet-stream', value.path) - when ::Array, nil - # let Faraday handle Array and nil parameters - data[key] = value - else - data[key] = value.to_s - end - end - elsif body - data = body.is_a?(String) ? body : body.to_json - else - data = nil - end - data - end diff --git a/templates/ruby/configuration.mustache b/templates/ruby/configuration.mustache deleted file mode 100644 index ef8532d..0000000 --- a/templates/ruby/configuration.mustache +++ /dev/null @@ -1,282 +0,0 @@ -=begin -{{> api_info}} -=end - -module {{moduleName}} - class Configuration - # Defines url scheme - attr_accessor :scheme - - # Defines url host - attr_accessor :host - - # Defines url base path - attr_accessor :base_path - - # Defines API keys used with API Key authentications. - # - # @return [Hash] key: parameter name, value: parameter value (API key) - # - # @example parameter name is "api_key", API key is "xxx" (e.g. "api_key=xxx" in query string) - # config.api_key['api_key'] = 'xxx' - attr_accessor :api_key - - # Defines API key prefixes used with API Key authentications. - # - # @return [Hash] key: parameter name, value: API key prefix - # - # @example parameter name is "Authorization", API key prefix is "Token" (e.g. "Authorization: Token xxx" in headers) - # config.api_key_prefix['api_key'] = 'Token' - attr_accessor :api_key_prefix - - # Defines the username used with HTTP basic authentication. - # - # @return [String] - attr_accessor :username - - # Defines the password used with HTTP basic authentication. - # - # @return [String] - attr_accessor :password - - # Defines the access token (Bearer) used with OAuth2. - attr_accessor :access_token - - # Set this to enable/disable debugging. When enabled (set to true), HTTP request/response - # details will be logged with `logger.debug` (see the `logger` attribute). - # Default to false. - # - # @return [true, false] - attr_accessor :debugging - - # Defines the logger used for debugging. - # Default to `Rails.logger` (when in Rails) or logging to STDOUT. - # - # @return [#debug] - attr_accessor :logger - - # Defines the temporary folder to store downloaded files - # (for API endpoints that have file response). - # Default to use `Tempfile`. - # - # @return [String] - attr_accessor :temp_folder_path - - # The time limit for HTTP request in seconds. - # Default to 0 (never times out). - attr_accessor :timeout - - # Set this to false to skip client side validation in the operation. - # Default to true. - # @return [true, false] - attr_accessor :client_side_validation - -{{^isFaraday}} -{{> configuration_tls_typhoeus_partial}} -{{/isFaraday}} -{{#isFaraday}} -{{> configuration_tls_faraday_partial}} -{{/isFaraday}} - # Set this to customize parameters encoding of array parameter with multi collectionFormat. - # Default to nil. - # - # @see The params_encoding option of Ethon. Related source code: - # https://github.com/typhoeus/ethon/blob/master/lib/ethon/easy/queryable.rb#L96 - attr_accessor :params_encoding - - attr_accessor :inject_format - - attr_accessor :force_ending_format - - def initialize - @scheme = '{{scheme}}' - @host = '{{host}}' - @base_path = '{{contextPath}}' - @api_key = {} - @api_key_prefix = {} - @timeout = 0 - @client_side_validation = true - {{#isFaraday}} - @ssl_verify = true - @ssl_verify_mode = nil - @ssl_ca_file = nil - @ssl_client_cert = nil - @ssl_client_key = nil - @params_encoder = nil - @timeout = 60 - {{/isFaraday}} - {{^isFaraday}} - @verify_ssl = true - @verify_ssl_host = true - @params_encoding = nil - @cert_file = nil - @key_file = nil - @timeout = 0 - {{/isFaraday}} - @debugging = false - @inject_format = false - @force_ending_format = false - @logger = defined?(Rails) ? Rails.logger : Logger.new(STDOUT) - - yield(self) if block_given? - end - - # The default Configuration object. - def self.default - @@default ||= Configuration.new - end - - def configure - yield(self) if block_given? - end - - def scheme=(scheme) - # remove :// from scheme - @scheme = scheme.sub(/:\/\//, '') - end - - def host=(host) - # remove http(s):// and anything after a slash - @host = host.sub(/https?:\/\//, '').split('/').first - end - - def base_path=(base_path) - # Add leading and trailing slashes to base_path - @base_path = "/#{base_path}".gsub(/\/+/, '/') - @base_path = '' if @base_path == '/' - end - - def base_url - "#{scheme}://#{[host, base_path].join('/').gsub(/\/+/, '/')}".sub(/\/+\z/, '') - end - - # Gets API key (with prefix if set). - # @param [String] param_name the parameter name of API key auth - def api_key_with_prefix(param_name) - if @api_key_prefix[param_name] - "#{@api_key_prefix[param_name]} #{@api_key[param_name]}" - else - @api_key[param_name] - end - end - - # Gets Basic Auth token string - def basic_auth_token - 'Basic ' + ["#{username}:#{password}"].pack('m').delete("\r\n") - end - - # Returns Auth Settings hash for api client. - def auth_settings - { -{{#authMethods}} -{{#isApiKey}} - '{{name}}' => - { - type: 'api_key', - in: {{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}}, - key: '{{keyParamName}}', - value: api_key_with_prefix('{{keyParamName}}') - }, -{{/isApiKey}} -{{#isBasic}} -{{#isBasicBasic}} - '{{name}}' => - { - type: 'basic', - in: 'header', - key: 'Authorization', - value: basic_auth_token - }, -{{/isBasicBasic}} -{{#isBasicBearer}} - '{{name}}' => - { - type: 'bearer', - in: 'header', - {{#bearerFormat}} - format: '{{{.}}}', - {{/bearerFormat}} - key: 'Authorization', - value: "Bearer #{access_token}" - }, -{{/isBasicBearer}} -{{/isBasic}} -{{#isOAuth}} - '{{name}}' => - { - type: 'oauth2', - in: 'header', - key: 'Authorization', - value: "Bearer #{access_token}" - }, -{{/isOAuth}} -{{/authMethods}} - } - end - - # Returns an array of Server setting - def server_settings - [ - {{#servers}} - { - url: "{{{url}}}", - description: "{{{description}}}{{^description}}No description provided{{/description}}", - {{#variables}} - {{#-first}} - variables: { - {{/-first}} - {{{name}}}: { - description: "{{{description}}}{{^description}}No description provided{{/description}}", - default_value: "{{{defaultValue}}}", - {{#enumValues}} - {{#-first}} - enum_values: [ - {{/-first}} - "{{{.}}}"{{^-last}},{{/-last}} - {{#-last}} - ] - {{/-last}} - {{/enumValues}} - }{{^-last}},{{/-last}} - {{#-last}} - } - {{/-last}} - {{/variables}} - }{{^-last}},{{/-last}} - {{/servers}} - ] - end - - # Returns URL based on server settings - # - # @param index array index of the server settings - # @param variables hash of variable and the corresponding value - def server_url(index, variables = {}) - servers = server_settings - - # check array index out of bound - if (index < 0 || index >= servers.size) - fail ArgumentError, "Invalid index #{index} when selecting the server. Must be less than #{servers.size}" - end - - server = servers[index] - url = server[:url] - - # go through variable and assign a value - server[:variables].each do |name, variable| - if variables.key?(name) - if (server[:variables][name][:enum_values].include? variables[name]) - url.gsub! "{" + name.to_s + "}", variables[name] - else - fail ArgumentError, "The variable `#{name}` in the server URL has invalid value #{variables[name]}. Must be #{server[:variables][name][:enum_values]}." - end - else - # use default value - url.gsub! "{" + name.to_s + "}", server[:variables][name][:default_value] - end - end - - url - end - end -end diff --git a/templates/ruby/configuration_tls_faraday_partial.mustache b/templates/ruby/configuration_tls_faraday_partial.mustache deleted file mode 100644 index 7e17f4b..0000000 --- a/templates/ruby/configuration_tls_faraday_partial.mustache +++ /dev/null @@ -1,36 +0,0 @@ - ### TLS/SSL setting - # Set this to false to skip verifying SSL certificate when calling API from https server. - # Default to true. - # - # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks. - # - # @return [true, false] - attr_accessor :ssl_verify - - ### TLS/SSL setting - # Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html) - # - # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks. - # - attr_accessor :ssl_verify_mode - - ### TLS/SSL setting - # Set this to customize the certificate file to verify the peer. - # - # @return [String] the path to the certificate file - attr_accessor :ssl_ca_file - - ### TLS/SSL setting - # Client certificate file (for client certificate) - attr_accessor :ssl_client_cert - - ### TLS/SSL setting - # Client private key file (for client certificate) - attr_accessor :ssl_client_key - - # Set this to customize parameters encoder of array parameter. - # Default to nil. Faraday uses NestedParamsEncoder when nil. - # - # @see The params_encoder option of Faraday. Related source code: - # https://github.com/lostisland/faraday/tree/main/lib/faraday/encoders - attr_accessor :params_encoder \ No newline at end of file diff --git a/templates/ruby/gemspec.mustache b/templates/ruby/gemspec.mustache index bc70415..8bd5d08 100644 --- a/templates/ruby/gemspec.mustache +++ b/templates/ruby/gemspec.mustache @@ -16,21 +16,23 @@ Gem::Specification.new do |s| s.homepage = "{{gemHomepage}}{{^gemHomepage}}https://openapi-generator.tech{{/gemHomepage}}" s.summary = "{{gemSummary}}{{^gemSummary}}{{{appName}}} Ruby Gem{{/gemSummary}}" s.description = "{{gemDescription}}{{^gemDescription}}{{{appDescription}}}{{^appDescription}}{{{appName}}} Ruby Gem{{/appDescription}}{{/gemDescription}}" - {{#gemLicense}} - s.license = '{{{gemLicense}}}' - {{/gemLicense}} - {{^gemLicense}} - s.license = "Unlicense" - {{/gemLicense}} - s.required_ruby_version = "{{{gemRequiredRubyVersion}}}{{^gemRequiredRubyVersion}}>= 1.9{{/gemRequiredRubyVersion}}" + s.license = "{{{gemLicense}}}{{^gemLicense}}Unlicense{{/gemLicense}}" + s.required_ruby_version = "{{{gemRequiredRubyVersion}}}{{^gemRequiredRubyVersion}}>= 2.7{{/gemRequiredRubyVersion}}" + s.metadata = {{{gemMetadata}}}{{^gemMetadata}}{}{{/gemMetadata}} {{#isFaraday}} - s.add_runtime_dependency 'faraday', '>= 1.0.1', '< 2.0' + {{! override-note: 2.0.1 lowerbound is recommended by faraday upgrade guide }} + {{! override-note: 2.9.0 upperbound is the max version below 3 that support ruby>2.6 }} + s.add_runtime_dependency 'faraday', '>= 2.0.1', '< 2.9.0' + s.add_runtime_dependency 'faraday-multipart' + s.add_runtime_dependency 'marcel' {{/isFaraday}} - {{^isFaraday}} + {{#isTyphoeus}} s.add_runtime_dependency 'typhoeus', '~> 1.0', '>= 1.0.1' - {{/isFaraday}} - s.add_runtime_dependency 'json', '~> 2.1', '>= 2.1.0' + {{/isTyphoeus}} + {{#isHttpx}} + s.add_runtime_dependency 'httpx', '~> 1.0', '>= 1.0.0' + {{/isHttpx}} s.add_development_dependency 'rspec', '~> 3.6', '>= 3.6.0'