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.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +0 -11
  3. data/.gitignore +1 -0
  4. data/.rspec +0 -1
  5. data/.rubocop.yml +22 -19
  6. data/.travis.yml +1 -0
  7. data/CHANGELOG.md +251 -6
  8. data/LICENSE.txt +3 -1
  9. data/README.md +47 -81
  10. data/docs/helpers/body.md +93 -0
  11. data/docs/helpers/connection.md +19 -0
  12. data/docs/helpers/headers.md +72 -0
  13. data/docs/helpers/http_method.md +39 -0
  14. data/docs/helpers/let.md +14 -0
  15. data/docs/helpers/logger.md +24 -0
  16. data/docs/helpers/middleware.md +56 -0
  17. data/docs/helpers/operation.md +103 -0
  18. data/docs/helpers/option.md +50 -0
  19. data/docs/helpers/path.md +37 -0
  20. data/docs/helpers/query.md +59 -0
  21. data/docs/helpers/response.md +40 -0
  22. data/docs/helpers/scope.md +121 -0
  23. data/docs/helpers/security.md +102 -0
  24. data/docs/helpers/validate.md +68 -0
  25. data/docs/index.md +70 -78
  26. data/docs/license.md +5 -1
  27. data/docs/rspec.md +96 -0
  28. data/evil-client.gemspec +10 -8
  29. data/lib/evil/client.rb +126 -72
  30. data/lib/evil/client/builder.rb +47 -0
  31. data/lib/evil/client/builder/operation.rb +40 -0
  32. data/lib/evil/client/builder/scope.rb +31 -0
  33. data/lib/evil/client/chaining.rb +17 -0
  34. data/lib/evil/client/connection.rb +60 -20
  35. data/lib/evil/client/container.rb +66 -0
  36. data/lib/evil/client/container/operation.rb +23 -0
  37. data/lib/evil/client/container/scope.rb +28 -0
  38. data/lib/evil/client/exceptions/definition_error.rb +15 -0
  39. data/lib/evil/client/exceptions/name_error.rb +32 -0
  40. data/lib/evil/client/exceptions/response_error.rb +42 -0
  41. data/lib/evil/client/exceptions/type_error.rb +29 -0
  42. data/lib/evil/client/exceptions/validation_error.rb +27 -0
  43. data/lib/evil/client/formatter.rb +49 -0
  44. data/lib/evil/client/formatter/form.rb +45 -0
  45. data/lib/evil/client/formatter/multipart.rb +33 -0
  46. data/lib/evil/client/formatter/part.rb +66 -0
  47. data/lib/evil/client/formatter/text.rb +21 -0
  48. data/lib/evil/client/resolver.rb +84 -0
  49. data/lib/evil/client/resolver/body.rb +22 -0
  50. data/lib/evil/client/resolver/format.rb +30 -0
  51. data/lib/evil/client/resolver/headers.rb +46 -0
  52. data/lib/evil/client/resolver/http_method.rb +34 -0
  53. data/lib/evil/client/resolver/middleware.rb +36 -0
  54. data/lib/evil/client/resolver/query.rb +39 -0
  55. data/lib/evil/client/resolver/request.rb +96 -0
  56. data/lib/evil/client/resolver/response.rb +26 -0
  57. data/lib/evil/client/resolver/security.rb +113 -0
  58. data/lib/evil/client/resolver/uri.rb +35 -0
  59. data/lib/evil/client/rspec.rb +127 -0
  60. data/lib/evil/client/schema.rb +105 -0
  61. data/lib/evil/client/schema/operation.rb +177 -0
  62. data/lib/evil/client/schema/scope.rb +73 -0
  63. data/lib/evil/client/settings.rb +172 -0
  64. data/lib/evil/client/settings/validator.rb +64 -0
  65. data/mkdocs.yml +21 -15
  66. data/spec/features/custom_connection_spec.rb +17 -0
  67. data/spec/features/operation/middleware_spec.rb +50 -0
  68. data/spec/features/operation/options_spec.rb +71 -0
  69. data/spec/features/operation/request_spec.rb +94 -0
  70. data/spec/features/operation/response_spec.rb +48 -0
  71. data/spec/features/scope/options_spec.rb +52 -0
  72. data/spec/fixtures/locales/en.yml +16 -0
  73. data/spec/fixtures/test_client.rb +76 -0
  74. data/spec/spec_helper.rb +18 -6
  75. data/spec/support/fixtures_helper.rb +7 -0
  76. data/spec/unit/builder/operation_spec.rb +90 -0
  77. data/spec/unit/builder/scope_spec.rb +84 -0
  78. data/spec/unit/client_spec.rb +137 -0
  79. data/spec/unit/connection_spec.rb +78 -0
  80. data/spec/unit/container/operation_spec.rb +81 -0
  81. data/spec/unit/container/scope_spec.rb +61 -0
  82. data/spec/unit/container_spec.rb +107 -0
  83. data/spec/unit/exceptions/definition_error_spec.rb +15 -0
  84. data/spec/unit/exceptions/name_error_spec.rb +77 -0
  85. data/spec/unit/exceptions/response_error_spec.rb +22 -0
  86. data/spec/unit/exceptions/type_error_spec.rb +71 -0
  87. data/spec/unit/exceptions/validation_error_spec.rb +13 -0
  88. data/spec/unit/formatter/form_spec.rb +27 -0
  89. data/spec/unit/formatter/multipart_spec.rb +23 -0
  90. data/spec/unit/formatter/part_spec.rb +49 -0
  91. data/spec/unit/formatter/text_spec.rb +37 -0
  92. data/spec/unit/formatter_spec.rb +46 -0
  93. data/spec/unit/resolver/body_spec.rb +65 -0
  94. data/spec/unit/resolver/format_spec.rb +66 -0
  95. data/spec/unit/resolver/headers_spec.rb +93 -0
  96. data/spec/unit/resolver/http_method_spec.rb +67 -0
  97. data/spec/unit/resolver/middleware_spec.rb +83 -0
  98. data/spec/unit/resolver/query_spec.rb +85 -0
  99. data/spec/unit/resolver/request_spec.rb +121 -0
  100. data/spec/unit/resolver/response_spec.rb +64 -0
  101. data/spec/unit/resolver/security_spec.rb +156 -0
  102. data/spec/unit/resolver/uri_spec.rb +117 -0
  103. data/spec/unit/rspec_spec.rb +342 -0
  104. data/spec/unit/schema/operation_spec.rb +309 -0
  105. data/spec/unit/schema/scope_spec.rb +110 -0
  106. data/spec/unit/schema_spec.rb +157 -0
  107. data/spec/unit/settings/validator_spec.rb +128 -0
  108. data/spec/unit/settings_spec.rb +248 -0
  109. metadata +192 -135
  110. data/docs/base_url.md +0 -38
  111. data/docs/documentation.md +0 -9
  112. data/docs/headers.md +0 -59
  113. data/docs/http_method.md +0 -31
  114. data/docs/model.md +0 -173
  115. data/docs/operation.md +0 -0
  116. data/docs/overview.md +0 -0
  117. data/docs/path.md +0 -48
  118. data/docs/query.md +0 -99
  119. data/docs/responses.md +0 -66
  120. data/docs/security.md +0 -102
  121. data/docs/settings.md +0 -32
  122. data/lib/evil/client/connection/net_http.rb +0 -57
  123. data/lib/evil/client/dsl.rb +0 -127
  124. data/lib/evil/client/dsl/base.rb +0 -26
  125. data/lib/evil/client/dsl/files.rb +0 -37
  126. data/lib/evil/client/dsl/headers.rb +0 -16
  127. data/lib/evil/client/dsl/http_method.rb +0 -24
  128. data/lib/evil/client/dsl/operation.rb +0 -91
  129. data/lib/evil/client/dsl/operations.rb +0 -41
  130. data/lib/evil/client/dsl/path.rb +0 -25
  131. data/lib/evil/client/dsl/query.rb +0 -16
  132. data/lib/evil/client/dsl/response.rb +0 -61
  133. data/lib/evil/client/dsl/responses.rb +0 -29
  134. data/lib/evil/client/dsl/scope.rb +0 -27
  135. data/lib/evil/client/dsl/security.rb +0 -57
  136. data/lib/evil/client/dsl/verifier.rb +0 -35
  137. data/lib/evil/client/middleware.rb +0 -81
  138. data/lib/evil/client/middleware/base.rb +0 -11
  139. data/lib/evil/client/middleware/merge_security.rb +0 -20
  140. data/lib/evil/client/middleware/normalize_headers.rb +0 -17
  141. data/lib/evil/client/middleware/stringify_form.rb +0 -40
  142. data/lib/evil/client/middleware/stringify_json.rb +0 -19
  143. data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
  144. data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
  145. data/lib/evil/client/middleware/stringify_query.rb +0 -35
  146. data/lib/evil/client/operation.rb +0 -34
  147. data/lib/evil/client/operation/request.rb +0 -26
  148. data/lib/evil/client/operation/response.rb +0 -39
  149. data/lib/evil/client/operation/response_error.rb +0 -13
  150. data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
  151. data/spec/features/instantiation_spec.rb +0 -68
  152. data/spec/features/middleware_spec.rb +0 -79
  153. data/spec/features/operation_with_documentation_spec.rb +0 -41
  154. data/spec/features/operation_with_files_spec.rb +0 -40
  155. data/spec/features/operation_with_form_body_spec.rb +0 -158
  156. data/spec/features/operation_with_headers_spec.rb +0 -99
  157. data/spec/features/operation_with_http_method_spec.rb +0 -45
  158. data/spec/features/operation_with_json_body_spec.rb +0 -156
  159. data/spec/features/operation_with_nested_responses_spec.rb +0 -95
  160. data/spec/features/operation_with_path_spec.rb +0 -47
  161. data/spec/features/operation_with_query_spec.rb +0 -84
  162. data/spec/features/operation_with_security_spec.rb +0 -228
  163. data/spec/features/scoping_spec.rb +0 -48
  164. data/spec/support/test_client.rb +0 -15
  165. data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
  166. data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
  167. data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
  168. data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
  169. data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
  170. data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
  171. data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
  172. data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
  173. data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
  174. data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
  175. data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
  176. data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
  177. data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
  178. data/spec/unit/evil/client/middleware_spec.rb +0 -46
  179. data/spec/unit/evil/client/operation/request_spec.rb +0 -49
  180. data/spec/unit/evil/client/operation/response_spec.rb +0 -63
