Fixed various issue with URI template, added corresponding tests

This commit is contained in:
Jocelyn Fiat
2011-07-28 18:43:20 +02:00
parent 0da4b7d61b
commit 94d4909644
4 changed files with 187 additions and 20 deletions

View File

@@ -153,6 +153,8 @@ feature -- Builder
feature -- Match feature -- Match
match (a_uri: STRING): detachable URI_TEMPLATE_MATCH_RESULT match (a_uri: STRING): detachable URI_TEMPLATE_MATCH_RESULT
require
is_valid: is_valid
local local
b: BOOLEAN b: BOOLEAN
tpl: like template tpl: like template
@@ -163,6 +165,8 @@ feature -- Match
vv: STRING vv: STRING
l_vars, l_path_vars, l_query_vars: HASH_TABLE [STRING, STRING] l_vars, l_path_vars, l_query_vars: HASH_TABLE [STRING, STRING]
l_uri_count: INTEGER l_uri_count: INTEGER
tpl_count: INTEGER
l_next_literal_separator: detachable STRING
do do
--| Extract expansion parts "\\{([^\\}]*)\\}" --| Extract expansion parts "\\{([^\\}]*)\\}"
analyze analyze
@@ -173,8 +177,10 @@ feature -- Match
b := True b := True
l_uri_count := a_uri.count l_uri_count := a_uri.count
tpl := template tpl := template
tpl_count := tpl.count
if l_expressions.is_empty then if l_expressions.is_empty then
b := a_uri.substring (1, tpl.count).same_string (tpl) -- b := a_uri.substring (1, tpl_count).same_string (tpl)
b := a_uri.same_string (tpl)
else else
from from
l_expressions.start l_expressions.start
@@ -200,6 +206,8 @@ feature -- Match
b := s.same_string (t) b := s.same_string (t)
p := exp.end_position p := exp.end_position
end end
l_expressions.forth --| we forth `l_expressions' so be careful
--| Check related variable --| Check related variable
if b and then not vn.is_empty then if b and then not vn.is_empty then
if exp.is_query then if exp.is_query then
@@ -211,10 +219,21 @@ feature -- Match
inspect vn[1] inspect vn[1]
when '?' then when '?' then
import_form_style_parameters_into (a_uri.substring (q + l_offset + 1, l_uri_count), l_vars) import_form_style_parameters_into (a_uri.substring (q + l_offset + 1, l_uri_count), l_vars)
p := tpl_count + 1
l_offset := l_offset + (l_uri_count - (q + l_offset + 1))
when ';' then when ';' then
import_path_style_parameters_into (a_uri.substring (q + l_offset, l_uri_count), l_vars) import_path_style_parameters_into (a_uri.substring (q + l_offset, l_uri_count), l_vars)
p := tpl_count + 1
else else
vv := next_path_variable_value (a_uri, q + l_offset) if not l_expressions.after then
exp := l_expressions.item --| We change `exp' here
l_next_literal_separator := tpl.substring (p, exp.position -1)
elseif p < tpl_count then
l_next_literal_separator := tpl.substring (p, tpl_count)
else
l_next_literal_separator := Void
end
vv := next_path_variable_value (a_uri, q + l_offset, l_next_literal_separator)
l_vars.force (vv, vn) l_vars.force (vv, vn)
l_offset := l_offset + vv.count - (vn.count + 2) l_offset := l_offset + vv.count - (vn.count + 2)
end end
@@ -222,7 +241,17 @@ feature -- Match
b := exp.is_query --| query are optional b := exp.is_query --| query are optional
end end
end end
l_expressions.forth if b and l_expressions.after then
if
(p < tpl_count) or
(p + l_offset < l_uri_count)
then
--| Remaining literal part
t := tpl.substring (p, tpl_count)
s := a_uri.substring (p + l_offset, l_uri_count)
b := s.same_string (t)
end
end
end end
end end
if b then if b then
@@ -231,8 +260,36 @@ feature -- Match
end end
end end
feature -- Basic operation
parse
-- Parse template
do
reset
analyze
end
feature -- Status report
is_valid: BOOLEAN
-- Is Current URI template valid?
do
analyze
Result := not has_syntax_error
end
feature {NONE} -- Internal Access feature {NONE} -- Internal Access
reset
do
expressions := Void
has_syntax_error := False
end
has_syntax_error: BOOLEAN
-- Has syntax error
--| Make sense only if `analyze' was processed before
expressions: detachable LIST [URI_TEMPLATE_EXPRESSION] expressions: detachable LIST [URI_TEMPLATE_EXPRESSION]
-- Expansion parts -- Expansion parts
@@ -248,6 +305,7 @@ feature {NONE} -- Implementation
in_query: BOOLEAN in_query: BOOLEAN
x: STRING x: STRING
exp: URI_TEMPLATE_EXPRESSION exp: URI_TEMPLATE_EXPRESSION
l_has_query_expression: BOOLEAN
do do
l_expressions := expressions l_expressions := expressions
if l_expressions = Void then if l_expressions = Void then
@@ -258,6 +316,7 @@ feature {NONE} -- Implementation
from from
i := 1 i := 1
n := tpl.count n := tpl.count
l_has_query_expression := False
create x.make_empty create x.make_empty
until until
i > n i > n
@@ -269,6 +328,10 @@ feature {NONE} -- Implementation
l_expressions.force (exp) l_expressions.force (exp)
x.wipe_out x.wipe_out
in_x := False in_x := False
if l_has_query_expression and then i < n then
--| Remaining text after {?exp}
has_syntax_error := True
end
else else
x.extend (c) x.extend (c)
end end
@@ -278,8 +341,11 @@ feature {NONE} -- Implementation
check x_is_empty: x.is_empty end check x_is_empty: x.is_empty end
p := i p := i
in_x := True in_x := True
if not l_has_query_expression then
l_has_query_expression := tpl.valid_index (i+1) and then tpl[i+1] = '?'
end
if not in_query then if not in_query then
in_query := tpl.valid_index (i+1) and then tpl[i+1] = '?' in_query := l_has_query_expression
end end
when '?' then when '?' then
in_query := True in_query := True
@@ -344,23 +410,39 @@ feature {NONE} -- Implementation
end end
end end
next_path_variable_value (a_uri: STRING; a_index: INTEGER): STRING next_path_variable_value (a_uri: STRING; a_index: INTEGER; a_end_token: detachable STRING): STRING
require require
valid_index: a_index <= a_uri.count valid_index: a_index <= a_uri.count
local local
c: CHARACTER
i,n,p: INTEGER i,n,p: INTEGER
l_end_token_first_char: CHARACTER
l_end_token_count: INTEGER
do do
from from
if a_end_token /= Void and then not a_end_token.is_empty then
l_end_token_first_char := a_end_token.item (1)
l_end_token_count := a_end_token.count
end
i := a_index i := a_index
n := a_uri.count n := a_uri.count
until until
i > n i > n
loop loop
inspect a_uri[i] c := a_uri[i]
inspect c
when '/', '?' then when '/', '?' then
i := n i := n
else else
p := i if
a_end_token /= Void and then
c = l_end_token_first_char and then
a_uri.substring (i, i + l_end_token_count - 1).same_string (a_end_token)
then
i := n
else
p := i
end
end end
i := i + 1 i := i + 1
end end

View File

@@ -11,6 +11,7 @@ inherit
ANY ANY
DEBUG_OUTPUT DEBUG_OUTPUT
export {NONE} all end
URI_TEMPLATE_CONSTANTS URI_TEMPLATE_CONSTANTS
export {NONE} all end export {NONE} all end

View File

@@ -24,22 +24,75 @@ feature {NONE} -- Initialization
make (create {like path_variables}.make (0), create {like query_variables}.make (0)) make (create {like path_variables}.make (0), create {like query_variables}.make (0))
end end
feature -- Access
variable (n: STRING): detachable STRING feature -- Access
-- Value related to variable name `n'
do
Result := query_variables.item (n)
if Result = Void then
Result := path_variables.item (n)
end
end
path_variables: HASH_TABLE [STRING, STRING] path_variables: HASH_TABLE [STRING, STRING]
-- Variables being part of the path segments -- Variables being part of the path segments
query_variables: HASH_TABLE [STRING, STRING] query_variables: HASH_TABLE [STRING, STRING]
-- Variables being part of the query segments (i.e: after the ? ) -- Variables being part of the query segments (i.e: after the ?)
feature -- Query
path_variable (n: STRING): detachable STRING
-- Value related to query variable name `n'
do
Result := path_variables.item (n)
end
query_variable (n: STRING): detachable STRING
-- Value related to path variable name `n'
do
Result := query_variables.item (n)
end
variable (n: STRING): detachable STRING
-- Value related to variable name `n'
do
Result := query_variable (n)
if Result = Void then
Result := path_variable (n)
end
end
feature -- Query: url-decoded
url_decoded_query_variable (n: STRING): detachable STRING_32
-- Unencoded value related to variable name `n'
do
if attached query_variable (n) as v then
Result := url_decoded_string (v)
end
end
url_decoded_path_variable (n: STRING): detachable STRING_32
-- Unencoded value related to variable name `n'
do
if attached path_variable (n) as v then
Result := url_decoded_string (v)
end
end
url_decoded_variable (n: STRING): detachable STRING_32
-- Unencoded value related to variable name `n'
do
if attached variable (n) as v then
Result := url_decoded_string (v)
end
end
feature {NONE} -- Implementation
url_decoded_string (s: READABLE_STRING_GENERAL): STRING_32
do
Result := url_encoder.decoded_string (s.as_string_8)
end
url_encoder: URL_ENCODER
once
create Result
end
;note ;note
copyright: "2011-2011, Eiffel Software and others" copyright: "2011-2011, Eiffel Software and others"

