apia-open_api 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/apia/open_api/helpers.rb +14 -0
- data/lib/apia/open_api/objects/parameters.rb +56 -4
- data/lib/apia/open_api/objects/path.rb +51 -1
- data/lib/apia/open_api/rack.rb +18 -1
- data/lib/apia/open_api/specification.rb +34 -14
- data/lib/apia/open_api/version.rb +1 -1
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9105b1350ceecf58bdf2037f3052a50f38ec76be818c67016c46381a68fb2f4b
         | 
| 4 | 
            +
              data.tar.gz: 433da5d349c9aabd1f71277e5b41885b8bcc514cda52ca0f8c659470307c01c7
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: a6bc4e0c62791abe13bcc5742270e0faefc30cbed76a140a1fed6afa766dd3635f344fbb3b322a7f5f1fec02c2c4d69cc90ec0866a661a971e619fe76662a543
         | 
| 7 | 
            +
              data.tar.gz: dbb0d647cea2e6f208fee6426dd5b7bbb3270a8bcbbdbb7a84ca2bc9a1975cecfe98caa7a729e859357493085a1639e9b8c2be24662b96c3c4cf69e238fed0ca
         | 
| @@ -52,6 +52,20 @@ module Apia | |
| 52 52 | 
             
                    schema
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 |  | 
| 55 | 
            +
                  def generate_array_schema(definition)
         | 
| 56 | 
            +
                    type = definition.type
         | 
| 57 | 
            +
                    schema = {
         | 
| 58 | 
            +
                      type: "array",
         | 
| 59 | 
            +
                      items: {
         | 
| 60 | 
            +
                        type: convert_type_to_open_api_data_type(type)
         | 
| 61 | 
            +
                      }
         | 
| 62 | 
            +
                    }
         | 
| 63 | 
            +
                    schema[:description] = definition.description if definition.description.present?
         | 
| 64 | 
            +
                    schema[:items][:format] = "float" if type.klass == Apia::Scalars::Decimal
         | 
| 65 | 
            +
                    schema[:items][:format] = "date" if type.klass == Apia::Scalars::Date
         | 
| 66 | 
            +
                    schema
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 55 69 | 
             
                  def generate_schema_ref(definition, id: nil, sibling_props: false, **schema_opts)
         | 
| 56 70 | 
             
                    id ||= generate_id_from_definition(definition.type.klass.definition)
         | 
| 57 71 | 
             
                    success = add_to_components_schemas(definition, id, **schema_opts)
         | 
| @@ -69,6 +69,9 @@ module Apia | |
| 69 69 | 
             
                          schema: generate_scalar_schema(@argument)
         | 
| 70 70 | 
             
                        }
         | 
| 71 71 | 
             
                        param[:description] = @argument.description if @argument.description.present?
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                        add_pagination_params(param)
         | 
| 74 | 
            +
             | 
| 72 75 | 
             
                        param[:required] = true if @argument.required?
         | 
| 73 76 | 
             
                        add_to_parameters(param)
         | 
| 74 77 | 
             
                      end
         | 
| @@ -76,6 +79,19 @@ module Apia | |
| 76 79 |  | 
| 77 80 | 
             
                    private
         | 
| 78 81 |  | 
| 82 | 
            +
                    def add_pagination_params(param)
         | 
| 83 | 
            +
                      if param[:name] == "page"
         | 
| 84 | 
            +
                        param[:description] = "The page number to request. If not provided, the first page will be returned."
         | 
| 85 | 
            +
                        param[:schema][:default] = 1
         | 
| 86 | 
            +
                        param[:schema][:minimum] = 1
         | 
| 87 | 
            +
                      elsif param[:name] == "per_page"
         | 
| 88 | 
            +
                        param[:description] =
         | 
| 89 | 
            +
                          "The number of items to return per page. If not provided, the default value will be used."
         | 
| 90 | 
            +
                        param[:schema][:default] = @argument.default
         | 
| 91 | 
            +
                        param[:schema][:minimum] = 1
         | 
| 92 | 
            +
                      end
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 79 95 | 
             
                    # Complex argument sets are not supported in query params (e.g. nested objects)
         | 
