evil-client 0.3.3 → 1.0.0
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/.codeclimate.yml +0 -11
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +22 -19
- data/.travis.yml +1 -0
- data/CHANGELOG.md +251 -6
- data/LICENSE.txt +3 -1
- data/README.md +47 -81
- data/docs/helpers/body.md +93 -0
- data/docs/helpers/connection.md +19 -0
- data/docs/helpers/headers.md +72 -0
- data/docs/helpers/http_method.md +39 -0
- data/docs/helpers/let.md +14 -0
- data/docs/helpers/logger.md +24 -0
- data/docs/helpers/middleware.md +56 -0
- data/docs/helpers/operation.md +103 -0
- data/docs/helpers/option.md +50 -0
- data/docs/helpers/path.md +37 -0
- data/docs/helpers/query.md +59 -0
- data/docs/helpers/response.md +40 -0
- data/docs/helpers/scope.md +121 -0
- data/docs/helpers/security.md +102 -0
- data/docs/helpers/validate.md +68 -0
- data/docs/index.md +70 -78
- data/docs/license.md +5 -1
- data/docs/rspec.md +96 -0
- data/evil-client.gemspec +10 -8
- data/lib/evil/client.rb +126 -72
- data/lib/evil/client/builder.rb +47 -0
- data/lib/evil/client/builder/operation.rb +40 -0
- data/lib/evil/client/builder/scope.rb +31 -0
- data/lib/evil/client/chaining.rb +17 -0
- data/lib/evil/client/connection.rb +60 -20
- data/lib/evil/client/container.rb +66 -0
- data/lib/evil/client/container/operation.rb +23 -0
- data/lib/evil/client/container/scope.rb +28 -0
- data/lib/evil/client/exceptions/definition_error.rb +15 -0
- data/lib/evil/client/exceptions/name_error.rb +32 -0
- data/lib/evil/client/exceptions/response_error.rb +42 -0
- data/lib/evil/client/exceptions/type_error.rb +29 -0
- data/lib/evil/client/exceptions/validation_error.rb +27 -0
- data/lib/evil/client/formatter.rb +49 -0
- data/lib/evil/client/formatter/form.rb +45 -0
- data/lib/evil/client/formatter/multipart.rb +33 -0
- data/lib/evil/client/formatter/part.rb +66 -0
- data/lib/evil/client/formatter/text.rb +21 -0
- data/lib/evil/client/resolver.rb +84 -0
- data/lib/evil/client/resolver/body.rb +22 -0
- data/lib/evil/client/resolver/format.rb +30 -0
- data/lib/evil/client/resolver/headers.rb +46 -0
- data/lib/evil/client/resolver/http_method.rb +34 -0
- data/lib/evil/client/resolver/middleware.rb +36 -0
- data/lib/evil/client/resolver/query.rb +39 -0
- data/lib/evil/client/resolver/request.rb +96 -0
- data/lib/evil/client/resolver/response.rb +26 -0
- data/lib/evil/client/resolver/security.rb +113 -0
- data/lib/evil/client/resolver/uri.rb +35 -0
- data/lib/evil/client/rspec.rb +127 -0
- data/lib/evil/client/schema.rb +105 -0
- data/lib/evil/client/schema/operation.rb +177 -0
- data/lib/evil/client/schema/scope.rb +73 -0
- data/lib/evil/client/settings.rb +172 -0
- data/lib/evil/client/settings/validator.rb +64 -0
- data/mkdocs.yml +21 -15
- data/spec/features/custom_connection_spec.rb +17 -0
- data/spec/features/operation/middleware_spec.rb +50 -0
- data/spec/features/operation/options_spec.rb +71 -0
- data/spec/features/operation/request_spec.rb +94 -0
- data/spec/features/operation/response_spec.rb +48 -0
- data/spec/features/scope/options_spec.rb +52 -0
- data/spec/fixtures/locales/en.yml +16 -0
- data/spec/fixtures/test_client.rb +76 -0
- data/spec/spec_helper.rb +18 -6
- data/spec/support/fixtures_helper.rb +7 -0
- data/spec/unit/builder/operation_spec.rb +90 -0
- data/spec/unit/builder/scope_spec.rb +84 -0
- data/spec/unit/client_spec.rb +137 -0
- data/spec/unit/connection_spec.rb +78 -0
- data/spec/unit/container/operation_spec.rb +81 -0
- data/spec/unit/container/scope_spec.rb +61 -0
- data/spec/unit/container_spec.rb +107 -0
- data/spec/unit/exceptions/definition_error_spec.rb +15 -0
- data/spec/unit/exceptions/name_error_spec.rb +77 -0
- data/spec/unit/exceptions/response_error_spec.rb +22 -0
- data/spec/unit/exceptions/type_error_spec.rb +71 -0
- data/spec/unit/exceptions/validation_error_spec.rb +13 -0
- data/spec/unit/formatter/form_spec.rb +27 -0
- data/spec/unit/formatter/multipart_spec.rb +23 -0
- data/spec/unit/formatter/part_spec.rb +49 -0
- data/spec/unit/formatter/text_spec.rb +37 -0
- data/spec/unit/formatter_spec.rb +46 -0
- data/spec/unit/resolver/body_spec.rb +65 -0
- data/spec/unit/resolver/format_spec.rb +66 -0
- data/spec/unit/resolver/headers_spec.rb +93 -0
- data/spec/unit/resolver/http_method_spec.rb +67 -0
- data/spec/unit/resolver/middleware_spec.rb +83 -0
- data/spec/unit/resolver/query_spec.rb +85 -0
- data/spec/unit/resolver/request_spec.rb +121 -0
- data/spec/unit/resolver/response_spec.rb +64 -0
- data/spec/unit/resolver/security_spec.rb +156 -0
- data/spec/unit/resolver/uri_spec.rb +117 -0
- data/spec/unit/rspec_spec.rb +342 -0
- data/spec/unit/schema/operation_spec.rb +309 -0
- data/spec/unit/schema/scope_spec.rb +110 -0
- data/spec/unit/schema_spec.rb +157 -0
- data/spec/unit/settings/validator_spec.rb +128 -0
- data/spec/unit/settings_spec.rb +248 -0
- metadata +192 -135
- data/docs/base_url.md +0 -38
- data/docs/documentation.md +0 -9
- data/docs/headers.md +0 -59
- data/docs/http_method.md +0 -31
- data/docs/model.md +0 -173
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +0 -48
- data/docs/query.md +0 -99
- data/docs/responses.md +0 -66
- data/docs/security.md +0 -102
- data/docs/settings.md +0 -32
- data/lib/evil/client/connection/net_http.rb +0 -57
- data/lib/evil/client/dsl.rb +0 -127
- data/lib/evil/client/dsl/base.rb +0 -26
- data/lib/evil/client/dsl/files.rb +0 -37
- data/lib/evil/client/dsl/headers.rb +0 -16
- data/lib/evil/client/dsl/http_method.rb +0 -24
- data/lib/evil/client/dsl/operation.rb +0 -91
- data/lib/evil/client/dsl/operations.rb +0 -41
- data/lib/evil/client/dsl/path.rb +0 -25
- data/lib/evil/client/dsl/query.rb +0 -16
- data/lib/evil/client/dsl/response.rb +0 -61
- data/lib/evil/client/dsl/responses.rb +0 -29
- data/lib/evil/client/dsl/scope.rb +0 -27
- data/lib/evil/client/dsl/security.rb +0 -57
- data/lib/evil/client/dsl/verifier.rb +0 -35
- data/lib/evil/client/middleware.rb +0 -81
- data/lib/evil/client/middleware/base.rb +0 -11
- data/lib/evil/client/middleware/merge_security.rb +0 -20
- data/lib/evil/client/middleware/normalize_headers.rb +0 -17
- data/lib/evil/client/middleware/stringify_form.rb +0 -40
- data/lib/evil/client/middleware/stringify_json.rb +0 -19
- data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
- data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
- data/lib/evil/client/middleware/stringify_query.rb +0 -35
- data/lib/evil/client/operation.rb +0 -34
- data/lib/evil/client/operation/request.rb +0 -26
- data/lib/evil/client/operation/response.rb +0 -39
- data/lib/evil/client/operation/response_error.rb +0 -13
- data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
- data/spec/features/instantiation_spec.rb +0 -68
- data/spec/features/middleware_spec.rb +0 -79
- data/spec/features/operation_with_documentation_spec.rb +0 -41
- data/spec/features/operation_with_files_spec.rb +0 -40
- data/spec/features/operation_with_form_body_spec.rb +0 -158
- data/spec/features/operation_with_headers_spec.rb +0 -99
- data/spec/features/operation_with_http_method_spec.rb +0 -45
- data/spec/features/operation_with_json_body_spec.rb +0 -156
- data/spec/features/operation_with_nested_responses_spec.rb +0 -95
- data/spec/features/operation_with_path_spec.rb +0 -47
- data/spec/features/operation_with_query_spec.rb +0 -84
- data/spec/features/operation_with_security_spec.rb +0 -228
- data/spec/features/scoping_spec.rb +0 -48
- data/spec/support/test_client.rb +0 -15
- data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
- data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
- data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
- data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
- data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
- data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
- data/spec/unit/evil/client/middleware_spec.rb +0 -46
- data/spec/unit/evil/client/operation/request_spec.rb +0 -49
- data/spec/unit/evil/client/operation/response_spec.rb +0 -63
@@ -0,0 +1,33 @@
|
|
1
|
+
module Evil::Client::Formatter
|
2
|
+
#
|
3
|
+
# Utility module to format file (IO) as a part of multipart body
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# Evil::Client::Formatter::Form.call foo: { bar: :baz }
|
7
|
+
# # => "foo[bar]=baz"
|
8
|
+
#
|
9
|
+
module Multipart
|
10
|
+
extend self
|
11
|
+
require_relative "part"
|
12
|
+
|
13
|
+
# Formats nested hash as a string
|
14
|
+
#
|
15
|
+
# @param [Array<IO>] value
|
16
|
+
# @option opts [String] :boundary
|
17
|
+
# @return [String]
|
18
|
+
#
|
19
|
+
def call(*sources, boundary:, **)
|
20
|
+
parts = sources.flatten.map.with_index(1) do |src, num|
|
21
|
+
"--#{boundary}\r\n#{part(src, num)}"
|
22
|
+
end
|
23
|
+
|
24
|
+
[nil, nil, parts, "--#{boundary}--", nil].join("\r\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def part(source, index)
|
30
|
+
Part.call(source, index)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Evil::Client::Formatter
|
2
|
+
#
|
3
|
+
# Utility module to format source as a part of multipart body
|
4
|
+
#
|
5
|
+
module Part
|
6
|
+
extend self
|
7
|
+
require_relative "form"
|
8
|
+
|
9
|
+
# Formats nested hash as a string
|
10
|
+
#
|
11
|
+
# @param [IO, #to_s] source
|
12
|
+
# @param [Integer] number
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
def call(source, number)
|
16
|
+
filename = extract_filename(source)
|
17
|
+
name = extract_name(filename, number)
|
18
|
+
path = Pathname.new(filename) if filename
|
19
|
+
content = extract_content(source)
|
20
|
+
mime = extract_mime(path)
|
21
|
+
charset = extract_charset(content)
|
22
|
+
headers = [disposition(name, filename), type(mime, charset), nil]
|
23
|
+
|
24
|
+
[*headers, content].join("\r\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def disposition(name, filename)
|
30
|
+
"Content-Disposition: form-data; name=\"#{name}\"".tap do |line|
|
31
|
+
line << "; filename=\"#{filename}\"" if filename
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def type(mime, charset)
|
36
|
+
"Content-Type: #{mime}; charset=#{charset}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def extract_name(filename, number)
|
40
|
+
filename ? filename : "Part#{number}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_content(source)
|
44
|
+
case source
|
45
|
+
when File, Tempfile then source.read
|
46
|
+
when StringIO then source.string
|
47
|
+
when Hash then Form.call(source)
|
48
|
+
else source.to_s
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def extract_filename(source)
|
53
|
+
case source
|
54
|
+
when File, Tempfile then Pathname.new(source.path).basename.to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def extract_mime(path)
|
59
|
+
MIME::Types.type_for(path&.extname.to_s).first || "text/plain"
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_charset(content)
|
63
|
+
content.encoding.to_s.downcase
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Evil::Client::Formatter
|
2
|
+
#
|
3
|
+
# Utility module to format data as a single text
|
4
|
+
#
|
5
|
+
module Text
|
6
|
+
extend self
|
7
|
+
|
8
|
+
# Formats data as a text
|
9
|
+
#
|
10
|
+
# @param [Object] source
|
11
|
+
# @return [String]
|
12
|
+
#
|
13
|
+
def call(source)
|
14
|
+
case source
|
15
|
+
when File, Tempfile then source.read
|
16
|
+
when StringIO then source.string
|
17
|
+
else source.to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# @abstract
|
4
|
+
# Base class for resolvers of schema definition for a particular settings.
|
5
|
+
#
|
6
|
+
# Its every subclass responds for resolving a specific part of schema
|
7
|
+
# (request, middleware or response).
|
8
|
+
#
|
9
|
+
class Resolver
|
10
|
+
# Loads concrete implementation of the abstract resolver
|
11
|
+
require_relative "resolver/request"
|
12
|
+
require_relative "resolver/middleware"
|
13
|
+
require_relative "resolver/response"
|
14
|
+
|
15
|
+
# Builds and calls one-off resolver at once
|
16
|
+
#
|
17
|
+
# @param [Object] args The arguments of current class' constructor
|
18
|
+
# @return [Object] resolved definition
|
19
|
+
#
|
20
|
+
def self.call(*args)
|
21
|
+
new(*args).send(:__call__)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Human-friendly representation of the resolver
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
#
|
28
|
+
def to_s
|
29
|
+
"#{@__keys__.join(' ')} from #{@__schema__} for #{@__settings__}"
|
30
|
+
end
|
31
|
+
alias_method :to_str, :to_s
|
32
|
+
alias_method :inspect, :to_s
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def initialize(schema, settings, *keys)
|
37
|
+
@__schema__ = schema
|
38
|
+
@__settings__ = settings
|
39
|
+
@__keys__ = keys
|
40
|
+
end
|
41
|
+
|
42
|
+
def __call__
|
43
|
+
logger = @__settings__.logger
|
44
|
+
yield.tap do |obj|
|
45
|
+
logger&.debug(self.class) { "resolved #{self} to #{obj.inspect}" }
|
46
|
+
end
|
47
|
+
rescue => err
|
48
|
+
logger&.error(self.class) { "failed to resolve #{self}: #{err.message}" }
|
49
|
+
raise
|
50
|
+
end
|
51
|
+
|
52
|
+
def __blocks__
|
53
|
+
@__blocks__ ||= [].tap do |blocks|
|
54
|
+
schema = @__schema__
|
55
|
+
loop do
|
56
|
+
break unless schema
|
57
|
+
block = schema.definitions.dig(*@__keys__)
|
58
|
+
schema = schema.parent
|
59
|
+
blocks.unshift block if block
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def __definition_error__(text)
|
65
|
+
DefinitionError.new(@__schema__, @__keys__, @__settings__, text)
|
66
|
+
end
|
67
|
+
|
68
|
+
def __symbolize_keys__(hash)
|
69
|
+
hash.each_with_object({}) { |(key, val), obj| obj[key.to_sym] = val }
|
70
|
+
end
|
71
|
+
|
72
|
+
def __stringify_keys__(hash)
|
73
|
+
hash.each_with_object({}) { |(key, val), obj| obj[key.to_s] = val }
|
74
|
+
end
|
75
|
+
|
76
|
+
def respond_to_missing?(name, *)
|
77
|
+
@__settings__.respond_to? name
|
78
|
+
end
|
79
|
+
|
80
|
+
def method_missing(*args, &block)
|
81
|
+
respond_to_missing?(*args) ? @__settings__.send(*args, &block) : super
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves body of the request from operation schema for given settings.
|
4
|
+
#
|
5
|
+
# It uses last (nested) definition without any coercion or validation.
|
6
|
+
# Formatting and validation is made later by [Evil::Client::Resolver#__call__]
|
7
|
+
# because it depends from both :body and :format definitions.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
#
|
11
|
+
class Resolver::Body < Resolver
|
12
|
+
private
|
13
|
+
|
14
|
+
def initialize(schema, settings)
|
15
|
+
super(schema, settings, :body)
|
16
|
+
end
|
17
|
+
|
18
|
+
def __call__
|
19
|
+
super { instance_exec(&__blocks__.last) if __blocks__.any? }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves request format from operation settings and schema
|
4
|
+
# @private
|
5
|
+
#
|
6
|
+
class Resolver::Format < Resolver
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize(schema, settings)
|
10
|
+
super schema, settings, :format
|
11
|
+
end
|
12
|
+
|
13
|
+
def __call__
|
14
|
+
super do
|
15
|
+
value = instance_exec(&__blocks__.last)&.to_sym if __blocks__.any?
|
16
|
+
value = :json if value.to_s == ""
|
17
|
+
raise __invalid_error__(value) unless LIST.include? value
|
18
|
+
value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def __invalid_error__(value)
|
23
|
+
__definition_error__ "Format :#{value} not supported." \
|
24
|
+
" Use one of the following formats:" \
|
25
|
+
" :#{LIST.join(', :')}."
|
26
|
+
end
|
27
|
+
|
28
|
+
LIST = %i[json yaml form text multipart].freeze
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves headers of the request from operation settings and schema
|
4
|
+
# by merging headers defined by schema and all its parents.
|
5
|
+
# @private
|
6
|
+
#
|
7
|
+
class Resolver::Headers < Resolver
|
8
|
+
private
|
9
|
+
|
10
|
+
def initialize(schema, settings)
|
11
|
+
super schema, settings, :headers
|
12
|
+
end
|
13
|
+
|
14
|
+
def __call__
|
15
|
+
super do
|
16
|
+
__blocks__.map { |block| __normalize__ instance_exec(&block) }
|
17
|
+
.reduce({}, :merge)
|
18
|
+
.reject { |_, value| value&.empty? }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def __normalize__(headers)
|
23
|
+
__check__(headers)
|
24
|
+
keys = __extract_keys__(headers)
|
25
|
+
values = __extract_values__(headers)
|
26
|
+
keys.zip(values).to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
def __check__(data)
|
30
|
+
raise __definition_error__ "#{data} is not a hash" unless data.is_a? Hash
|
31
|
+
end
|
32
|
+
|
33
|
+
def __extract_keys__(data)
|
34
|
+
keys = data.keys.map(&:to_s)
|
35
|
+
wrong = keys.reject { |key| key[VALID_KEY] }.map(&:inspect)
|
36
|
+
return keys unless wrong.any?
|
37
|
+
raise __definition_error__ "inacceptable headers #{wrong.join(', ')}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def __extract_values__(data)
|
41
|
+
data.values.map { |v| v.respond_to?(:map) ? v.map(&:to_s) : v.to_s }
|
42
|
+
end
|
43
|
+
|
44
|
+
VALID_KEY = /^.+$/
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves a http_method for the request from operation settings and schema
|
4
|
+
# @private
|
5
|
+
#
|
6
|
+
class Resolver::HttpMethod < Resolver
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize(schema, settings)
|
10
|
+
super schema, settings, :http_method
|
11
|
+
end
|
12
|
+
|
13
|
+
def __call__
|
14
|
+
super do
|
15
|
+
value = instance_exec(&__blocks__.last)&.to_s&.upcase if __blocks__.any?
|
16
|
+
raise __not_defined_error__ if value.to_s == ""
|
17
|
+
raise __invalid_error__(value) unless LIST.include? value
|
18
|
+
value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def __not_defined_error__
|
23
|
+
__definition_error__ "HTTP method not defined"
|
24
|
+
end
|
25
|
+
|
26
|
+
def __invalid_error__(value)
|
27
|
+
__definition_error__ "Unknown HTTP method #{value}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# @see https://tools.ietf.org/html/rfc7231#section-4
|
31
|
+
# @see https://tools.ietf.org/html/rfc5789#section-2
|
32
|
+
LIST = %w[GET POST PUT PATCH DELETE OPTIONS HEAD TRACE CONNECT].freeze
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves scope/operation-specific middleware from schema for some settings
|
4
|
+
#
|
5
|
+
# New middleware are added to previously defined ones.
|
6
|
+
# To reset all predefined middleware, set value to nil.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
#
|
10
|
+
class Resolver::Middleware < Resolver
|
11
|
+
private
|
12
|
+
|
13
|
+
def initialize(schema, settings)
|
14
|
+
super schema, settings, :middleware
|
15
|
+
end
|
16
|
+
|
17
|
+
def __call__
|
18
|
+
super do
|
19
|
+
__blocks__.map.with_object([]) do |block, obj|
|
20
|
+
list = __normalize__ instance_exec(&block)
|
21
|
+
obj.replace([]) unless list
|
22
|
+
obj.concat Array(list)
|
23
|
+
end.reverse
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def __normalize__(value)
|
28
|
+
case value
|
29
|
+
when nil then nil
|
30
|
+
when Class then value
|
31
|
+
when Array then value.flatten.compact.map { |val| __normalize__(val) }
|
32
|
+
else raise __definition_error__("#{value} is neither class nor array")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves query of the request from operation settings and schema
|
4
|
+
# by deeply merging queries defined by schema and all its parents.
|
5
|
+
#
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
class Resolver::Query < Resolver
|
9
|
+
private
|
10
|
+
|
11
|
+
def initialize(schema, settings)
|
12
|
+
super schema, settings, :query
|
13
|
+
end
|
14
|
+
|
15
|
+
def __call__
|
16
|
+
super do
|
17
|
+
Hash __blocks__
|
18
|
+
.map { |block| __normalize__ instance_exec(&block) }
|
19
|
+
.reduce({}) { |left, right| __deep_merge__(left, right) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def __normalize__(data)
|
24
|
+
return if data.nil?
|
25
|
+
raise __definition_error__("#{data} is not a hash") unless data.is_a? Hash
|
26
|
+
__stringify_keys__(data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def __deep_merge__(left, right)
|
30
|
+
return right unless left.is_a?(Hash) && right.is_a?(Hash)
|
31
|
+
|
32
|
+
left = __stringify_keys__(left)
|
33
|
+
right = __stringify_keys__(right)
|
34
|
+
right.keys.each { |key| left[key] = __deep_merge__ left[key], right[key] }
|
35
|
+
|
36
|
+
left
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Resolves request schema for given settings to the minimal Rack environment
|
4
|
+
#
|
5
|
+
# @return [Hash<String, Object>]
|
6
|
+
# @private
|
7
|
+
#
|
8
|
+
class Resolver::Request < Resolver
|
9
|
+
require_relative "uri"
|
10
|
+
require_relative "http_method"
|
11
|
+
require_relative "security"
|
12
|
+
require_relative "headers"
|
13
|
+
require_relative "query"
|
14
|
+
require_relative "body"
|
15
|
+
require_relative "format"
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def initialize(schema, settings)
|
20
|
+
super schema, settings, :request
|
21
|
+
end
|
22
|
+
|
23
|
+
def __call__
|
24
|
+
super { environment }
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop: disable Metrics/MethodLength
|
28
|
+
# rubocop: disable Metrics/AbcSize
|
29
|
+
def environment
|
30
|
+
{
|
31
|
+
"REQUEST_METHOD" => http_method,
|
32
|
+
"PATH_INFO" => uri.path,
|
33
|
+
"SCRIPT_NAME" => "",
|
34
|
+
"QUERY_STRING" => Formatter.call(query, :form),
|
35
|
+
"SERVER_NAME" => uri.host,
|
36
|
+
"SERVER_PORT" => uri.port,
|
37
|
+
"HTTP_Variables" => headers,
|
38
|
+
"rack.version" => Rack::VERSION,
|
39
|
+
"rack.url_scheme" => uri.scheme,
|
40
|
+
"rack.input" => Formatter.call(body, format, boundary: boundary),
|
41
|
+
"rack.multithread" => false,
|
42
|
+
"rack.multiprocess" => false,
|
43
|
+
"rack.run_once" => false,
|
44
|
+
"rack.hijack?" => false,
|
45
|
+
"rack.logger" => @__settings__&.logger
|
46
|
+
}
|
47
|
+
end
|
48
|
+
# rubocop: enable Metrics/MethodLength
|
49
|
+
# rubocop: enable Metrics/AbcSize
|
50
|
+
|
51
|
+
def uri
|
52
|
+
@uri ||= Resolver::Uri.call(@__schema__, @__settings__)
|
53
|
+
end
|
54
|
+
|
55
|
+
def http_method
|
56
|
+
@http_method ||= Resolver::HttpMethod.call(@__schema__, @__settings__)
|
57
|
+
end
|
58
|
+
|
59
|
+
def format
|
60
|
+
@format ||= Resolver::Format.call(@__schema__, @__settings__)
|
61
|
+
end
|
62
|
+
|
63
|
+
def security
|
64
|
+
@security ||= Resolver::Security.call(@__schema__, @__settings__)
|
65
|
+
end
|
66
|
+
|
67
|
+
def headers
|
68
|
+
@headers ||= Resolver::Headers.call(@__schema__, @__settings__)
|
69
|
+
.merge(security.fetch(:headers, {}))
|
70
|
+
.merge("Content-Type" => content_type)
|
71
|
+
end
|
72
|
+
|
73
|
+
def query
|
74
|
+
@query ||= Resolver::Query.call(@__schema__, @__settings__)
|
75
|
+
.merge(security.fetch(:query, {}))
|
76
|
+
end
|
77
|
+
|
78
|
+
def body
|
79
|
+
@body ||= Resolver::Body.call(@__schema__, @__settings__)
|
80
|
+
end
|
81
|
+
|
82
|
+
def boundary
|
83
|
+
@boundary ||= SecureRandom.hex(10)
|
84
|
+
end
|
85
|
+
|
86
|
+
def content_type
|
87
|
+
case format
|
88
|
+
when :text then "text/plain"
|
89
|
+
when :json then "application/json"
|
90
|
+
when :yaml then "application/yaml"
|
91
|
+
when :form then "application/x-www-form-urlencoded"
|
92
|
+
when :multipart then "multipart/form-data; boundary=#{boundary}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|