View File

@@ -21,6 +21,9 @@ feature -- Test routines
do do
uri_template_parse ("api/foo/{foo_id}/{?id,extra}", <<"foo_id">>, <<"id", "extra">>) uri_template_parse ("api/foo/{foo_id}/{?id,extra}", <<"foo_id">>, <<"id", "extra">>)
uri_template_parse ("weather/{state}/{city}?forecast={day}", <<"state", "city">>, <<"day">>) uri_template_parse ("weather/{state}/{city}?forecast={day}", <<"state", "city">>, <<"day">>)
uri_template_parse ("/hello/{name}.{format}", <<"name", "format">>, <<>>)
uri_template_parse ("/hello.{format}/{name}", <<"format", "name">>, <<>>)
uri_template_parse ("/hello/Joce.{format}/foo{?foobar};crazy=IDEA", <<"name">>, <<"foobar">>)
end end
test_uri_template_matcher test_uri_template_matcher
@@ -49,6 +52,32 @@ feature -- Test routines
create tpl.make ("weather/{state}/{city}?forecast={day}") create tpl.make ("weather/{state}/{city}?forecast={day}")
uri_template_match (tpl, "weather/California/Goleta?forecast=today", <<["state", "California"], ["city", "Goleta"]>>, <<["day", "today"]>>) uri_template_match (tpl, "weather/California/Goleta?forecast=today", <<["state", "California"], ["city", "Goleta"]>>, <<["day", "today"]>>)
create tpl.make ("/hello")
uri_template_match (tpl, "/hello", <<>>, <<>>)
uri_template_mismatch (tpl, "/hello/Foo2") -- longer
uri_template_mismatch (tpl, "/hell") -- shorter
create tpl.make ("/hello.{format}")
uri_template_match (tpl, "/hello.xml", <<["format", "xml"]>>, <<>>)
uri_template_mismatch (tpl, "/hello.xml/Bar")
create tpl.make ("/hello.{format}/{name}")
uri_template_match (tpl, "/hello.xml/Joce", <<["format", "xml"], ["name", "Joce"]>>, <<>>)
create tpl.make ("/hello/{name}.{format}")
uri_template_match (tpl, "/hello/Joce.json", <<["name", "Joce"], ["format", "json"]>>, <<>>)
create tpl.make ("/hello/{name}.{format}/foo")
uri_template_match (tpl, "/hello/Joce.xml/foo", <<["name", "Joce"], ["format", "xml"]>>, <<>>)
uri_template_mismatch (tpl, "/hello/Joce.xml/fooBAR")
create tpl.make ("/hello/{name}.{format}/foo{?foo};crazy={idea}")
-- uri_template_match (tpl, "/hello/Joce.xml/foo", <<["name", "Joce"], ["format", "xml"]>>, <<>>)
uri_template_match (tpl, "/hello/Joce.xml/foo?foo=FOO", <<["name", "Joce"], ["format", "xml"]>>, <<["foo", "FOO"]>>)
uri_template_match (tpl, "/hello/Joce.xml/foo;crazy=IDEA", <<["name", "Joce"], ["format", "xml"]>>, <<["idea", "IDEA"], ["crazy", "IDEA"]>>)
end end
uri_template_string_errors: detachable LIST [STRING] uri_template_string_errors: detachable LIST [STRING]
@@ -544,6 +573,8 @@ feature -- Test routines
i: INTEGER i: INTEGER
do do
create u.make (s) create u.make (s)
u.parse
assert ("Template %""+ s +"%" is valid", u.is_valid)
if attached u.path_variable_names as vars then if attached u.path_variable_names as vars then
matched := vars.count = path_vars.count matched := vars.count = path_vars.count
from from
@@ -559,7 +590,7 @@ feature -- Test routines
else else
matched := path_vars.is_empty matched := path_vars.is_empty
end end
assert ("path variables matched", matched) assert ("path variables matched for %""+ s +"%"", matched)
if attached u.query_variable_names as vars then if attached u.query_variable_names as vars then
matched := vars.count = query_vars.count matched := vars.count = query_vars.count
@@ -576,7 +607,7 @@ feature -- Test routines
else else
matched := query_vars.is_empty matched := query_vars.is_empty
end end
assert ("query variables matched", matched) assert ("query variables matched %""+ s +"%"", matched)
end end
uri_template_mismatch (a_uri_template: URI_TEMPLATE; a_uri: STRING) uri_template_mismatch (a_uri_template: URI_TEMPLATE; a_uri: STRING)