| 80 96 | 
             
                    # For any LookupArgumentSet only one argument is expected to be provided.
         | 
| 81 97 | 
             
                    # However, OpenAPI does not currently support describing mutually exclusive query params.
         | 
| @@ -87,19 +103,55 @@ module Apia | |
| 87 103 | 
             
                        next if child_arg.type.argument_set?
         | 
| 88 104 |  | 
| 89 105 | 
             
                        param = {
         | 
| 90 | 
            -
                           | 
| 91 | 
            -
                          in: "query",
         | 
| 92 | 
            -
                          schema: generate_scalar_schema(child_arg)
         | 
| 106 | 
            +
                          in: "query"
         | 
| 93 107 | 
             
                        }
         | 
| 108 | 
            +
             | 
| 94 109 | 
             
                        description = []
         | 
| 95 110 | 
             
                        description << formatted_description(@argument.description) if @argument.description.present?
         | 
| 96 111 | 
             
                        description << formatted_description(child_arg.description) if child_arg.description.present?
         | 
| 97 | 
            -
             | 
| 112 | 
            +
             | 
| 113 | 
            +
                        add_lookup_description(description)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                        if @argument.array
         | 
| 116 | 
            +
                          param[:name] = "#{@argument.name}[][#{child_arg.name}]"
         | 
| 117 | 
            +
                          param[:schema] = generate_array_schema(child_arg)
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                          add_description_section(
         | 
| 120 | 
            +
                            description,
         | 
| 121 | 
            +
                            "All `#{@argument.name}[]` params should have the same amount of elements."
         | 
| 122 | 
            +
                          )
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                        else
         | 
| 125 | 
            +
                          param[:name] = "#{@argument.name}[#{child_arg.name}]"
         | 
| 126 | 
            +
                          param[:schema] = generate_scalar_schema(child_arg)
         | 
| 127 | 
            +
                        end
         | 
| 128 | 
            +
             | 
| 98 129 | 
             
                        param[:description] = description.join(" ")
         | 
| 99 130 | 
             
                        add_to_parameters(param)
         | 
| 100 131 | 
             
                      end
         | 
| 101 132 | 
             
                    end
         | 
| 102 133 |  | 
| 134 | 
            +
                    # Adds a section to the description of a parameter.
         | 
| 135 | 
            +
                    # If the description is not empty, a blank line is added before the section.
         | 
| 136 | 
            +
                    #
         | 
| 137 | 
            +
                    # @param description [String] The current description of the parameter.
         | 
| 138 | 
            +
                    # @param addition [String] The section to be added to the description.
         | 
| 139 | 
            +
                    # @return [String] The updated description with the added section.
         | 
| 140 | 
            +
                    def add_description_section(description, addition)
         | 
| 141 | 
            +
                      unless description.empty?
         | 
| 142 | 
            +
                        description << "\n\n"
         | 
| 143 | 
            +
                      end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                      description << addition
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                    def add_lookup_description(description)
         | 
| 149 | 
            +
                      add_description_section(
         | 
| 150 | 
            +
                        description,
         | 
| 151 | 
            +
                        "All '#{@argument.name}[]' params are mutually exclusive, only one can be provided."
         | 
| 152 | 
            +
                      )
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
             | 
| 103 155 | 
             
                    def add_to_parameters(param)
         | 
| 104 156 | 
             
                      @route_spec[:parameters] << param
         | 
| 105 157 | 
             
                    end
         | 
| @@ -38,12 +38,14 @@ module Apia | |
| 38 38 | 
             
                        operationId: convert_route_to_id,
         | 
| 39 39 | 
             
                        summary: @route.endpoint.definition.name,
         | 
| 40 40 | 
             
                        description: @route.endpoint.definition.description,
         | 
| 41 | 
            -
                        tags: route.group ? get_group_tags(route.group) : [name]
         | 
| 41 | 
            +
                        tags: route.group ? get_group_tags(route.group) : [name],
         | 
| 42 | 
            +
                        security: []
         | 
| 42 43 | 
             
                      }
         | 
| 43 44 | 
             
                    end
         | 
