evil-client 1.1.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 237e89873909ecf0ad225c9a714e68702c96b5c6
4
- data.tar.gz: 5a030e4b7629338ebe24359311e7dac5b4d74fe8
3
+ metadata.gz: 2740449772138472d84ec8f0a8f4e5fdeea23af8
4
+ data.tar.gz: d165a97dababfa3e40f1838a18d06b61847ff1da
5
5
  SHA512:
6
- metadata.gz: 0ae5ee8377ef7ca78252356a4cad2726ef854a676b4b0715c4e6a881d7c8d381896307b48d7d8e6cbe7920362369f4886dc8f654a39bab10e2cf160cf8c6fed8
7
- data.tar.gz: 54acb1b3f1d7e94d879240eaa6eba66576c719b5833707ef68393dcded7c971b7a1ed248e840f514d1a377a3daa6f33892caaf93a4acecffd485459e9c9f177f
6
+ metadata.gz: 4274645076138cbba089ecbcc3c3c4034e50789c7128e06b1abb45e9316c41908cf367174002227ddc51e124bd2963a4b0de3ba7a5be8e3bee345ba34e2cf92b
7
+ data.tar.gz: 98cc762b3c67d6688879dd3724a24c19ab78823acb162f9c6ea94e8fd4f53aa1f44834f005df3e99f483128ba2de7e43549823135e71b2e136927ec844deb5bd
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog], and this project adheres
5
5
  to [Semantic Versioning].
6
6
 
7
+ ## [2.0.0] [2017-09-02]
8
+
9
+ ### Changed
10
+
11
+ - In this version I've changed interface for validations by switching
12
+ to [tram-policy] based validation (nepalez)
13
+
14
+ Instead of giving name to validator:
15
+
16
+ ```ruby
17
+ validate :name_present { name != "" }
18
+ ```
19
+
20
+ You should use `errors.add` with the name for exception
21
+
22
+ ```ruby
23
+ validate { errors.add :blank_name if name == "" }
24
+ ```
25
+
26
+ This time the exception will be risen with all validation errors at once,
27
+ not only the first one.
28
+
29
+ - The `:en` locale is always used for translations (nepalez)
30
+
31
+ You don't need to make this locale available -- this is made under the hood!
32
+
7
33
  ## [1.1.0] [2017-08-10]
8
34
 
9
35
  Some syntax sugar has been added to both the client and its RSpec helpers.
data/README.md CHANGED
@@ -10,6 +10,7 @@ Human-friendly DSL for writing HTTP(s) clients in Ruby
10
10
  [![Dependency Status][gemnasium-badger]][gemnasium]
11
11
  [![Code Climate][codeclimate-badger]][codeclimate]
12
12
  [![Inline docs][inch-badger]][inch]
13
+ [![Documentation Status][readthedocs-badger]][readthedocs]
13
14
 
14
15
  ## Intro
15
16
 
@@ -37,7 +38,7 @@ $ gem install evil-client
37
38
 
38
39
  ## Synopsis
39
40
 
40
- The following example gives an idea of how a client to remote API looks like when written on top of `Evil::Client`.
41
+ The following example gives an idea of how a client to remote API looks like when written on top of `Evil::Client`. See [full documentation][readthedocs] for more details.
41
42
 
42
43
  ```ruby
43
44
  require "evil-client"
@@ -65,7 +66,7 @@ class CatsClient < Evil::Client
65
66
  option :age, optional: true
66
67
 
67
68
  let(:data) { options.select { |key, _| %i(name color age).include? key } }
68
- validate(:data_present) { !data.empty? }
69
+ validate { errors.add :no_filters if data.empty? }
69
70
 
70
71
  path { "cats/#{id}" } # added to root path
71
72
  http_method :patch # you can use plain syntax instead of a block
@@ -119,3 +120,5 @@ The gem is available as open source under the terms of the [MIT License](http://
119
120
  [swagger]: http://swagger.io
120
121
  [travis-badger]: https://img.shields.io/travis/evilmartians/evil-client/master.svg?style=flat
121
122
  [travis]: https://travis-ci.org/evilmartians/evil-client
123
+ [readthedocs-badger]: https://readthedocs.org/projects/evilclient/badge/?version=latest
124
+ [readthedocs]: http://evilclient.readthedocs.io/en/latest
@@ -10,7 +10,10 @@ class CatsClient < Evil::Client
10
10
  option :password, proc(&:to_s)
11
11
  option :subdomain, proc(&:to_s)
12
12
 
13
- validate(:valid_subdomain) { %w[wild domestic].include? subdomain }
13
+ validate do
14
+ return if %w[wild domestic].include? subdomain
15
+ errors.add :invalid_subdomain, subdomain: subdomain
16
+ end
14
17
 
15
18
  path { "https://#{subdomain}.example.com/" }
16
19
  http_method "get"
@@ -39,7 +42,7 @@ class CatsClient < Evil::Client
39
42
  # scope-specific options
40
43
  option :version, proc(&:to_i)
41
44
 
42
- validate(:supported_version) { version < 5 }
45
+ validate { errors.add :wrong_version unless version < 5 }
43
46
 
44
47
  # scope-specific redefinition of the root settings
45
48
  http_method { version.zero? ? :get : :post }
@@ -1,6 +1,8 @@
1
1
  Use `validate` helper to check interconnection between several options.
2
2
 
3
- The helper takes name (unique for a current scope) and a block. Validation fails when a block returns falsey value.
3
+ It takes a block, where all options are available. You should call method
4
+ `errors.add :some_key, **some_options` or `errors.add "some message"`
5
+ to invalidate options.
4
6
 
5
7
  ```ruby
