evil-client 1.1.0 → 2.0.0

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