@@ -0,0 +1,26 @@
1
+ class Evil::Client
2
+ #
3
+ # Resolves rack-compatible response from schema for given settings
4
+ # @private
5
+ #
6
+ class Resolver::Response < Resolver
7
+ private
8
+
9
+ def initialize(schema, settings, response)
10
+ @__response__ = Array response
11
+ super schema, settings, :responses, @__response__.first.to_i
12
+ end
13
+
14
+ def __call__
15
+ super do
16
+ __check_status__
17
+ instance_exec(*@__response__, &__blocks__.last)
18
+ end
19
+ end
20
+
21
+ def __check_status__
22
+ return if __blocks__.any?
23
+ raise ResponseError.new(@__schema__, @__settings__, @__response__)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,113 @@
1
+ class Evil::Client
2
+ #
3
+ # Resolves security definitions from operation settings and schema.
4
+ # Defines helpers for different methods of the authentication.
5
+ # @private
6
+ #
7
+ class Resolver::Security < Resolver
8
+ # DSL method to provide basic authentication schema
9
+ # by user name and password
10
+ #
11
+ # It provides base64-encoded "user:password" token and adds it
12
+ # to the "Authorization" header with a "Basic" prefix.
13
+ #
14
+ # @example
15
+ # operation do
16
+ # option :user
17
+ # option :password
18
+ #
19
+ # security { basic_auth user, password }
20
+ # end
21
+ #
22
+ # @param [#to_s] user User name
23
+ # @param [#to_s] password Password
24
+ # @return [Hash<:headers, Hash<Symbol, String>>]
25
+ #
26
+ def basic_auth(user, password)
27
+ token = Base64.encode64("#{user}:#{password}").delete("\n")
28
+ token_auth(token, prefix: "Basic")
29
+ end
30
+
31
+ # DSL method to provide token-based authentication schema
32
+ #
33
+ # It places the token under either standard "Authorization" header,
34
+ # or standard "access_token" parameter of body or query.
35
+ # If you need custom key use [#key_auth] schema instead.
36
+ #
37
+ # @example
38
+ # operation do
39
+ # option :token
40
+ # security { token_auth token, prefix: "Bearer" }
41
+ # end
42
+ #
43
+ # @param [#to_s] token User secret token
44
+ # @option [#to_s, nil] :prefix The standard prefix to be added before token
45
+ # @option [:headers, :query] :inside (:headers)
46
+ # The part of the request for the token
47
+ # @return [Hash<Symbol, Hash<Symbol, String>>]
48
+ #
49
+ def token_auth(token, inside: :headers, prefix: nil)
50
+ if inside == :headers
51
+ prefixed_token = [prefix&.to_s&.capitalize, token].compact.join(" ")
52
+ key_auth("Authorization", prefixed_token, inside: :headers)
53
+ else
54
+ key_auth("access_token", token, inside: inside)
55
+ end
56
+ end
57
+
58
+ # DSL method to provide the key-based authentication schema
59
+ #
60
+ # @example
61
+ # operation do
62
+ # option :key
63
+ # security { key_auth "Authorize", key }
64
+ # end
65
+ #
66
+ # @param [#to_s] key Name of the parameter
67
+ # @param [#to_s] value Value of the parameter
68
+ # @option [:headers, :query] :inside (:headers)
69
+ # The part of the request for the key-value pair
70
+ # @return [Hash<Symbol, Hash<Symbol, String>>]
71
+ #
72
+ def key_auth(key, value, inside: :headers)
73
+ { inside => { key.to_s => value.to_s } }
74
+ end
75
+
76
+ private
77
+
78
+ def initialize(schema, settings)
79
+ super schema, settings, :security
80
+ end
81
+
82
+ def __call__
83
+ super do
84
+ value = __blocks__.any? ? Hash(instance_exec(&__blocks__.last)) : {}
85
+ raise __wrong_format_error__(value) unless value.is_a? Hash
86
+
87
+ __symbolize_keys__(value).tap do |val|
88
+ __check_format__(val)
89
+ __check_values__(val)
90
+ end
91
+ end
92
+ end
93
+
94
+ def __check_format__(value)
95
+ data = value.keys - %i[headers query]
96
+ return if data.empty?
97
+
98
+ raise __definition_error__ "#{value.inspect} is not a hash"
99
+ end
100
+
101
+ def __check_values__(value)
102
+ data = value.reject { |_, val| val.is_a? Hash }
103
+ return if data.empty?
104
+
105
+ message = "inacceptable parts :#{data} of the request"
106
+ raise __definition_error__(message)
107
+ end
108
+
109
+ def __wrong_value_error__(data)
110
+ __definition_error__ "inacceptable security settings #{data}"
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,35 @@
1
+ class Evil::Client
2
+ #
3
+ # Resolves request URI from operation settings and schema
4
+ # @private
5
+ #
6
+ class Resolver::Uri < Resolver
7
+ private
8
+
9
+ def initialize(schema, settings)
10
+ super schema, settings, :path
11
+ end
12
+
13
+ def __call__
14
+ super do
15
+ parts = __blocks__.map { |block| instance_exec(&block)&.to_s }
16
+ path = File.join(parts)
17
+ __uri__(path).tap { |uri| __check__(uri) }
18
+ end
19
+ end
20
+
21
+ def __uri__(path)
22
+ URI path
23
+ rescue => error
24
+ raise __definition_error__(error.message)
25
+ end
26
+
27
+ def __check__(uri)
28
+ scheme = uri.scheme
29
+ details = "base url should be defined" unless scheme
30
+ details ||= "base url should use HTTP(S). '#{scheme}' used instead"
31
+
32
+ raise __definition_error__(details) unless scheme&.match(/^https?$/)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,127 @@
1
+ #
2
+ # Checks that an operation has been performed with given options
3
+ #
4
+ # @example
5
+ # subject do
6
+ # MyClient.new(token: "foo").users(version: 3).fetch(token: "bar", id: 42)
7
+ # end
8
+ #
9
+ # # call only matcher
10
+ # expect { subject }.to perform_operation("MyClient.users.fetch")
11
+ #
12
+ # # exact matcher
13
+ # expect { subject }
14
+ # .to perform_operation("MyClient.users.fetch")
15
+ # .with_exactly(token: "bar", version: 3, id: 42)
16
+ #
17
+ # # partial matcher
18
+ # expect { subject }
19
+ # .to perform_operation("MyClient.users.fetch")
20
+ # .with(token: "bar", version: 3)
21
+ #
22
+ # # absence matcher
23
+ # expect { subject }
24
+ # .to perform_operation("MyClient.users.fetch")
25
+ # .without(:user, :password)
26
+ #
27
+ # # block syntax
28
+ # expect { subject }
29
+ # .to perform_operation("MyClient.users.fetch")
30
+ # .with { |token:, **| expect(token).to eq "bar" }
31
+ #
32
+ # @param [String] name The full name of the operation
33
+ #
34
+ RSpec::Matchers.define :perform_operation do |name|
35
+ supports_block_expectations
36
+
37
+ description { "perform operation #{name} " }
38
+
39
+ chain :with do |**options|
40
+ @some_options = options
41
+ end
42
+
43
+ chain :with_exactly do |**options|
44
+ @exact_options = options
45
+ end
46
+
47
+ chain :without do |*options|
48
+ @no_options = options.flatten.map(&:to_sym)
49
+ end
50
+
51
+ def full_signature(name)
52
+ name.dup.tap do |text|
53
+ text << " with options #{@exact_options}" if @exact_options
54
+ text << " with options including #{@some_options}" if @some_options
55
+ text << " without options :#{@no_options.join(', :')}" if @no_options
56
+ end
57
+ end
58
+
59
+ # rubocop: disable Metrics/CyclomaticComplexity
60
+ # rubocop: disable Style/InverseMethods
61
+ def expected_options?(options, check)
62
+ return if @exact_options && options != @exact_options
63
+ return if @some_options && !(options >= @some_options)
64
+ return if (options.keys & @no_options.to_a).any?
65
+ check.nil? || check.call(options)
66
+ end
67
+ # rubocop: enable Metrics/CyclomaticComplexity
68
+ # rubocop: enable Style/InverseMethods
69
+
70
+ def stub_resolver
71
+ resolver = Evil::Client::Resolver::Request
72
+
73
+ allow(resolver).to receive(:call) do |schema, settings|
74
+ register(schema, settings)
75
+ resolver.new(schema, settings).send(:__call__)
76
+ end
77
+ end
78
+
79
+ def actual_operations
80
+ @actual_operations ||= []
81
+ end
82
+
83
+ def register(schema, settings)
84
+ actual_operations << [schema.to_s, settings.options]
85
+ end
86
+
87
+ def performed(name, check)
88
+ @performed ||= actual_operations.find do |(key, options)|
89
+ (key == name) && expected_options?(options, check)
90
+ end
91
+ end
92
+
93
+ match do |block|
94
+ stub_resolver
95
+ block.call
96
+ !performed(name, block_arg).nil?
97
+ end
98
+
99
+ match_when_negated do |block|
100
+ stub_resolver
101
+ block.call
102
+ performed(name, block_arg).nil?
103
+ end
104
+
105
+ def describe_expectations(name, perform)
106
+ "It was expected the operation #{full_signature(name)}" \
107
+ " #{'NOT ' unless perform}to be performed.\n" \
108
+ "The following operations has been actually performed:"
109
+ end
110
+
111
+ failure_message do
112
+ text = describe_expectations(name, true)
113
+ actual_operations.each.with_index(1) do |(key, opts), index|
114
+ text << format("\n %02d) #{key} with #{opts}", index)
115
+ end
116
+ text
117
+ end
118
+
119
+ failure_message_when_negated do
120
+ text = describe_expectations(name, false)
121
+ actual_operations.each.with_index(1) do |(key, opts), index|
122
+ marker = performed(name, block_arg) == [key, opts] ? "->" : " "
123
+ text << format("\n#{marker} % 2d) #{key} with #{opts}", index)
124
+ end
125
+ text
126
+ end
127
+ end
@@ -0,0 +1,105 @@
1
+ class Evil::Client
2
+ #
3
+ # @abstract
4
+ # Base class for mutable containers of client-specific definitions
5
+ # of nested scopes and operations along with a corresponding [#settings] class
6
+ # subclassing [Evil::Client::Settings]
7
+ #
8
+ # Every concrete container defines its only DSL for scope/operation
9
+ # definitions.
10
+ #
11
+ class Schema
12
+ # Loads concrete implementations of the abstract schema
13
+ require_relative "schema/operation"
14
+ require_relative "schema/scope"
15
+
16
+ # The name of current schema which is unique for the existing [#parent],
17
+ # or equals to client class name without any [#parent] (root scope name).
18
+ #
19
+ # @return [String]
20
+ #
21
+ attr_reader :name
22
+
23
+ # Scope schema the operation belongs to
24
+ #
25
+ # Only the root schema has no parents.
26
+ # Its definitions are shared by all operations
27
+ #
28
+ # @return [Evil::Client::Schema::Scope, nil]
29
+ #
30
+ attr_reader :parent
31
+
32
+ # Back reference to client the schema belongs to
33
+ #
34
+ # @return [Evil::Client]
35
+ #
36
+ attr_reader :client # TODO: add spec
37
+
38
+ # The human-friendly representation of the schema
39
+ #
40
+ # @example
41
+ # "MyClient.users.fetch" # custom operation's schema
42
+ #
43
+ # @return [String]
44
+ #
45
+ def to_s
46
+ [parent, name].compact.join(".")
47
+ end
48
+ alias_method :to_str, :to_s
49
+ alias_method :inspect, :to_s
50
+
51
+ # The settings class inherited from the [#parent]'s one
52
+ #
53
+ # @return [Class]
54
+ #
55
+ def settings
56
+ @settings ||= Class.new(parent&.settings || Settings).tap do |klass|
57
+ klass.send(:instance_variable_set, :@schema, self)
58
+ end
59
+ end
60
+
61
+ # Adds an option to the [#settings] class
62
+ #
63
+ # @param (see Evil::Client::Settings.option)
64
+ # @option (see Evil::Client::Settings.option)
65
+ # @return [self]
66
+ #
67
+ def option(key, type = nil, **opts)
68
+ settings.option(key, type, **opts)
69
+ self
70
+ end
71
+
72
+ # Adds a memoized method to the [#settings] class
73
+ #
74
+ # @param (see Evil::Client::Settings.let)
75
+ # @return [self]
76
+ #
77
+ def let(key, &block)
78
+ settings.let(key, &block)
79
+ self
80
+ end
81
+
82
+ # Adds validator to the [#settings] class
83
+ #
84
+ # @param (see Evil::Client::Settings.validate)
85
+ # @return [self]
86
+ #
87
+ def validate(key, &block)
88
+ settings.validate(key, &block)
89
+ self
90
+ end
91
+
92
+ private
93
+
94
+ def initialize(parent, name = nil)
95
+ if parent.is_a? self.class
96
+ @parent = parent
97
+ @client = parent&.client
98
+ @name = name
99
+ else
100
+ @client = parent
101
+ @name = parent.name
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,177 @@
1
+ class Evil::Client
2
+ #
3
+ # Mutable container of operation definitions
4
+ # with DSL to configure both settings and parts of request/response.
5
+ #
6
+ class Schema::Operation < Schema
7
+ # Tells that this is a schema for end route (operation)
8
+ #
9
+ # @return [true]
10
+ #
11
+ def leaf?
12
+ true
13
+ end
14
+
15
+ # Definitions for the current operation
16
+ #
17
+ # @return [Hash<Symbol, [Proc, Hash<Integer, Proc>]>]
18
+ #
19
+ def definitions
20
+ @definitions ||= { responses: {} }
21
+ end
22
+
23
+ # Adds path definition to the schema
24
+ #
25
+ # Root path should be a valid URL for HTTP(S) protocol
26
+ #
27
+ # @param [#to_s, nil] value
28
+ # @param [Proc] block
29
+ # @return [self]
30
+ #
31
+ def path(value = nil, &block)
32
+ __define__(:path, value, block)
33
+ end
34
+
35
+ # Adds http method definition to the schema
36
+ #
37
+ # @see https://tools.ietf.org/html/rfc7231#section-4
38
+ # @see https://tools.ietf.org/html/rfc5789#section-2
39
+ #
40
+ # @param [#to_s, nil] value Acceptable http_method
41
+ # @param [Proc] block
42
+ # @return [self]
43
+ #
44
+ def http_method(value = nil, &block)
45
+ __define__(:http_method, value, block)
46
+ end
47
+
48
+ # Adds format for request body
49
+ #
50
+ # @param [:json, :form, :text, :multipart, nil] value (:json)
51
+ # @param [Proc] block
52
+ # @return [self]
53
+ #
54
+ def format(value = nil, &block)
55
+ __define__(:format, value, block)
56
+ end
57
+
58
+ # Adds security definition to the schema
59
+ #
60
+ # The definition should be nested hash with a root keys :headers, or :query.
61
+ #
62
+ # @example
63
+ # security { { headers: { "idempotent-token" => "foobar" } } }
64
+ #
65
+ # Inside the block we provide several helpers for standard authentication
66
+ # schemas, namely `basic_auth`, `token_auth`, and `key_auth`. Those are
67
+ # preferred ways to define a security schema:
68
+ #
69
+ # @example
70
+ # security { token_auth token, prefix: "Bearer" }
71
+ #
72
+ # @param [Hash<[:headers, :query], Hash>, nil]
73
+ # @param [Proc] block
74
+ # @return [self]
75
+ #
76
+ def security(value = nil, &block)
77
+ __define__(:security, value, block)
78
+ end
79
+
80
+ # Adds request headers definition to the schema
81
+ #
82
+ # Headers should be hash of header-value pairs.
83
+ # Values should be either stringified values or array of stringified values.
84
+ #
85
+ # Nested definition will be merged to the root one(s),
86
+ # so that you can add headers step-by-step from root of the client
87
+ # to its scopes and operations.
88
+ #
89
+ # To reset previous settings you can either set all headers to `nil`,
90
+ # or assign nil to custom headers. All headers with empty values
91
+ # will be ignored.
92
+ #
93
+ # @param [Hash<#to_s, [#to_s, Array<#to_s>]>, nil] value
94
+ # @param [Proc] block
95
+ # @return [self]
96
+ #
97
+ def headers(value = nil, &block)
98
+ __define__(:headers, value, block)
99
+ end
100
+
101
+ # Adds query definition to the schema
102
+ #
103
+ # Query should be a nested hash.
104
+ # Wnen subscope or operation reloads previously defined query,
105
+ # new definition are merged deeply to older one. You can populate
106
+ # a query step-by-step from client root to an operation.
107
+ #
108
+ # @param [Hash, nil] value
109
+ # @param [Proc] block
110
+ # @return [self]
111
+ #
112
+ def query(value = nil, &block)
113
+ __define__(:query, value, block)
114
+ end
115
+
116
+ # Adds body definition to the schema
117
+ #
118
+ # It is expected the body to correspond to [#format].
119
+ #
120
+ # When a format is :json, the body should be convertable to json
121
+ # When a format is :text, the body should be stringified
122
+ # When a format is :form, the body should be a hash
123
+ # When a format is :multipart, the body can be object or array of objects
124
+ #
125
+ # Unlike queries, previous body definitions aren't inherited.
126
+ # The body defined for root scope can be fully reloaded
127
+ # at subscope/operation level without any merging.
128
+ #
129
+ # @param [Object] value
130
+ # @param [Proc] block
131
+ # @return [self]
132
+ #
133
+ def body(value = nil, &block)
134
+ __define__(:body, value, block)
135
+ end
136
+
137
+ # Adds list of middleware to the schema
138
+ #
139
+ # New middleware are added to previously defined (by root).
140
+ # This means the operation-specific middleware will handle the request
141
+ # after a root-specific one, and will handle the response before
142
+ # a roog-specific middleware.
143
+ #
144
+ # Values should be either a Rack middleware class, or array of
145
+ # Rack middleware classes.
146
+ #
147
+ # @param [Rack::Middleware, <Array<Rack::Middleware>>] value
148
+ # @param [Proc] block
149
+ # @return [self]
150
+ #
151
+ def middleware(value = nil, &block)
152
+ __define__(:middleware, value, block)
153
+ end
154
+
155
+ # Adds response handler definition to the schema
156
+ #
157
+ # @param [Integer, Array<Integer>] codes List of response codes
158
+ # @param [Proc] block
159
+ # @return [self]
160
+ #
161
+ def response(*codes, &block)
162
+ codes.flatten.map(&:to_i).each do |code|
163
+ definitions[:responses][code] = block || proc { |*response| response }
164
+ end
165
+ self
166
+ end
167
+ alias_method :responses, :response
168
+
169
+ private
170
+
171
+ # @private Method to add definitions to the schema
172
+ def __define__(key, value, block)
173
+ definitions[key] = block || proc { value }
174
+ self
175
+ end
176
+ end
177
+ end