6
8
  class CatsAPI < Evil::Client
@@ -10,15 +12,15 @@ class CatsAPI < Evil::Client
10
12
 
11
13
  # All requests should be made with either token or user/password
12
14
  # This is required by any request
13
- validate(:valid_credentials) { token ^ password }
14
- validate(:password_given) { user ^ !password }
15
+ validate { errors.add :wrong_credentials unless token.nil? ^ password.nil? }
16
+ validate { errors.add :missed_password unless user.nil? ^ !password }
15
17
 
16
18
  scope :cats do
17
19
  option :version, proc(&:to_i)
18
20
 
19
21
  # Check that operation of cats scope include token after API v1
20
22
  # This doesn't affect other subscopes of CatsAPI root scope
21
- validate(:token_based) { token || version.zero? }
23
+ validate { errors.add :missed_token unless token || version.zero? }
22
24
  end
23
25
 
24
26
  # ...
@@ -27,7 +29,12 @@ end
27
29
  CatsAPI.new password: "foo" # raises Evil::Client::ValidationError
28
30
  ```
29
31
 
30
- The error message is translated using i18n gem. You should provide translations for a corresponding scope:
32
+ The error message is translated using i18n gem in **english** locale.
33
+ You don't need to add `:en` to `I18n.available_locales`, we make it
34
+ under the hood and then restore previous settings.
35
+
36
+ All you need is to provide translations for a corresponding scope which is
37
+ `en.evil.client.errors.{class_name}.{scopes and operations}` as shown below.
31
38
 
32
39
  ```yaml
33
40
  # config/locales/evil-client.en.yml
@@ -37,15 +44,15 @@ en:
37
44
  client:
38
45
  errors:
39
46
  cats_api:
40
- valid_credentials: "Provide either a token or a password"
41
- password_given: "User and password should accompany one another"
47
+ wrong_credentials: "Provide either a token or a password"
48
+ missed_password: "User and password should accompany one another"
42
49
  cats:
43
- token_based: "The token is required for operations with cats in API v1+"
50
+ missed_token: "The token is required for operations with cats in API v1+"
44
51
  ```
45
52
 
46
- The root scope for error messages is `{locale}.evil.client.errors.{class_name}` as shown above.
53
+ Alternatively you can call `errors.add "some message"` without any translation. Only symbolic keys are translated via i18n, while string messages used in exceptions as is. This time you don't need adding translation at all.
47
54
 
48
- Remember, that you can initialize client with some valid options, and then reload that options in a nested subscope/operation. All validations defined from the root of the client will be used for any set of options. See the example:
55
+ Remember, that you can initialize client with some valid options, and then reload that options in a nested subscope/operation. All validations defined from the root of the client will be used for any set of options. For example:
49
56
 
50
57
  ```ruby
51
58
  client = CatsAPI.new token: "foo"
@@ -58,11 +65,11 @@ cats.fetch id: 3
58
65
  # valid
59
66
 
60
67
  cats.fetch id: 3, token: nil
61
- # fails due to 'valid_credentials' is broken
68
+ # fails due to wrong credentials
62
69
 
63
70
  cats.fetch id: 3, token: nil, user: "andy", password: "qux"
64
71
  # valid
65
72
 
66
73
  cats.fetch id: 3, token: nil, user: "andy", password: "qux", version: 1
