evil-client 0.2.3 → 0.3.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/.gitignore +1 -0
- data/.rubocop.yml +5 -60
- data/CHANGELOG.md +50 -0
- data/README.md +13 -4
- data/evil-client.gemspec +1 -1
- data/lib/evil/client.rb +1 -1
- data/lib/evil/client/connection.rb +5 -5
- data/lib/evil/client/connection/net_http.rb +1 -1
- data/lib/evil/client/dsl.rb +1 -10
- data/lib/evil/client/dsl/operation.rb +10 -9
- data/lib/evil/client/dsl/response.rb +62 -0
- data/lib/evil/client/dsl/responses.rb +29 -0
- data/lib/evil/client/dsl/scope.rb +1 -8
- data/lib/evil/client/dsl/security.rb +1 -1
- data/lib/evil/client/model.rb +2 -2
- data/lib/evil/client/operation/request.rb +5 -10
- data/lib/evil/client/operation/response.rb +16 -17
- data/lib/evil/client/operation/response_error.rb +4 -3
- data/lib/evil/client/operation/unexpected_response_error.rb +7 -4
- data/spec/features/middleware_spec.rb +1 -3
- data/spec/features/operation_with_documentation_spec.rb +2 -2
- data/spec/features/operation_with_files_spec.rb +1 -1
- data/spec/features/operation_with_form_body_spec.rb +3 -3
- data/spec/features/operation_with_headers_spec.rb +1 -1
- data/spec/features/operation_with_http_method_spec.rb +1 -1
- data/spec/features/operation_with_json_body_spec.rb +3 -3
- data/spec/features/{operation_with_response_spec.rb → operation_with_nested_responses_spec.rb} +22 -36
- data/spec/features/operation_with_path_spec.rb +1 -1
- data/spec/features/operation_with_query_spec.rb +1 -1
- data/spec/features/operation_with_security_spec.rb +2 -2
- data/spec/features/scoping_spec.rb +1 -1
- data/spec/unit/evil/client/dsl/operation_spec.rb +156 -15
- data/spec/unit/evil/client/dsl/operations_spec.rb +3 -1
- data/spec/unit/evil/client/dsl/security_spec.rb +1 -1
- data/spec/unit/evil/client/operation/response_spec.rb +11 -9
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7709a566e45dfcf398b95933a6d91639fd1ad47d
|
4
|
+
data.tar.gz: fa499f5f9856cb8a451bc87c0ff3f44f41ae7733
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98e0553379fa9a0b2105758cf271f7b7c0816a82d0116519e60f84aeadadc1ba8a25c8d4b53028f4fab5d0deaa1dc3a68da5a9efb01482a803b38ae6c59fc34d
|
7
|
+
data.tar.gz: b8f0873e672bbf51531ae4dbcee3948a11b4a16d52cd3c0f06adb50120e4e80186f864b97084cbb16864f9100fbf96e1f0e83908e600276323ca35e465a1b639
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -5,94 +5,39 @@ AllCops:
|
|
5
5
|
StyleGuideCopsOnly: true
|
6
6
|
TargetRubyVersion: 2.3
|
7
7
|
|
8
|
-
Lint/HandleExceptions:
|
9
|
-
Exclude:
|
10
|
-
- spec/**/*_spec.rb
|
11
|
-
|
12
|
-
Lint/RescueException:
|
13
|
-
Exclude:
|
14
|
-
- spec/**/*_spec.rb
|
15
|
-
|
16
|
-
Metrics/LineLength:
|
17
|
-
Max: 80
|
18
|
-
Exclude:
|
19
|
-
- spec/**/*_spec.rb
|
20
|
-
|
21
|
-
Style/AccessorMethodName:
|
22
|
-
Exclude:
|
23
|
-
- spec/**/*_spec.rb
|
24
|
-
|
25
8
|
Style/Alias:
|
26
|
-
EnforcedStyle: prefer_alias_method
|
27
|
-
|
28
|
-
Style/AsciiComments:
|
29
9
|
Enabled: false
|
30
10
|
|
31
11
|
Style/CaseEquality:
|
32
12
|
Enabled: false
|
33
13
|
|
34
|
-
Style/ClassAndModuleChildren:
|
35
|
-
Enabled: false
|
36
|
-
|
37
14
|
Style/Documentation:
|
38
15
|
Enabled: false
|
39
16
|
|
40
17
|
Style/DoubleNegation:
|
41
18
|
Enabled: false
|
42
19
|
|
43
|
-
Style/
|
44
|
-
Enabled: false
|
45
|
-
|
46
|
-
Style/EmptyLinesAroundModuleBody:
|
47
|
-
Enabled: false
|
48
|
-
|
49
|
-
Style/EmptyLineBetweenDefs:
|
50
|
-
Enabled: false
|
51
|
-
|
52
|
-
Style/FileName:
|
20
|
+
Style/LambdaCall:
|
53
21
|
Enabled: false
|
54
22
|
|
55
|
-
Style/
|
56
|
-
Exclude:
|
57
|
-
- spec/**/*_spec.rb
|
58
|
-
|
59
|
-
Style/LambdaCall:
|
23
|
+
Style/MethodMissing:
|
60
24
|
Enabled: false
|
61
25
|
|
62
|
-
Style/
|
26
|
+
Style/NumericPredicate:
|
63
27
|
Enabled: false
|
64
28
|
|
65
29
|
Style/RaiseArgs:
|
66
|
-
|
67
|
-
|
68
|
-
Style/Semicolon:
|
69
|
-
Exclude:
|
70
|
-
- spec/**/*_spec.rb
|
71
|
-
|
72
|
-
Style/SignalException:
|
73
|
-
EnforcedStyle: semantic
|
30
|
+
Enabled: false
|
74
31
|
|
75
|
-
Style/
|
32
|
+
Style/RescueModifier:
|
76
33
|
Enabled: false
|
77
34
|
|
78
35
|
Style/SingleLineMethods:
|
79
36
|
Exclude:
|
80
37
|
- spec/**/*_spec.rb
|
81
38
|
|
82
|
-
Style/SpaceBeforeFirstArg:
|
83
|
-
Enabled: false
|
84
|
-
|
85
|
-
Style/SpecialGlobalVars:
|
86
|
-
Exclude:
|
87
|
-
- Gemfile
|
88
|
-
- dry-initializer.gemspec
|
89
|
-
|
90
39
|
Style/StringLiterals:
|
91
40
|
EnforcedStyle: double_quotes
|
92
41
|
|
93
42
|
Style/StringLiteralsInInterpolation:
|
94
43
|
EnforcedStyle: double_quotes
|
95
|
-
|
96
|
-
Style/TrivialAccessors:
|
97
|
-
Exclude:
|
98
|
-
- spec/**/*_spec.rb
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# v0.3.0 2016-11-18
|
2
|
+
|
3
|
+
This version changes the way of processing responses. Instead of dealing
|
4
|
+
with raw rake responses, we add opinionated methods to gracefully process
|
5
|
+
responses from JSON or plain text.
|
6
|
+
|
7
|
+
In the next minor versions processors for both "form" and "file" (multipart)
|
8
|
+
formats will be added.
|
9
|
+
|
10
|
+
## BREAKING CHANGES
|
11
|
+
- Method `DSL#response` was redefined with a new signature (nepalez)
|
12
|
+
|
13
|
+
The method takes 2 _mandatory_ positional params: unique name and
|
14
|
+
integer status. This allows to process responses with the same status,
|
15
|
+
and different structures, like in the following example, where errors
|
16
|
+
are returned with the same status 200 (not 4**) as success:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
operation :update_user do
|
20
|
+
# ...
|
21
|
+
response :success, 200, model: User
|
22
|
+
response :error, 200, model: Error
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
This time response handler will try processing a response using various
|
27
|
+
definitions (in order of their declaration) until some suits. The hanlder
|
28
|
+
returns `UnexpectedResponseError` in case no definition proves suitable.
|
29
|
+
|
30
|
+
Names (the first param) are unique. When several definitions use the same name,
|
31
|
+
only the last one will be applicable.
|
32
|
+
|
33
|
+
## Added
|
34
|
+
- Method `DSL#responses` to share options between response definitions (nepalez)
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
responses format: "json" do
|
38
|
+
responses raise: true do
|
39
|
+
response :failure, 400
|
40
|
+
response :not_found, 404
|
41
|
+
end
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
This is the same as:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
response :failure, 400, format: "json", raise: true
|
49
|
+
response :not_found, 404, format: "json", raise: true
|
50
|
+
```
|
data/README.md
CHANGED
@@ -90,12 +90,21 @@ class CatsClient < Evil::Client
|
|
90
90
|
attribute :age, optional: true
|
91
91
|
end
|
92
92
|
|
93
|
-
response
|
94
|
-
|
93
|
+
# Parses json response and wraps it into Cat instance with additional
|
94
|
+
# parameter
|
95
|
+
response 200, format: :json, type: Cat do
|
96
|
+
attribute :success
|
95
97
|
end
|
96
98
|
|
97
|
-
response
|
98
|
-
|
99
|
+
# Parses json response, wraps it into model with [#error] and raises
|
100
|
+
# an exception where [ResponseError#response] contains the model istance
|
101
|
+
response 422, format: :json, raise: true do
|
102
|
+
attribute :error
|
103
|
+
end
|
104
|
+
|
105
|
+
# Takes raw body and converts it into the hashie
|
106
|
+
response 404, raise: true do |body|
|
107
|
+
Hashie::Mash.new error: body
|
99
108
|
end
|
100
109
|
end
|
101
110
|
|
data/evil-client.gemspec
CHANGED
data/lib/evil/client.rb
CHANGED
@@ -12,11 +12,11 @@ class Evil::Client
|
|
12
12
|
# @return [Class]
|
13
13
|
#
|
14
14
|
def self.[](name = nil)
|
15
|
-
|
16
|
-
|
15
|
+
keys = REGISTRY.keys
|
16
|
+
key = (name || keys.first).to_sym
|
17
17
|
klass = REGISTRY.fetch(key) do
|
18
|
-
|
19
|
-
|
18
|
+
raise ArgumentError.new "Connection '#{key}' is not registered." \
|
19
|
+
" Use the following keys: #{keys}"
|
20
20
|
end
|
21
21
|
|
22
22
|
require_relative "connection/#{key}"
|
@@ -29,7 +29,7 @@ class Evil::Client
|
|
29
29
|
# @return [Array]
|
30
30
|
#
|
31
31
|
def call(_env)
|
32
|
-
|
32
|
+
raise NotImplementedError
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/lib/evil/client/dsl.rb
CHANGED
@@ -4,15 +4,6 @@ class Evil::Client
|
|
4
4
|
require_relative "dsl/operations"
|
5
5
|
require_relative "dsl/scope"
|
6
6
|
|
7
|
-
# Stack of default middleware before custom midleware and a connection
|
8
|
-
# This stack cannot be modified
|
9
|
-
DEFAULT_MIDDLEWARE = Middleware.new do
|
10
|
-
run Middleware::MergeSecurity
|
11
|
-
run Middleware::StringifyJson
|
12
|
-
run Middleware::StringifyQuery
|
13
|
-
run Middleware::NormalizeHeaders
|
14
|
-
end
|
15
|
-
|
16
7
|
# Adds [#operations] to a specific client's instances
|
17
8
|
def self.extended(klass)
|
18
9
|
klass.include Dry::Initializer.define -> { param :operations }
|
@@ -106,7 +97,7 @@ class Evil::Client
|
|
106
97
|
|
107
98
|
private
|
108
99
|
|
109
|
-
BASE_URL =
|
100
|
+
BASE_URL = proc { raise NotImplementedError.new "Base url is not defined" }
|
110
101
|
|
111
102
|
def schema
|
112
103
|
@schema ||= {
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Evil::Client::DSL
|
2
2
|
require_relative "security"
|
3
3
|
require_relative "files"
|
4
|
+
require_relative "response"
|
5
|
+
require_relative "responses"
|
4
6
|
|
5
7
|
# Builds a schema for single operation
|
6
8
|
class Operation
|
@@ -69,13 +71,12 @@ module Evil::Client::DSL
|
|
69
71
|
@schema[:query] = __model__(options, &block)
|
70
72
|
end
|
71
73
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
74
|
+
def responses(options = {}, &block)
|
75
|
+
Responses.new(self, options, &block)
|
76
|
+
end
|
77
|
+
|
78
|
+
def response(name, status, **options, &block)
|
79
|
+
@schema[:responses][name] = Response[status, block: block, **options]
|
79
80
|
end
|
80
81
|
|
81
82
|
# ==========================================================================
|
@@ -85,8 +86,8 @@ module Evil::Client::DSL
|
|
85
86
|
def __valid_format__(format)
|
86
87
|
formats = %w(json form)
|
87
88
|
return format.to_s if formats.include? format.to_s
|
88
|
-
|
89
|
-
|
89
|
+
raise ArgumentError.new "Invalid format #{format} for body." \
|
90
|
+
" Use one of formats: #{formats}"
|
90
91
|
end
|
91
92
|
|
92
93
|
def __model__(model: nil, **, &block)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Evil::Client::DSL
|
2
|
+
# Builds a schema for response processor
|
3
|
+
class Response
|
4
|
+
extend Dry::Initializer::Mixin
|
5
|
+
param :status
|
6
|
+
option :raise, default: proc { false }
|
7
|
+
option :format, default: proc {}
|
8
|
+
option :model, default: proc { nil }
|
9
|
+
option :block, default: proc { nil }
|
10
|
+
|
11
|
+
def self.[](*args)
|
12
|
+
new(*args).to_h
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h
|
16
|
+
{ status: status.to_i, coercer: coercer, raise: raise }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def json?
|
22
|
+
format.to_s == "json"
|
23
|
+
end
|
24
|
+
|
25
|
+
def arity
|
26
|
+
block&.arity
|
27
|
+
end
|
28
|
+
|
29
|
+
def coercer
|
30
|
+
handlers = [parser, processor, wrapper, finalizer].compact
|
31
|
+
proc { |body| handlers.inject(body) { |obj, handler| handler.call(obj) } }
|
32
|
+
end
|
33
|
+
|
34
|
+
def parser
|
35
|
+
proc { |body| JSON.parse(body) } if json?
|
36
|
+
end
|
37
|
+
|
38
|
+
def wrapper
|
39
|
+
return unless json?
|
40
|
+
proc { |data| Hash === data ? data : { data: data } }
|
41
|
+
end
|
42
|
+
|
43
|
+
def processor
|
44
|
+
return unless block&.arity == 1
|
45
|
+
block
|
46
|
+
end
|
47
|
+
|
48
|
+
def addon
|
49
|
+
return unless arity == 0
|
50
|
+
proc { |klass| klass.instance_eval(&block) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def finalizer
|
54
|
+
case [model.nil?, addon.nil?]
|
55
|
+
when [false, true] then model
|
56
|
+
when [false, false] then Class.new(model).tap(&addon)
|
57
|
+
when [true, false] then Class.new(Evil::Client::Model).tap(&addon)
|
58
|
+
else nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Evil::Client::DSL::Responses
|
2
|
+
# Add settings shared by several responses
|
3
|
+
#
|
4
|
+
# @param [#to_sym] name
|
5
|
+
# @param [#to_i] status
|
6
|
+
# @param [Hash<Symbol, Object>] options
|
7
|
+
# @param [Proc] block
|
8
|
+
#
|
9
|
+
def response(name, status, **options, &block)
|
10
|
+
@client.send :response, name, status, @options.merge(options), &block
|
11
|
+
end
|
12
|
+
|
13
|
+
# Add settings shared by several responses
|
14
|
+
#
|
15
|
+
# @param [Hash<Symbol, Object>] options
|
16
|
+
# @param [Proc] block
|
17
|
+
#
|
18
|
+
def responses(**options, &block)
|
19
|
+
self.class.new(@client, @options.merge(options), &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def initialize(client, **options, &block)
|
25
|
+
@client = client
|
26
|
+
@options = options
|
27
|
+
instance_eval(&block)
|
28
|
+
end
|
29
|
+
end
|
@@ -2,7 +2,7 @@ module Evil::Client::DSL
|
|
2
2
|
# Provides a namespace for client's top-level DSL
|
3
3
|
class Scope
|
4
4
|
extend Dry::Initializer::Mixin
|
5
|
-
option :__scope__, default: proc {}
|
5
|
+
option :__scope__, default: proc {}, reader: :private
|
6
6
|
|
7
7
|
# Declares a method that opens new scope inside the current one
|
8
8
|
# An instance of new scope has access to methods of its parent
|
@@ -20,14 +20,7 @@ module Evil::Client::DSL
|
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
-
private :__scope__
|
24
|
-
|
25
|
-
def respond_to_missing?(name, *)
|
26
|
-
__scope__.respond_to? name
|
27
|
-
end
|
28
|
-
|
29
23
|
def method_missing(name, *args)
|
30
|
-
super unless respond_to? name
|
31
24
|
__scope__.send(name, *args)
|
32
25
|
end
|
33
26
|
end
|
@@ -51,7 +51,7 @@ module Evil::Client::DSL
|
|
51
51
|
def __validate__(part)
|
52
52
|
parts = %i(body query headers)
|
53
53
|
return if parts.include? part
|
54
|
-
|
54
|
+
raise ArgumentError.new "Wrong part '#{part}'. Use one of parts: #{parts}"
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
data/lib/evil/client/model.rb
CHANGED
@@ -25,8 +25,8 @@ class Evil::Client
|
|
25
25
|
@attributes ||= []
|
26
26
|
end
|
27
27
|
|
28
|
-
def option(name, type = nil, **opts)
|
29
|
-
super.tap { attributes << name.to_sym }
|
28
|
+
def option(name, type = nil, as: nil, **opts)
|
29
|
+
super.tap { attributes << (as || name).to_sym }
|
30
30
|
end
|
31
31
|
alias_method :attribute, :option
|
32
32
|
alias_method :param, :option
|
@@ -13,8 +13,8 @@ class Evil::Client::Operation
|
|
13
13
|
def build(options)
|
14
14
|
{
|
15
15
|
format: schema[:format],
|
16
|
-
http_method:
|
17
|
-
path: path.call(options),
|
16
|
+
http_method: extract(:method),
|
17
|
+
path: extract(:path).call(options),
|
18
18
|
security: schema[:security]&.call(options),
|
19
19
|
files: schema[:files]&.call(options),
|
20
20
|
query: schema[:query]&.new(options).to_h,
|
@@ -29,14 +29,9 @@ class Evil::Client::Operation
|
|
29
29
|
@key ||= schema[:key]
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
return schema[
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
def path
|
38
|
-
return schema[:path] if schema[:path]
|
39
|
-
fail NotImplementedError.new "Path not defined for operation '#{key}'"
|
32
|
+
def extract(property)
|
33
|
+
return schema[property] if schema[property]
|
34
|
+
raise NotImplementedError, "No #{property} defined for operation '#{key}'"
|
40
35
|
end
|
41
36
|
end
|
42
37
|
end
|
@@ -11,30 +11,29 @@ class Evil::Client::Operation
|
|
11
11
|
#
|
12
12
|
# @param [Array] array Rack-compatible array of response data
|
13
13
|
# @return [Object]
|
14
|
-
# @raise [Evil::Client::ResponseError] if it is required by the schema
|
15
14
|
#
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
# @raise [Evil::Client::ResponseError] when needed by the schema
|
16
|
+
# @raise [Evil::Client::UnexpectedResponseError]
|
17
|
+
# when a response cannot be processed
|
18
|
+
#
|
19
|
+
def handle(response)
|
20
|
+
status, _, body = response
|
21
|
+
body = body.any? ? body.join("\n") : nil
|
19
22
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
handlers(status).each do |handler|
|
24
|
+
data = handler[:coercer][body] rescue next
|
25
|
+
raise ResponseError.new(schema, status, data) if handler[:raise]
|
26
|
+
return data
|
27
|
+
end
|
24
28
|
|
25
|
-
|
29
|
+
raise UnexpectedResponseError.new(schema, status, body)
|
26
30
|
end
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
def response_schema(response)
|
35
|
-
schema[:responses].fetch response.status do
|
36
|
-
fail UnexpectedResponseError.new(schema, response)
|
37
|
-
end
|
34
|
+
def handlers(status)
|
35
|
+
schema[:responses].values
|
36
|
+
.select { |handler| handler[:status] == status }
|
38
37
|
end
|
39
38
|
end
|
40
39
|
end
|
@@ -1,11 +1,12 @@
|
|
1
1
|
class Evil::Client::Operation
|
2
2
|
class ResponseError < RuntimeError
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :status, :data
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def initialize(schema, status,
|
8
|
-
@
|
7
|
+
def initialize(schema, status, data)
|
8
|
+
@status = status
|
9
|
+
@data = data
|
9
10
|
super "Response to operation '#{schema[:key]}' has http status #{status}"
|
10
11
|
end
|
11
12
|
end
|
@@ -1,15 +1,18 @@
|
|
1
1
|
class Evil::Client::Operation
|
2
2
|
class UnexpectedResponseError < RuntimeError
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :status, :data
|
4
4
|
|
5
5
|
private
|
6
6
|
|
7
|
-
def initialize(schema,
|
8
|
-
@
|
7
|
+
def initialize(schema, status, data)
|
8
|
+
@status = status
|
9
|
+
@data = data
|
9
10
|
|
10
11
|
message = "Response to operation '#{schema[:key]}'" \
|
11
|
-
"
|
12
|
+
" with http status #{status} and body #{data}" \
|
13
|
+
" cannot be processed."
|
12
14
|
message << " See #{schema[:doc]} for details." if schema[:doc]
|
15
|
+
|
13
16
|
super message
|
14
17
|
end
|
15
18
|
end
|
@@ -25,7 +25,7 @@ RSpec.describe "operation with documentation" do
|
|
25
25
|
rescue => error
|
26
26
|
expect(error.message).to include "https://docs.example.com/v3/index.html"
|
27
27
|
else
|
28
|
-
|
28
|
+
raise
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
@@ -35,7 +35,7 @@ RSpec.describe "operation with documentation" do
|
|
35
35
|
rescue => error
|
36
36
|
expect(error.message).to include "https://docs.example.com/v3/findData"
|
37
37
|
else
|
38
|
-
|
38
|
+
raise
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -9,7 +9,7 @@ RSpec.describe "operation with form body" do
|
|
9
9
|
operation do
|
10
10
|
http_method :get
|
11
11
|
path { "users" }
|
12
|
-
response 200
|
12
|
+
response :success, 200
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -26,7 +26,7 @@ RSpec.describe "operation with form body" do
|
|
26
26
|
operation do
|
27
27
|
http_method :get
|
28
28
|
path { "users" }
|
29
|
-
response 200
|
29
|
+
response :success, 200
|
30
30
|
|
31
31
|
body format: "form" do
|
32
32
|
attribute :foo
|
@@ -57,7 +57,7 @@ RSpec.describe "operation with form body" do
|
|
57
57
|
operation do
|
58
58
|
http_method :get
|
59
59
|
path { "users" }
|
60
|
-
response 200
|
60
|
+
response :success, 200
|
61
61
|
|
62
62
|
body format: "form" do
|
63
63
|
attribute :foo
|
@@ -9,7 +9,7 @@ RSpec.describe "operation with json body" do
|
|
9
9
|
operation do
|
10
10
|
http_method :get
|
11
11
|
path { "users" }
|
12
|
-
response 200
|
12
|
+
response :success, 200
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -26,7 +26,7 @@ RSpec.describe "operation with json body" do
|
|
26
26
|
operation do
|
27
27
|
http_method :get
|
28
28
|
path { "users" }
|
29
|
-
response 200
|
29
|
+
response :success, 200
|
30
30
|
|
31
31
|
body do
|
32
32
|
attribute :foo
|
@@ -55,7 +55,7 @@ RSpec.describe "operation with json body" do
|
|
55
55
|
operation do
|
56
56
|
http_method :get
|
57
57
|
path { "users" }
|
58
|
-
response 200
|
58
|
+
response :success, 200
|
59
59
|
|
60
60
|
body do
|
61
61
|
attribute :foo
|
data/spec/features/{operation_with_response_spec.rb → operation_with_nested_responses_spec.rb}
RENAMED
@@ -1,4 +1,4 @@
|
|
1
|
-
RSpec.describe "operation with
|
1
|
+
RSpec.describe "operation with nested responses" do
|
2
2
|
# see Test::Client definition in `/spec/support/test_client.rb`
|
3
3
|
before do
|
4
4
|
class Test::User < Evil::Client::Model
|
@@ -10,18 +10,22 @@ RSpec.describe "operation with query" do
|
|
10
10
|
http_method :get
|
11
11
|
path { "users" }
|
12
12
|
|
13
|
-
response 200
|
13
|
+
response :success, 200, format: :plain
|
14
14
|
end
|
15
15
|
|
16
16
|
operation :example do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
responses format: :plain do
|
18
|
+
response :created, 201 do |body|
|
19
|
+
body.to_sym
|
20
|
+
end
|
21
|
+
|
22
|
+
responses raise: true do
|
23
|
+
response :not_found, 404
|
24
|
+
|
25
|
+
response :error, 422 do |body|
|
26
|
+
body.to_sym
|
27
|
+
end
|
28
|
+
end
|
25
29
|
end
|
26
30
|
end
|
27
31
|
end
|
@@ -36,9 +40,7 @@ RSpec.describe "operation with query" do
|
|
36
40
|
headers: { "Foo" => "BAR" },
|
37
41
|
body: "Hi!"
|
38
42
|
|
39
|
-
expect(subject).to
|
40
|
-
expect(subject.headers).to include "foo" => ["BAR"]
|
41
|
-
expect(subject.body).to eq ["Hi!"]
|
43
|
+
expect(subject).to eq "Hi!"
|
42
44
|
end
|
43
45
|
|
44
46
|
it "applies block to coerce data" do
|
@@ -46,13 +48,7 @@ RSpec.describe "operation with query" do
|
|
46
48
|
headers: { "Foo" => "BAR" },
|
47
49
|
body: "Hi!"
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
-
expect(response.headers).to include "foo" => ["BAR"]
|
52
|
-
expect(response.body).to eq ["Hi!"]
|
53
|
-
|
54
|
-
expect(body).to eq response.body
|
55
|
-
expect(headers).to eq response.headers
|
51
|
+
expect(subject).to eq :Hi!
|
56
52
|
end
|
57
53
|
|
58
54
|
it "raises ResponseError when necessary" do
|
@@ -63,11 +59,9 @@ RSpec.describe "operation with query" do
|
|
63
59
|
begin
|
64
60
|
subject
|
65
61
|
rescue Evil::Client::Operation::ResponseError => error
|
66
|
-
expect(error.
|
67
|
-
expect(error.response.headers).to include "foo" => ["BAR"]
|
68
|
-
expect(error.response.body).to eq ["Hi!"]
|
62
|
+
expect(error.data).to eq "Hi!"
|
69
63
|
else
|
70
|
-
|
64
|
+
raise
|
71
65
|
end
|
72
66
|
end
|
73
67
|
|
@@ -79,15 +73,9 @@ RSpec.describe "operation with query" do
|
|
79
73
|
begin
|
80
74
|
subject
|
81
75
|
rescue Evil::Client::Operation::ResponseError => error
|
82
|
-
|
83
|
-
|
84
|
-
expect(response.headers).to include "foo" => ["BAR"]
|
85
|
-
expect(response.body).to eq ["Hi!"]
|
86
|
-
|
87
|
-
expect(body).to eq response.body
|
88
|
-
expect(headers).to eq response.headers
|
76
|
+
expect(error.data).to eq :Hi!
|
89
77
|
else
|
90
|
-
|
78
|
+
raise
|
91
79
|
end
|
92
80
|
end
|
93
81
|
|
@@ -99,11 +87,9 @@ RSpec.describe "operation with query" do
|
|
99
87
|
begin
|
100
88
|
subject
|
101
89
|
rescue Evil::Client::Operation::UnexpectedResponseError => error
|
102
|
-
expect(error.
|
103
|
-
expect(error.response.headers).to include "foo" => ["BAR"]
|
104
|
-
expect(error.response.body).to eq ["Hi!"]
|
90
|
+
expect(error.data).to eq "Hi!"
|
105
91
|
else
|
106
|
-
|
92
|
+
raise
|
107
93
|
end
|
108
94
|
end
|
109
95
|
end
|
@@ -5,7 +5,7 @@ RSpec.describe "operation with security" do
|
|
5
5
|
operation do
|
6
6
|
http_method :get
|
7
7
|
path { "data" }
|
8
|
-
response 200
|
8
|
+
response :success, 200
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -29,7 +29,7 @@ RSpec.describe "operation with security" do
|
|
29
29
|
operation do |settings|
|
30
30
|
http_method :get
|
31
31
|
path { "data" }
|
32
|
-
response 200
|
32
|
+
response :success, 200
|
33
33
|
|
34
34
|
security do
|
35
35
|
basic_auth settings.user, settings.password
|
@@ -204,30 +204,171 @@ RSpec.describe Evil::Client::DSL::Operation do
|
|
204
204
|
end
|
205
205
|
|
206
206
|
context "with #response" do
|
207
|
-
let(:
|
208
|
-
|
209
|
-
|
210
|
-
|
207
|
+
let(:response_schema) { subject[:responses][:success] }
|
208
|
+
let(:response_raise) { response_schema[:raise] }
|
209
|
+
let(:response_coercer) { response_schema[:coercer] }
|
210
|
+
|
211
|
+
context "with plain format" do
|
212
|
+
let(:body) { "foo" }
|
213
|
+
let(:block) { proc { |_| response :success, 200, format: :plain } }
|
214
|
+
|
215
|
+
it "works" do
|
216
|
+
expect(response_coercer.call(body)).to eq "foo"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context "with plain format and handler" do
|
221
|
+
let(:body) { "foo" }
|
222
|
+
let(:block) do
|
223
|
+
proc do |_|
|
224
|
+
response :success, 200, format: :plain do |body|
|
225
|
+
body.to_sym
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it "applies coercer" do
|
231
|
+
expect(response_coercer.call(body)).to eq :foo
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context "with plain format and type" do
|
236
|
+
let(:body) { "0" }
|
237
|
+
let(:block) do
|
238
|
+
proc do |_|
|
239
|
+
response :success,
|
240
|
+
200,
|
241
|
+
format: :plain,
|
242
|
+
model: Dry::Types["coercible.int"]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
it "uses type" do
|
247
|
+
expect(response_coercer.call(body)).to eq 0
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context "with plain format, coercer and type" do
|
252
|
+
let(:body) { "0" }
|
253
|
+
let(:block) do
|
254
|
+
proc do |_|
|
255
|
+
response :success,
|
256
|
+
200,
|
257
|
+
format: :plain,
|
258
|
+
model: Dry::Types["coercible.string"] { |val| val.to_i + 1 }
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
it "applies coercer and then type" do
|
263
|
+
expect(response_coercer.call(body)).to eq "1"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context "with json format" do
|
268
|
+
let(:body) { '{"foo":1,"baz":"qux"}' }
|
269
|
+
let(:block) { proc { |_| response :success, 200, format: :json } }
|
270
|
+
|
271
|
+
it "returns parsed body" do
|
272
|
+
expect(response_coercer.call(body)).to eq "foo" => 1, "baz" => "qux"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context "with json format and handler" do
|
277
|
+
let(:body) { '{"foo":1,"baz":"qux"}' }
|
278
|
+
let(:block) do
|
279
|
+
proc do |_|
|
280
|
+
response :success, 200, format: :json do |body|
|
281
|
+
body.keys
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
it "returns parsed and handled body" do
|
287
|
+
expect(response_coercer.call(body)).to eq data: %w(foo baz)
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "with json format and type" do
|
292
|
+
before do
|
293
|
+
class Test::Foo < Evil::Client::Model
|
294
|
+
attribute :foo
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
let(:body) { '{"foo":1,"baz":"qux"}' }
|
299
|
+
let(:block) do
|
300
|
+
proc do |_|
|
301
|
+
response :success, 200, format: :json, model: Test::Foo
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
it "returns parsed and filtered body" do
|
306
|
+
expect(response_coercer.call(body)).to eq foo: 1
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
context "with json format, type and handler" do
|
311
|
+
before do
|
312
|
+
class Test::Foo < Evil::Client::Model
|
313
|
+
attribute :foo
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
let(:body) { '{"foo":1,"baz":"qux"}' }
|
318
|
+
let(:block) do
|
319
|
+
proc do |_|
|
320
|
+
response :success, 200, format: :json, model: Test::Foo do |body|
|
321
|
+
body["foo"] = body["foo"].to_s
|
322
|
+
body
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
it "returns parsed, handled and filtered body" do
|
328
|
+
expect(response_coercer.call(body)).to eq Test::Foo.new(foo: "1")
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
context "with json format and filter" do
|
333
|
+
before do
|
334
|
+
class Test::Foo < Evil::Client::Model
|
335
|
+
attribute :foo
|
211
336
|
end
|
337
|
+
end
|
212
338
|
|
213
|
-
|
214
|
-
|
339
|
+
let(:body) { '{"foo":1,"baz":"qux"}' }
|
340
|
+
let(:block) do
|
341
|
+
proc do |_|
|
342
|
+
response :success, 200, format: :json do
|
343
|
+
attribute :baz
|
344
|
+
end
|
215
345
|
end
|
346
|
+
end
|
216
347
|
|
217
|
-
|
348
|
+
it "returns parsed and filtered body" do
|
349
|
+
expect(response_coercer.call(body)).to eq baz: "qux"
|
218
350
|
end
|
219
351
|
end
|
220
352
|
|
221
|
-
|
222
|
-
|
353
|
+
context "with json format, type and filter" do
|
354
|
+
before do
|
355
|
+
class Test::Foo < Evil::Client::Model
|
356
|
+
attribute :foo
|
357
|
+
end
|
358
|
+
end
|
223
359
|
|
224
|
-
|
225
|
-
|
226
|
-
|
360
|
+
let(:body) { '{"foo":1,"baz":2}' }
|
361
|
+
let(:block) do
|
362
|
+
proc do |_|
|
363
|
+
response :success, 200, format: :json, model: Test::Foo do
|
364
|
+
attribute :baz, Dry::Types["coercible.string"]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
227
368
|
|
228
|
-
|
229
|
-
|
230
|
-
|
369
|
+
it "returns parsed and filtered body" do
|
370
|
+
expect(response_coercer.call(body)).to eq foo: 1, baz: "2"
|
371
|
+
end
|
231
372
|
end
|
232
373
|
end
|
233
374
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
describe Evil::Client::DSL::Operations do
|
2
2
|
let(:operations) { described_class.new }
|
3
|
-
let(:settings)
|
3
|
+
let(:settings) do
|
4
|
+
double(:settings, version: 1, user: "foo", password: "bar")
|
5
|
+
end
|
4
6
|
|
5
7
|
before do
|
6
8
|
operations.register(nil) do |settings|
|
@@ -5,12 +5,14 @@ RSpec.describe Evil::Client::Operation::Response do
|
|
5
5
|
key: :find_user,
|
6
6
|
doc: "http://example.com/users",
|
7
7
|
responses: {
|
8
|
-
|
9
|
-
|
8
|
+
success: {
|
9
|
+
status: 200,
|
10
|
+
coercer: proc { |body| body.upcase.to_sym },
|
10
11
|
raise: false
|
11
12
|
},
|
12
|
-
|
13
|
-
|
13
|
+
error: {
|
14
|
+
status: 400,
|
15
|
+
coercer: proc { |body| body.upcase.to_sym },
|
14
16
|
raise: true
|
15
17
|
}
|
16
18
|
}
|
@@ -35,9 +37,9 @@ RSpec.describe Evil::Client::Operation::Response do
|
|
35
37
|
subject
|
36
38
|
rescue Evil::Client::Operation::ResponseError => error
|
37
39
|
expect(error.message).to include "find_user"
|
38
|
-
expect(error.
|
40
|
+
expect(error.data).to eq :FOO
|
39
41
|
else
|
40
|
-
|
42
|
+
raise
|
41
43
|
end
|
42
44
|
end
|
43
45
|
end
|
@@ -51,10 +53,10 @@ RSpec.describe Evil::Client::Operation::Response do
|
|
51
53
|
rescue Evil::Client::Operation::UnexpectedResponseError => error
|
52
54
|
expect(error.message).to include "find_user"
|
53
55
|
expect(error.message).to include "http://example.com/users"
|
54
|
-
expect(error.
|
55
|
-
expect(error.
|
56
|
+
expect(error.data).to eq "foo"
|
57
|
+
expect(error.status).to eq 404
|
56
58
|
else
|
57
|
-
|
59
|
+
raise
|
58
60
|
end
|
59
61
|
end
|
60
62
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evil-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kozin (nepalez)
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-11-
|
11
|
+
date: 2016-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-initializer
|
@@ -128,12 +128,14 @@ executables: []
|
|
128
128
|
extensions: []
|
129
129
|
extra_rdoc_files:
|
130
130
|
- README.md
|
131
|
+
- CHANGELOG.md
|
131
132
|
files:
|
132
133
|
- ".codeclimate.yml"
|
133
134
|
- ".gitignore"
|
134
135
|
- ".rspec"
|
135
136
|
- ".rubocop.yml"
|
136
137
|
- ".travis.yml"
|
138
|
+
- CHANGELOG.md
|
137
139
|
- Gemfile
|
138
140
|
- LICENSE.txt
|
139
141
|
- README.md
|
@@ -160,6 +162,8 @@ files:
|
|
160
162
|
- lib/evil/client/dsl/files.rb
|
161
163
|
- lib/evil/client/dsl/operation.rb
|
162
164
|
- lib/evil/client/dsl/operations.rb
|
165
|
+
- lib/evil/client/dsl/response.rb
|
166
|
+
- lib/evil/client/dsl/responses.rb
|
163
167
|
- lib/evil/client/dsl/scope.rb
|
164
168
|
- lib/evil/client/dsl/security.rb
|
165
169
|
- lib/evil/client/middleware.rb
|
@@ -186,9 +190,9 @@ files:
|
|
186
190
|
- spec/features/operation_with_headers_spec.rb
|
187
191
|
- spec/features/operation_with_http_method_spec.rb
|
188
192
|
- spec/features/operation_with_json_body_spec.rb
|
193
|
+
- spec/features/operation_with_nested_responses_spec.rb
|
189
194
|
- spec/features/operation_with_path_spec.rb
|
190
195
|
- spec/features/operation_with_query_spec.rb
|
191
|
-
- spec/features/operation_with_response_spec.rb
|
192
196
|
- spec/features/operation_with_security_spec.rb
|
193
197
|
- spec/features/scoping_spec.rb
|
194
198
|
- spec/spec_helper.rb
|
@@ -243,9 +247,9 @@ test_files:
|
|
243
247
|
- spec/features/operation_with_headers_spec.rb
|
244
248
|
- spec/features/operation_with_http_method_spec.rb
|
245
249
|
- spec/features/operation_with_json_body_spec.rb
|
250
|
+
- spec/features/operation_with_nested_responses_spec.rb
|
246
251
|
- spec/features/operation_with_path_spec.rb
|
247
252
|
- spec/features/operation_with_query_spec.rb
|
248
|
-
- spec/features/operation_with_response_spec.rb
|
249
253
|
- spec/features/operation_with_security_spec.rb
|
250
254
|
- spec/features/scoping_spec.rb
|
251
255
|
- spec/spec_helper.rb
|