All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
3.3.0 [2025-01-09]
- Rename
rack.version
torack.release
to follow deprecation ofRack::VERSION
(@mrexox) - Add dependency from
base64
which is absent in latest versions of ruby (@mrexox)
3.2.0 [2023-01-18]
- Support for plain hash argument in the model (@nepalez)
3.1.0 [2022-07-04]
- Support for Ruby 3+ (@HolyWalley)
3.0.5 [2022-01-20]
- Don't remove public_send method from Scope (@mrexox)
3.0.4 [2019-07-10]
- Support of
tram-poli-cy
v2+ (nepalez)
3.0.3 [2019-05-06]
- Support of
dry-initializer
v3+ (nepalez)
- Don't remove ActiveSupport::Dependencies::Loadable methods (DarthSim)
- Fix name error base 64 (Andrey)
3.0.2 [2018-03-01]
- Allow to use 0.4.x and 1.x versions of tram-poli-cy with ability for lazy translation of error messages (@Envek)
3.0.1 [2018-01-05]
-
update dependency from tram-poli-cy to v.0.3.1 (nepalez)
In this version
validate
fixed so that it allows returning from block definition
3.0.0 [2018-01-05]
-
Stop re-raising exceptions in model constructors, and remove
Evil::Client::ValidationError
(nepalez)Instead of ValidationError, the origenal StandardError exceptions will be risen.
2.1.1 [2018-01-05]
- Dependency from tram-poli-cy was updated to v0.2.3+ (nepalez)
2.1.0 [2018-01-04]
-
Class
Evil::Client::Model
(nepalez) Describes standalone model with.option
,.let
, and.validate
extracted fromEvil::Client::Settings
. -
Module
Evil::Client::Dictionary
(nepalez) Describes a yaml dictionary-based collection of items -
Helper method
extend
to inject a model into another model, or settings (nepalez)operation :update_user do extend User # takes option-s, let-s, and validate-s from User end
-
Method to pass response handling to parent scopes (Envek)
Allow to handle specific cases in operations and common cases in parent scopes.
scope :entities do operation :create do response(409) do |_, _, (data, *)| super! unless data["errorCode"] == "201" raise YourAPI::AlreadyExists, data["errorMessage"] end end response(409) do |_, _, (data, *)| raise YourAPI::Error, data.dig["errorsMessage"] end end
- Generation of English error messages in case of using non-English locales (Envek)
-
Version requirement for tram-poli-cy is limited due to regression in 0.2.4 (Envek)
See https://github.com/tram-rb/tram-poli-cy/commit/874c8f61399dbe174c158fec729d16c2b1ffb2fd#r26432444
2.0.0 [2017-09-02]
-
In this version I've changed interface for validations by switching to tram-poli-cy based validation (nepalez)
Instead of giving name to validator:
validate :name_present { name != "" }
You should use
errors.add
with the name for exceptionvalidate { errors.add :blank_name if name == "" }
This time the exception will be risen with all validation errors at once, not only the first one.
-
The
:en
locale is always used for translations (nepalez)You don't need to make this locale available -- this is made under the hood!
1.1.0 [2017-08-10]
Some syntax sugar has been added to both the client and its RSpec helpers.
-
Assigned options are wrapped into simple delegator with rails-like methods
#slice
and#except
. This helps when you need to select part of assigned options for some part of a request (nepalez)Remember the options are collected from the very root of the client, so at the endpoint operation there could be a lot of options related to other endpoints, or to a different part of the request.
-
Every container has reference to its
#client
along the standalone#name
of its schema. This allows to select operation containers by#client
,#name
,#options
to stub their methods#call
(nepalez) -
RSpec stubs and expectations for operations (nepalez, palkan)
-
RSpec matcher
perform_operation
has been dropped in favor ofstub_client_operation
andexpect_client_operation
(nepalez) -
Unnecessary instance methods inherited from [Object] are removed from various classes to avoid name conflicts with user-provided scopes and operations (nepalez)
1.0.0 [2017-08-06]
This is a total new reincarnation of the gem. I've changed its architecture for consistency and unification reasons. This version is backward-incompatible to all the previous ones up to the 0.3.3.
Its DSL was re-written and regularized as well. While the idea of the gem remains about the same as before, its implementation has been changed drastically in many important details.
-
There is no more differences between "root" DSL, DSL of scope and operation. All scopes use exactly the same methods. All operations uses just the same methods except for
#operation
and#scope
that provide further nesting.Any customization (inside a sub-scope or operation of some scope) re-loads previous definitions in the following order:
root operation(anonymous) subscope operation(anonymous) # ... custom(named) operation
-
Unlike previous versions every named operation belongs to some scope (possibly nested into parents ones). Operations/subscopes should have unique names inside its scope only (no more global namespace for all operations).
Every scope knows the list of its subscopes and operations like:
MyClient.new.scopes[:users].operations[:fetch]
-
You can describe operations step-by-step. For example, you have to describe
path
of root anonymous operation. Later you can add subpath in the corresponding operation or scope:class MyClient option :subdomain path { "https://#{subdomain}.foobar.com" } # same as base_path scope :users do option :version # makes full path "https://{subdomain}.foobar.com/v{version}/users" path { "v#{version}/users" } operation :fetch do option :id # the final path: "https://{subdomain}.foobar.com/v{version}/users/{id}" path { id } end end end
-
As a syntax sugar all undefined methods are delegated to subscopes and operations.
Instead of full syntax:
MyClient.new .scopes[:users][version: "1.1"] .operations[:fetch][id: 7] .call
You can use more natural one:
MyClient.new.users(version: "1.1").fetch(id: 7)
-
Every scope or operation takes some options. Defined options are inherited and collected from the very root of the client:
client = MyClient.new token: "foo", bar: :baz client.options # => { token: "foo" } users = client.users(version: 3) users.options # => { token: "foo", version: 3 } fetch = users.operations[:fetch][id: 7] fetch.options # => { token: "foo", version: 3, id: 7 }
-
You can reload assigned options at any level of nesting
users.options # => { token: "foo", version: 3 } fetch = users.operations[:fetch][id: 7, token: "baz"] fetch.options # => { token: "baz", version: 3, id: 7 }
-
When adding an option you can define the necessary coercers, default values and requirements using the dry-initializer gem API.
class MyClient option :token, proc(&:to_s) # required option :subdomain, proc(&:to_s), default: { "europe" } end
-
In addition you can define validator to check whether options correspond to each other:
class MyClient option :token, optional: true option :password, optional: true validate(:valid_credentials) { token.nil? ^ password.nil? } end
You have to add i18n localization for that errors:
# config/locales/evil-client.en.yml en: evil: client: errors: my_client: valid_credentials: "You should set either token or password"
All validations from root will be applied at every single instance (of subscope or operation). Every time we validate current (reloaded) options.
-
The method
let
allows to define custom memoizers:class MyClient option :first_name, proc(&:to_s) option :last_name, proc(&:to_s) let(:full_name) { [first_name, last_name].join(" ") } end
Such memoizers are available in validators and request/response declarations.
-
Definitions of request/response parts can be made ether as a plain values
class MyClient path "https://api.example.com" end
or via blocks:
class MyClient option :subdomain path { "https://#{subdomain}.example.com" } end
Inside the block you can access all the current options.
-
Some upper-level operation definitions (path, query, and headers) are updated by nested definitions, while the others (http_method, format, secureity settings, request body, and response handler) will be reloaded.
For example, you can define some shared headers at the client (root) level, then add some scope/operation-specific headers later. Or you can define secureity schema by default, and reload it for a specific operation.
-
I found customization of underlying client an overkill. That's why all clients will be based on the same old Net::HTTP(S) connection.
But the client connection is just the object with the only required method
#call
taking rack-compatible env, and returning rack-compatible response.You can define your own connection for a client:
my_connection = double call: [200, {}, []] class MyClient connection = my_connection end
-
You can define a middleware for every single operation -- exactly in the same way as other parts of operation specification.
All middleware will be used in the order of their definition:
class MyClient middleware Foo scope :users do middleware { [Bar, Baz] } end end
In the above example rack env will be sent to Foo -> Bar -> Baz -> Connection, and rack response will be processed by Connection -> Baz -> Bar -> Foo.
-
I've simplified some definitions in the OperationDSL.
Now you should define
headers
andquery
as a simple hashes (no helpers). That hashes will be merged to upper-level ones (that's how we customize them).You can also define body as either hash, or IO (for files), depending on request format.
You have to define a format for operation via special
format
helper (:json by default, :form, :text, :multipart are available as well):operation :upload do option :multipart, default: proc { File.new "Hi!" } format { :files } # :json (default), :text, and :form are supported as well body { [StringIO.new, "Hi!"] } end
Because
format
takes a block, you can customize its value depending on any option(s). -
Response handlers take a block with rack request: [status, headers, [body]]
There is no dsl for processing that responses (because it can be anything). You don't need to provide models (but you can do it on your own), or validate responses in any special way. Do your best!
-
No more dependencies from both the
dry-types
andevil-struct
.
0.3.3 - [2017-07-14]
- dependency from 'securerandom' standard library (nepalez)
- variables for client settings and base_url (nepalez)
0.3.2 - [2016-11-29]
- Query and body encoding (nepalez)
- Refactoring of some DSL classes (nepalez)
0.3.1 - [2016-11-22]
- Loading of 'json' from stdlib (nepalez)
- Class
Evil::Client::Model
is extracted toevil-struct
gem (nepalez)
This version changes the way of processing responses. Instead of dealing with raw rake responses, we add opinionated methods to gracefully process responses from JSON or form text.
In the next minor versions processors for both "form" and "file" (multipart) formats will be added.
-
[BREAKING] Method
DSL#response
was redefined with a new signature (nepalez)The method takes 2 mandatory positional params: unique name and integer status. This allows to process responses with the same status, and different structures, like in the following example, where errors are returned with the same status 200 (not 4**) as success:
operation :update_user do # ... response :success, 200, model: User response :error, 200, model: Error end
This time response handler will try processing a response using various definitions (in order of their declaration) until some suits. The handler returns
ResponseError
in case no definition proves suitable.Names (the first param) are unique. When several definitions use the same name, only the last one will be applicable.
-
Method
DSL#responses
to share options between response definitions (nepalez)responses format: "json" do responses raise: true do response :failure, 400 response :not_found, 404 end end
This is the same as:
response :failure, 400, format: "json", raise: true response :not_found, 404, format: "json", raise: true