| 44 45 |  | 
| 45 46 | 
             
                    def add_to_spec
         | 
| 46 47 | 
             
                      add_scopes_description
         | 
| 48 | 
            +
                      add_scopes_security
         | 
| 47 49 | 
             
                      path = @route.path
         | 
| 48 50 |  | 
| 49 51 | 
             
                      if @route.request_method == :get
         | 
| @@ -104,6 +106,45 @@ module Apia | |
| 104 106 | 
             
                            "- `#{scope}`"
         | 
| 105 107 | 
             
                          end.join("\n")}
         | 
| 106 108 | 
             
                        DESCRIPTION
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                      @spec[:security].each do |auth|
         | 
| 111 | 
            +
                        auth.each_key do |key|
         | 
| 112 | 
            +
                          scope_prefix = @spec[:components][:securitySchemes][key][:"x-scope-prefix"]
         | 
| 113 | 
            +
                          next unless scope_prefix.present?
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                          @route_spec[:description] =
         | 
| 116 | 
            +
                            <<~DESCRIPTION
         | 
| 117 | 
            +
                              #{@route_spec[:description]}
         | 
| 118 | 
            +
                              ### #{key} Scopes
         | 
| 119 | 
            +
                              When using #{key} authentication, scopes are prefixed with `#{scope_prefix}`.
         | 
| 120 | 
            +
                            DESCRIPTION
         | 
| 121 | 
            +
                        end
         | 
| 122 | 
            +
                      end
         | 
| 123 | 
            +
                    end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    # Adds scopes security to the OpenAPI path specification.
         | 
| 126 | 
            +
                    #
         | 
| 127 | 
            +
                    # This method checks if the route's endpoint definition has any scopes defined.
         | 
| 128 | 
            +
                    # If scopes are present, it iterates over the security schemes in the OpenAPI
         | 
| 129 | 
            +
                    # specification and adds the corresponding scopes to the route's security section.
         | 
| 130 | 
            +
                    #
         | 
| 131 | 
            +
                    # @return [void]
         | 
| 132 | 
            +
                    def add_scopes_security
         | 
| 133 | 
            +
                      unless @route.endpoint.definition.scopes.any?
         | 
| 134 | 
            +
                        @route_spec.delete(:security)
         | 
| 135 | 
            +
                        return
         | 
| 136 | 
            +
                      end
         | 
| 137 | 
            +
             | 
| 138 | 
            +
                      @spec[:security].each do |auth|
         | 
| 139 | 
            +
                        auth.each_key do |key|
         | 
| 140 | 
            +
                          scopes = @route.endpoint.definition.scopes
         | 
| 141 | 
            +
                          if scope_prefix = @spec[:components][:securitySchemes][key][:"x-scope-prefix"]
         | 
| 142 | 
            +
                            scopes = scopes.map { |v| "#{scope_prefix}/#{v}" }
         | 
| 143 | 
            +
                          end
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                          @route_spec[:security] << { key => scopes }
         | 
| 146 | 
            +
                        end
         | 
| 147 | 
            +
                      end
         | 
| 107 148 | 
             
                    end
         | 
| 108 149 |  | 
| 109 150 | 
             
                    # It's worth creating a 'nice' operationId for each route, as this is used as the
         | 
| @@ -162,6 +203,15 @@ module Apia | |
| 162 203 | 
             
                      current_group = group
         | 
| 163 204 |  | 
| 164 205 | 
             
                      while current_group
         | 
| 206 | 
            +
                        # Add tags to the spec global tags if they don't already exist
         | 
| 207 | 
            +
                        # Include a description if the group has one.
         | 
| 208 | 
            +
                        unless @spec[:tags].any? { |t| t[:name] == current_group.name }
         | 
| 209 | 
            +
                          global_tag = { name: current_group.name }
         | 
| 210 | 
            +
                          global_tag[:description] = current_group.description if current_group.description
         | 
| 211 | 
            +
                          @spec[:tags] << global_tag
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                        end
         | 
| 214 | 
            +
             | 
| 165 215 | 
             
                        tags.unshift(current_group.name)
         | 
| 166 216 | 
             
                        current_group = current_group.parent
         | 
