From ba89afc5bc7ce35124ace29bcfbcde08a4ea4d4a Mon Sep 17 00:00:00 2001 From: Kim Burgess Date: Thu, 4 Jun 2020 17:13:16 +1000 Subject: [PATCH] feat: add support for third-party request libs --- README.md | 16 ++++++++++++++++ src/core_ext/http/client/response.cr | 8 -------- src/responsible.cr | 10 +++++++++- src/responsible/response.cr | 12 +++++++----- src/responsible/response_interface.cr | 18 ++++++++++++++++++ 5 files changed, 50 insertions(+), 14 deletions(-) delete mode 100644 src/core_ext/http/client/response.cr create mode 100644 src/responsible/response_interface.cr diff --git a/README.md b/README.md index 86fb58e..a0ffa71 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ In the wise words of the modern poet Ice Cube: 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](https://github.com/icyleaf/halitea), [crest](https://github.com/mamantoha/crest) and others) are [supported](#third-party-support) too! ## What this isn't @@ -149,6 +150,21 @@ To return `nil` in case of a parser error, use: response.parse_to?(MaybeMyType) ``` +## Third-party support + +Responsible works around a [minimal response object interface](./src/responsible/response_interface.cr). +When you `require "responsible"`, support loads for `HTTP::Client::Response` objects by default. +To support third party response objects use the `Responsible.support` macro. +```crystal +require "Halite" + +Responsible.support Halite::Response + +response = ~Halite.get("https://www.example.com") >> NamedTuple(message: String) +response[:message] +``` + + ## Contributors - [Kim Burgess](https://github.com/KimBurgess) - creator and maintainer diff --git a/src/core_ext/http/client/response.cr b/src/core_ext/http/client/response.cr deleted file mode 100644 index 0729a34..0000000 --- a/src/core_ext/http/client/response.cr +++ /dev/null @@ -1,8 +0,0 @@ -require "../../../responsible/response" - -class HTTP::Client::Response - # Wraps `self` into a `Responsible::Response`. - def ~ - Responsible::Response.new(self) - end -end diff --git a/src/responsible.cr b/src/responsible.cr index b3fb664..af209a2 100644 --- a/src/responsible.cr +++ b/src/responsible.cr @@ -1,5 +1,5 @@ require "./responsible/**" -require "./core_ext/**" +require "http/client/response" module Responsible HANDLERS = {} of ResponseType => Action @@ -10,4 +10,12 @@ module Responsible HANDLERS[ResponseType::{{type}}] = block end {% end %} + + macro support(response_type) + class {{response_type.id}} + include Responsible::ResponseInterface + end + end end + +Responsible.support HTTP::Client::Response diff --git a/src/responsible/response.cr b/src/responsible/response.cr index b612fcb..3e3f139 100644 --- a/src/responsible/response.cr +++ b/src/responsible/response.cr @@ -1,9 +1,9 @@ -require "http/client/response" require "json" +require "./response_interface" require "./response_type" class Responsible::Response - @response : HTTP::Client::Response + @response : ResponseInterface @type : ResponseType @in_handler : Bool = false @@ -48,10 +48,12 @@ class Responsible::Response def parse_to(x : T.class, ignore_response_code = @in_handler, &block : Exception -> U) : T | U forall T, U raise Error.from(self) unless success? || ignore_response_code - case headers["content-type"] - when .starts_with? "application/json" + content_type = headers["content-type"].split(' ').first.downcase + + case content_type + when "application/json" begin - T.from_json(body || body_io) + T.from_json(@response.body? || @response.body_io) rescue e yield e end diff --git a/src/responsible/response_interface.cr b/src/responsible/response_interface.cr new file mode 100644 index 0000000..2b06546 --- /dev/null +++ b/src/responsible/response_interface.cr @@ -0,0 +1,18 @@ +require "http/status" +require "http/headers" +require "./response" + +module Responsible::ResponseInterface + abstract def status : HTTP::Status + + abstract def headers : HTTP::Headers + + abstract def body? : String? + + abstract def body_io : IO + + # Wraps `self` into a `Responsible::Response`. + def ~ + Responsible::Response.new(self) + end +end