//! Tests for URI template. #![cfg(feature = "alloc")] #[macro_use] mod utils; use std::cell::Cell; use iri_string::spec::UriSpec; use iri_string::template::context::{Context, DynamicContext, Visitor}; use iri_string::template::simple_context::{SimpleContext, Value}; use iri_string::template::UriTemplateStr; /// Returns the context used by examples in RFC 6570 section 3.2. fn rfc6570_context() -> SimpleContext { let mut ctx = SimpleContext::new(); ctx.insert( "count", Value::List(vec!["one".to_owned(), "two".to_owned(), "three".to_owned()]), ); ctx.insert( "dom", Value::List(vec!["example".to_owned(), "com".to_owned()]), ); ctx.insert("dub", Value::String("me/too".to_owned())); ctx.insert("hello", Value::String("Hello World!".to_owned())); ctx.insert("half", Value::String("50%".to_owned())); ctx.insert("var", Value::String("value".to_owned())); ctx.insert("who", Value::String("fred".to_owned())); ctx.insert("base", Value::String("http://example.com/home/".to_owned())); ctx.insert("path", Value::String("/foo/bar".to_owned())); ctx.insert( "list", Value::List(vec![ "red".to_owned(), "green".to_owned(), "blue".to_owned(), ]), ); ctx.insert( "keys", Value::Assoc(vec![ ("semi".to_owned(), ";".to_owned()), ("dot".to_owned(), ".".to_owned()), ("comma".to_owned(), ",".to_owned()), ]), ); ctx.insert("v", Value::String("6".to_owned())); ctx.insert("x", Value::String("1024".to_owned())); ctx.insert("y", Value::String("768".to_owned())); ctx.insert("empty", Value::String("".to_owned())); ctx.insert("empty_keys", Value::Assoc(vec![])); ctx.insert("undef", Value::Undefined); ctx } /// Expression and expected expansion. const SUCCESS_CASES: &[(&str, &str)] = &[ // Section 3.2.1. Variable Expansion. ("{count}", "one,two,three"), ("{count*}", "one,two,three"), ("{/count}", "/one,two,three"), ("{/count*}", "/one/two/three"), ("{;count}", ";count=one,two,three"), ("{;count*}", ";count=one;count=two;count=three"), ("{?count}", "?count=one,two,three"), ("{?count*}", "?count=one&count=two&count=three"), ("{&count*}", "&count=one&count=two&count=three"), // Section 3.2.2. Simple String Expansion. ("{var}", "value"), ("{hello}", "Hello%20World%21"), ("{half}", "50%25"), ("O{empty}X", "OX"), ("O{undef}X", "OX"), ("{x,y}", "1024,768"), ("{x,hello,y}", "1024,Hello%20World%21,768"), ("?{x,empty}", "?1024,"), ("?{x,undef}", "?1024"), ("?{undef,y}", "?768"), ("{var:3}", "val"), ("{var:30}", "value"), ("{list}", "red,green,blue"), ("{list*}", "red,green,blue"), ("{keys}", "semi,%3B,dot,.,comma,%2C"), ("{keys*}", "semi=%3B,dot=.,comma=%2C"), // Section 3.2.3. Reserved Expansion. ("{+var}", "value"), ("{+hello}", "Hello%20World!"), ("{+half}", "50%25"), ("{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex"), ("{+base}index", "http://example.com/home/index"), ("O{+empty}X", "OX"), ("O{+undef}X", "OX"), ("{+path}/here", "/foo/bar/here"), ("here?ref={+path}", "here?ref=/foo/bar"), ("up{+path}{var}/here", "up/foo/barvalue/here"), ("{+x,hello,y}", "1024,Hello%20World!,768"), ("{+path,x}/here", "/foo/bar,1024/here"), ("{+path:6}/here", "/foo/b/here"), ("{+list}", "red,green,blue"), ("{+list*}", "red,green,blue"), ("{+keys}", "semi,;,dot,.,comma,,"), ("{+keys*}", "semi=;,dot=.,comma=,"), // Section 3.2.4. Fragment Expansion. ("{#var}", "#value"), ("{#hello}", "#Hello%20World!"), ("{#half}", "#50%25"), ("foo{#empty}", "foo#"), ("foo{#undef}", "foo"), ("{#x,hello,y}", "#1024,Hello%20World!,768"), ("{#path,x}/here", "#/foo/bar,1024/here"), ("{#path:6}/here", "#/foo/b/here"), ("{#list}", "#red,green,blue"), ("{#list*}", "#red,green,blue"), ("{#keys}", "#semi,;,dot,.,comma,,"), ("{#keys*}", "#semi=;,dot=.,comma=,"), // Section 3.2.5. Label Expansion with Dot-Prefix. ("{.who}", ".fred"), ("{.who,who}", ".fred.fred"), ("{.half,who}", ".50%25.fred"), ("www{.dom*}", "www.example.com"), ("X{.var}", "X.value"), ("X{.empty}", "X."), ("X{.undef}", "X"), ("X{.var:3}", "X.val"), ("X{.list}", "X.red,green,blue"), ("X{.list*}", "X.red.green.blue"), ("X{.keys}", "X.semi,%3B,dot,.,comma,%2C"), ("X{.keys*}", "X.semi=%3B.dot=..comma=%2C"), ("X{.empty_keys}", "X"), ("X{.empty_keys*}", "X"), // Section 3.2.6. Path Segment Expansion. ("{/who}", "/fred"), ("{/who,who}", "/fred/fred"), ("{/half,who}", "/50%25/fred"), ("{/who,dub}", "/fred/me%2Ftoo"), ("{/var}", "/value"), ("{/var,empty}", "/value/"), ("{/var,undef}", "/value"), ("{/var,x}/here", "/value/1024/here"), ("{/var:1,var}", "/v/value"), ("{/list}", "/red,green,blue"), ("{/list*}", "/red/green/blue"), ("{/list*,path:4}", "/red/green/blue/%2Ffoo"), ("{/keys}", "/semi,%3B,dot,.,comma,%2C"), ("{/keys*}", "/semi=%3B/dot=./comma=%2C"), // Section 3.2.7. Path-Style Parameter Expansion. ("{;who}", ";who=fred"), ("{;half}", ";half=50%25"), ("{;empty}", ";empty"), ("{;v,empty,who}", ";v=6;empty;who=fred"), ("{;v,bar,who}", ";v=6;who=fred"), ("{;x,y}", ";x=1024;y=768"), ("{;x,y,empty}", ";x=1024;y=768;empty"), ("{;x,y,undef}", ";x=1024;y=768"), ("{;hello:5}", ";hello=Hello"), ("{;list}", ";list=red,green,blue"), ("{;list*}", ";list=red;list=green;list=blue"), ("{;keys}", ";keys=semi,%3B,dot,.,comma,%2C"), ("{;keys*}", ";semi=%3B;dot=.;comma=%2C"), // Section 3.2.8. Form-Style Query Expansion. ("{?who}", "?who=fred"), ("{?half}", "?half=50%25"), ("{?x,y}", "?x=1024&y=768"), ("{?x,y,empty}", "?x=1024&y=768&empty="), ("{?x,y,undef}", "?x=1024&y=768"), ("{?var:3}", "?var=val"), ("{?list}", "?list=red,green,blue"), ("{?list*}", "?list=red&list=green&list=blue"), ("{?keys}", "?keys=semi,%3B,dot,.,comma,%2C"), ("{?keys*}", "?semi=%3B&dot=.&comma=%2C"), // Section 3.2.9. Form-Style Query Continuation. ("{&who}", "&who=fred"), ("{&half}", "&half=50%25"), ("?fixed=yes{&x}", "?fixed=yes&x=1024"), ("{&x,y,empty}", "&x=1024&y=768&empty="), ("{&x,y,undef}", "&x=1024&y=768"), ("{&var:3}", "&var=val"), ("{&list}", "&list=red,green,blue"), ("{&list*}", "&list=red&list=green&list=blue"), ("{&keys}", "&keys=semi,%3B,dot,.,comma,%2C"), ("{&keys*}", "&semi=%3B&dot=.&comma=%2C"), ]; /// Tests for examples in RFC 6570 section 3.2. #[test] fn rfc6570_section3_2() { let context = rfc6570_context(); for (template, expected) in SUCCESS_CASES { let template = UriTemplateStr::new(template).expect("must be valid template"); let expanded = template .expand::(&context) .expect("must not have variable type error"); assert_eq_display!(expanded, expected, "template={template:?}"); assert_eq!(expanded.to_string(), *expected, "template={template:?}"); } } #[test] fn prefix_modifier_for_percent_encoded_content() { let mut context = SimpleContext::new(); context.insert("abcdef", "%61%62%63%64%65%66"); // `%CE`, `%CE%B1`, `%B1`, `%CE`, `%CE%B2`, `%B2`. context.insert("invalid1", "%CE%CE%B1%B1%CE%CE%B2%B2"); // Each `%ff` is considered as an independent "character". context.insert("invalid2", "%ff%ff%ff%ff%ff%ff"); // `&[(template, expected)]`. const CASES: &[(&str, &str)] = &[ ("{abcdef:4}", "%2561%25"), ("{+abcdef:4}", "%61%62%63%64"), ("{invalid1:2}", "%25C"), ("{invalid1:4}", "%25CE%25"), ("{+invalid1:2}", "%CE%CE%B1"), ("{+invalid1:4}", "%CE%CE%B1%B1%CE"), ("{invalid2:2}", "%25f"), ("{invalid2:4}", "%25ff%25"), ("{+invalid2:2}", "%ff%ff"), ("{+invalid2:4}", "%ff%ff%ff%ff"), ]; for (template, expected) in CASES { let template = UriTemplateStr::new(template).expect("must be valid template"); let expanded = template .expand::(&context) .expect("must not have variable type error"); assert_eq_display!(expanded, *expected, "template={template:?}"); assert_eq!(expanded.to_string(), *expected, "template={template:?}"); let expanded_dynamic = template .expand_dynamic_to_string::(&mut context.clone()) .expect("must not have variable type error"); assert_eq!( expanded_dynamic, *expected, "dynamic, template={template:?}" ); } } #[test] fn incomplete_percent_encode() { let mut context = SimpleContext::new(); context.insert("incomplete1", "%ce%b1%"); context.insert("incomplete2", "%ce%b1%c"); context.insert("incomplete3", "%ce%b1%ce"); // `&[(template, expected)]`. const CASES: &[(&str, &str)] = &[ ("{incomplete1:1}", "%25"), ("{incomplete1:2}", "%25c"), ("{incomplete1:3}", "%25ce"), ("{incomplete1:4}", "%25ce%25"), ("{+incomplete1:1}", "%ce%b1"), ("{+incomplete1:2}", "%ce%b1%25"), ("{+incomplete2:1}", "%ce%b1"), ("{+incomplete2:2}", "%ce%b1%25"), ("{+incomplete2:3}", "%ce%b1%25c"), ("{+incomplete3:1}", "%ce%b1"), ("{+incomplete3:2}", "%ce%b1%ce"), ("{+incomplete3:3}", "%ce%b1%ce"), ]; for (template, expected) in CASES { let template = UriTemplateStr::new(template).expect("must be valid template"); let expanded = template .expand::(&context) .expect("must not have variable type error"); assert_eq_display!(expanded, *expected, "template={template:?}"); assert_eq!(expanded.to_string(), *expected, "template={template:?}"); let expanded_dynamic = template .expand_dynamic_to_string::(&mut context.clone()) .expect("must not have variable type error"); assert_eq!( expanded_dynamic, *expected, "dynamic, template={template:?}" ); } } #[test] fn fragmented_write() { use core::fmt; #[derive(Clone)] enum Foo { Incomplete1, Incomplete2, Incomplete3, } impl fmt::Display for Foo { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use core::fmt::Write; f.write_char('%')?; f.write_char('c')?; f.write_char('e')?; f.write_char('%')?; f.write_char('b')?; f.write_char('1')?; f.write_char('%')?; match self { Foo::Incomplete1 => {} Foo::Incomplete2 => { f.write_char('c')?; } Foo::Incomplete3 => { f.write_char('c')?; f.write_char('e')?; } } Ok(()) } } #[derive(Clone)] struct MyContext { incomplete1: Foo, incomplete2: Foo, incomplete3: Foo, } impl Context for MyContext { fn visit(&self, visitor: V) -> V::Result { let name = visitor.var_name().as_str(); match name { "incomplete1" => visitor.visit_string(&self.incomplete1), "incomplete2" => visitor.visit_string(&self.incomplete2), "incomplete3" => visitor.visit_string(&self.incomplete3), _ => visitor.visit_undefined(), } } } let context = MyContext { incomplete1: Foo::Incomplete1, incomplete2: Foo::Incomplete2, incomplete3: Foo::Incomplete3, }; // `&[(template, expected)]`. const CASES: &[(&str, &str)] = &[ ("{incomplete1:1}", "%25"), ("{incomplete1:2}", "%25c"), ("{incomplete1:3}", "%25ce"), ("{incomplete1:4}", "%25ce%25"), ("{+incomplete1:1}", "%ce%b1"), ("{+incomplete1:2}", "%ce%b1%25"), ("{+incomplete2:1}", "%ce%b1"), ("{+incomplete2:2}", "%ce%b1%25"), ("{+incomplete2:3}", "%ce%b1%25c"), ("{+incomplete3:1}", "%ce%b1"), ("{+incomplete3:2}", "%ce%b1%ce"), ("{+incomplete3:3}", "%ce%b1%ce"), ]; for (template, expected) in CASES { let template = UriTemplateStr::new(template).expect("must be valid template"); let expanded = template .expand::(&context) .expect("must not have variable type error"); assert_eq_display!(expanded, *expected, "template={template:?}"); assert_eq!(expanded.to_string(), *expected, "template={template:?}"); let expanded_dynamic = template .expand_dynamic_to_string::(&mut context.clone()) .expect("must not have variable type error"); assert_eq!( expanded_dynamic, *expected, "dynamic, template={template:?}" ); } } #[test] fn github_issue_39() { #[derive(Default)] struct MyContext { on_expansion_start_called: Cell, on_expansion_end_called: Cell, } impl DynamicContext for MyContext { fn visit_dynamic(&mut self, visitor: V) -> V::Result { visitor.visit_undefined() } fn on_expansion_start(&mut self) { self.on_expansion_start_called.set(true); } fn on_expansion_end(&mut self) { self.on_expansion_end_called.set(true); } } let mut dyctx = MyContext::default(); let template = UriTemplateStr::new("hello/{world}").expect("valid template string"); let s = template .expand_dynamic_to_string::(&mut dyctx) .expect("must not have variable type error"); assert_eq!(s, "hello/"); assert!(dyctx.on_expansion_start_called.get()); assert!(dyctx.on_expansion_end_called.get()); }