| 167 217 | 
             
                      end
         | 
    
        data/lib/apia/open_api/rack.rb
    CHANGED
    
    | @@ -29,6 +29,18 @@ module Apia | |
| 29 29 | 
             
                    @options[:base_url] || "https://api.example.com/api/v1"
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| 32 | 
            +
                  def security_schemes
         | 
| 33 | 
            +
                    @options[:security_schemes] || {}
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def external_docs
         | 
| 37 | 
            +
                    @options[:external_docs] || {}
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def info
         | 
| 41 | 
            +
                    @options[:info] || {}
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 32 44 | 
             
                  def call(env)
         | 
| 33 45 | 
             
                    if @options[:hosts]&.none? { |host| host == env["HTTP_HOST"] }
         | 
| 34 46 | 
             
                      return @app.call(env)
         | 
| @@ -38,7 +50,12 @@ module Apia | |
| 38 50 | 
             
                      return @app.call(env)
         | 
| 39 51 | 
             
                    end
         | 
| 40 52 |  | 
| 41 | 
            -
                    specification = Specification.new(api_class, base_url, @options[:name] | 
| 53 | 
            +
                    specification = Specification.new(api_class, base_url, @options[:name],
         | 
| 54 | 
            +
                                                      {
         | 
| 55 | 
            +
                                                          info: info,
         | 
| 56 | 
            +
                                                          external_docs: external_docs,
         | 
| 57 | 
            +
                                                          security_schemes: security_schemes
         | 
| 58 | 
            +
                                                      })
         | 
| 42 59 | 
             
                    body = specification.json
         | 
| 43 60 |  | 
| 44 61 | 
             
                    [200, { "content-type" => "application/json", "content-length" => body.bytesize.to_s }, [body]]
         | 
| @@ -14,13 +14,17 @@ module Apia | |
| 14 14 |  | 
| 15 15 | 
             
                  OPEN_API_VERSION = "3.0.0" # The Ruby client generator currently only supports v3.0.0 https://openapi-generator.tech/
         | 
| 16 16 |  | 
| 17 | 
            -
                  def initialize(api, base_url, name)
         | 
| 17 | 
            +
                  def initialize(api, base_url, name, additions = {})
         | 
| 18 | 
            +
                    default_additions = { info: {}, external_docs: {}, security_schemes: {} }
         | 
| 19 | 
            +
                    additions = default_additions.merge(additions)
         | 
| 20 | 
            +
             | 
| 18 21 | 
             
                    @api = api
         | 
| 19 22 | 
             
                    @base_url = base_url
         | 
| 20 23 | 
             
                    @name = name || "Core" # will be suffixed with 'Api' and used in the client generator
         | 
| 21 24 | 
             
                    @spec = {
         | 
| 22 25 | 
             
                      openapi: OPEN_API_VERSION,
         | 
| 23 | 
            -
                      info:  | 
| 26 | 
            +
                      info: additions[:info],
         | 
| 27 | 
            +
                      externalDocs: additions[:external_docs],
         | 
| 24 28 | 
             
                      servers: [],
         | 
| 25 29 | 
             
                      paths: {},
         | 
| 26 30 | 
             
                      components: {
         | 
| @@ -31,6 +35,12 @@ module Apia | |
| 31 35 | 
             
                      "x-tagGroups": []
         | 
| 32 36 | 
             
                    }
         | 
| 33 37 |  | 
| 38 | 
            +
                    if @spec[:externalDocs].nil? || @spec[:externalDocs].empty?
         | 
| 39 | 
            +
                      @spec.delete(:externalDocs)
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    add_additional_security_schemes(additions[:security_schemes])
         | 
| 43 | 
            +
             | 
| 34 44 | 
             
                    # path_ids is used to keep track of all the IDs of all the paths we've generated, to avoid duplicates
         | 
| 35 45 | 
             
                    # refer to the Path object for more info
         | 
| 36 46 | 
             
                    @path_ids = []
         | 
| @@ -52,8 +62,8 @@ module Apia | |
| 52 62 | 
             
                  def build_spec
         | 
| 53 63 | 
             
                    add_info
         | 
| 54 64 | 
             
                    add_servers
         | 
| 55 | 
            -
                    add_paths
         | 
| 56 65 | 
             
                    add_security
         | 
| 66 | 
            +
                    add_paths
         | 
| 57 67 | 
             
                    add_tag_groups
         | 
| 58 68 |  | 
| 59 69 | 
             
                    @spec[:paths] = sort_hash_by_nested_tag(@spec[:paths])
         | 
| @@ -61,11 +71,13 @@ module Apia | |
| 61 71 |  | 
| 62 72 | 
             
                  def add_info
         | 
| 63 73 | 
             
                    title = @api.definition.name || @api.definition.id
         | 
| 64 | 
            -
                    @spec[:info] =  | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
                     | 
| 68 | 
            -
             | 
| 74 | 
            +
                    @spec[:info][:version] = "1.0.0" if @spec[:info][:version].nil?
         | 
| 75 | 
            +
                    @spec[:info][:title] = title if @spec[:info][:title].nil?
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                    return unless @spec[:info][:description].nil?
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    @spec[:info][:description] =
         | 
| 80 | 
            +
                      @api.definition.description || "Welcome to the documentation for the #{title}"
         | 
| 69 81 | 
             
                  end
         | 
| 70 82 |  | 
| 71 83 | 
             
                  def add_servers
         | 
| @@ -119,15 +131,11 @@ module Apia | |
| 119 131 | 
             
                  def add_tag_groups
         | 
| 120 132 | 
             
                    @spec[:paths].each_value do |methods|
         | 
| 121 133 | 
             
                      methods.each_value do |method_spec|
         | 
| 122 | 
            -
                        method_spec[:tags] | 
| 123 | 
            -
                          unless @spec[:tags].any? { |t| t[:name] == tag }
         | 
| 124 | 
            -
                            @spec[:tags] << { name: tag }
         | 
| 125 | 
            -
                          end
         | 
| 134 | 
            +
                        tags = method_spec[:tags]
         | 
| 126 135 |  | 
| 136 | 
            +
                        tags.each_with_index do |tag, tag_index|
         | 
| 127 137 | 
             
                          next if tag_index.zero?
         | 
| 128 138 |  | 
| 129 | 
            -
                          tags = method_spec[:tags]
         | 
| 130 | 
            -
             | 
| 131 139 | 
             
                          parent_tag = tags[tag_index - 1]
         | 
| 132 140 | 
             
                          parent_index = get_tag_group_index(parent_tag)
         | 
| 133 141 |  | 
| @@ -140,6 +148,10 @@ module Apia | |
| 140 148 | 
             
                            @spec[:"x-tagGroups"][parent_index][:tags] << tag
         | 
| 141 149 | 
             
                          end
         | 
| 142 150 | 
             
                        end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                        # Set the last tag as the tag for the method
         | 
| 153 | 
            +
                        # After we have built the tag group.
         | 
| 154 | 
            +
                        method_spec[:tags] = [tags.last] if tags.any?
         | 
| 143 155 | 
             
                      end
         | 
| 144 156 | 
             
                    end
         | 
| 145 157 |  | 
| @@ -153,6 +165,14 @@ module Apia | |
| 153 165 | 
             
                    @spec[:"x-tagGroups"].each { |group| group[:tags].sort! }
         | 
| 154 166 | 
             
                  end
         | 
| 155 167 |  | 
| 168 | 
            +
                  def add_additional_security_schemes(security_schemes)
         | 
| 169 | 
            +
                    security_schemes.each do |key, value|
         | 
| 170 | 
            +
                      @spec[:components][:securitySchemes] ||= {}
         | 
| 171 | 
            +
                      @spec[:components][:securitySchemes][key] = value.transform_keys(&:to_sym)
         | 
| 172 | 
            +
                      @spec[:security] << { key => [] }
         | 
| 173 | 
            +
                    end
         | 
| 174 | 
            +
                  end
         | 
| 175 | 
            +
             | 
| 156 176 | 
             
                end
         | 
| 157 177 | 
             
              end
         | 
| 158 178 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: apia-open_api
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.11
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Paul Sturgess
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-09-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         |