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.
Add the gem to your project:
bundle add purple-client
Or install it manually with:
gem install purple-client
Below are some basic examples of how to define requests and call them. Each
snippet defines a custom class that inherits from Purple::Client
.
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.status
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)
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.profile
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.widgets
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)
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').web
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.
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
end
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.schedule
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.profile
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.merchants
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_message
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
.
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.
The gem is available as open source under the terms of the MIT License.