Skip to content

Commit

Permalink
feat: provide parse_to and parse_to? macros
Browse files Browse the repository at this point in the history
  • Loading branch information
kimburgess committed Jun 10, 2020
1 parent 2794937 commit b7f1dce
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ response[:message]
# => hello world
```

Macros are also provided to keep common tasks clean, clear and concise.
A small selection of macros are also provided to keep common tasks clean, clear and concise.
```crystal
def example_request_wrapper : { message: String }
Responsible.parse_to_return_type do
Expand Down
44 changes: 44 additions & 0 deletions spec/responsible_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
require "./spec_helper"

def test_get(url : String, type : T.class) : T forall T
Responsible.parse_to_return_type do
HTTP::Client.get(url)
end
end

describe Responsible do
it "supports registering global handlers" do
WebMock.stub(:get, "www.example.com")
Expand Down Expand Up @@ -32,4 +38,42 @@ describe Responsible do
error.should be_true
success.should be_false
end

describe ".parse_to" do
it "supports wrapping an expression" do
WebMock.stub(:get, "www.example.com").to_return(
headers: { "Content-Type" => "application/json" },
body: %({"value":"foo"})
)
result = Responsible.parse_to(NamedTuple(value: String)) do
HTTP::Client.get("www.example.com")
end
result[:value].should eq("foo")
end
end

describe ".parse_to?" do
it "returns nil for incompatible types" do
WebMock.stub(:get, "www.example.com").to_return(
headers: { "Content-Type" => "application/json" },
body: %(42)
)
result = Responsible.parse_to?(Bool) do
HTTP::Client.get("www.example.com")
end
result.should be_nil
end
end

describe ".parse_to_return_type" do
it "supports wrapping an expression" do
WebMock.stub(:get, "www.example.com").to_return(
headers: { "Content-Type" => "application/json" },
body: %({"a":"foo","b":42})
)
result = test_get("www.example.com", NamedTuple(a: String, b: Int32))
result[:a].should eq("foo")
end
end

end
28 changes: 25 additions & 3 deletions src/responsible.cr
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ module Responsible
end
end

# Wraps a expression that returns a supported response object into a
# `Responsible::Response` and attempts to parse this into the specified type.
macro parse_to(type, ignore_response_code = false, &block)
begin
%response = begin
{{ block.body }}
end
Responsible::Response.new(%response).parse_to({{ type.id }}, {{ ignore_response_code.id }})
end
end

# Wraps a expression that returns a supported response object into a
# `Responsible::Response` and parse this into the specified type, or nil if
# not compatible.
macro parse_to?(type, ignore_response_code = false, &block)
begin
%response = begin
{{ block.body }}
end
Responsible::Response.new(%response).parse_to?({{ type.id }}, {{ ignore_response_code.id }})
end
end

# Wraps a expression that returns a supported response object into a
# `Responsible::Response` before attempting to parse this out into the return
# type of the surrounding method.
Expand All @@ -41,12 +64,11 @@ module Responsible
# end
# end
# ```
macro parse_to_return_type(&block)
macro parse_to_return_type(ignore_response_code = false, &block)
\{{ raise "no return type on method" if @def.return_type.is_a? Nop }}
%response = begin
Responsible.parse_to(\{{ @def.return_type }}, {{ ignore_response_code}}) do
{{ block.body }}
end
Responsible::Response.new(%response).parse_to(\{{ @def.return_type }})
end
end

Expand Down
8 changes: 4 additions & 4 deletions src/responsible/response.cr
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,15 @@ class Responsible::Response
# Parses the contents of this response to the type *x*, or raises an
# `Responsible::Error` if this is not possible.
def parse_to(x : T.class, ignore_response_code = @in_handler) : T forall T
parse_to(x, ignore_response_code) do |error|
self.parse_to(x, ignore_response_code) do |error|
raise Error.new "Could not parse to #{T}: #{error.message}", cause: error
end
end

# Parses the contents of this response to the type *x*, or nil if this is not
# possible.
def parse_to?(x : T.class, ignore_response_code = @in_handler) : T? forall T
parse_to(x, ignore_response_code) do
self.parse_to(x, ignore_response_code) do
nil
end
end
Expand All @@ -91,9 +91,9 @@ class Responsible::Response
# otherwise a `Responsible::Error` will be raised if parsing is not possible.
def >>(x : T.class) : T forall T
{% if T.nilable? %}
parse_to? x, ignore_response_code: true
self.parse_to? x, ignore_response_code: true
{% else %}
parse_to x
self.parse_to x
{% end %}
end
end

0 comments on commit b7f1dce

Please sign in to comment.