evil-client 0.2.1
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 +7 -0
 - data/.codeclimate.yml +7 -0
 - data/.gitignore +9 -0
 - data/.rspec +3 -0
 - data/.rubocop.yml +98 -0
 - data/.travis.yml +17 -0
 - data/Gemfile +9 -0
 - data/LICENSE.txt +21 -0
 - data/README.md +144 -0
 - data/Rakefile +6 -0
 - data/docs/base_url.md +38 -0
 - data/docs/documentation.md +9 -0
 - data/docs/headers.md +59 -0
 - data/docs/http_method.md +31 -0
 - data/docs/index.md +127 -0
 - data/docs/license.md +19 -0
 - data/docs/model.md +173 -0
 - data/docs/operation.md +0 -0
 - data/docs/overview.md +0 -0
 - data/docs/path.md +48 -0
 - data/docs/query.md +99 -0
 - data/docs/responses.md +66 -0
 - data/docs/security.md +102 -0
 - data/docs/settings.md +32 -0
 - data/evil-client.gemspec +25 -0
 - data/lib/evil/client.rb +97 -0
 - data/lib/evil/client/connection.rb +35 -0
 - data/lib/evil/client/connection/net_http.rb +57 -0
 - data/lib/evil/client/dsl.rb +110 -0
 - data/lib/evil/client/dsl/files.rb +37 -0
 - data/lib/evil/client/dsl/operation.rb +102 -0
 - data/lib/evil/client/dsl/operations.rb +41 -0
 - data/lib/evil/client/dsl/scope.rb +34 -0
 - data/lib/evil/client/dsl/security.rb +57 -0
 - data/lib/evil/client/middleware.rb +81 -0
 - data/lib/evil/client/middleware/base.rb +15 -0
 - data/lib/evil/client/middleware/merge_security.rb +16 -0
 - data/lib/evil/client/middleware/normalize_headers.rb +13 -0
 - data/lib/evil/client/middleware/stringify_form.rb +36 -0
 - data/lib/evil/client/middleware/stringify_json.rb +15 -0
 - data/lib/evil/client/middleware/stringify_multipart.rb +32 -0
 - data/lib/evil/client/middleware/stringify_multipart/part.rb +36 -0
 - data/lib/evil/client/middleware/stringify_query.rb +31 -0
 - data/lib/evil/client/model.rb +65 -0
 - data/lib/evil/client/operation.rb +34 -0
 - data/lib/evil/client/operation/request.rb +42 -0
 - data/lib/evil/client/operation/response.rb +40 -0
 - data/lib/evil/client/operation/response_error.rb +12 -0
 - data/lib/evil/client/operation/unexpected_response_error.rb +16 -0
 - data/mkdocs.yml +21 -0
 - data/spec/features/instantiation_spec.rb +68 -0
 - data/spec/features/middleware_spec.rb +75 -0
 - data/spec/features/operation_with_documentation_spec.rb +41 -0
 - data/spec/features/operation_with_files_spec.rb +40 -0
 - data/spec/features/operation_with_form_body_spec.rb +158 -0
 - data/spec/features/operation_with_headers_spec.rb +99 -0
 - data/spec/features/operation_with_http_method_spec.rb +45 -0
 - data/spec/features/operation_with_json_body_spec.rb +156 -0
 - data/spec/features/operation_with_path_spec.rb +47 -0
 - data/spec/features/operation_with_query_spec.rb +84 -0
 - data/spec/features/operation_with_response_spec.rb +109 -0
 - data/spec/features/operation_with_security_spec.rb +228 -0
 - data/spec/features/scoping_spec.rb +48 -0
 - data/spec/spec_helper.rb +23 -0
 - data/spec/support/test_client.rb +15 -0
 - data/spec/unit/evil/client/connection/net_http_spec.rb +38 -0
 - data/spec/unit/evil/client/dsl/files_spec.rb +37 -0
 - data/spec/unit/evil/client/dsl/operation_spec.rb +233 -0
 - data/spec/unit/evil/client/dsl/operations_spec.rb +27 -0
 - data/spec/unit/evil/client/dsl/scope_spec.rb +30 -0
 - data/spec/unit/evil/client/dsl/security_spec.rb +135 -0
 - data/spec/unit/evil/client/dsl_spec.rb +57 -0
 - data/spec/unit/evil/client/middleware/merge_security_spec.rb +32 -0
 - data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +17 -0
 - data/spec/unit/evil/client/middleware/stringify_form_spec.rb +63 -0
 - data/spec/unit/evil/client/middleware/stringify_json_spec.rb +61 -0
 - data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +59 -0
 - data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +62 -0
 - data/spec/unit/evil/client/middleware/stringify_query_spec.rb +40 -0
 - data/spec/unit/evil/client/middleware_spec.rb +46 -0
 - data/spec/unit/evil/client/model_spec.rb +100 -0
 - data/spec/unit/evil/client/operation/request_spec.rb +49 -0
 - data/spec/unit/evil/client/operation/response_spec.rb +61 -0
 - metadata +271 -0
 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Evil::Client::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Nested definition for attached files
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Files
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Builds a final upload schema from request options
         
     | 
