evil-client 0.3.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +0 -11
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +22 -19
- data/.travis.yml +1 -0
- data/CHANGELOG.md +251 -6
- data/LICENSE.txt +3 -1
- data/README.md +47 -81
- data/docs/helpers/body.md +93 -0
- data/docs/helpers/connection.md +19 -0
- data/docs/helpers/headers.md +72 -0
- data/docs/helpers/http_method.md +39 -0
- data/docs/helpers/let.md +14 -0
- data/docs/helpers/logger.md +24 -0
- data/docs/helpers/middleware.md +56 -0
- data/docs/helpers/operation.md +103 -0
- data/docs/helpers/option.md +50 -0
- data/docs/helpers/path.md +37 -0
- data/docs/helpers/query.md +59 -0
- data/docs/helpers/response.md +40 -0
- data/docs/helpers/scope.md +121 -0
- data/docs/helpers/security.md +102 -0
- data/docs/helpers/validate.md +68 -0
- data/docs/index.md +70 -78
- data/docs/license.md +5 -1
- data/docs/rspec.md +96 -0
- data/evil-client.gemspec +10 -8
- data/lib/evil/client.rb +126 -72
- data/lib/evil/client/builder.rb +47 -0
- data/lib/evil/client/builder/operation.rb +40 -0
- data/lib/evil/client/builder/scope.rb +31 -0
- data/lib/evil/client/chaining.rb +17 -0
- data/lib/evil/client/connection.rb +60 -20
- data/lib/evil/client/container.rb +66 -0
- data/lib/evil/client/container/operation.rb +23 -0
- data/lib/evil/client/container/scope.rb +28 -0
- data/lib/evil/client/exceptions/definition_error.rb +15 -0
- data/lib/evil/client/exceptions/name_error.rb +32 -0
- data/lib/evil/client/exceptions/response_error.rb +42 -0
- data/lib/evil/client/exceptions/type_error.rb +29 -0
- data/lib/evil/client/exceptions/validation_error.rb +27 -0
- data/lib/evil/client/formatter.rb +49 -0
- data/lib/evil/client/formatter/form.rb +45 -0
- data/lib/evil/client/formatter/multipart.rb +33 -0
- data/lib/evil/client/formatter/part.rb +66 -0
- data/lib/evil/client/formatter/text.rb +21 -0
- data/lib/evil/client/resolver.rb +84 -0
- data/lib/evil/client/resolver/body.rb +22 -0
- data/lib/evil/client/resolver/format.rb +30 -0
- data/lib/evil/client/resolver/headers.rb +46 -0
- data/lib/evil/client/resolver/http_method.rb +34 -0
- data/lib/evil/client/resolver/middleware.rb +36 -0
- data/lib/evil/client/resolver/query.rb +39 -0
- data/lib/evil/client/resolver/request.rb +96 -0
- data/lib/evil/client/resolver/response.rb +26 -0
- data/lib/evil/client/resolver/security.rb +113 -0
- data/lib/evil/client/resolver/uri.rb +35 -0
- data/lib/evil/client/rspec.rb +127 -0
- data/lib/evil/client/schema.rb +105 -0
- data/lib/evil/client/schema/operation.rb +177 -0
- data/lib/evil/client/schema/scope.rb +73 -0
- data/lib/evil/client/settings.rb +172 -0
- data/lib/evil/client/settings/validator.rb +64 -0
- data/mkdocs.yml +21 -15
- data/spec/features/custom_connection_spec.rb +17 -0
- data/spec/features/operation/middleware_spec.rb +50 -0
- data/spec/features/operation/options_spec.rb +71 -0
- data/spec/features/operation/request_spec.rb +94 -0
- data/spec/features/operation/response_spec.rb +48 -0
- data/spec/features/scope/options_spec.rb +52 -0
- data/spec/fixtures/locales/en.yml +16 -0
- data/spec/fixtures/test_client.rb +76 -0
- data/spec/spec_helper.rb +18 -6
- data/spec/support/fixtures_helper.rb +7 -0
- data/spec/unit/builder/operation_spec.rb +90 -0
- data/spec/unit/builder/scope_spec.rb +84 -0
- data/spec/unit/client_spec.rb +137 -0
- data/spec/unit/connection_spec.rb +78 -0
- data/spec/unit/container/operation_spec.rb +81 -0
- data/spec/unit/container/scope_spec.rb +61 -0
- data/spec/unit/container_spec.rb +107 -0
- data/spec/unit/exceptions/definition_error_spec.rb +15 -0
- data/spec/unit/exceptions/name_error_spec.rb +77 -0
- data/spec/unit/exceptions/response_error_spec.rb +22 -0
- data/spec/unit/exceptions/type_error_spec.rb +71 -0
- data/spec/unit/exceptions/validation_error_spec.rb +13 -0
- data/spec/unit/formatter/form_spec.rb +27 -0
- data/spec/unit/formatter/multipart_spec.rb +23 -0
- data/spec/unit/formatter/part_spec.rb +49 -0
- data/spec/unit/formatter/text_spec.rb +37 -0
- data/spec/unit/formatter_spec.rb +46 -0
- data/spec/unit/resolver/body_spec.rb +65 -0
- data/spec/unit/resolver/format_spec.rb +66 -0
- data/spec/unit/resolver/headers_spec.rb +93 -0
- data/spec/unit/resolver/http_method_spec.rb +67 -0
- data/spec/unit/resolver/middleware_spec.rb +83 -0
- data/spec/unit/resolver/query_spec.rb +85 -0
- data/spec/unit/resolver/request_spec.rb +121 -0
- data/spec/unit/resolver/response_spec.rb +64 -0
- data/spec/unit/resolver/security_spec.rb +156 -0
- data/spec/unit/resolver/uri_spec.rb +117 -0
- data/spec/unit/rspec_spec.rb +342 -0
- data/spec/unit/schema/operation_spec.rb +309 -0
- data/spec/unit/schema/scope_spec.rb +110 -0
- data/spec/unit/schema_spec.rb +157 -0
- data/spec/unit/settings/validator_spec.rb +128 -0
- data/spec/unit/settings_spec.rb +248 -0
- metadata +192 -135
- data/docs/base_url.md +0 -38
- data/docs/documentation.md +0 -9
- data/docs/headers.md +0 -59
- data/docs/http_method.md +0 -31
- data/docs/model.md +0 -173
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +0 -48
- data/docs/query.md +0 -99
- data/docs/responses.md +0 -66
- data/docs/security.md +0 -102
- data/docs/settings.md +0 -32
- data/lib/evil/client/connection/net_http.rb +0 -57
- data/lib/evil/client/dsl.rb +0 -127
- data/lib/evil/client/dsl/base.rb +0 -26
- data/lib/evil/client/dsl/files.rb +0 -37
- data/lib/evil/client/dsl/headers.rb +0 -16
- data/lib/evil/client/dsl/http_method.rb +0 -24
- data/lib/evil/client/dsl/operation.rb +0 -91
- data/lib/evil/client/dsl/operations.rb +0 -41
- data/lib/evil/client/dsl/path.rb +0 -25
- data/lib/evil/client/dsl/query.rb +0 -16
- data/lib/evil/client/dsl/response.rb +0 -61
- data/lib/evil/client/dsl/responses.rb +0 -29
- data/lib/evil/client/dsl/scope.rb +0 -27
- data/lib/evil/client/dsl/security.rb +0 -57
- data/lib/evil/client/dsl/verifier.rb +0 -35
- data/lib/evil/client/middleware.rb +0 -81
- data/lib/evil/client/middleware/base.rb +0 -11
- data/lib/evil/client/middleware/merge_security.rb +0 -20
- data/lib/evil/client/middleware/normalize_headers.rb +0 -17
- data/lib/evil/client/middleware/stringify_form.rb +0 -40
- data/lib/evil/client/middleware/stringify_json.rb +0 -19
- data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
- data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
- data/lib/evil/client/middleware/stringify_query.rb +0 -35
- data/lib/evil/client/operation.rb +0 -34
- data/lib/evil/client/operation/request.rb +0 -26
- data/lib/evil/client/operation/response.rb +0 -39
- data/lib/evil/client/operation/response_error.rb +0 -13
- data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
- data/spec/features/instantiation_spec.rb +0 -68
- data/spec/features/middleware_spec.rb +0 -79
- data/spec/features/operation_with_documentation_spec.rb +0 -41
- data/spec/features/operation_with_files_spec.rb +0 -40
- data/spec/features/operation_with_form_body_spec.rb +0 -158
- data/spec/features/operation_with_headers_spec.rb +0 -99
- data/spec/features/operation_with_http_method_spec.rb +0 -45
- data/spec/features/operation_with_json_body_spec.rb +0 -156
- data/spec/features/operation_with_nested_responses_spec.rb +0 -95
- data/spec/features/operation_with_path_spec.rb +0 -47
- data/spec/features/operation_with_query_spec.rb +0 -84
- data/spec/features/operation_with_security_spec.rb +0 -228
- data/spec/features/scoping_spec.rb +0 -48
- data/spec/support/test_client.rb +0 -15
- data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
- data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
- data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
- data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
- data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
- data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
- data/spec/unit/evil/client/middleware_spec.rb +0 -46
- data/spec/unit/evil/client/operation/request_spec.rb +0 -49
- data/spec/unit/evil/client/operation/response_spec.rb +0 -63
@@ -0,0 +1,17 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Support chaining of calls for nested scopes/operations
|
4
|
+
#
|
5
|
+
module Chaining
|
6
|
+
private
|
7
|
+
|
8
|
+
def respond_to_missing?(name, *)
|
9
|
+
operations[name] || scopes[name]
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args, &block)
|
13
|
+
return super unless respond_to_missing? name
|
14
|
+
(operations[name] || scopes[name]).call(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,31 +1,71 @@
|
|
1
1
|
class Evil::Client
|
2
|
-
#
|
3
|
-
|
4
|
-
|
2
|
+
#
|
3
|
+
# Object that sends rack-compatible request environment to remote API,
|
4
|
+
# and wraps a response into rack-compatible array of [status, headers, body].
|
5
|
+
#
|
6
|
+
# @see http://www.rubydoc.info/github/rack/rack/master/file/SPEC
|
7
|
+
#
|
8
|
+
module Connection
|
9
|
+
extend self
|
5
10
|
|
6
|
-
|
7
|
-
param :base_uri
|
8
|
-
|
9
|
-
# Envokes a specific connection class
|
11
|
+
# Makes the request by taking rack env and returning rack response
|
10
12
|
#
|
11
|
-
# @param [
|
12
|
-
# @return [
|
13
|
+
# @param [Hash<String, Object>] env Rack environment
|
14
|
+
# @return [Array] Rack-compatible response
|
13
15
|
#
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def call(env)
|
17
|
+
request = Rack::Request.new(env)
|
18
|
+
with_logger_for request do
|
19
|
+
open_http_connection_for request do |http|
|
20
|
+
res = http.request build_from(request)
|
21
|
+
[res.code.to_i, Hash(res.header), Array(res.body)]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def open_http_connection_for(req)
|
29
|
+
Net::HTTP.start req.host, req.port, use_ssl: req.ssl? do |http|
|
30
|
+
yield(http)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_from(request)
|
35
|
+
uri = URI request.url
|
36
|
+
body = request.body
|
37
|
+
type = request.env["REQUEST_METHOD"].capitalize
|
38
|
+
headers = request.env["HTTP_Variables"]
|
39
|
+
|
40
|
+
Net::HTTP.const_get(type).new(uri).tap do |req|
|
41
|
+
req.body = body
|
42
|
+
headers.each { |key, val| req[key] = val }
|
20
43
|
end
|
44
|
+
end
|
21
45
|
|
22
|
-
|
23
|
-
|
46
|
+
def with_logger_for(request)
|
47
|
+
logger = request.logger
|
48
|
+
log_request(logger, request)
|
49
|
+
yield.tap { |response| log_response(logger, response) }
|
24
50
|
end
|
25
51
|
|
26
|
-
|
27
|
-
|
28
|
-
|
52
|
+
def log_request(logger, request)
|
53
|
+
return unless logger
|
54
|
+
|
55
|
+
logger.info(self) { "sending request:" }
|
56
|
+
logger.info(self) { " Url | #{request.url}" }
|
57
|
+
logger.info(self) { " Headers | #{request.env['HTTP_Variables']}" }
|
58
|
+
logger.info(self) { " Body | #{request.body}" }
|
59
|
+
end
|
60
|
+
|
61
|
+
def log_response(logger, response)
|
62
|
+
return unless logger
|
63
|
+
|
64
|
+
status, headers, body = Array response
|
65
|
+
logger.info(self) { "receiving response:" }
|
66
|
+
logger.info(self) { " Status | #{status}" }
|
67
|
+
logger.info(self) { " Headers | #{headers}" }
|
68
|
+
logger.info(self) { " Body | #{body}" }
|
29
69
|
end
|
30
70
|
end
|
31
71
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# @abstract
|
4
|
+
# Container that carries schema of operation/scope along with its settings
|
5
|
+
# and methods to build sub-scope/operation or perform the current operation.
|
6
|
+
#
|
7
|
+
class Container
|
8
|
+
# Loads concrete implementations of the abstract container
|
9
|
+
require_relative "container/scope"
|
10
|
+
require_relative "container/operation"
|
11
|
+
|
12
|
+
# The schema containing info about sub-scopes and operations of the scope
|
13
|
+
# @return [Evil::Client::Container::ScopeDefinition]
|
14
|
+
attr_reader :schema
|
15
|
+
|
16
|
+
# The settings current scope is initialized with
|
17
|
+
# @return [Evil::Client::Settings]
|
18
|
+
attr_reader :settings
|
19
|
+
|
20
|
+
# Options assigned to the [#settings]
|
21
|
+
#
|
22
|
+
# These are opts given to the [#initializer],
|
23
|
+
# processed (via defaults, coercion, renaming) by a constructor of settings.
|
24
|
+
#
|
25
|
+
# @return [Hash<Symbol, Object>]
|
26
|
+
def options
|
27
|
+
@options ||= settings.options
|
28
|
+
end
|
29
|
+
|
30
|
+
# The human-friendly representation of the scope instance
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# '#<MyClient.scopes[:users] @version=1>'
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
def to_s
|
37
|
+
"#<#{schema} #{options.map { |key, val| "@#{key}=#{val}" }.join(', ')}>"
|
38
|
+
end
|
39
|
+
alias_method :to_str, :to_s
|
40
|
+
alias_method :inspect, :to_s
|
41
|
+
|
42
|
+
# (Re)sets current logger
|
43
|
+
#
|
44
|
+
# @param [Logger, nil] logger
|
45
|
+
# @return [Logger, nil]
|
46
|
+
#
|
47
|
+
def logger=(logger)
|
48
|
+
settings.logger = logger
|
49
|
+
end
|
50
|
+
|
51
|
+
# Current logger
|
52
|
+
#
|
53
|
+
# @return [Logger, nil]
|
54
|
+
#
|
55
|
+
def logger
|
56
|
+
settings.logger
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def initialize(schema, logger = nil, **opts)
|
62
|
+
@schema = schema
|
63
|
+
@settings = schema.settings.new(logger, opts)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Contains operation schema and settings along with DSL method [#call]
|
4
|
+
# to sends a request to API and handle the response.
|
5
|
+
#
|
6
|
+
class Container::Operation < Container
|
7
|
+
# Executes the operation and returns rack-compatible response
|
8
|
+
#
|
9
|
+
# @return [Array]
|
10
|
+
#
|
11
|
+
# rubocop: disable Metrics/AbcSize
|
12
|
+
def call
|
13
|
+
request = Resolver::Request.call(schema, settings)
|
14
|
+
middleware = Resolver::Middleware.call(schema, settings)
|
15
|
+
connection = schema.client.connection
|
16
|
+
stack = middleware.inject(connection) { |app, layer| layer.new app }
|
17
|
+
response = stack.call request
|
18
|
+
|
19
|
+
Resolver::Response.call schema, settings, response
|
20
|
+
end
|
21
|
+
# rubocop: enable Metrics/AbcSize
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Contains schema and settings of some scope along with methods
|
4
|
+
# to initialize its sub-[#scopes] and [#operations]
|
5
|
+
#
|
6
|
+
class Container::Scope < Container
|
7
|
+
include Chaining
|
8
|
+
|
9
|
+
# The collection of named sub-scope constructors
|
10
|
+
# @return [Hash<Symbol, Evil::Client::Container::Scope::Builder>]
|
11
|
+
def scopes
|
12
|
+
@scopes ||= \
|
13
|
+
schema.scopes.each_with_object({}) do |(key, sub_schema), obj|
|
14
|
+
obj[key] = Builder::Scope.new(sub_schema, settings)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# The collection of named operations constructors
|
19
|
+
# @return [Hash<Symbol, Evil::Client::Container::Operation::Builder>]
|
20
|
+
def operations
|
21
|
+
@operations ||= \
|
22
|
+
schema.operations.each_with_object({}) do |(key, sub_schema), obj|
|
23
|
+
next unless key
|
24
|
+
obj[key] = Builder::Operation.new(sub_schema, settings)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Exception to be risen when schema definitions cannot be resolved.
|
4
|
+
# This is possibly a bug in client definition.
|
5
|
+
#
|
6
|
+
class DefinitionError < StandardError
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize(schema, keys, settings, text)
|
10
|
+
super "failed to resolve #{keys.join(' ')} from #{schema} schema" \
|
11
|
+
" for #{settings}: #{text}. Possibly this means a lack of" \
|
12
|
+
" necessary validations in definition of the client."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Exception to be risen when selected name cannot be used in a custom client.
|
4
|
+
#
|
5
|
+
class NameError < ::NameError
|
6
|
+
# Checks whether a name is valid
|
7
|
+
#
|
8
|
+
# @param [#to_sym] name The name to check
|
9
|
+
# @param [Array<Symbol>] forbidden ([]) The list of forbidden names
|
10
|
+
# @return [Symbol] if name is valid
|
11
|
+
# @raise [self] if name isn't valid
|
12
|
+
#
|
13
|
+
def self.check!(name, forbidden = [])
|
14
|
+
name = name.to_sym
|
15
|
+
return name if name[FORMAT] && !forbidden.include?(name)
|
16
|
+
raise new(name, forbidden)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def initialize(name, forbidden)
|
22
|
+
super "Invalid name :#{name}." \
|
23
|
+
" It should contain latin letters in the lower case, digits," \
|
24
|
+
" and underscores only; have minimum 2 chars;" \
|
25
|
+
" start from a letter; end with either letter or digit." \
|
26
|
+
" The following names: '#{forbidden.join("', '")}'" \
|
27
|
+
" are already used by Evil::Client."
|
28
|
+
end
|
29
|
+
|
30
|
+
FORMAT = /^[a-z]([a-z\d_])*[a-z\d]$/
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Exception to be risen when remote API responded with undefined status
|
4
|
+
#
|
5
|
+
class ResponseError < RuntimeError
|
6
|
+
# @!attribute [r] schema
|
7
|
+
# @return [Evil::Client::Container::Operation::Schema] The operation schema
|
8
|
+
attr_reader :schema
|
9
|
+
|
10
|
+
# @!attribute [r] settings
|
11
|
+
# @return [Evil::Client::Settings] The settings used by the request
|
12
|
+
attr_reader :settings
|
13
|
+
|
14
|
+
# @!attribute [r] response
|
15
|
+
# @return [Array] The rack response to the request
|
16
|
+
attr_reader :response
|
17
|
+
|
18
|
+
# @!attribute [r] settings
|
19
|
+
# @return [Integer] The status of the [#response]
|
20
|
+
attr_reader :status
|
21
|
+
|
22
|
+
# @!attribute [r] headers
|
23
|
+
# @return [Hash] The hash of the [#response] headers
|
24
|
+
attr_reader :headers
|
25
|
+
|
26
|
+
# @!attribute [r] settings
|
27
|
+
# @return [Enumerable] The enumerable object describing the [#response] body
|
28
|
+
attr_reader :body
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def initialize(schema, settings, response)
|
33
|
+
@schema = schema
|
34
|
+
@settings = settings
|
35
|
+
@response = response
|
36
|
+
@status, @headers, @body = Array(response)
|
37
|
+
|
38
|
+
super "remote API responded to #{@schema}" \
|
39
|
+
" with unexpected status #{@status}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Exception to be risen when user defines a scope/operation with a name,
|
4
|
+
# that has been used by existing operation/scope.
|
5
|
+
#
|
6
|
+
class TypeError < ::TypeError
|
7
|
+
# Checks whether a name can be used to define operation/scope of the schema
|
8
|
+
#
|
9
|
+
# @param [Evil::Client::Schema::Scope] scope
|
10
|
+
# @param [Symbol] name
|
11
|
+
# @param [Symbol] type
|
12
|
+
# @return [Symbol] nil
|
13
|
+
# @raise [self] if name cannot be used
|
14
|
+
#
|
15
|
+
def self.check!(schema, name, type)
|
16
|
+
return if type == :scope && schema.operations[name].nil?
|
17
|
+
return if type == :operation && schema.scopes[name].nil?
|
18
|
+
raise new(name, type)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def initialize(name, new_type)
|
24
|
+
old_type = new_type == :scope ? :operation : :scope
|
25
|
+
super "The #{old_type} :#{name} was already defined." \
|
26
|
+
" You cannot create #{new_type} with the same name."
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
#
|
3
|
+
# Exception to be risen when scope or operation cannot be initialized
|
4
|
+
# due to some options or their composition are invalid
|
5
|
+
#
|
6
|
+
class ValidationError < ArgumentError
|
7
|
+
private
|
8
|
+
|
9
|
+
def initialize(key, scope = nil, **options)
|
10
|
+
scope = "evil.client.errors.#{scope}"
|
11
|
+
.split(".")
|
12
|
+
.map { |part| __underscore__(part) }
|
13
|
+
|
14
|
+
super key.is_a?(Symbol) ? I18n.t(key, scope: scope, **options) : key
|
15
|
+
end
|
16
|
+
|
17
|
+
def __underscore__(name)
|
18
|
+
name.dup.tap do |n|
|
19
|
+
n.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
20
|
+
n.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
|
21
|
+
n.gsub!("::", "/")
|
22
|
+
n.tr!("-", "_")
|
23
|
+
n.downcase!
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Evil::Client
|
2
|
+
# Utility to format body/query into one of formats: :json, :form, :multipart
|
3
|
+
module Formatter
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Loads concrete formatters called by factory method [#call]
|
7
|
+
require_relative "formatter/text"
|
8
|
+
require_relative "formatter/form"
|
9
|
+
require_relative "formatter/multipart"
|
10
|
+
|
11
|
+
# Factory that knows how to format source depending on given format
|
12
|
+
#
|
13
|
+
# @param [Object] source
|
14
|
+
# @param [:json, :form, :multipart, :text] format
|
15
|
+
# @option opts [String] :boundary The boundary for a multipart body
|
16
|
+
# @return [String] formatted body
|
17
|
+
#
|
18
|
+
def call(source, format, **opts)
|
19
|
+
return unless source
|
20
|
+
return to_json(source) if format == :json
|
21
|
+
return to_yaml(source) if format == :yaml
|
22
|
+
return to_form(source) if format == :form
|
23
|
+
return to_text(source) if format == :text
|
24
|
+
to_multipart(source, opts)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def to_json(source)
|
30
|
+
JSON.dump source
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_yaml(source)
|
34
|
+
YAML.dump source
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_text(source)
|
38
|
+
Text.call source
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_form(source)
|
42
|
+
Form.call source
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_multipart(source, opts)
|
46
|
+
Multipart.call [source], opts
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Evil::Client::Formatter
|
2
|
+
#
|
3
|
+
# Utility module to format body/query as a form
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# Evil::Client::Formatter::Form.call foo: { bar: [:baz] }, qux: 1
|
7
|
+
# # => "foo[bar][]=baz&qux=1"
|
8
|
+
#
|
9
|
+
module Form
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Formats nested hash as a string
|
13
|
+
#
|
14
|
+
# @param [Hash] source
|
15
|
+
# @return [String]
|
16
|
+
#
|
17
|
+
def call(source)
|
18
|
+
case source
|
19
|
+
when nil then nil
|
20
|
+
when Hash then normalize(source)
|
21
|
+
else raise "#{source} is not a hash"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def normalize(value, *keys)
|
28
|
+
case value
|
29
|
+
when Hash then
|
30
|
+
value.flat_map { |key, val| normalize(val, *keys, key) }.join("&")
|
31
|
+
when Array then
|
32
|
+
value.flat_map { |val| normalize(val, *keys, nil) }.join("&")
|
33
|
+
else
|
34
|
+
finalize(value, *keys)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def finalize(value, key, *keys)
|
39
|
+
value = CGI.escape(value.to_s)
|
40
|
+
key = CGI.escape(key.to_s)
|
41
|
+
keys = keys.map { |k| "[#{CGI.escape(k.to_s)}]" }
|
42
|
+
"#{key}#{keys.join}=#{value}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|