evil-client 0.3.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +0 -11
- data/.gitignore +1 -0
- data/.rspec +0 -1
- data/.rubocop.yml +22 -19
- data/.travis.yml +1 -0
- data/CHANGELOG.md +251 -6
- data/LICENSE.txt +3 -1
- data/README.md +47 -81
- data/docs/helpers/body.md +93 -0
- data/docs/helpers/connection.md +19 -0
- data/docs/helpers/headers.md +72 -0
- data/docs/helpers/http_method.md +39 -0
- data/docs/helpers/let.md +14 -0
- data/docs/helpers/logger.md +24 -0
- data/docs/helpers/middleware.md +56 -0
- data/docs/helpers/operation.md +103 -0
- data/docs/helpers/option.md +50 -0
- data/docs/helpers/path.md +37 -0
- data/docs/helpers/query.md +59 -0
- data/docs/helpers/response.md +40 -0
- data/docs/helpers/scope.md +121 -0
- data/docs/helpers/security.md +102 -0
- data/docs/helpers/validate.md +68 -0
- data/docs/index.md +70 -78
- data/docs/license.md +5 -1
- data/docs/rspec.md +96 -0
- data/evil-client.gemspec +10 -8
- data/lib/evil/client.rb +126 -72
- data/lib/evil/client/builder.rb +47 -0
- data/lib/evil/client/builder/operation.rb +40 -0
- data/lib/evil/client/builder/scope.rb +31 -0
- data/lib/evil/client/chaining.rb +17 -0
- data/lib/evil/client/connection.rb +60 -20
- data/lib/evil/client/container.rb +66 -0
- data/lib/evil/client/container/operation.rb +23 -0
- data/lib/evil/client/container/scope.rb +28 -0
- data/lib/evil/client/exceptions/definition_error.rb +15 -0
- data/lib/evil/client/exceptions/name_error.rb +32 -0
- data/lib/evil/client/exceptions/response_error.rb +42 -0
- data/lib/evil/client/exceptions/type_error.rb +29 -0
- data/lib/evil/client/exceptions/validation_error.rb +27 -0
- data/lib/evil/client/formatter.rb +49 -0
- data/lib/evil/client/formatter/form.rb +45 -0
- data/lib/evil/client/formatter/multipart.rb +33 -0
- data/lib/evil/client/formatter/part.rb +66 -0
- data/lib/evil/client/formatter/text.rb +21 -0
- data/lib/evil/client/resolver.rb +84 -0
- data/lib/evil/client/resolver/body.rb +22 -0
- data/lib/evil/client/resolver/format.rb +30 -0
- data/lib/evil/client/resolver/headers.rb +46 -0
- data/lib/evil/client/resolver/http_method.rb +34 -0
- data/lib/evil/client/resolver/middleware.rb +36 -0
- data/lib/evil/client/resolver/query.rb +39 -0
- data/lib/evil/client/resolver/request.rb +96 -0
- data/lib/evil/client/resolver/response.rb +26 -0
- data/lib/evil/client/resolver/security.rb +113 -0
- data/lib/evil/client/resolver/uri.rb +35 -0
- data/lib/evil/client/rspec.rb +127 -0
- data/lib/evil/client/schema.rb +105 -0
- data/lib/evil/client/schema/operation.rb +177 -0
- data/lib/evil/client/schema/scope.rb +73 -0
- data/lib/evil/client/settings.rb +172 -0
- data/lib/evil/client/settings/validator.rb +64 -0
- data/mkdocs.yml +21 -15
- data/spec/features/custom_connection_spec.rb +17 -0
- data/spec/features/operation/middleware_spec.rb +50 -0
- data/spec/features/operation/options_spec.rb +71 -0
- data/spec/features/operation/request_spec.rb +94 -0
- data/spec/features/operation/response_spec.rb +48 -0
- data/spec/features/scope/options_spec.rb +52 -0
- data/spec/fixtures/locales/en.yml +16 -0
- data/spec/fixtures/test_client.rb +76 -0
- data/spec/spec_helper.rb +18 -6
- data/spec/support/fixtures_helper.rb +7 -0
- data/spec/unit/builder/operation_spec.rb +90 -0
- data/spec/unit/builder/scope_spec.rb +84 -0
- data/spec/unit/client_spec.rb +137 -0
- data/spec/unit/connection_spec.rb +78 -0
- data/spec/unit/container/operation_spec.rb +81 -0
- data/spec/unit/container/scope_spec.rb +61 -0
- data/spec/unit/container_spec.rb +107 -0
- data/spec/unit/exceptions/definition_error_spec.rb +15 -0
- data/spec/unit/exceptions/name_error_spec.rb +77 -0
- data/spec/unit/exceptions/response_error_spec.rb +22 -0
- data/spec/unit/exceptions/type_error_spec.rb +71 -0
- data/spec/unit/exceptions/validation_error_spec.rb +13 -0
- data/spec/unit/formatter/form_spec.rb +27 -0
- data/spec/unit/formatter/multipart_spec.rb +23 -0
- data/spec/unit/formatter/part_spec.rb +49 -0
- data/spec/unit/formatter/text_spec.rb +37 -0
- data/spec/unit/formatter_spec.rb +46 -0
- data/spec/unit/resolver/body_spec.rb +65 -0
- data/spec/unit/resolver/format_spec.rb +66 -0
- data/spec/unit/resolver/headers_spec.rb +93 -0
- data/spec/unit/resolver/http_method_spec.rb +67 -0
- data/spec/unit/resolver/middleware_spec.rb +83 -0
- data/spec/unit/resolver/query_spec.rb +85 -0
- data/spec/unit/resolver/request_spec.rb +121 -0
- data/spec/unit/resolver/response_spec.rb +64 -0
- data/spec/unit/resolver/security_spec.rb +156 -0
- data/spec/unit/resolver/uri_spec.rb +117 -0
- data/spec/unit/rspec_spec.rb +342 -0
- data/spec/unit/schema/operation_spec.rb +309 -0
- data/spec/unit/schema/scope_spec.rb +110 -0
- data/spec/unit/schema_spec.rb +157 -0
- data/spec/unit/settings/validator_spec.rb +128 -0
- data/spec/unit/settings_spec.rb +248 -0
- metadata +192 -135
- data/docs/base_url.md +0 -38
- data/docs/documentation.md +0 -9
- data/docs/headers.md +0 -59
- data/docs/http_method.md +0 -31
- data/docs/model.md +0 -173
- data/docs/operation.md +0 -0
- data/docs/overview.md +0 -0
- data/docs/path.md +0 -48
- data/docs/query.md +0 -99
- data/docs/responses.md +0 -66
- data/docs/security.md +0 -102
- data/docs/settings.md +0 -32
- data/lib/evil/client/connection/net_http.rb +0 -57
- data/lib/evil/client/dsl.rb +0 -127
- data/lib/evil/client/dsl/base.rb +0 -26
- data/lib/evil/client/dsl/files.rb +0 -37
- data/lib/evil/client/dsl/headers.rb +0 -16
- data/lib/evil/client/dsl/http_method.rb +0 -24
- data/lib/evil/client/dsl/operation.rb +0 -91
- data/lib/evil/client/dsl/operations.rb +0 -41
- data/lib/evil/client/dsl/path.rb +0 -25
- data/lib/evil/client/dsl/query.rb +0 -16
- data/lib/evil/client/dsl/response.rb +0 -61
- data/lib/evil/client/dsl/responses.rb +0 -29
- data/lib/evil/client/dsl/scope.rb +0 -27
- data/lib/evil/client/dsl/security.rb +0 -57
- data/lib/evil/client/dsl/verifier.rb +0 -35
- data/lib/evil/client/middleware.rb +0 -81
- data/lib/evil/client/middleware/base.rb +0 -11
- data/lib/evil/client/middleware/merge_security.rb +0 -20
- data/lib/evil/client/middleware/normalize_headers.rb +0 -17
- data/lib/evil/client/middleware/stringify_form.rb +0 -40
- data/lib/evil/client/middleware/stringify_json.rb +0 -19
- data/lib/evil/client/middleware/stringify_multipart.rb +0 -36
- data/lib/evil/client/middleware/stringify_multipart/part.rb +0 -36
- data/lib/evil/client/middleware/stringify_query.rb +0 -35
- data/lib/evil/client/operation.rb +0 -34
- data/lib/evil/client/operation/request.rb +0 -26
- data/lib/evil/client/operation/response.rb +0 -39
- data/lib/evil/client/operation/response_error.rb +0 -13
- data/lib/evil/client/operation/unexpected_response_error.rb +0 -19
- data/spec/features/instantiation_spec.rb +0 -68
- data/spec/features/middleware_spec.rb +0 -79
- data/spec/features/operation_with_documentation_spec.rb +0 -41
- data/spec/features/operation_with_files_spec.rb +0 -40
- data/spec/features/operation_with_form_body_spec.rb +0 -158
- data/spec/features/operation_with_headers_spec.rb +0 -99
- data/spec/features/operation_with_http_method_spec.rb +0 -45
- data/spec/features/operation_with_json_body_spec.rb +0 -156
- data/spec/features/operation_with_nested_responses_spec.rb +0 -95
- data/spec/features/operation_with_path_spec.rb +0 -47
- data/spec/features/operation_with_query_spec.rb +0 -84
- data/spec/features/operation_with_security_spec.rb +0 -228
- data/spec/features/scoping_spec.rb +0 -48
- data/spec/support/test_client.rb +0 -15
- data/spec/unit/evil/client/connection/net_http_spec.rb +0 -38
- data/spec/unit/evil/client/dsl/files_spec.rb +0 -37
- data/spec/unit/evil/client/dsl/operation_spec.rb +0 -374
- data/spec/unit/evil/client/dsl/operations_spec.rb +0 -29
- data/spec/unit/evil/client/dsl/scope_spec.rb +0 -32
- data/spec/unit/evil/client/dsl/security_spec.rb +0 -135
- data/spec/unit/evil/client/middleware/merge_security_spec.rb +0 -32
- data/spec/unit/evil/client/middleware/normalize_headers_spec.rb +0 -17
- data/spec/unit/evil/client/middleware/stringify_form_spec.rb +0 -63
- data/spec/unit/evil/client/middleware/stringify_json_spec.rb +0 -61
- data/spec/unit/evil/client/middleware/stringify_multipart/part_spec.rb +0 -59
- data/spec/unit/evil/client/middleware/stringify_multipart_spec.rb +0 -62
- data/spec/unit/evil/client/middleware/stringify_query_spec.rb +0 -40
- data/spec/unit/evil/client/middleware_spec.rb +0 -46
- data/spec/unit/evil/client/operation/request_spec.rb +0 -49
- data/spec/unit/evil/client/operation/response_spec.rb +0 -63
@@ -0,0 +1,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
|