In the wise words of the modern poet Ice Cube:
So come on and chickity check yo self before you wreck yo self
Responsible is a library that makes it simple, fast an easy to check HTTP responses in crystal-lang.
It provides a lightweight API for dealing with errors, logging and static, type-safe parsing.
It works directly with HTTP::Client::Response
objects and can function in tandem with clients that build on top of these.
Third party libraries (like halite, crest and others) are supported too.
Responsible is not a HTTP client. It does not handle request building, execution, retries, or authentication. It deals purely with response objects once you already have them, regardless of their source.
-
Add the dependency to your
shard.yml
:dependencies: responsible: github: place-labs/responsible
-
Run
shards install
-
Load via:
require "responsible"
To become responsible, prefix any response or method that returns a response with ~
.
response = ~HTTP::Client.get("https://www.example.com")
This allows a clear way to express what should happen when errors occur.
response.on_server_error do
# oh noes
end
When working with JSON, it also lets you efficiently parse to type-safe objects.
response = ~HTTP::Client.get("https://www.example.com") >> NamedTuple(message: String)
response[:message]
# => hello world
A small selection of macros are also provided to keep common tasks clean, clear and concise.
def example_request_wrapper : { message: String }
Responsible.parse_to_return_type do
HTTP::Client.get("https://www.example.com")
end
end
Responsible Responses™ maintain all existing functionality of a vanilla response object.
chuck_norris_response = ~HTTP::Client.get("https://api.chucknorris.io/jokes/random")
chuck_norris_response.body
# =>
{
"categories": [],
"created_at": "2020-01-05 13:42:19.897976",
"icon_url": "https://assets.chucknorris.host/img/avatar/chuck-norris.png",
"id": "h6EF5PXvQoK5cW60-lUeDg",
"updated_at": "2020-01-05 13:42:19.897976",
"url": "https://api.chucknorris.io/jokes/h6EF5PXvQoK5cW60-lUeDg",
"value": "Chuck Norris has a large, curved talon on each foot, much like the Velociraptor."
}
They also let you inject behaviour for dealing with different response scenarios.
# Global handlers - this will apply to every reponse
Responsible.on_client_error do |response|
raise "I'm a teapot" if response.status_code == 418
end
# Local action - these apply to the target response
chuck_norris_response.on_redirect do |response|
my_request_method(response.headers["Location"])
end
To parse to a matching type use the >>
operator.
struct ChuckNorrisFact
include JSON::Serializable
getter created_at : Time
getter id : String
getter updated_at : Time
getter value : String
end
fact = chuck_norris_response >> ChuckNorrisFact
fact.value
# => Chuck Norris has a large, curved talon on each foot, much like the Velociraptor.
This also works inline for terse, type-safe, error checked responses.
fact = ~HTTP::Client.get("https://api.chucknorris.io/jokes/random") >> NamedTuple(value: String)
fact[:value]
# => Chuck Norris can binary search unsorted data.
If the response format is incompatible with the specified type a Responsible::Error
will raise.
This contains the parser exception via error.cause
.
To return nil
in place of a raising an error, use a nilable target type.
chuck_norris_response >> Float64?
# => nil
If the operator overloading is too terse, named methods are available.
To wrap a response object:
response = HTTP::Client.get("www.example.com")
response = Responsible::Response.new(response)
This performs the same action as using the ~
operator.
To parse into a type:
response.parse_to(MyType)
This is the same behaviour as >>
.
An optional block argument supports defining custom parser error handling.
response.parse_to(MyType) do |exception|
# Handle, raise or re-try as appropriate.
end
To return nil
in case of a parser error, use:
response.parse_to?(MaybeMyType)
Responsible works around a minimal response object interface.
When you require "responsible"
, support loads for HTTP::Client::Response
objects by default.
To support third party response objects use the Responsible.support
macro.
require "Halite"
Responsible.support Halite::Response
response = ~Halite.get("https://www.example.com") >> NamedTuple(message: String)
response[:message]
- Kim Burgess - creator and maintainer