use anyhow::Result; use path2regex::{ CompilerBuilder, CompilerOptions, Key, MatchResult, MatcherBuilder, MatcherOptions, Parser, ParserOptions, PathRegex, PathRegexBuilder, PathRegexOptions, Token, TryIntoWith, }; use serde_json::{json, Value}; struct CompileCase<'a> { params: Value, result: &'a str, options: CompilerOptions, } impl<'a> Default for CompileCase<'a> { fn default() -> Self { Self { params: json!({}), result: "", options: Default::default(), } } } #[derive(Default)] struct MatchCase<'a> { path_name: &'a str, matches: Option>, params: Option, options: MatcherOptions, } fn assert_re( path: impl TryIntoWith, tokens: &[Token], options: PathRegexOptions, should_parse_keys: bool, ) -> Result { let re = PathRegexBuilder::new_with_options(path, options).build()?; let keys = re.keys(); if should_parse_keys { let keys_in_tokens = tokens .iter() .map(|token| match token { Token::Key(key) => key.clone(), _ => Key::default(), }) .filter(|x| !x.name.is_empty()) .collect::>(); assert_eq!(keys, &keys_in_tokens, "should parse keys"); } Ok(re) } fn assert_parse(path: impl AsRef, tokens: &Vec, options: ParserOptions) -> Result<()> { let parser = Parser::new_with_options(options); assert_eq!(&parser.parse_str(path)?, tokens, "should parse"); Ok(()) } fn assert_compile( path: impl TryIntoWith, ParserOptions>, complie_cases: &Vec, options: CompilerOptions, ) -> Result<()> { for case in complie_cases { #[allow(clippy::needless_update)] let options = CompilerOptions { delimiter: options.delimiter.clone(), prefixes: options.prefixes.clone(), sensitive: options.sensitive, encode: options.encode, validate: options.validate, ..case.options }; let compiler = CompilerBuilder::new_with_options(path.clone(), options).build()?; if case.result.is_empty() { assert!( compiler.render(&case.params).is_err(), "should not compile using {}", case.params ); } else { assert_eq!( compiler.render(&case.params)?, case.result, "should compile using {}", case.params ); } } Ok(()) } fn assert_match( path: impl TryIntoWith, re: &PathRegex, match_cases: &Vec, ) -> Result<()> { for case in match_cases { let message = format!( "should {}match {}", if case.matches.is_none() { "not " } else { "" }, case.path_name ); let matches = re.captures(case.path_name).map(|cap| { cap.iter() .map(|x| match x { Some(x) => x.as_str(), None => Default::default(), }) .collect::>() }); assert_eq!(matches, case.matches, "{}", message); if case.params.is_some() { let matcher = MatcherBuilder::new_with_options(path.clone(), case.options.clone()).build()?; assert_eq!( matcher.find(case.path_name), case.params, "{} params", message ); } } Ok(()) } #[test] fn test_rule_1() -> Result<()> { let path = "/"; let ops = PathRegexOptions::default(); let tokens = vec![Token::Static("/".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_compile( path, &vec![ CompileCase { result: "/", ..Default::default() }, CompileCase { params: json!({"id":123}), result: "/", ..Default::default() }, ], CompilerOptions::default(), )?; assert_match( path, &re, &vec![ MatchCase { path_name: "/", matches: Some(vec!["/"]), params: Some(MatchResult { path: "/".to_owned(), index: 0, params: json!({}), }), ..Default::default() }, MatchCase { path_name: "/route", ..Default::default() }, ], )?; Ok(()) } #[test] fn test_rule_2() -> Result<()> { let path = "/test"; let ops = PathRegexOptions::default(); let tokens = vec![Token::Static("/test".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_compile( path, &vec![CompileCase { result: "/test", ..Default::default() }], CompilerOptions::default(), )?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", matches: Some(vec!["/test"]), params: Some(MatchResult { path: "/test".to_owned(), index: 0, params: json!({}), }), ..Default::default() }, MatchCase { path_name: "/route", ..Default::default() }, MatchCase { path_name: "/test/route", ..Default::default() }, MatchCase { path_name: "/test/", matches: Some(vec!["/test/"]), params: Some(MatchResult { path: "/test/".to_owned(), index: 0, params: json!({}), }), ..Default::default() }, ], )?; Ok(()) } #[test] fn test_rule_3() -> Result<()> { let path = "/test/"; let ops = PathRegexOptions::default(); let tokens = vec![Token::Static("/test/".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_compile( path, &vec![CompileCase { result: "/test/", ..Default::default() }], CompilerOptions::default(), )?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", matches: None, ..Default::default() }, MatchCase { path_name: "/test/", matches: Some(vec!["/test/"]), ..Default::default() }, MatchCase { path_name: "/test//", matches: Some(vec!["/test//"]), ..Default::default() }, ], )?; Ok(()) } #[test] fn test_rule_4() -> Result<()> { let path = "/test"; let ops = PathRegexOptions { sensitive: true, ..PathRegexOptions::default() }; let tokens = vec![Token::Static("/test".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", matches: Some(vec!["/test"]), ..Default::default() }, MatchCase { path_name: "/TEST", ..Default::default() }, ], )?; assert_compile( path, &vec![CompileCase { result: "/test", ..Default::default() }], CompilerOptions::default(), )?; Ok(()) } #[test] fn test_rule_5() -> Result<()> { let path = "/test"; let ops = PathRegexOptions { strict: true, ..PathRegexOptions::default() }; let tokens = vec![Token::Static("/test".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", matches: Some(vec!["/test"]), ..Default::default() }, MatchCase { path_name: "/test/", ..Default::default() }, MatchCase { path_name: "/TEST", matches: Some(vec!["/TEST"]), ..Default::default() }, ], )?; assert_compile( path, &vec![CompileCase { result: "/test", ..Default::default() }], CompilerOptions::default(), )?; Ok(()) } #[test] fn test_rule_6() -> Result<()> { let path = "/test/"; let ops = PathRegexOptions { strict: true, ..PathRegexOptions::default() }; let tokens = vec![Token::Static("/test/".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", ..Default::default() }, MatchCase { path_name: "/test/", matches: Some(vec!["/test/"]), ..Default::default() }, MatchCase { path_name: "/test//", ..Default::default() }, ], )?; assert_compile( path, &vec![CompileCase { result: "/test/", ..Default::default() }], CompilerOptions::default(), )?; Ok(()) } #[test] fn test_rule_7() -> Result<()> { let path = "/test"; let ops = PathRegexOptions { end: false, ..PathRegexOptions::default() }; let tokens = vec![Token::Static("/test".to_owned())]; let re = assert_re(path, &tokens, ops.clone(), false)?; assert_parse(path, &tokens, ParserOptions::from(ops))?; assert_match( path, &re, &vec![ MatchCase { path_name: "/test", matches: Some(vec!["/test", ""]), ..Default::default() }, MatchCase { path_name: "/test/", matches: Some(vec!["/test/", ""]), ..Default::default() }, MatchCase { path_name: "/test/route", matches: Some(vec!["/test/", "/"]), ..Default::default() }, MatchCase { path_name: "/route", ..Default::default() }, ], )?; assert_compile( path, &vec![CompileCase { result: "/test", ..Default::default() }], CompilerOptions::default(), )?; Ok(()) }