-
Notifications
You must be signed in to change notification settings - Fork 204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Respond_with uri extensions path.[json|xml|text|txt|html] #367
Changes from 4 commits
27b082c
6c26a1c
a4b04e9
f9eae91
d391c70
d8fbfdf
dce4065
cfe11a1
606f906
d2eac6f
b037006
a03ed80
1452b8b
25b972e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -83,6 +83,39 @@ module Amber::Controller | |
context.request.headers["Accept"] = "text/plain" | ||
ResponsesController.new(context).index.should eq expected_result | ||
end | ||
|
||
|
||
it "responds with json for path.json" do | ||
expected_result = %({"type":"json","name":"Amberator"}) | ||
context.request.path = "/response/1.json" | ||
ResponsesController.new(context).index.should eq expected_result | ||
end | ||
|
||
it "responds with xml for path.xml" do | ||
expected_result = "<xml><body><h1>Sort of xml</h1></body></xml>" | ||
context.request.path = "/response/1.xml" | ||
ResponsesController.new(context).index.should eq expected_result | ||
end | ||
|
||
it "responds with text for path.txt" do | ||
expected_result = "Hello I'm text!" | ||
context.request.path = "/response/1.txt" | ||
ResponsesController.new(context).index.should eq expected_result | ||
end | ||
|
||
it "responds with text for path.text" do | ||
expected_result = "Hello I'm text!" | ||
context.request.path = "/response/1.text" | ||
ResponsesController.new(context).index.should eq expected_result | ||
end | ||
|
||
it "responds with 406 for path.text when text hasn't been defined" do | ||
expected_result = "Response unexceptable." | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Response Not Acceptable" There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
context.request.path = "/response/1.text" | ||
ResponsesController.new(context).show.should eq expected_result | ||
context.response.status_code.should eq 406 | ||
end | ||
|
||
end | ||
|
||
describe "#render" do | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,16 @@ | ||
module Amber::Controller::Helpers | ||
module Responders | ||
class Content | ||
TYPE = {html: "text/html", json: "application/json", text: "text/plain", xml: "application/xml"} | ||
class Response | ||
TYPE = { | ||
html: "text/html", | ||
json: "application/json", | ||
txt: "text/plain", | ||
text: "text/plain", | ||
xml: "application/xml", | ||
} | ||
|
||
REGEX = /\.(#{TYPE.keys.join("|")})$/ | ||
|
||
@requested_responses : Array(String) | ||
@available_responses = Hash(String, String).new | ||
@type : String? = nil | ||
|
@@ -35,13 +44,14 @@ module Amber::Controller::Helpers | |
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move def type
@type ||= select_type.to_s
end I like the convention of def property_name
@property_name ||= calculate_property_name
end Of course, 'calculate' can be 'select' or 'generate' - what ever verb makes sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would have done that but I needed to return @type.to_s so that @type wouldn't have the possibility of being Nil. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did come up with a solution here that I think we'll both like. |
||
|
||
def body | ||
@available_responses[type] | ||
@available_responses[type]? | ||
end | ||
|
||
private def select_type | ||
@type ||= begin | ||
raise "You must define at least one response_type." if @available_responses.empty? | ||
@requested_responses << @available_responses.keys.first | ||
# NOTE: If only one response is requested don't return something else. | ||
@requested_responses << @available_responses.keys.first if @requested_responses.size != 1 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, this is "non-strict" mode - where even if they didn't explicitly request the type we return the first available response (if they had more than one thing in the 'accept' header), correct? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I feel that if json is the only thing available it should be return even if the browser is asking for html, it seems like a different case if someone asks for |
||
@requested_responses.find do |resp| | ||
@available_responses.keys.includes?(resp) | ||
end | ||
|
@@ -51,24 +61,25 @@ module Amber::Controller::Helpers | |
|
||
private def requested_responses | ||
req_responses = Array(String).new | ||
|
||
if (accept = context.request.headers["Accept"]?) && !accept.empty? | ||
if (ext = request.path.match(Response::REGEX).try(&.[1])) | ||
req_responses << Response::TYPE[ext] | ||
elsif (accept = context.request.headers["Accept"]?) && !accept.empty? | ||
accepts = accept.split(";").first?.try(&.split(/,|,\s/)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make this regex There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To me 4 regex characters are easier to read than tracking down the definition of a constant, but sure. |
||
req_responses.concat(accepts) if accepts.is_a?(Array) && accepts.any? | ||
end | ||
req_responses | ||
end | ||
|
||
protected def respond_with(&block) | ||
content = with Content.new(requested_responses) yield | ||
content = with Response.new(requested_responses) yield | ||
if content.body | ||
set_response(body: content.body, status_code: 200, content_type: content.type) | ||
else | ||
set_response(body: "Response unexceptable.", status_code: 406, content_type: Content::TYPE[:text]) | ||
set_response(body: "Response unexceptable.", status_code: 406, content_type: Response::TYPE[:text]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be - "Response Not Acceptable" ? |
||
end | ||
end | ||
|
||
private def set_response(body, status_code = 200, content_type = Content::TYPE[:html]) | ||
private def set_response(body, status_code = 200, content_type = Response::TYPE[:html]) | ||
context.response.status_code = status_code | ||
context.response.content_type = content_type | ||
context.content = body | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -78,7 +78,13 @@ module Amber::Router | |
end | ||
|
||
def merge_route_params | ||
route_params.each { |k, v| params[k] = v } | ||
route_params.each do |k, v| | ||
if k == route_params.keys.last | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 2 lines should probably be in its own method, called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it don't remove these then paths like |
||
params[k] = v.sub(Controller::Base::Response::REGEX, "") | ||
else | ||
params[k] = v | ||
end | ||
end | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For line 90, And changing the value at |
||
|
||
def route_params | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,7 +81,12 @@ module Amber | |
end | ||
|
||
def match(http_verb, resource) : Radix::Result(Amber::Route) | ||
@routes.find build_node(http_verb, resource) | ||
result = @routes.find build_node(http_verb, resource) | ||
if result.found? | ||
result | ||
else | ||
@routes.find build_node(http_verb, resource.sub(/\.[^$\/]+$/, "")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this regex be named with a constant to indicate what it is for? It is meant to chop off the extension, right? .json or .text, etc. |
||
end | ||
end | ||
|
||
private def merge_params(params, context) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test for an invalid (unsupported) extension?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I'll commit that in a second. I already have a test for a supported but undefined extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.