67
- # fails due to 'cats.token_based' is broken
74
+ # fails due to missed token
68
75
  ```
data/docs/index.md CHANGED
@@ -63,7 +63,7 @@ class CatsClient < Evil::Client
63
63
  option :age, optional: true
64
64
 
65
65
  let(:data) { options.select { |key, _| %i(name color age).include? key } }
66
- validate(:data_present) { !data.empty? }
66
+ validate { errors.add :no_data if data.empty? }
67
67
 
68
68
  path { "cats/#{id}" } # added to root path
69
69
  http_method :patch # you can use plain syntax instead of a block
data/evil-client.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |gem|
2
2
  gem.name = "evil-client"
3
- gem.version = "1.1.0"
3
+ gem.version = "2.0.0"
4
4
  gem.author = ["Andrew Kozin (nepalez)", "Ravil Bairamgalin (brainopia)"]
5
5
  gem.email = ["andrew.kozin@gmail.com", "nepalez@evilmartians.com"]
6
6
  gem.homepage = "https://github.com/evilmartians/evil-client"
@@ -11,10 +11,10 @@ Gem::Specification.new do |gem|
11
11
  gem.test_files = gem.files.grep(/^spec/)
12
12
  gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
13
13
 
14
- gem.required_ruby_version = ">= 2.3"
14
+ gem.required_ruby_version = "~> 2.3"
15
15
 
16
- gem.add_runtime_dependency "dry-initializer", "~> 1.4"
17
- gem.add_runtime_dependency "i18n", "~> 0.8.6"
16
+ gem.add_runtime_dependency "dry-initializer", "~> 2.0.0"
17
+ gem.add_runtime_dependency "tram-policy", "~> 0.2.1"
18
18
  gem.add_runtime_dependency "mime-types", "~> 3.1"
19
19
  gem.add_runtime_dependency "rack", "~> 2"
20
20
 
data/lib/evil/client.rb CHANGED
@@ -4,11 +4,10 @@ require "rack"
4
4
  require "cgi"
5
5
  require "json"
6
6
  require "yaml"
7
- require "i18n"
8
7
  require "mime-types"
9
8
  require "securerandom"
10
9
  require "delegate"
11
- require "dry-initializer"
10
+ require "tram-policy"
12
11
  require "net/http"
13
12
  require "net/https"
14
13
  #
@@ -30,6 +29,7 @@ module Evil
30
29
 
31
30
  require_relative "client/chaining"
32
31
  require_relative "client/options"
32
+ require_relative "client/policy"
33
33
  require_relative "client/settings"
34
34
  require_relative "client/schema"
35
35
  require_relative "client/container"
@@ -125,6 +125,14 @@ module Evil
125
125
  @scope.scopes
126
126
  end
127
127
 
128
+ # Settings of the client
129
+ #
130
+ # @return (see Evil::Client::Container#settings)
131
+ #
132
+ def settings
133
+ @scope.settings
134
+ end
135
+
128
136
  # Options assigned to the client
129
137
  #
130
138
  # @return (see Evil::Client::Container#options)
@@ -43,6 +43,7 @@ class Evil::Client
43
43
  send
44
44
  settings
45
45
  singleton_class
46
+ tap
46
47
  to_s
47
48
  to_str
48
49
  token_auth
@@ -0,0 +1,56 @@
1
+ class Evil::Client
2
+ #
3
+ # Base class for policies that validate settings
4
+ #
5
+ class Policy < Tram::Policy
6
+ class << self
7
+ # Subclasses itself for a settings class
8
+ #
9
+ # @param [Class] settings Settings class to validate
10
+ # @return [Class]
11
+ #
12
+ def for(settings)
13
+ Class.new(self).tap do |klass|
14
+ klass.send :instance_variable_set, :@settings, settings
15
+ end
16
+ end
17
+
18
+ # Reference to the settings class whose instances validates the policy
19
+ #
20
+ # @return [Class, nil]
21
+ #
22
+ attr_reader :settings
23
+
24
+ # Delegates the name of the policy to the name of checked settings
25
+ #
26
+ # @return [String, nil]
27
+ #
28
+ def name
29
+ "#{settings}.policy"
30
+ end
31
+ alias_method :to_s, :name
32
+ alias_method :to_sym, :name
33
+ alias_method :inspect, :name
34
+
35
+ private
36
+
37
+ def scope
38
+ @scope ||= %i[evil client errors] << \
39
+ Tram::Policy::Inflector.underscore(settings.to_s)
40
+ end
41
+ end
42
+
43
+ # An instance of settings to be checked by the policy
44
+ param :settings
45
+
46
+ private
47
+
48
+ def respond_to_missing?(name, *)
49
+ settings.respond_to?(name)
50
+ end
51
+
52
+ def method_missing(*args)
53
+ respond_to_missing?(*args) ? settings.__send__(*args) : super
54
+ end
55
+ end
56
+ end
@@ -55,9 +55,7 @@ class Evil::Client
55
55
  # @return [Class]
56
56
  #
57
57
  def settings
58
- @settings ||= Class.new(parent&.settings || Settings).tap do |klass|
59
- klass.send(:instance_variable_set, :@schema, self)
60
- end
58
+ @settings ||= (parent&.settings || Settings).for(self)
61
59
  end
62
60
 
63
61
  # Adds an option to the [#settings] class
@@ -86,8 +84,8 @@ class Evil::Client
86
84
  # @param (see Evil::Client::Settings.validate)
87
85
  # @return [self]
88
86
  #
89
- def validate(key, &block)
90
- settings.validate(key, &block)
87
+ def validate(&block)
88
+ settings.validate(&block)
91
89
  self
92
90
  end
93
91
 
@@ -4,15 +4,38 @@ class Evil::Client
4
4
  #
5
5
  class Settings
6
6
  Names.clean(self) # Remove unnecessary methods from the instance
7
- require_relative "settings/validator"
8
7
  extend ::Dry::Initializer
9
8
 
9
+ @policy = Policy
10
+
10
11
  class << self
11
- # The schema klass settings belongs to
12
+ # Subclasses itself for a given schema
13
+ #
14
+ # @param [Class] schema
15
+ # @return [Class] a subclass for the schema
16
+ #
17
+ def for(schema)
18
+ Class.new(self).tap do |klass|
19
+ klass.send(:instance_variable_set, :@schema, schema)
20
+ end
21
+ end
22
+
23
+ # Reference to the schema klass the settings belongs to
12
24
  #
13
25
  # @return [Class]
14
26
  #
15
- attr_reader :schema
27
+ attr_reader :schema, :locale
28
+
29
+ # Human-friendly representation of settings class
30
+ #
31
+ # @return [String]
32
+ #
33
+ def name
34
+ super || @schema.to_s
35
+ end
36
+ alias_method :to_s, :name
37
+ alias_method :to_str, :name
38
+ alias_method :inspect, :name
16
39
 
17
40
  # Only options can be defined for the settings container
18
41
  # @private
@@ -54,35 +77,23 @@ class Evil::Client
54
77
  self
55
78
  end
56
79
 
57
- # Define validator for the attribute
80
+ # Policy class that collects all the necessary validators
58
81
  #
59
- # @param [#to_sym] key The name of the attribute
60
- # @param [Proc] block The body of new attribute
61
- # @return [self]
82
+ # @return [Class] a subclass of [Tram::Policy] named after the scope
62
83
  #
63
- def validate(key, &block)
64
- validators[key] = Validator.new(@schema, key, &block)
65
- self
84
+ def policy
85
+ @policy ||= superclass.policy.for(self)
66
86
  end
67
87
 
68
- # Collection of validators to check initialized settings
88
+ # Add validation rule to the [#policy]
69
89
  #
70
- # @return [Hash<Symbol, Evil::Client::Validator>]
71
- #
72
- def validators
73
- @validators ||= {}
74
- end
75
-
76
- # Human-friendly representation of settings class
77
- #
78
- # @return [String]
90
+ # @param [Proc] block The body of new attribute
91
+ # @return [self]
79
92
  #
80
- def name
81
- super || @schema.to_s
93
+ def validate(&block)
94
+ policy.validate(&block)
95
+ self
82
96
  end
83
- alias_method :to_s, :name
84
- alias_method :to_str, :name
85
- alias_method :inspect, :name
86
97
 
87
98
  # Builds settings with options
88
99
  #
@@ -93,10 +104,20 @@ class Evil::Client
93
104
  def new(logger, opts = {})
94
105
  logger&.debug(self) { "initializing with options #{opts}..." }
95
106
  opts = Hash(opts).each_with_object({}) { |(k, v), o| o[k.to_sym] = v }
96
- super logger, opts
107
+ in_english { super logger, opts }
97
108
  rescue => error
98
109
  raise ValidationError, error.message
99
110
  end
111
+
112
+ private
113
+
114
+ def in_english(&block)
115
+ available_locales = I18n.available_locales
116
+ I18n.available_locales = %i[en]
117
+ I18n.with_locale(:en, &block)
118
+ ensure
119
+ I18n.available_locales = available_locales
120
+ end
100
121
  end
101
122
 
102
123
  # The processed hash of options contained by the instance of settings
@@ -104,7 +125,7 @@ class Evil::Client
104
125
  # @return [Hash<Symbol, Object>]
105
126
  #
106
127
  def options
107
- Options.new @__options__
128
+ @options ||= Options.new self.class.dry_initializer.attributes(self)
108
129
  end
109
130
 
110
131
  # @!attribute logger
@@ -143,26 +164,10 @@ class Evil::Client
143
164
  private
144
165
 
145
166
  def initialize(logger, **options)
146
- @logger = logger
147
167
  super(options)
148
-
168
+ @logger = logger
169
+ self.class.policy[self].validate!
149
170
  logger&.debug(self) { "initialized" }
150
- __validate__!
151
- end
152
-
153
- def __validate__!
154
- __validators__.reverse.each { |validator| validator.call(self) }
155
- end
156
-
157
- def __validators__
158
- klass = self.class
159
- [].tap do |list|
160
- loop do
161
- list.concat klass.validators.values
162
- klass = klass.superclass
163
- break if klass == Evil::Client::Settings
164
- end
165
- end
166
171
  end
167
172
  end
168
173
  end
data/mkdocs.yml CHANGED
@@ -6,22 +6,22 @@ repo_url: https://github.com/nepalez/evil-client
6
6
  site_author: Andrew Kozin
7
7
  theme: readthedocs
8
8
  pages:
9
- Synopsis: index.md
10
- Helpers:
11
- scope: helpers/scope.md
12
- operation: helpers/operation.md
13
- option: helpers/option.md
14
- let: helpers/let.md
15
- validate: helpers/validate.md
16
- path: helpers/path.md
17
- http_method: helpers/http_method.md
18
- security: helpers/security.md
19
- headers: helpers/headers.md
20
- query: helpers/query.md
21
- 'body and format': helpers/body.md
22
- response: helpers/response.md
23
- middleware: helpers/middleware.md
24
- connection: helpers/connection.md
25
- logger: helpers/logger.md
26
- 'RSpec matcher': rspec.md
27
- License: license.md
9
+ - Synopsis: index.md
10
+ - Helpers:
11
+ - scope: helpers/scope.md
12
+ - operation: helpers/operation.md
13
+ - option: helpers/option.md
14
+ - let: helpers/let.md
15
+ - validate: helpers/validate.md
16
+ - path: helpers/path.md
17
+ - http_method: helpers/http_method.md
18
+ - security: helpers/security.md
19
+ - headers: helpers/headers.md
20
+ - query: helpers/query.md
21
+ - 'body and format': helpers/body.md
22
+ - response: helpers/response.md
23
+ - middleware: helpers/middleware.md
24
+ - connection: helpers/connection.md
25
+ - logger: helpers/logger.md
26
+ - 'RSpec matcher': rspec.md
27
+ - License: license.md
@@ -4,7 +4,8 @@ module Test
4
4
  option :user
5
5
  option :token, optional: true
6
6
  option :password, optional: true
7
- validate(:valid_credentials) { token.nil? ^ password.nil? }
7
+
8
+ validate { errors.add :valid_credentials unless token.nil? ^ password.nil? }
8
9
 
9
10
  path { "https://#{subdomain}.example.com" }
10
11
 
@@ -25,7 +26,7 @@ module Test
25
26
  option :id, optional: true
26
27
  option :email, optional: true
27
28
 
28
- validate(:filter_given) { name || id || email }
29
+ validate { errors.add :filter_given unless name || id || email }
29
30
 
30
31
  http_method :get
31
32
  response(200) { |*res| res.last.flat_map { |item| JSON.parse(item) } }
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,7 @@ require "bundler/setup"
8
8
  require "webmock/rspec"
9
9
  require "rspec/its"
10
10
  require "timecop"
11
+ require "tempfile"
11
12
  require "evil/client"
12
13
  require "evil/client/rspec"
13
14
 
@@ -24,9 +25,7 @@ RSpec.configure do |config|
24
25
  # Prepare the Test namespace for constants defined in specs
25
26
  module Test; end
26
27
  # Load translations
27
- en_translations = yaml_fixture_file "locales/en.yml"
28
- I18n.available_locales = %i[en]
29
- I18n.backend.store_translations :en, en_translations["en"]
28
+ I18n.load_path += Dir["spec/fixtures/locales/*.yml"]
30
29
  end
31
30
 
32
31
  config.after(:each) do
@@ -93,6 +93,14 @@ RSpec.describe Evil::Client do
93
93
  klass.new(token: "foo", version: 1)
94
94
  end
95
95
 
96
+ describe "#settings" do
97
+ subject { client.settings }
98
+
99
+ it "returns settings assigned to client settings" do
100
+ expect(subject.token).to eq "foo"
101
+ end
102
+ end
103
+
96
104
  describe "#options" do
97
105
  subject { client.options }
98
106
 
@@ -0,0 +1,32 @@
1
+ RSpec.describe Evil::Client::Policy do
2
+ it "subsclasses Tram::Policy" do
3
+ expect(described_class.superclass).to eq Tram::Policy
4
+ end
5
+
6
+ it "takes a parameter (settings) to validate" do
7
+ expect(described_class[double]).to be_valid
8
+ end
9
+
10
+ it "delegates instance methods to settings" do
11
+ settings = double :settings, foo: :BAR
12
+ policy = described_class[settings]
13
+ expect(policy.foo).to eq :BAR
14
+ end
15
+
16
+ describe ".for" do
17
+ let(:settings) { class_double Evil::Client::Settings, to_s: "Foo" }
18
+ subject { described_class.for settings }
19
+
20
+ it "builds a subclass of its own" do
21
+ expect(subject.superclass).to eq described_class
22
+ end
23
+
24
+ it "keeps reference to the settings" do
25
+ expect(subject.settings).to eq settings
26
+ end
27
+
28
+ it "takes the name from settings clsass" do
29
+ expect(subject.name).to eq "Foo.policy"
30
+ end
31
+ end
32
+ end
@@ -143,10 +143,10 @@ RSpec.describe Evil::Client::Schema do
143
143
 
144
144
  describe "#validate" do
145
145
  before { allow(schema.settings).to receive(:validate) }
146
- subject { schema.validate(:id_present) {} }
146
+ subject { schema.validate {} }
147
147
 
148
148
  it "is delegated to settings" do
149
- expect(schema.settings).to receive(:validate).with(:id_present)
149
+ expect(schema.settings).to receive(:validate)
150
150
  subject
151
151
  end
152
152
 
@@ -2,19 +2,50 @@ RSpec.describe Evil::Client::Settings do
2
2
  let(:settings) { klass.new(logger, options) }
3
3
  let(:log) { StringIO.new }
4
4
  let(:logger) { Logger.new log }
5
- let(:klass) { Class.new(described_class) }
5
+ let(:schema) { double :schema, to_s: "Test::Api.users.update" }
6
+ let(:klass) { described_class.for(schema) }
6
7
  let(:options) { { "id" => 42, "name" => "Andrew" } }
7
8
  let(:dsl_methods) do
8
9
  %i[options datetime logger scope basic_auth key_auth token_auth]
9
10
  end
10
11
 
11
- before { klass.instance_variable_set :@schema, "Test::Api.users.update" }
12
+ describe ".for" do
13
+ subject { klass }
12
14
 
13
- describe ".schema" do
14
- subject { klass.schema }
15
+ it "subclasses itself" do
16
+ expect(subject.superclass).to eq described_class
17
+ end
15
18
 
16
- it "returns the schema class the settings belongs to" do
17
- expect(subject).to eq "Test::Api.users.update"
19
+ it "keeps the schema" do
20
+ expect(subject.schema).to eq schema
21
+ end
22
+ end
23
+
24
+ describe ".policy" do
25
+ context "for the root schema settings" do
26
+ subject { klass.policy }
27
+
28
+ it "subclasses Evil::Client::Policy" do
29
+ expect(subject.superclass).to eq Evil::Client::Policy
30
+ end
31
+
32
+ it "refers back to the settings" do
33
+ expect(subject.settings).to eq klass
34
+ end
35
+ end
36
+
37
+ context "for the scope settings" do
38
+ let(:scope_schema) { double :scope_schema }
39
+ let(:scope_klass) { klass.for(scope_schema) }
40
+ subject { scope_klass.policy }
41
+
42
+ it "subclasses policy of parent settings" do
43
+ expect(subject.superclass).to eq klass.policy
44
+ end
45
+
46
+ it "refers back to the settings" do
47
+ expect(subject.settings).to eq scope_klass
48
+ end
18
49
  end
19
50
  end
20
51
 
@@ -71,7 +102,7 @@ RSpec.describe Evil::Client::Settings do
71
102
  describe ".validate" do
72
103
  before do
73
104
  klass.param :name
74
- klass.validate(:name_present) { name.to_s != "" }
105
+ klass.validate { errors.add :name_present if name.to_s == "" }
75
106
  end
76
107
 
77
108
  let(:options) { { "name" => "" } }
@@ -79,14 +110,7 @@ RSpec.describe Evil::Client::Settings do
79
110
  it "adds validation for an instance" do
80
111
  # see spec/fixtures/locale/en.yml
81
112
  expect { settings }
82
- .to raise_error(Evil::Client::ValidationError, "The user has no name")
83
- end
84
-
85
- it "gives logger to validators" do
86
- settings rescue nil
87
-
88
- expect(log.string).to include "#{klass.schema}.validator[:name_present]"
89
- expect(log.string).to include "failed"
113
+ .to raise_error(Evil::Client::ValidationError, /The user has no name/)
90
114
  end
91
115
  end
92
116
 
metadata CHANGED
@@ -1,157 +1,157 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evil-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kozin (nepalez)
8
8
  - Ravil Bairamgalin (brainopia)
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-08-10 00:00:00.000000000 Z
12
+ date: 2017-09-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
- name: dry-initializer
16
15
  requirement: !ruby/object:Gem::Requirement
17
16
  requirements:
18
17
  - - "~>"
19
18
  - !ruby/object:Gem::Version
20
- version: '1.4'
21
- type: :runtime
19
+ version: 2.0.0
20
+ name: dry-initializer
22
21
  prerelease: false
22
+ type: :runtime
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: '1.4'
27
+ version: 2.0.0
28
28
  - !ruby/object:Gem::Dependency
29
- name: i18n
30
29
  requirement: !ruby/object:Gem::Requirement
31
30
  requirements:
32
31
  - - "~>"
33
32
  - !ruby/object:Gem::Version
34
- version: 0.8.6
35
- type: :runtime
33
+ version: 0.2.1
34
+ name: tram-policy
36
35
  prerelease: false
36
+ type: :runtime
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: 0.8.6
41
+ version: 0.2.1
42
42
  - !ruby/object:Gem::Dependency
43
- name: mime-types
44
43
  requirement: !ruby/object:Gem::Requirement
45
44
  requirements:
46
45
  - - "~>"
47
46
  - !ruby/object:Gem::Version
48
47
  version: '3.1'
49
- type: :runtime
48
+ name: mime-types
50
49
  prerelease: false
50
+ type: :runtime
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '3.1'
56
56
  - !ruby/object:Gem::Dependency
57
- name: rack
58
57
  requirement: !ruby/object:Gem::Requirement
59
58
  requirements:
60
59
  - - "~>"
61
60
  - !ruby/object:Gem::Version
62
61
  version: '2'
63
- type: :runtime
62
+ name: rack
64
63
  prerelease: false
64
+ type: :runtime
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '2'
70
70
  - !ruby/object:Gem::Dependency
71
- name: rake
72
71
  requirement: !ruby/object:Gem::Requirement
73
72
  requirements:
74
73
  - - ">="
75
74
  - !ruby/object:Gem::Version
76
75
  version: '10'
77
- type: :development
76
+ name: rake
78
77
  prerelease: false
78
+ type: :development
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '10'
84
84
  - !ruby/object:Gem::Dependency
85
- name: rspec
86
85
  requirement: !ruby/object:Gem::Requirement
87
86
  requirements:
88
87
  - - "~>"
89
88
  - !ruby/object:Gem::Version
90
89
  version: '3.0'
91
- type: :development
90
+ name: rspec
92
91
  prerelease: false
92
+ type: :development
93
93
  version_requirements: !ruby/object:Gem::Requirement
94
94
  requirements:
95
95
  - - "~>"
96
96
  - !ruby/object:Gem::Version
97
97
  version: '3.0'
98
98
  - !ruby/object:Gem::Dependency
99
- name: rspec-its
100
99
  requirement: !ruby/object:Gem::Requirement
101
100
  requirements:
102
101
  - - "~>"
103
102
  - !ruby/object:Gem::Version
104
103
  version: '1.2'
105
- type: :development
104
+ name: rspec-its
106
105
  prerelease: false
106
+ type: :development
107
107
  version_requirements: !ruby/object:Gem::Requirement
108
108
  requirements:
109
109
  - - "~>"
110
110
  - !ruby/object:Gem::Version
111
111
  version: '1.2'
112
112
  - !ruby/object:Gem::Dependency
113
- name: rubocop
114
113
  requirement: !ruby/object:Gem::Requirement
115
114
  requirements:
116
115
  - - "~>"
117
116
  - !ruby/object:Gem::Version
118
117
  version: '0.42'
119
- type: :development
118
+ name: rubocop
120
119
  prerelease: false
120
+ type: :development
121
121
  version_requirements: !ruby/object:Gem::Requirement
122
122
  requirements:
123
123
  - - "~>"
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0.42'
126
126
  - !ruby/object:Gem::Dependency
127
- name: timecop
128
127
  requirement: !ruby/object:Gem::Requirement
129
128
  requirements:
130
129
  - - "~>"
131
130
  - !ruby/object:Gem::Version
132
131
  version: '0.9'
133
- type: :development
132
+ name: timecop
134
133
  prerelease: false
134
+ type: :development
135
135
  version_requirements: !ruby/object:Gem::Requirement
136
136
  requirements:
137
137
  - - "~>"
138
138
  - !ruby/object:Gem::Version
139
139
  version: '0.9'
140
140
  - !ruby/object:Gem::Dependency
141
- name: webmock
142
141
  requirement: !ruby/object:Gem::Requirement
143
142
  requirements:
144
143
  - - "~>"
145
144
  - !ruby/object:Gem::Version
146
145
  version: '2.1'
147
- type: :development
146
+ name: webmock
148
147
  prerelease: false
148
+ type: :development
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
153
  version: '2.1'
154
- description:
154
+ description:
155
155
  email:
156
156
  - andrew.kozin@gmail.com
157
157
  - nepalez@evilmartians.com
@@ -212,6 +212,7 @@ files:
212
212
  - lib/evil/client/formatter/text.rb
213
213
  - lib/evil/client/names.rb
214
214
  - lib/evil/client/options.rb
215
+ - lib/evil/client/policy.rb
215
216
  - lib/evil/client/resolver.rb
216
217
  - lib/evil/client/resolver/body.rb
217
218
  - lib/evil/client/resolver/format.rb
@@ -232,7 +233,6 @@ files:
232
233
  - lib/evil/client/schema/operation.rb
233
234
  - lib/evil/client/schema/scope.rb
234
235
  - lib/evil/client/settings.rb
235
- - lib/evil/client/settings/validator.rb
236
236
  - mkdocs.yml
237
237
  - spec/features/custom_connection_spec.rb
238
238
  - spec/features/operation/middleware_spec.rb
@@ -262,6 +262,7 @@ files:
262
262
  - spec/unit/formatter/text_spec.rb
263
263
  - spec/unit/formatter_spec.rb
264
264
  - spec/unit/options_spec.rb
265
+ - spec/unit/policy_spec.rb
265
266
  - spec/unit/resolver/body_spec.rb
266
267
  - spec/unit/resolver/format_spec.rb
267
268
  - spec/unit/resolver/headers_spec.rb
@@ -278,19 +279,18 @@ files:
278
279
  - spec/unit/schema/operation_spec.rb
279
280
  - spec/unit/schema/scope_spec.rb
280
281
  - spec/unit/schema_spec.rb
281
- - spec/unit/settings/validator_spec.rb
282
282
  - spec/unit/settings_spec.rb
283
283
  homepage: https://github.com/evilmartians/evil-client
284
284
  licenses:
285
285
  - MIT
286
286
  metadata: {}
287
- post_install_message:
287
+ post_install_message:
288
288
  rdoc_options: []
289
289
  require_paths:
290
290
  - lib
291
291
  required_ruby_version: !ruby/object:Gem::Requirement
292
292
  requirements:
293
- - - ">="
293
+ - - "~>"
294
294
  - !ruby/object:Gem::Version
295
295
  version: '2.3'
296
296
  required_rubygems_version: !ruby/object:Gem::Requirement
@@ -299,9 +299,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
299
299
  - !ruby/object:Gem::Version
300
300
  version: '0'
301
301
  requirements: []
302
- rubyforge_project:
303
- rubygems_version: 2.5.2
304
- signing_key:
302
+ rubyforge_project:
303
+ rubygems_version: 2.6.4
304
+ signing_key:
305
305
  specification_version: 4
306
306
  summary: Human-friendly DSL for building HTTP(s) clients in Ruby
307
307
  test_files:
@@ -333,6 +333,7 @@ test_files:
333
333
  - spec/unit/formatter/text_spec.rb
334
334
  - spec/unit/formatter_spec.rb
335
335
  - spec/unit/options_spec.rb
336
+ - spec/unit/policy_spec.rb
336
337
  - spec/unit/resolver/body_spec.rb
337
338
  - spec/unit/resolver/format_spec.rb
338
339
  - spec/unit/resolver/headers_spec.rb
@@ -349,6 +350,4 @@ test_files:
349
350
  - spec/unit/schema/operation_spec.rb
350
351
  - spec/unit/schema/scope_spec.rb
351
352
  - spec/unit/schema_spec.rb
352
- - spec/unit/settings/validator_spec.rb
353
353
  - spec/unit/settings_spec.rb
354
- has_rdoc:
@@ -1,64 +0,0 @@
1
- class Evil::Client
2
- #
3
- # Validator to be called in context of initialized settings
4
- #
5
- class Settings::Validator
6
- # Performs validation of initialized settings
7
- #
8
- # @param [Evil::Client::Settings]
9
- # @return true
10
- # @raise [Evil::Client::ValidationError] when a validation fails
11
- #
12
- def call(settings)
13
- if validate!(settings, @block)
14
- settings&.logger&.debug(self) { "passed for #{settings}" }
15
- true
16
- else
17
- settings&.logger&.error(self) { "failed for #{settings}" }
18
- raise ValidationError.new(@key, @schema, settings.options)
19
- end
20
- end
21
-
22
- # Human-friendly representation of current validator
23
- #
24
- # @return [String]
25
- #
26
- def to_s
27
- "#{@schema}.validator[:#{@key}]"
28
- end
29
- alias_method :to_str, :to_s
30
- alias_method :inspect, :to_s
31
-
32
- private
33
-
34
- def initialize(schema, key, &block)
35
- check_key! key
36
- check_block! block
37
-
38
- @schema = schema
39
- @key = key.to_sym
40
- @block = block
41
- end
42
-
43
- def check_key!(key)
44
- message = if !key.respond_to? :to_sym
45
- "Validator should have a symbolic name"
46
- elsif key.empty?
47
- "Validator name should not be empty"
48
- end
49
-
50
- raise ArgumentError.new(message) if message
51
- end
52
-
53
- def check_block!(block)
54
- raise ArgumentError, "You should set block for validation" unless block
55
- end
56
-
57
- def validate!(settings, block)
58
- settings.instance_exec(&block) && true
59
- rescue => error
60
- settings&.logger&.error(self) { "broken for #{settings} with #{error}" }
61
- raise
62
- end
63
- end
64
- end
@@ -1,128 +0,0 @@
1
- RSpec.describe Evil::Client::Settings::Validator do
2
- let(:validator) { described_class.new(schema, key, &block) }
3
- let(:schema) { double to_s: "Test::Api.users.update" }
4
- let(:key) { :token_present }
5
- let(:block) { proc { 1 if token.to_s != "" } }
6
-
7
- describe ".new" do
8
- subject { validator }
9
- it { is_expected.to be_a described_class }
10
-
11
- context "when key is missed" do
12
- let(:key) { nil }
13
-
14
- it "raises ArgumentError" do
15
- expect { subject }.to raise_error ArgumentError
16
- end
17
- end
18
-
19
- context "when key is empty" do
20
- let(:key) { "" }
21
-
22
- it "raises ArgumentError" do
23
- expect { subject }.to raise_error ArgumentError
24
- end
25
- end
26
-
27
- context "when key cannot be symbolized" do
28
- let(:key) { 1 }
29
-
30
- it "raises ArgumentError" do
31
- expect { subject }.to raise_error ArgumentError
32
- end
33
- end
34
-
35
- context "when block is missed" do
36
- let(:block) { nil }
37
-
38
- it "raises ArgumentError" do
39
- expect { subject }.to raise_error ArgumentError
40
- end
41
- end
42
- end
43
-
44
- describe "#to_s" do
45
- subject { validator.to_s }
46
-
47
- it "represents validator in a human-friendly manner" do
48
- expect(subject).to eq "Test::Api.users.update.validator[:token_present]"
49
- end
50
- end
51
-
52
- describe "#to_str" do
53
- subject { validator.to_str }
54
-
55
- it "represents validator in a human-friendly manner" do
56
- expect(subject).to eq "Test::Api.users.update.validator[:token_present]"
57
- end
58
- end
59
-
60
- describe "#inspect" do
61
- subject { validator.inspect }
62
-
63
- it "represents validator in a human-friendly manner" do
64
- expect(subject).to eq "Test::Api.users.update.validator[:token_present]"
65
- end
66
- end
67
-
68
- describe "#call" do
69
- let(:log) { StringIO.new }
70
- let(:logger) { Logger.new(log) }
71
- let(:options) { { id: 7, token: token } }
72
- let(:token) { "foo" }
73
-
74
- let(:settings) do
75
- double options: options, to_s: "my_settings", logger: logger, **options
76
- end
77
-
78
- subject { validator.call settings }
79
-
80
- context "when block returns truthy value" do
81
- it { is_expected.to eql true }
82
-
83
- it "logs the result" do
84
- subject
85
-
86
- expect(log.string).to include validator.to_s
87
- expect(log.string).to include "passed for my_settings"
88
- end
89
-
90
- context "when logger level was set to INFO" do
91
- before { logger.level = Logger::INFO }
92
-
93
- it "skips logging" do
94
- expect { subject }.not_to change { log.string }
95
- end
96
- end
97
- end
98
-
99
- context "when block returns falsey value" do
100
- let(:token) { nil }
101
-
102
- it "raises Evil::Client::ValidationError" do
103
- # see spec/fixtures/locales/en.yml
104
- expect { subject }
105
- .to raise_error Evil::Client::ValidationError,
106
- "To update user id:7 you must provide a token"
107
- end
108
-
109
- it "logs the result" do
110
- subject rescue nil
111
-
112
- expect(log.string).to include validator.to_s
113
- expect(log.string).to include "failed for my_settings"
114
- end
115
- end
116
-
117
- context "when block raises" do
118
- let(:block) { proc { raise "my_problem" } }
119
-
120
- it "logs the result" do
121
- subject rescue nil
122
-
123
- expect(log.string).to include validator.to_s
124
- expect(log.string).to include "broken for my_settings with my_problem"
125
- end
126
- end
127
- end
128
- end