Skip to content

Commit

Permalink
Add support for additional derived components
Browse files Browse the repository at this point in the history
@target-uri, @scheme, @request-target, @query and @query-param.
  • Loading branch information
nomadium committed Apr 1, 2024
1 parent c90998b commit 2b0698b
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 8 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## [Unreleased]

## [0.5.1] - 2024-04-01

- Add support for additional derived components:
@target-uri, @scheme, @request-target, @query and @query-param.

## [0.5.0] - 2024-03-30

- Build Linzer::Message instances from Rack request and response objects
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
linzer (0.5.0)
linzer (0.5.1)
ed25519 (~> 1.3, >= 1.3.0)
rack (~> 3.0)
starry (~> 0.1)
Expand Down
37 changes: 31 additions & 6 deletions lib/linzer/message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,33 @@ def field?(f)
!!self[f]
end

DERIVED_COMPONENT = {
"@method" => :request_method,
"@authority" => :authority,
"@path" => :path_info,
"@status" => :status,
"@target-uri" => :url,
"@scheme" => :scheme,
"@request-target" => :fullpath,
"@query" => :query_string
}.freeze

def [](field_name)
if !field_name.start_with?("@")
return @operation.env[Request.rack_header_name(field_name)] if request?
return @operation.headers[field_name] # if response?
end

method = DERIVED_COMPONENT[field_name]

case field_name
when "@method" then @operation.request_method
when "@authority" then @operation.authority
when "@path" then @operation.path_info
when "@status" then @operation.status
else # XXX: improve this and add support for all fields in the RFC
raise Error.new "Unknown/unsupported field: \"#{field_name}\""
when "@query"
return "?#{@operation.public_send(method)}"
when /\A(?<field>(?<prefix>@query-param)(?<rest>;name=.+)\Z)/
return parse_query_param Regexp.last_match
end

method ? @operation.public_send(method) : nil
end

class << self
Expand All @@ -48,5 +61,17 @@ def parse_structured_dictionary(str, field_name = nil)
raise Error.new "Cannot parse \"#{field_name}\" field. Bailing out!"
end
end

private

def parse_query_param(match_data)
raw_item = '"%s"%s' % [match_data[:prefix], match_data[:rest]]
parsed_item = Starry.parse_item(raw_item)
fail unless parsed_item.value == "@query-param"
param_name = URI.decode_uri_component(parsed_item.parameters["name"])
URI.encode_uri_component(@operation.params.fetch(param_name))
rescue => _
nil
end
end
end
2 changes: 1 addition & 1 deletion lib/linzer/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Linzer
VERSION = "0.5.0"
VERSION = "0.5.1"
end
84 changes: 84 additions & 0 deletions spec/message_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,90 @@
expect(message["@status"]).to eq(202)
end

it "returns the full target URI for a request" do
server = "www.example.org"
scheme = "http"
path = "/target/example"
headers = {"Host" => server}
request = Linzer.new_request(:get, path, {}, headers)
request.env["rack.url_scheme"] = scheme
expected_target_uri = "#{scheme}://#{server}#{path}"
message = described_class.new(request)
expect(message["@target-uri"]).to eq(expected_target_uri)
end

it "returns the scheme of the target URI for a request" do
scheme = "https"
path = "/target/example2"
request = Linzer.new_request(:get, path, {}, {})
request.env["rack.url_scheme"] = scheme
message = described_class.new(request)
expect(message["@scheme"]).to eq(scheme)
end

it "returns the request target" do
path = "/path"
request = Linzer.new_request(:post, path, {}, {})
query_string = "param=value"
request.env["QUERY_STRING"] = query_string
message = described_class.new(request)
expected_target = "#{path}?#{query_string}"
expect(message["@request-target"]).to eq(expected_target)
end

it "returns the query portion of the target URI for a request, example 1" do
path = "/path"
request = Linzer.new_request(:get, path, {}, {})
query_string = "param=value&foo=bar&baz=bat%2Dman"
request.env["QUERY_STRING"] = query_string
message = described_class.new(request)
expected_query = "?#{query_string}"
expect(message["@query"]).to eq(expected_query)
end

it "returns the query portion of the target URI for a request, example 2" do
path = "/path"
request = Linzer.new_request(:get, path, {}, {})
query_string = "queryString"
request.env["QUERY_STRING"] = query_string
message = described_class.new(request)
expected_query = "?#{query_string}"
expect(message["@query"]).to eq(expected_query)
end

it "returns the query portion of the target URI for a request, example 3" do
path = "/path"
request = Linzer.new_request(:get, path, {}, {})
message = described_class.new(request)
expected_query = "?"
expect(message["@query"]).to eq(expected_query)
end

it "returns query parameter of the request target URI" do
path = "/path"
query_string = "param=value&foo=bar&baz=batman&qux="
request = Linzer.new_request(:get, path, {}, {})
request.env["QUERY_STRING"] = query_string
message = described_class.new(request)
expect(message["@query-param;name=\"baz\""]).to eq("batman")
expect(message["@query-param;name=\"qux\""]).to eq("")
expect(message["@query-param;name=\"param\""]).to eq("value")
end

it "returns parsed and encoded query parameter of the request target URI" do
path = "/parameters"
query_string = "var=this%20is%20a%20big%0Amultiline%20value&bar=with+plus+whitespace&fa%C3%A7ade%22%3A%20=something"
request = Linzer.new_request(:get, path, {}, {})
request.env["QUERY_STRING"] = query_string
message = described_class.new(request)
expected_var_value = "this%20is%20a%20big%0Amultiline%20value"
expected_bar_value = "with%20plus%20whitespace"
expected_facade_value = "something"
expect(message["@query-param;name=\"var\""]).to eq(expected_var_value)
expect(message["@query-param;name=\"bar\""]).to eq(expected_bar_value)
expect(message["@query-param;name=\"fa%C3%A7ade%22%3A%20\""]).to eq(expected_facade_value)
end

it "returns null on undefined field on request" do
request = Linzer.new_request(:put, "/bar", {}, {"x-foo" => "baz"})
message = described_class.new(request)
Expand Down

0 comments on commit 2b0698b

Please sign in to comment.