| 
      
 5 
     | 
    
         
            +
                #
         
     | 
| 
      
 6 
     | 
    
         
            +
                # @param  [Hash<Symbol, Object>] options
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [Hash<Symbol, Object>]
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                def call(**options)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @schema = []
         
     | 
| 
      
 12 
     | 
    
         
            +
                    instance_exec(options, &@block)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @schema
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def initialize(&block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @mutex = Mutex.new
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @block = block
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Helper methods that mutate files @schema
         
     | 
| 
      
 26 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def add(data, type: "text/plain", charset: "utf-8", filename: nil, **)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @schema << {
         
     | 
| 
      
 30 
     | 
    
         
            +
                    file:     data.respond_to?(:read) ? data : StringIO.new(data),
         
     | 
| 
      
 31 
     | 
    
         
            +
                    type:     MIME::Types[type].first,
         
     | 
| 
      
 32 
     | 
    
         
            +
                    charset:  charset,
         
     | 
| 
      
 33 
     | 
    
         
            +
                    filename: filename
         
     | 
| 
      
 34 
     | 
    
         
            +
                  }
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,102 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Evil::Client::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              require_relative "security"
         
     | 
| 
      
 3 
     | 
    
         
            +
              require_relative "files"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              # Builds a schema for single operation
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Operation
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_reader :schema
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                # Builds a schema for a single operation
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @param  [Object] settings
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @param  [Proc] block A block of definitions (should accept settings)
         
     | 
| 
      
 13 
     | 
    
         
            +
                # @return [Hash<Symbol, Object>]
         
     | 
| 
      
 14 
     | 
    
         
            +
                #
         
     | 
| 
      
 15 
     | 
    
         
            +
                def finalize(settings)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 17 
     | 
    
         
            +
                    @schema = @default.dup
         
     | 
| 
      
 18 
     | 
    
         
            +
                    instance_exec(settings, &@block) if @block
         
     | 
| 
      
 19 
     | 
    
         
            +
                    @schema[:middleware]&.finalize(settings)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    @schema
         
     | 
| 
      
 21 
     | 
    
         
            +
                  end
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                private
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def initialize(key, block)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @mutex   = Mutex.new
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @block   = block
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @default = { key: key, responses: {} }
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 33 
     | 
    
         
            +
                # Helper methods that mutate a @schema
         
     | 
| 
      
 34 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def documentation(value)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @schema[:doc] = value
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                def http_method(value)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  @schema[:method] = value.to_s.downcase
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                def path
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @schema[:path] = ->(**opts) { yield(opts).gsub(%r{\A/+|/+\z}, "") }
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def security(&block)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @schema[:security] = Security.new(&block)
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def files(&block)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @schema[:files]  = Files.new(&block)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @schema[:format] = "multipart"
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @schema.delete :body
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def body(format: "json", **options, &block)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @schema[:body]   = __model__(options, &block)
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @schema[:format] = __valid_format__(format)
         
     | 
| 
      
 61 
     | 
    
         
            +
                  @schema.delete :files
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def headers(**options, &block)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  @schema[:headers] = __model__(options, &block)
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def query(**options, &block)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @schema[:query] = __model__(options, &block)
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def response(*statuses, raise: false, &block)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  statuses.each do |status|
         
     | 
| 
      
 74 
     | 
    
         
            +
                    @schema[:responses][status] = {
         
     | 
| 
      
 75 
     | 
    
         
            +
                      raise:   raise,
         
     | 
| 
      
 76 
     | 
    
         
            +
                      coercer: block || proc { |response:, **| response }
         
     | 
| 
      
 77 
     | 
    
         
            +
                    }
         
     | 
| 
      
 78 
     | 
    
         
            +
                  end
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 82 
     | 
    
         
            +
                # Utilities for helpers TODO: extract to a separate module
         
     | 
| 
      
 83 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
      
 85 
     | 
    
         
            +
                def __valid_format__(format)
         
     | 
| 
      
 86 
     | 
    
         
            +
                  formats = %w(json form)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  return format.to_s if formats.include? format.to_s
         
     | 
| 
      
 88 
     | 
    
         
            +
                  fail ArgumentError.new "Invalid format #{format} for body." \
         
     | 
| 
      
 89 
     | 
    
         
            +
                                         " Use one of formats: #{formats}"
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                def __model__(model: nil, **, &block)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  if model && block
         
     | 
| 
      
 94 
     | 
    
         
            +
                    Class.new(model, &block)
         
     | 
| 
      
 95 
     | 
    
         
            +
                  elsif block
         
     | 
| 
      
 96 
     | 
    
         
            +
                    Class.new(Evil::Client::Model, &block)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  elsif model
         
     | 
| 
      
 98 
     | 
    
         
            +
                    model
         
     | 
| 
      
 99 
     | 
    
         
            +
                  end
         
     | 
| 
      
 100 
     | 
    
         
            +
                end
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,41 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Evil::Client::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              require_relative "operation"
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              # Container for operations definitions
         
     | 
| 
      
 5 
     | 
    
         
            +
              # Applies settings to definitions and returns a final schema
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Operations
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Adds block definition as a named operation
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                # @param [#to_sym] key
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param [Proc] block
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @return [self]
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                def register(key, &block)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @schema[key] = Operation.new(key, block)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  self
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                # Applies settings to all definitions and returns a final schema
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @param  [Object] settings
         
     | 
| 
      
 21 
     | 
    
         
            +
                # @return [Hash<Symbol, Object>]
         
     | 
| 
      
 22 
     | 
    
         
            +
                #
         
     | 
| 
      
 23 
     | 
    
         
            +
                def finalize(settings)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  default = @schema[nil].finalize(settings)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  custom  = @schema.select { |key| key }
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  custom.each_with_object({}) do |(key, operation), hash|
         
     | 
| 
      
 28 
     | 
    
         
            +
                    custom = operation.finalize(settings)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    hash[key] = default.merge(custom)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    hash[key][:format] ||= "json"
         
     | 
| 
      
 31 
     | 
    
         
            +
                    hash[key][:responses] = default[:responses].merge(custom[:responses])
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                private
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @schema = { nil => Operation.new(nil, nil) }
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
              end
         
     | 
| 
      
 41 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Evil::Client::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Provides a namespace for client's top-level DSL
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Scope
         
     | 
| 
      
 4 
     | 
    
         
            +
                extend Dry::Initializer::Mixin
         
     | 
| 
      
 5 
     | 
    
         
            +
                option :__scope__, default: proc {}
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                # Declares a method that opens new scope inside the current one
         
     | 
| 
      
 8 
     | 
    
         
            +
                # An instance of new scope has access to methods of its parent
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param  [#to_sym] name (:[]) The name of the new scope
         
     | 
| 
      
 11 
     | 
    
         
            +
                # @return [self]
         
     | 
| 
      
 12 
     | 
    
         
            +
                #
         
     | 
| 
      
 13 
     | 
    
         
            +
                def self.scope(name = :[], &block)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  klass = Class.new(Scope, &block)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  define_method(name) do |*args, **options|
         
     | 
| 
      
 16 
     | 
    
         
            +
                    klass.new(*args, __scope__: self, **options)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                  self
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                private
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                private :__scope__
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def respond_to_missing?(name, *)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  __scope__.respond_to? name
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def method_missing(name, *args)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  super unless respond_to? name
         
     | 
| 
      
 31 
     | 
    
         
            +
                  __scope__.send(name, *args)
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,57 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Evil::Client::DSL
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Nested definition for a security schemas
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Security
         
     | 
| 
      
 4 
     | 
    
         
            +
                # Builds final security schema dependent on request options
         
     | 
| 
      
 5 
     | 
    
         
            +
                #
         
     | 
| 
      
 6 
     | 
    
         
            +
                # @param  [Hash<Symbol, Object>] options
         
     | 
| 
      
 7 
     | 
    
         
            +
                # @return [Hash<Symbol, Object>]
         
     | 
| 
      
 8 
     | 
    
         
            +
                #
         
     | 
| 
      
 9 
     | 
    
         
            +
                def call(**options)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @schema = {}
         
     | 
| 
      
 12 
     | 
    
         
            +
                    instance_exec(options, &@block)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @schema
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                private
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def initialize(&block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @mutex = Mutex.new
         
     | 
| 
      
 21 
     | 
    
         
            +
                  @block = block
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Helper methods that mutate a security @schema
         
     | 
| 
      
 26 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                # @see [https://tools.ietf.org/html/rfc7617]
         
     | 
| 
      
 29 
     | 
    
         
            +
                def basic_auth(user, password)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  token = Base64.encode64("#{user}:#{password}").delete("\n")
         
     | 
| 
      
 31 
     | 
    
         
            +
                  token_auth(token, prefix: "Basic")
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def token_auth(token, using: :headers, prefix: nil)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  if using == :headers
         
     | 
| 
      
 36 
     | 
    
         
            +
                    prefixed_token = [prefix&.to_s&.capitalize, token].compact.join(" ")
         
     | 
| 
      
 37 
     | 
    
         
            +
                    key_auth("authorization", prefixed_token, using: :headers)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  else
         
     | 
| 
      
 39 
     | 
    
         
            +
                    key_auth("access_token", token, using: using)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def key_auth(key, value, using: :headers)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  __validate__ using
         
     | 
| 
      
 45 
     | 
    
         
            +
                  @schema[using] ||= {}
         
     | 
| 
      
 46 
     | 
    
         
            +
                  @schema[using][key.to_s] = value
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                # ==========================================================================
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def __validate__(part)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  parts = %i(body query headers)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  return if parts.include? part
         
     | 
| 
      
 54 
     | 
    
         
            +
                  fail ArgumentError.new("Wrong part '#{part}'. Use one of parts: #{parts}")
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
              end
         
     | 
| 
      
 57 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,81 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Evil::Client
         
     | 
| 
      
 2 
     | 
    
         
            +
              # Builds and carries stack of middleware parameterized by settings
         
     | 
| 
      
 3 
     | 
    
         
            +
              #
         
     | 
| 
      
 4 
     | 
    
         
            +
              # @example
         
     | 
| 
      
 5 
     | 
    
         
            +
              #   # during client definition
         
     | 
| 
      
 6 
     | 
    
         
            +
              #   middleware = Evil::Client::Middleware.new do |settings|
         
     | 
| 
      
 7 
     | 
    
         
            +
              #     run CustomMiddleware if settings.version > 1
         
     | 
| 
      
 8 
     | 
    
         
            +
              #   end
         
     | 
| 
      
 9 
     | 
    
         
            +
              #
         
     | 
| 
      
 10 
     | 
    
         
            +
              #   # during client instantiation
         
     | 
| 
      
 11 
     | 
    
         
            +
              #   stack = middleware.finalize(settings)
         
     | 
| 
      
 12 
     | 
    
         
            +
              #   conn  = stack.wrap(connection)
         
     | 
| 
      
 13 
     | 
    
         
            +
              #
         
     | 
| 
      
 14 
     | 
    
         
            +
              #   # during runtime to make a request
         
     | 
| 
      
 15 
     | 
    
         
            +
              #   conn.call request
         
     | 
| 
      
 16 
     | 
    
         
            +
              #
         
     | 
| 
      
 17 
     | 
    
         
            +
              class Middleware
         
     | 
| 
      
 18 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 19 
     | 
    
         
            +
                  require_relative "middleware/base"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  require_relative "middleware/merge_security"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  require_relative "middleware/normalize_headers"
         
     | 
| 
      
 22 
     | 
    
         
            +
                  require_relative "middleware/stringify_json"
         
     | 
| 
      
 23 
     | 
    
         
            +
                  require_relative "middleware/stringify_multipart"
         
     | 
| 
      
 24 
     | 
    
         
            +
                  require_relative "middleware/stringify_query"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  require_relative "middleware/stringify_form"
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                  # Middleware to be added on top of full stack (before custom ones)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  def prepend
         
     | 
| 
      
 29 
     | 
    
         
            +
                    new do
         
     | 
| 
      
 30 
     | 
    
         
            +
                      run NormalizeHeaders
         
     | 
| 
      
 31 
     | 
    
         
            +
                      run MergeSecurity
         
     | 
| 
      
 32 
     | 
    
         
            +
                    end.finalize
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  # Middleware to be added on bottom of full stack
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # (between custom stack and connection)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  def append
         
     | 
| 
      
 38 
     | 
    
         
            +
                    new do
         
     | 
| 
      
 39 
     | 
    
         
            +
                      run StringifyQuery
         
     | 
| 
      
 40 
     | 
    
         
            +
                      run StringifyJson
         
     | 
| 
      
 41 
     | 
    
         
            +
                      run StringifyForm
         
     | 
| 
      
 42 
     | 
    
         
            +
                      run StringifyMultipart
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end.finalize
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # Applies client settings to build stack of middleware
         
     | 
| 
      
 48 
     | 
    
         
            +
                #
         
     | 
| 
      
 49 
     | 
    
         
            +
                # @param  [Object] settings
         
     | 
| 
      
 50 
     | 
    
         
            +
                # @return [self]
         
     | 
| 
      
 51 
     | 
    
         
            +
                #
         
     | 
| 
      
 52 
     | 
    
         
            +
                def finalize(settings = nil)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @mutex.synchronize do
         
     | 
| 
      
 54 
     | 
    
         
            +
                    @stack = []
         
     | 
| 
      
 55 
     | 
    
         
            +
                    instance_exec(settings, &@block) if @block
         
     | 
| 
      
 56 
     | 
    
         
            +
                    self
         
     | 
| 
      
 57 
     | 
    
         
            +
                  end
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                # Wraps the connection instance to the current stack of middleware
         
     | 
| 
      
 61 
     | 
    
         
            +
                #
         
     | 
| 
      
 62 
     | 
    
         
            +
                # @param  [#call] connection
         
     | 
| 
      
 63 
     | 
    
         
            +
                # @return [#call]
         
     | 
| 
      
 64 
     | 
    
         
            +
                #
         
     | 
| 
      
 65 
     | 
    
         
            +
                def call(other)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @stack.reverse.inject(other) { |a, e| e.new(a) }
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                private
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def initialize(&block)
         
     | 
| 
      
 72 
     | 
    
         
            +
                  @mutex = Mutex.new
         
     | 
| 
      
 73 
     | 
    
         
            +
                  @block = block
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def run(klass)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  @stack << klass
         
     | 
| 
      
 78 
     | 
    
         
            +
                  self
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,16 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Evil::Client::Middleware
         
     | 
| 
      
 2 
     | 
    
         
            +
              class MergeSecurity < Base
         
     | 
| 
      
 3 
     | 
    
         
            +
                private
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def build(env)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  env.dup.tap do |hash|
         
     | 
| 
      
 7 
     | 
    
         
            +
                    security = hash.delete(:security).to_h
         
     | 
| 
      
 8 
     | 
    
         
            +
                    %i(headers body query).each do |key|
         
     | 
| 
      
 9 
     | 
    
         
            +
                      next unless security[key]
         
     | 
| 
      
 10 
     | 
    
         
            +
                      hash[key] ||= {}
         
     | 
| 
      
 11 
     | 
    
         
            +
                      hash[key].update security[key]
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,13 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Evil::Client::Middleware
         
     | 
| 
      
 2 
     | 
    
         
            +
              class NormalizeHeaders < Base
         
     | 
| 
      
 3 
     | 
    
         
            +
                private
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def build(env)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  headers = Hash(env[:headers]).each_with_object({}) do |(key, val), hash|
         
     | 
| 
      
 7 
     | 
    
         
            +
                    hash[key.to_s.downcase] = val.to_s
         
     | 
| 
      
 8 
     | 
    
         
            +
                  end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                  env.merge headers: headers
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Evil::Client::Middleware
         
     | 
| 
      
 2 
     | 
    
         
            +
              class StringifyForm < Base
         
     | 
| 
      
 3 
     | 
    
         
            +
                private
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def build(env)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  return env unless env[:format] == "form"
         
     | 
| 
      
 7 
     | 
    
         
            +
                  return env if env&.fetch(:body, nil).to_h.empty?
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  env.dup.tap do |hash|
         
     | 
| 
      
 10 
     | 
    
         
            +
                    hash[:headers] ||= {}
         
     | 
| 
      
 11 
     | 
    
         
            +
                    hash[:headers]["content-type"] = "application/x-www-form-urlencoded"
         
     | 
| 
      
 12 
     | 
    
         
            +
                    hash[:body_string] = env[:body]
         
     | 
| 
      
 13 
     | 
    
         
            +
                                         .flat_map { |key, val| normalize(val, key) }
         
     | 
| 
      
 14 
     | 
    
         
            +
                                         .flat_map { |item| stringify(item) }
         
     | 
| 
      
 15 
     | 
    
         
            +
                                         .join("&")
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def stringify(hash)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  hash.map do |keys, val|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    "#{keys.first}#{keys[1..-1].map { |key| "[#{key}]" }.join}=#{val}"
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                def normalize(value, *keys)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  case value
         
     | 
| 
      
 27 
     | 
    
         
            +
                  when Hash then
         
     | 
| 
      
 28 
     | 
    
         
            +
                    value.flat_map { |key, val| normalize(val, *keys, key) }
         
     | 
| 
      
 29 
     | 
    
         
            +
                  when Array then
         
     | 
| 
      
 30 
     | 
    
         
            +
                    value.flat_map { |val| normalize(val, *keys, nil) }
         
     | 
| 
      
 31 
     | 
    
         
            +
                  else
         
     | 
| 
      
 32 
     | 
    
         
            +
                    [{ keys.map { |key| CGI.escape(key.to_s) } => CGI.escape(value.to_s) }]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     |