Purple Client
Purple::Client is a small DSL that helps you describe HTTP APIs. You define a domain, paths, and response structures, and the library generates handy methods for interacting with your service.
Installation
Add the gem to your project:
bundle add purple-clientOr install it manually with:
gem install purple-clientUsage
Below are some basic examples of how to define requests and call them. Each
snippet defines a custom class that inherits from Purple::Client.
Simple GET request
class StatusClient < Purple::Client
domain 'https://api.example.com'
path :status do
response :ok do
body :default
end
root_method :status
end
end
# Performs GET https://api.example.com/status
StatusClient.statusPath with a dynamic parameter
class JobsClient < Purple::Client
domain 'https://api.example.com'
path :jobs do
path :job_id, is_param: true do
response :ok do
body id: Integer, name: String
end
root_method :job
end
end
end
# Performs GET https://api.example.com/jobs/123
JobsClient.job(123)Simple POST request
class Amocrm::Client < Purple::Client
domain 'https://www.amocrm.ru'
path :oauth2 do
path :access_token, method: :post do
root_method :access_token
params do |client_id:, client_secret:, redirect_uri:, code:, grant_type: :authorization_code|
{ client_id:, client_secret:, redirect_uri:, code:, grant_type: }
end
response :ok do
body(
token_type: String,
expires_in: Integer,
server_time: Integer,
access_token: String,
refresh_token: String
)
end
response :bad_request
end
end
endUsing authorization
class ProfileClient < Purple::Client
domain 'https://api.example.com'
authorization :bearer, 'TOKEN'
path :profile do
response :ok do
body :default
end
root_method :profile
end
end
# Authorization header will be sent automatically
ProfileClient.profileUsing custom headers
class CustomHeadersClient < Purple::Client
domain 'https://api.example.com'
authorization :custom_headers,
'X-API-KEY' => 'your-api-key',
'X-Secret' => 'your-api-secret'
path :widgets do
response :ok do
body :default
end
root_method :widgets
end
end
# Custom headers will be sent automatically
CustomHeadersClient.widgetsNested paths
class PostsClient < Purple::Client
domain 'https://api.example.com'
path :users do
path :user_id, is_param: true do
path :posts do
response :ok do
body [{ id: Integer, title: String }]
end
root_method :user_posts
end
end
end
end
# Performs GET https://api.example.com/users/7/posts
PostsClient.user_posts(user_id: 7)Paths nested under parameters
When a path segment is marked with is_param: true, any paths nested
inside it will not have a root_method generated. Instead of calling a
root method, you need to chain the segment methods manually.
class BrowserClient < Purple::Client
domain 'https://api.example.com'
path :browser do
path :id, is_param: true do
path :web, method: :post do
response :ok do
end
response :bad_request do
body do |res|
puts res
end
end
end
end
end
end
# root_method :web will not work here
BrowserClient.browser.id('123').webCallbacks with additional arguments
class EventsClient < Purple::Client
domain 'https://api.example.com'
additional_callback_arguments :resource
callback do |url, params, headers, response, resource|
StoreEvent.call(url:, params:, headers:, response:, resource:)
end
path :events do
response :ok do
body :default
end
root_method :events
end
end
resource = SomeModel.find(1)
EventsClient.events(resource:)additional_callback_arguments lets you specify parameter names that will be
extracted from the call and passed to your callback. In the example above the
resource keyword argument is removed from the request parameters, but is
available inside the callback so you can associate the stored event with a
record of your choice.
Boolean response types
If you have boolean types true or false in your response, use
Purple::Boolean in the response configuration.
class AccountsClient < Purple::Client
domain 'https://api.example.com'
path :accounts do
response :ok do
body(
last_name: String,
first_name: String,
is_creator: Purple::Boolean,
is_premium: Purple::Boolean,
)
end
root_method :accounts
end
endOptional fields
Sometimes an API response omits certain keys. You can mark those fields as optional in the body definition so their absence doesn't raise validation errors.
class CalendarClient < Purple::Client
domain 'https://api.example.com'
path :schedule do
response :ok do
body(
day: { type: Integer, optional: true },
)
end
root_method :schedule
end
end
# The `day` attribute may be missing in the response
CalendarClient.scheduleAllow blank fields
Some APIs return keys that are present but contain null or empty string values.
You can mark those fields with allow_blank so blank values do not raise
validation errors.
class ProfilesClient < Purple::Client
domain 'https://api.example.com'
path :profile do
response :ok do
body(
middle_name: { type: String, allow_blank: true },
)
end
root_method :profile
end
end
# The `middle_name` attribute may be blank or omitted in the response
ProfilesClient.profileArray responses
When an endpoint returns an array of objects, you can use :array_of to
describe the structure of each element in the array.
class MerchantsClient < Purple::Client
domain 'https://api.example.com'
path :merchants do
response :ok do
structure = {
id: Integer,
name: String,
address: String,
work_time: String,
accepts_qr: { type: String, optional: true }
}
body(:array_of, **structure)
end
root_method :merchants
end
end
# Each array element will be validated against the structure
MerchantsClient.merchantsResponse body processing
After the body structure is validated, you can supply a block to body
to transform or handle the parsed response. This is useful for mapping
error payloads to simpler return values or for normalizing data.
class MessagesClient < Purple::Client
domain 'https://api.example.com'
path :messages do
response :unprocessable_entity do
structure = {
status: Integer,
type: String,
title: String,
detail: String
}
body(**structure) do |res|
case res.type
when 'errors/invalid_recipient'
:not_found
else
res
end
end
end
root_method :send_message
end
end
# Returns :not_found when the recipient is invalid, otherwise returns the
# parsed response body.
MessagesClient.send_messageDevelopment
After checking out the repo, run bin/setup to install dependencies. Then run
rake spec to execute the tests. You can also run bin/console for an interactive
prompt to experiment with the library.
To install this gem onto your local machine, run bundle exec rake install.
To release a new version, update the version number in version.rb, then run
bundle exec rake release.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/purple-client. Contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.