evil-client 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|