diff --git a/Cargo.lock b/Cargo.lock index 2595af7..ebd0c1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,9 +138,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" [[package]] name = "cfg-if" @@ -911,9 +911,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "2.0.50" +version = "2.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" dependencies = [ "proc-macro2", "quote", diff --git a/default-config.json b/default-config.json index 1708367..15add32 100644 --- a/default-config.json +++ b/default-config.json @@ -1,4 +1,16 @@ { + "docs": { + "flags": { + "no-unmangle": "...", + "...": ["..."] + }, + "vars": { + "...": [ + "...", + "...2" + ] + } + }, "rules": [ { "RepeatUntilNonePass": { @@ -108,19 +120,55 @@ { - "condition": {"PartIs": {"part": "Scheme", "value": "http"}}, - "mapper": {"SetPart": {"part": "Scheme", "value": "https"}} + "comment": "https://abc.com/https://abc.com/user", + "condition": {"All": [ + {"Not": {"FlagIsSet": "no-unmangle"}}, + {"Any": [ + {"PartContains": {"part": "Path", "where": "Start", "value": "/http:"}}, + {"PartContains": {"part": "Path", "where": "Start", "value": "/https:"}} + ]} + ]}, + "mapper": {"SetPart": { + "part": "Whole", + "value": {"Modified": {"source": {"Part": "Path"}, "modification": {"StripPrefix": "/"}}} + }} + }, + { + "comment": "https://abc.com/profile/https://abc.com/profile/user", + "comment2": "The general case requires conditions and mappers to talk to each other and would break the WayBack machine.", + "condition": {"All": [ + {"Not": {"FlagIsSet": "no-unmangle"}}, + {"Any": [ + {"PartIs": {"part": {"PathSegment": 1}, "value": "http:"}}, + {"PartIs": {"part": {"PathSegment": 1}, "value": "https:"}} + ]} + ]}, + "mapper": {"SetPart": { + "part": "Whole", + "value": {"Part": {"PartSegments": {"part": "Path", "split": "/", "start": 2, "end": null}}} + }} }, + { + "comment": "https://profile.abc.com.abc.com", + "condition": {"All": [ + {"Not": {"FlagIsSet": "no-unmangle"}}, + {"TreatErrorAsFail": {"PartContains": { + "part": {"PartSegments": {"part": "Domain", "split": ".", "start": 1, "end": null}}, + "where": "Start", + "value": {"Join": {"sources": [{"Part": "NotSubdomain"}, {"String": "."}]}} + }}} + ]}, + "mapper": {"SetPart": {"part": "NotSubdomain", "value": null}} + }, { - "condition": {"TreatErrorAsFail": {"PartContains": { - "part": {"PartSegments": {"part": "Domain", "split": ".", "start": 1, "end": null}}, - "where": "Start", - "value": {"Join": {"sources": [{"Part": "NotSubdomain"}, {"String": "."}]}} - }}}, - "mapper": {"SetPart": {"part": "NotSubdomain", "value": null}} + "condition": {"All": [ + {"Not": {"FlagIsSet": "no-https"}}, + {"PartIs": {"part": "Scheme", "value": "http"}} + ]}, + "mapper": {"SetPart": {"part": "Scheme", "value": "https"}} }, @@ -132,7 +180,7 @@ ]}, "mapper": {"All": [ {"SetPart": {"part": "Path", "value": "/users"}}, - {"CopyPart": {"from": {"QueryParam": "id"}, "none_to_empty_string": false, "to": "NextPathSegment"}}, + {"CopyPart": {"from": {"QueryParam": "id"}, "from_none_to_empty_string": false, "to": "NextPathSegment"}}, "RemoveQuery" ]} }, diff --git a/src/lib.rs b/src/lib.rs index 62c2a8b..b52d677 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,10 +10,9 @@ pub mod rules; pub mod glue; /// Types that don't fit in the other modules. pub mod types; -/// Deserializing and handling configuration. -pub mod config; +pub(crate) mod util; -/// Takes a URL, an optional [`config::Config`], an optional [`config::Params`], and returns the result of applying the config and params to the URL. +/// Takes a URL, an optional [`types::Config`], an optional [`types::Params`], and returns the result of applying the config and params to the URL. /// This function's name is set to `clean_url` in WASM for API simplicity. /// # Errors /// If the config or params can't be parsed, returns the parsing error. @@ -25,30 +24,30 @@ pub fn wasm_clean_url(url: &str, config: wasm_bindgen::JsValue, params: wasm_bin Ok(JsValue::from_str(url.as_str())) } -/// Takes a URL, an optional [`config::Config`], an optional [`config::Params`], and returns the result of applying the config and params to the URL. +/// Takes a URL, an optional [`types::Config`], an optional [`types::Params`], and returns the result of applying the config and params to the URL. /// # Errors /// If applying the rules returns an error, that error is returned. -pub fn clean_url(url: &mut Url, config: Option<&config::Config>, params: Option<&config::Params>) -> Result<(), types::CleaningError> { +pub fn clean_url(url: &mut Url, config: Option<&types::Config>, params: Option<&types::Params>) -> Result<(), types::CleaningError> { let mut config=match config { Some(config) => config.clone(), - None => config::Config::get_default()?.clone() + None => types::Config::get_default()?.clone() }; if let Some(params) = params {config.params.merge(params.clone());} config.apply(url)?; Ok(()) } -fn js_value_to_config(config: wasm_bindgen::JsValue) -> Result, JsError> { +fn js_value_to_config(config: wasm_bindgen::JsValue) -> Result, JsError> { Ok(if config.is_null() { - Cow::Borrowed(config::Config::get_default()?) + Cow::Borrowed(types::Config::get_default()?) } else { Cow::Owned(serde_wasm_bindgen::from_value(config)?) }) } -fn js_value_to_params(params: wasm_bindgen::JsValue) -> Result { +fn js_value_to_params(params: wasm_bindgen::JsValue) -> Result { Ok(if params.is_null() { - config::Params::default() + types::Params::default() } else { serde_wasm_bindgen::from_value(params)? }) diff --git a/src/main.rs b/src/main.rs index 7d12ef6..0c94e54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use url::Url; mod rules; mod glue; mod types; -mod config; +mod util; #[derive(Parser)] struct Args { @@ -24,20 +24,20 @@ struct Args { flag: Vec } -impl TryFrom for (Vec, config::Config) { - type Error=config::GetConfigError; +impl TryFrom for (Vec, types::Config) { + type Error=types::GetConfigError; fn try_from(args: Args) -> Result { - let mut config=config::Config::get_default_or_load(args.config.as_deref())?.into_owned(); + let mut config=types::Config::get_default_or_load(args.config.as_deref())?.into_owned(); config.params.merge( #[allow(clippy::needless_update)] - config::Params { + types::Params { vars: args.var .into_iter() .filter_map(|mut kev| kev.find('=').map(|e| {let mut v=kev.split_off(e); v.drain(..1); kev.shrink_to_fit(); (kev, v)})) .collect(), flags: args.flag.into_iter().collect(), - ..config::Params::default() + ..types::Params::default() } ); Ok((args.urls, config)) @@ -45,7 +45,7 @@ impl TryFrom for (Vec, config::Config) { } fn main() -> Result<(), types::CleaningError> { - let (urls, config): (Vec, config::Config)=Args::parse().try_into()?; + let (urls, config): (Vec, types::Config)=Args::parse().try_into()?; for mut url in urls { match config.apply(&mut url) { diff --git a/src/rules.rs b/src/rules.rs index 77adbca..5660e67 100644 --- a/src/rules.rs +++ b/src/rules.rs @@ -11,7 +11,8 @@ mod conditions; pub use conditions::*; mod mappers; pub use mappers::*; -use crate::config; + +pub use crate::types::*; /// The core unit describing when and how URLs are modified. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -21,7 +22,7 @@ pub enum Rule { /// # Examples /// ``` /// # use url_cleaner::rules::{Rule, Mapper}; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::collections::HashMap; /// let rule=Rule::HostMap(HashMap::from_iter([ @@ -43,7 +44,7 @@ pub enum Rule { /// # Examples /// ``` /// # use url_cleaner::rules::{Rule, Condition, Mapper}; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::UrlPart; /// # use url::Url; /// # use std::str::FromStr; @@ -54,8 +55,8 @@ pub enum Rule { /// condition: Condition::Always, /// mapper: Mapper::SetPart { /// part: UrlPart::NextPathSegment, - /// none_to_empty_string: false, - /// value: Some(FromStr::from_str("a").unwrap()) + /// value: Some(FromStr::from_str("a").unwrap()), + /// value_none_to_empty_string: false /// } /// } /// ], @@ -77,7 +78,7 @@ pub enum Rule { /// # Examples /// ``` /// # use url_cleaner::rules::{Rule, Condition, Mapper}; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Rule::Normal{condition: Condition::Never, mapper: Mapper::None}.apply(&mut Url::parse("https://example.com").unwrap(), &Params::default()).is_err()); /// ``` @@ -119,7 +120,7 @@ impl Rule { /// If the rule is a [`Self::Normal`] and the contained condition or mapper returns an error, that error is returned. /// If the rule is a [`Self::HostMap`] and the provided URL doesn't have a host, returns the error [`RuleError::UrlHasNoHost`]. /// If the rule is a [`Self::HostMap`] and the provided URL's host isn't in the rule's map, returns the error [`RuleError::HostNotInMap`]. - pub fn apply(&self, url: &mut Url, params: &config::Params) -> Result<(), RuleError> { + pub fn apply(&self, url: &mut Url, params: &Params) -> Result<(), RuleError> { match self { Self::Normal{condition, mapper} => if condition.satisfied_by(url, params)? { mapper.apply(url, params)?; @@ -175,7 +176,7 @@ impl Rules { /// If an error is returned, `url` is left unmodified. /// # Errors /// If the error [`RuleError::FailedCondition`], [`RuleError::UrlHasNoHost`], or [`RuleError::HostNotInMap`] is encountered, it is ignored. - pub fn apply(&self, url: &mut Url, params: &config::Params) -> Result<(), RuleError> { + pub fn apply(&self, url: &mut Url, params: &Params) -> Result<(), RuleError> { let mut temp_url=url.clone(); for rule in &**self { match rule.apply(&mut temp_url, params) { diff --git a/src/rules/conditions.rs b/src/rules/conditions.rs index ee322cd..021fe0b 100644 --- a/src/rules/conditions.rs +++ b/src/rules/conditions.rs @@ -9,7 +9,6 @@ use url::Url; use crate::glue::*; use crate::types::*; -use crate::config::Params; /// The part of a [`crate::rules::Rule`] that specifies when the rule's mapper will be applied. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] @@ -27,7 +26,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::Error.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_err()); /// ``` @@ -45,7 +44,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::TreatErrorAsPass(Box::new(Condition::Always)).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::TreatErrorAsPass(Box::new(Condition::Never )).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -56,7 +55,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::TreatErrorAsFail(Box::new(Condition::Always)).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::TreatErrorAsFail(Box::new(Condition::Never )).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -70,7 +69,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::TryElse{r#try: Box::new(Condition::Always), r#else: Box::new(Condition::Always)}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::TryElse{r#try: Box::new(Condition::Always), r#else: Box::new(Condition::Never )}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -110,7 +109,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::Not(Box::new(Condition::Always)).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); /// assert!(Condition::Not(Box::new(Condition::Never )).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -124,7 +123,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::All(vec![Condition::Always, Condition::Always]).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::All(vec![Condition::Always, Condition::Never ]).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -144,7 +143,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::Any(vec![Condition::Always, Condition::Always]).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::Any(vec![Condition::Always, Condition::Never ]).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -164,7 +163,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::UnqualifiedDomain( "example.com".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::UnqualifiedDomain("www.example.com".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -177,7 +176,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::MaybeWWWDomain("example.com".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::MaybeWWWDomain("example.com".to_string()).satisfied_by(&Url::parse("https://www.example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -188,7 +187,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::QualifiedDomain( "example.com".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::QualifiedDomain("www.example.com".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -202,7 +201,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::collections::HashSet; /// assert!(Condition::HostIsOneOf(HashSet::from_iter([ "example.com".to_string(), "example2.com".to_string()])).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -216,7 +215,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::UnqualifiedAnyTld( "example".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::UnqualifiedAnyTld("www.example".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -237,7 +236,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::MaybeWWWAnyTld("example".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::MaybeWWWAnyTld("example".to_string()).satisfied_by(&Url::parse("https://www.example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -252,7 +251,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::QualifiedAnyTld( "example".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::QualifiedAnyTld("www.example".to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==false)); @@ -271,7 +270,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::QueryHasParam("a".to_string()).satisfied_by(&Url::parse("https://example.com?a=2&b=3").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// assert!(Condition::QueryHasParam("b".to_string()).satisfied_by(&Url::parse("https://example.com?a=2&b=3").unwrap(), &Params::default()).is_ok_and(|x| x==true )); @@ -282,7 +281,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Condition::PathIs("/" .to_string()).satisfied_by(&Url::parse("https://example.com" ).unwrap(), &Params::default()).is_ok_and(|x| x==true)); /// assert!(Condition::PathIs("/" .to_string()).satisfied_by(&Url::parse("https://example.com/" ).unwrap(), &Params::default()).is_ok_and(|x| x==true)); @@ -298,15 +297,15 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::UrlPart; /// # use url::Url; - /// assert!(Condition::PartIs{part: UrlPart::Username , none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::Password , none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartIs{part: UrlPart::PathSegment(0), none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::PathSegment(1), none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartIs{part: UrlPart::Path , none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::Fragment , none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::Username , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::Password , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::PathSegment(0), part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::PathSegment(1), part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::Path , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::Fragment , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// ``` #[cfg(feature = "string-source")] PartIs { @@ -315,7 +314,7 @@ pub enum Condition { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The expected value of the part. #[serde(deserialize_with = "optional_string_or_struct")] value: Option, @@ -329,15 +328,15 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::UrlPart; /// # use url::Url; - /// assert!(Condition::PartIs{part: UrlPart::Username , none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::Password , none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartIs{part: UrlPart::PathSegment(0), none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::PathSegment(1), none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartIs{part: UrlPart::Path , none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::PartIs{part: UrlPart::Fragment , none_to_empty_string: false, value: None}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::Username , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::Password , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::PathSegment(0), part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::PathSegment(1), part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartIs{part: UrlPart::Path , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartIs{part: UrlPart::Fragment , part_none_to_empty_string: false, value: None, value_none_to_empty_string: false}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); /// ``` #[cfg(not(feature = "string-source"))] PartIs { @@ -346,23 +345,27 @@ pub enum Condition { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The expected value of the part. - value: Option + value: Option, + /// Does nothing; Only here to fix tests between feature flags. + /// Defaults to `false`. + #[serde(default = "get_false")] + value_none_to_empty_string: bool }, /// Passes if the specified part contains the specified value in a range specified by `where`. /// # Errors - /// If the specified part is `None` and `none_to_empty_string` is set to `false`, returns the error [`ConditionError::UrlPartNotFound`]. + /// If the specified part is `None` and `part_none_to_empty_string` is set to `false`, returns the error [`ConditionError::UrlPartNotFound`]. /// If `value.get` returns `None`, returns the error [`ConditionError::StringSourceIsNone`]. /// # Examples /// ``` /// # use url::Url; /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::UrlPart; /// # use url_cleaner::types::StringLocation; - /// assert!(Condition::PartContains {part: UrlPart::Domain, none_to_empty_string: true, value: "ple".try_into().unwrap(), r#where: StringLocation::Anywhere}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartContains {part: UrlPart::Domain, none_to_empty_string: true, value: "ple".try_into().unwrap(), r#where: StringLocation::End }.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartContains {part: UrlPart::Domain, part_none_to_empty_string: true, value: "ple".try_into().unwrap(), value_none_to_empty_string: false, r#where: StringLocation::Anywhere}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartContains {part: UrlPart::Domain, part_none_to_empty_string: true, value: "ple".try_into().unwrap(), value_none_to_empty_string: false, r#where: StringLocation::End }.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); /// ``` #[cfg(all(feature = "string-source", feature = "string-location"))] PartContains { @@ -371,26 +374,30 @@ pub enum Condition { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The value to look for. #[serde(deserialize_with = "string_or_struct")] value: StringSource, + /// Decides if `value`'s call to [`StringSource::get`] should return `Some("")` instead of `None`. + /// Defaults to `true`. + #[serde(default)] + value_none_to_empty_string: bool, /// Where to look for the value. #[serde(default)] r#where: StringLocation }, /// Passes if the specified part contains the specified value in a range specified by `where`. /// # Errors - /// If the specified part is `None` and `none_to_empty_string` is set to `false`, returns the error [`ConditionError::UrlPartNotFound`]. + /// If the specified part is `None` and `part_none_to_empty_string` is set to `false`, returns the error [`ConditionError::UrlPartNotFound`]. /// # Examples /// ``` /// # use url::Url; /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::UrlPart; /// # use url_cleaner::types::StringLocation; - /// assert!(Condition::PartContains {part: UrlPart::Domain, none_to_empty_string: true, value: "ple".to_string(), r#where: StringLocation::Anywhere}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); - /// assert!(Condition::PartContains {part: UrlPart::Domain, none_to_empty_string: true, value: "ple".to_string(), r#where: StringLocation::End }.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::PartContains {part: UrlPart::Domain, part_none_to_empty_string: true, value: "ple".to_string(), value_none_to_empty_string: false, r#where: StringLocation::Anywhere}.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::PartContains {part: UrlPart::Domain, part_none_to_empty_string: true, value: "ple".to_string(), value_none_to_empty_string: false, r#where: StringLocation::End }.satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==false)); /// ``` #[cfg(all(not(feature = "string-source"), feature = "string-location"))] PartContains { @@ -399,9 +406,13 @@ pub enum Condition { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The value to look for. value: String, + /// Does nothing; Only here to fix tests between feature flags. + /// Defaults to `true`. + #[serde(default)] + value_none_to_empty_string: bool, /// Where to look for the value. #[serde(default)] r#where: StringLocation @@ -428,61 +439,64 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::types::StringSource; /// # use url::Url; /// # use std::collections::HashMap; /// let url=Url::parse("https://example.com").unwrap(); /// let params=Params {vars: HashMap::from([("a".to_string(), "2".to_string())]), ..Params::default()}; - /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), value: Some(StringSource::String("2".to_string())), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==true )); - /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), value: Some(StringSource::String("3".to_string())), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), value: Some(StringSource::String("3".to_string())), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), value: Some(StringSource::String("3".to_string())), none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), value: None , none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), name_none_to_empty_string: false, value: Some(StringSource::String("2".to_string())), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==true )); + /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), name_none_to_empty_string: false, value: Some(StringSource::String("3".to_string())), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), name_none_to_empty_string: false, value: Some(StringSource::String("3".to_string())), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), name_none_to_empty_string: false, value: Some(StringSource::String("3".to_string())), value_none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: StringSource::String("a".to_string()), name_none_to_empty_string: false, value: None , value_none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==true )); /// ``` #[cfg(feature = "string-source")] VarIs { /// The name of the variable to check. #[serde(deserialize_with = "string_or_struct")] name: StringSource, - /// The expected value of the variable. - #[serde(deserialize_with = "optional_string_or_struct")] - value: Option, /// Decides if `name`'s call to [`StringSource::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default)] name_none_to_empty_string: bool, + /// The expected value of the variable. + #[serde(deserialize_with = "optional_string_or_struct")] + value: Option, /// Decides if getting the variable should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default)] - none_to_empty_string: bool + value_none_to_empty_string: bool }, /// Passes if the specified rule variable is set to the specified value. /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; - /// # use url_cleaner::types::StringSource; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::collections::HashMap; /// let url=Url::parse("https://example.com").unwrap(); /// let params=Params {vars: HashMap::from([("a".to_string(), "2".to_string())]), ..Params::default()}; - /// assert!(Condition::VarIs{name: "a".to_string(), value: Some("2".to_string()), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==true )); - /// assert!(Condition::VarIs{name: "a".to_string(), value: Some("3".to_string()), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: "a".to_string(), value: Some("3".to_string()), none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: "a".to_string(), value: Some("3".to_string()), none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==false)); - /// assert!(Condition::VarIs{name: "a".to_string(), value: None , none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==true )); + /// assert!(Condition::VarIs{name: "a".to_string(), name_none_to_empty_string: false, value: Some("2".to_string()), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==true )); + /// assert!(Condition::VarIs{name: "a".to_string(), name_none_to_empty_string: false, value: Some("3".to_string()), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: "a".to_string(), name_none_to_empty_string: false, value: Some("3".to_string()), value_none_to_empty_string: false}.satisfied_by(&url, ¶ms ).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: "a".to_string(), name_none_to_empty_string: false, value: Some("3".to_string()), value_none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==false)); + /// assert!(Condition::VarIs{name: "a".to_string(), name_none_to_empty_string: false, value: None , value_none_to_empty_string: false}.satisfied_by(&url, &Params::default()).is_ok_and(|x| x==true )); /// ``` #[cfg(not(feature = "string-source"))] VarIs { /// The name of the variable name: String, + /// Does nothing; Only here for compatibility between feature flags. + /// Defaults to `true`. + #[serde(default)] + name_none_to_empty_string: bool, /// The expected value of the variable. value: Option, - /// Decides if getting the variable should return `Some("")` instead of `None`. + /// Does nothing; Only here to fix tests between feature flags. /// Defaults to `true`. #[serde(default)] - none_to_empty_string: bool + value_none_to_empty_string: bool }, /// Passes if the specified rule flag is set. @@ -491,7 +505,7 @@ pub enum Condition { /// # use std::collections::HashSet; /// # use url::Url; /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// assert!(Condition::FlagIsSet("abc".to_string()).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params {flags: HashSet::from_iter(["abc".to_string()]), ..Params::default()}).is_ok_and(|x| x==true )); /// assert!(Condition::FlagIsSet("abc".to_string()).satisfied_by(&Url::parse("https://example.com").unwrap(), &Params::default() ).is_ok_and(|x| x==false)); /// ``` @@ -506,12 +520,12 @@ pub enum Condition { /// The source of the left hand side of the comparison. #[serde(deserialize_with = "string_or_struct")] l: StringSource, - /// The source of the right hand side of the comparison. - #[serde(deserialize_with = "string_or_struct")] - r: StringSource, /// If `l` returns `None` and this is `true`, pretend `l` returned `Some("")`. #[serde(default = "get_true")] l_none_to_empty_string: bool, + /// The source of the right hand side of the comparison. + #[serde(deserialize_with = "string_or_struct")] + r: StringSource, /// If `r` returns `None` and this is `true`, pretend `r` returned `Some("")`. #[serde(default = "get_true")] r_none_to_empty_string: bool, @@ -530,7 +544,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::glue::CommandWrapper; /// # use url::Url; /// # use std::str::FromStr; @@ -546,7 +560,7 @@ pub enum Condition { /// # Examples /// ``` /// # use url_cleaner::rules::Condition; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url_cleaner::glue::CommandWrapper; /// # use url::Url; /// # use std::str::FromStr; @@ -693,21 +707,21 @@ impl Condition { // General parts. - #[cfg( feature = "string-source") ] Self::PartIs{part, none_to_empty_string, value, value_none_to_empty_string} => value.as_ref().map(|source| source.get(url, params, *value_none_to_empty_string)).transpose()?.flatten().as_deref()==part.get(url, *none_to_empty_string).as_deref(), - #[cfg(not(feature = "string-source"))] Self::PartIs{part, none_to_empty_string, value} => value.as_deref()==part.get(url, *none_to_empty_string).as_deref(), - #[cfg(all( feature = "string-source" , feature = "string-location"))] Self::PartContains{part, none_to_empty_string, value, r#where} => r#where.satisfied_by(&part.get(url, *none_to_empty_string).ok_or(ConditionError::UrlPartNotFound)?, &value.get(url, params, false)?.ok_or(ConditionError::StringSourceIsNone)?)?, - #[cfg(all(not(feature = "string-source"), feature = "string-location"))] Self::PartContains{part, none_to_empty_string, value, r#where} => r#where.satisfied_by(&part.get(url, *none_to_empty_string).ok_or(ConditionError::UrlPartNotFound)?, value)?, + #[cfg( feature = "string-source") ] Self::PartIs{part, part_none_to_empty_string, value, value_none_to_empty_string} => value.as_ref().map(|source| source.get(url, params, *value_none_to_empty_string)).transpose()?.flatten().as_deref()==part.get(url, *part_none_to_empty_string).as_deref(), + #[cfg(not(feature = "string-source"))] Self::PartIs{part, part_none_to_empty_string, value, value_none_to_empty_string: _} => value.as_deref()==part.get(url, *part_none_to_empty_string).as_deref(), + #[cfg(all( feature = "string-source" , feature = "string-location"))] Self::PartContains{part, part_none_to_empty_string, value, value_none_to_empty_string, r#where} => r#where.satisfied_by(&part.get(url, *part_none_to_empty_string).ok_or(ConditionError::UrlPartNotFound)?, &value.get(url, params, *value_none_to_empty_string)?.ok_or(ConditionError::StringSourceIsNone)?)?, + #[cfg(all(not(feature = "string-source"), feature = "string-location"))] Self::PartContains{part, part_none_to_empty_string, value, value_none_to_empty_string, r#where} => r#where.satisfied_by(&part.get(url, *part_none_to_empty_string).ok_or(ConditionError::UrlPartNotFound)?, value)?, #[cfg(feature = "string-matcher" )] Self::PartMatches {part, none_to_empty_string, matcher} => matcher.satisfied_by(&part.get(url, *none_to_empty_string).ok_or(ConditionError::UrlPartNotFound)?, url, params)?, // Miscellaneous. #[cfg(feature = "string-source")] - Self::VarIs {name, value, none_to_empty_string, name_none_to_empty_string} => match value.as_ref() { - Some(source) => params.vars.get(&name.get(url, params, *name_none_to_empty_string)?.ok_or(ConditionError::StringSourceIsNone)?.to_string()).map(|x| x.deref())==source.get(url, params, *none_to_empty_string)?.as_deref(), + Self::VarIs {name, name_none_to_empty_string, value, value_none_to_empty_string} => match value.as_ref() { + Some(source) => params.vars.get(&name.get(url, params, *name_none_to_empty_string)?.ok_or(ConditionError::StringSourceIsNone)?.to_string()).map(|x| x.deref())==source.get(url, params, *value_none_to_empty_string)?.as_deref(), None => params.vars.get(&name.get(url, params, *name_none_to_empty_string)?.ok_or(ConditionError::StringSourceIsNone)?.to_string()).is_none() }, #[cfg(not(feature = "string-source"))] - Self::VarIs {name, value, none_to_empty_string} => params.vars.get(name).map(|x| &**x).or(if *none_to_empty_string {Some("")} else {None})==value.as_deref(), + Self::VarIs {name, name_none_to_empty_string: _, value, value_none_to_empty_string} => params.vars.get(name).map(|x| &**x).or(if *value_none_to_empty_string {Some("")} else {None})==value.as_deref(), Self::FlagIsSet(name) => params.flags.contains(name), #[cfg(all(feature = "string-source", feature = "string-cmp"))] Self::StringCmp {l, r, l_none_to_empty_string, r_none_to_empty_string, cmp} => cmp.satisfied_by( diff --git a/src/rules/mappers.rs b/src/rules/mappers.rs index bdf09e3..454d7b8 100644 --- a/src/rules/mappers.rs +++ b/src/rules/mappers.rs @@ -49,7 +49,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(Mapper::TryElse {r#try: Box::new(Mapper::None ), r#else: Box::new(Mapper::None )}.apply(&mut Url::parse("https://www.example.com").unwrap(), &Params::default()).is_ok ()); /// assert!(Mapper::TryElse {r#try: Box::new(Mapper::None ), r#else: Box::new(Mapper::Error)}.apply(&mut Url::parse("https://www.example.com").unwrap(), &Params::default()).is_ok ()); @@ -71,7 +71,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// let mut url=Url::parse("https://www.example.com").unwrap(); /// assert!(Mapper::All(vec![Mapper::SetHost("2.com".to_string()), Mapper::Error]).apply(&mut url, &Params::default()).is_err()); @@ -85,7 +85,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// let mut url=Url::parse("https://www.example.com").unwrap(); /// assert!(Mapper::AllNoRevert(vec![Mapper::SetHost("3.com".to_string()), Mapper::Error, Mapper::SetHost("4.com".to_string())]).apply(&mut url, &Params::default()).is_err()); @@ -97,7 +97,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// let mut url=Url::parse("https://www.example.com").unwrap(); /// assert!(Mapper::AllIgnoreError(vec![Mapper::SetHost("5.com".to_string()), Mapper::Error, Mapper::SetHost("6.com".to_string())]).apply(&mut url, &Params::default()).is_ok()); @@ -110,7 +110,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// let mut url=Url::parse("https://www.example.com").unwrap(); /// assert!(Mapper::FirstNotError(vec![Mapper::SetHost("1.com".to_string()), Mapper::SetHost("2.com".to_string())]).apply(&mut url, &Params::default()).is_ok()); @@ -134,7 +134,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::collections::hash_set::HashSet; /// let mut url=Url::parse("https://example.com?a=2&b=3&c=4&d=5").unwrap(); @@ -151,7 +151,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::collections::hash_set::HashSet; /// let mut url=Url::parse("https://example.com?a=2&b=3&c=4&d=5").unwrap(); @@ -195,7 +195,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::*; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// let mut url=Url::parse("https://example.com/0/1/2/3/4/5/6").unwrap(); /// assert!(Mapper::RemovePathSegments(vec![1,3,5,6,8]).apply(&mut url, &Params::default()).is_ok()); @@ -219,13 +219,13 @@ pub enum Mapper { SetPart { /// The name of the part to replace. part: UrlPart, - /// Decides if `part`'s call to [`StringSource::get`] should return `Some("")` instead of `None`. - /// Defaults to `true`. - #[serde(default = "get_true")] - none_to_empty_string: bool, /// The value to set the part to. #[serde(deserialize_with = "optional_string_or_struct")] - value: Option + value: Option, + /// Decides if `value`'s call to [`StringSource::get`] should return `Some("")` instead of `None`. + /// Defaults to `true`. + #[serde(default = "get_true")] + value_none_to_empty_string: bool, }, /// Sets the specified URL part to `to`. /// # Errors @@ -235,7 +235,11 @@ pub enum Mapper { /// The name of the part to replace. part: UrlPart, /// The value to set the part to. - value: Option + value: Option, + /// Does nothing; Only here to fix tests between feature flags. + /// Defaults to `true`. + #[serde(default = "get_true")] + value_none_to_empty_string: bool, }, /// Modifies the specified part of the URL. /// # Errors @@ -248,28 +252,28 @@ pub enum Mapper { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// How exactly to modify the part. how: StringModification }, /// Copies the part specified by `from` to the part specified by `to`. /// # Errors - /// If the part specified by `from` is None, `none_to_empty_string` is `false`, and the part specified by `to` cannot be `None` (see [`Mapper::SetPart`]), returns the error [`SetPartError::PartCannotBeNone`]. + /// If the part specified by `from` is None, `from_none_to_empty_string` is `false`, and the part specified by `to` cannot be `None` (see [`Mapper::SetPart`]), returns the error [`SetPartError::PartCannotBeNone`]. CopyPart { /// The part to get the value from. from: UrlPart, /// Decides if `from`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + from_none_to_empty_string: bool, /// The part to set to `from`'s value. to: UrlPart }, /// Applies a regular expression substitution to the specified URL part. - /// if `none_to_empty_string` is `false`, then getting the password, host, domain, port, query, or fragment may result in a [`super::conditions::ConditionError::UrlPartNotFound`] error. + /// if `part_none_to_empty_string` is `false`, then getting the password, host, domain, port, query, or fragment may result in a [`super::conditions::ConditionError::UrlPartNotFound`] error. /// Also note that ports are strings because I can't be bothered to handle numbers for just ports. /// # Errors - /// If chosen part's getter returns `None` and `none_to_empty_string` is set to `false`, returns the error [`MapperError::UrlPartNotFound`]. + /// If chosen part's getter returns `None` and `part_none_to_empty_string` is set to `false`, returns the error [`MapperError::UrlPartNotFound`]. #[cfg(all(feature = "regex", feature = "string-source"))] RegexSubUrlPart { /// The name of the part to modify. @@ -277,7 +281,7 @@ pub enum Mapper { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The regex that is used to match and extract parts of the selected part. #[serde(deserialize_with = "string_or_struct")] regex: RegexWrapper, @@ -293,7 +297,7 @@ pub enum Mapper { /// Decides if `part`'s call to [`UrlPart::get`] should return `Some("")` instead of `None`. /// Defaults to `true`. #[serde(default = "get_true")] - none_to_empty_string: bool, + part_none_to_empty_string: bool, /// The regex that is used to match and extract parts of the selected part. #[serde(deserialize_with = "string_or_struct")] regex: RegexWrapper, @@ -316,7 +320,7 @@ pub enum Mapper { /// # Examples /// ``` /// # use url_cleaner::rules::Mapper; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use reqwest::header::HeaderMap; /// let mut url = Url::parse("https://t.co/H8IF8DHSFL").unwrap(); @@ -416,7 +420,7 @@ pub enum MapperError { /// Returned when a regex does not find any matches. #[error("A regex pattern did not find any matches.")] NoRegexMatchesFound, - /// Returned when the requested variable is not found in [`crate::config::Params::vars`]. + /// Returned when the requested variable is not found in [`Params::vars`]. #[error("A variable was requested but not found at runtime.")] VarNotFound, /// Returned when a call to [`StringSource::get`] returns `None` where it has to be `Some`. @@ -449,7 +453,7 @@ impl Mapper { /// # Errors /// If the mapper has an error, that error is returned. /// See [`Mapper`]'s documentation for details. - pub fn apply(&self, url: &mut Url, params: &crate::config::Params) -> Result<(), MapperError> { + pub fn apply(&self, url: &mut Url, params: &Params) -> Result<(), MapperError> { #[cfg(feature = "debug")] println!("Mapper: {self:?}"); match self { @@ -544,21 +548,21 @@ impl Mapper { // Generic part handling #[cfg(feature = "string-source")] - Self::SetPart{part, value, none_to_empty_string} => match value.as_ref() { + Self::SetPart{part, value, value_none_to_empty_string} => match value.as_ref() { Some(source) => { - let temp=source.get(url, params, *none_to_empty_string)?.map(|x| x.into_owned()); + let temp=source.get(url, params, *value_none_to_empty_string)?.map(|x| x.into_owned()); part.set(url, temp.as_deref()) }, None => part.set(url, None) }?, #[cfg(not(feature = "string-source"))] - Self::SetPart{part, value} => part.set(url, value.as_deref())?, + Self::SetPart{part, value, value_none_to_empty_string: _} => part.set(url, value.as_deref())?, #[cfg(feature = "string-modification")] - Self::ModifyPart{part, none_to_empty_string, how} => part.modify(url, *none_to_empty_string, how, params)?, - Self::CopyPart{from, none_to_empty_string, to} => to.set(url, from.get(url, *none_to_empty_string).map(|x| x.into_owned()).as_deref())?, + Self::ModifyPart{part, part_none_to_empty_string, how} => part.modify(url, *part_none_to_empty_string, how, params)?, + Self::CopyPart{from, from_none_to_empty_string, to} => to.set(url, from.get(url, *from_none_to_empty_string).map(|x| x.into_owned()).as_deref())?, #[cfg(feature = "regex")] - Self::RegexSubUrlPart {part, none_to_empty_string, regex, replace} => { - let old_part_value=part.get(url, *none_to_empty_string).ok_or(MapperError::UrlPartNotFound)?; + Self::RegexSubUrlPart {part, part_none_to_empty_string, regex, replace} => { + let old_part_value=part.get(url, *part_none_to_empty_string).ok_or(MapperError::UrlPartNotFound)?; #[allow(clippy::unnecessary_to_owned)] part.set(url, Some(®ex.replace(&old_part_value, replace.get(url, params, false)?.ok_or(MapperError::StringSourceIsNone)?).into_owned()))?; }, diff --git a/src/types.rs b/src/types.rs index ee530b5..5a705dc 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,12 @@ use std::io::Error as IoError; -use std::ops::Bound; use url::ParseError; use thiserror::Error; mod url_part; pub use url_part::*; +mod config; +pub use config::*; #[cfg(feature = "string-location" )] mod string_location; #[cfg(feature = "string-location" )] pub use string_location::*; #[cfg(feature = "string-modification")] mod string_modification; @@ -27,7 +28,7 @@ pub use url_part::*; pub enum CleaningError { /// There was an error getting the config. #[error(transparent)] - GetConfigError(#[from] crate::config::GetConfigError), + GetConfigError(#[from] config::GetConfigError), /// There was an error executing a rule. #[error(transparent)] RuleError(#[from] crate::rules::RuleError), @@ -58,80 +59,3 @@ pub enum StringError { #[error("The string being modified did not end with the provided suffix. Maybe try `StringModification::StripMaybeSuffix`?")] SuffixNotFound, } - -/// Loops negative `index`es around similar to Python. -pub(crate) const fn neg_index(index: isize, len: usize) -> Option { - if 0<=index && (index as usize)<=len { - Some(index as usize) - } else if index<0 { - len.checked_sub(index.unsigned_abs()) - } else { - None - } -} - -/// Equivalent to how python handles indexing. Minus the panicking, of course. -pub(crate) fn neg_nth(iter: I, i: isize) -> Option { - if i<0 { - let temp=iter.into_iter().collect::>(); - let fixed_i=neg_index(i, temp.len())?; - temp.into_iter().nth(fixed_i) - } else { - iter.into_iter().nth(i as usize) - } -} - -/// `f` but allows for `None` to represent open range ends. -fn neg_maybe_index(index: Option, len: usize) -> Option> { - index.map(|index| neg_index(index, len)) -} - -/// A range that may or may not have one or both ends open. -pub(crate) fn neg_range(start: Option, end: Option, len: usize) -> Option<(Bound, Bound)> { - Some(( - match neg_maybe_index(start, len) { - Some(Some(start)) => Bound::Included(start), - Some(None ) => None?, - None => Bound::Unbounded - }, - match neg_maybe_index(end, len) { - Some(Some(end)) => Bound::Excluded(end), - Some(None ) => None?, - None => Bound::Unbounded - } - )) -} - -pub(crate) fn neg_vec_keep(iter: I, start: Option, end: Option) -> Option> { - let mut ret=iter.into_iter().collect::>(); - Some(ret.drain(neg_range(start, end, ret.len())?).collect()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn neg_index_test() { - assert_eq!(neg_index( 0, 5), Some(0)); - assert_eq!(neg_index( 4, 5), Some(4)); - assert_eq!(neg_index(-1, 5), Some(4)); - assert_eq!(neg_index( 6, 5), None ); - assert_eq!(neg_index(-6, 5), None ); - } - - #[test] - fn neg_nth_test() { - assert_eq!(neg_nth([1,2,3], 0), Some(1)); - assert_eq!(neg_nth([1,2,3], -1), Some(3)); - assert_eq!(neg_nth([1,2,3], -4), None); - } - - #[test] - fn neg_range_test() { - assert_eq!(neg_range(Some( 3), None , 10), Some((Bound::Included( 3), Bound::Unbounded))); - assert_eq!(neg_range(Some(10), None , 10), Some((Bound::Included(10), Bound::Unbounded))); - assert_eq!(neg_range(Some(11), None , 10), None); - assert_eq!(neg_range(Some( 3), Some(5), 10), Some((Bound::Included( 3), Bound::Excluded(5)))); - } -} diff --git a/src/types/bool_source.rs b/src/types/bool_source.rs index d77dc84..e8dc1fe 100644 --- a/src/types/bool_source.rs +++ b/src/types/bool_source.rs @@ -2,9 +2,8 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; use url::Url; -use super::*; -use crate::glue::string_or_struct; -use crate::config::Params; +use crate::types::*; +use crate::glue::*; /// Various possible ways to get a boolean value. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] diff --git a/src/config.rs b/src/types/config.rs similarity index 98% rename from src/config.rs rename to src/types/config.rs index 052c1e9..c7d5f79 100644 --- a/src/config.rs +++ b/src/types/config.rs @@ -113,10 +113,10 @@ impl Params { /// `{"x": "y"}` is compressed but functionally unchanged, but `{"x y": "z"}` will be converted to `{"x y": "z"}`, which could alter the functionality of the rule. /// If you cannot avoid multiple spaces in a string, turn off the `minify-default-strings` feature to disable this compression. #[cfg(all(feature = "default-config", feature = "minify-included-strings"))] -pub static DEFAULT_CONFIG_STR: &str=const_str::squish!(include_str!("../default-config.json")); +pub static DEFAULT_CONFIG_STR: &str=const_str::squish!(include_str!("../../default-config.json")); /// The non-minified config loaded into URL Cleaner at compile time. #[cfg(all(feature = "default-config", not(feature = "minify-included-strings")))] -pub static DEFAULT_CONFIG_STR: &str=include_str!("../default-config.json"); +pub static DEFAULT_CONFIG_STR: &str=include_str!("../../default-config.json"); /// The container for caching the parsed version of [`DEFAULT_CONFIG_STR`]. #[cfg(feature = "default-config")] pub static DEFAULT_CONFIG: OnceLock=OnceLock::new(); @@ -150,3 +150,4 @@ mod tests { Config::get_default().unwrap(); } } + diff --git a/src/types/string_location.rs b/src/types/string_location.rs index 985f85a..d1bbe0a 100644 --- a/src/types/string_location.rs +++ b/src/types/string_location.rs @@ -1,7 +1,8 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; -use super::*; +use crate::types::*; +use crate::util::*; /// A wrapper around [`str`]'s various substring searching functions. /// [`isize`] is used to allow Python-style negative indexing. @@ -233,7 +234,7 @@ impl StringLocation { } return Ok(false) }, - Self::NthSegment {split, n, location} => location.satisfied_by(super::neg_nth(haystack.split(split), *n).ok_or(StringError::SegmentNotFound)?, needle)?, + Self::NthSegment {split, n, location} => location.satisfied_by(neg_nth(haystack.split(split), *n).ok_or(StringError::SegmentNotFound)?, needle)?, Self::All(locations) => { for location in locations { diff --git a/src/types/string_matcher.rs b/src/types/string_matcher.rs index 198986a..8ff81c3 100644 --- a/src/types/string_matcher.rs +++ b/src/types/string_matcher.rs @@ -4,13 +4,8 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; use url::Url; -use super::*; -#[cfg(feature = "regex")] -use crate::glue::RegexWrapper; -#[cfg(feature = "glob")] -use crate::glue::GlobWrapper; -use crate::glue::string_or_struct; -use crate::config::Params; +use crate::types::*; +use crate::glue::*; /// A general API for matching strings with a variety of methods. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -68,7 +63,7 @@ pub enum StringMatcher { /// # Examples /// ``` /// # use url_cleaner::types::{StringMatcher, StringLocation}; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(StringMatcher::StringLocation {location: StringLocation::Start, value: "utm_".to_string()}.satisfied_by("utm_abc", &Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true)); /// ``` @@ -83,7 +78,7 @@ pub enum StringMatcher { /// ``` /// # use url_cleaner::types::StringMatcher; /// # use url_cleaner::glue::RegexParts; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// assert!(StringMatcher::Regex(RegexParts::new("a.c").unwrap().try_into().unwrap()).satisfied_by("axc", &Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true)); /// ``` @@ -93,7 +88,7 @@ pub enum StringMatcher { /// ``` /// # use url_cleaner::types::StringMatcher; /// # use url_cleaner::glue::GlobWrapper; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use url::Url; /// # use std::str::FromStr; /// assert!(StringMatcher::Glob(GlobWrapper::from_str("a*c").unwrap()).satisfied_by("aabcc", &Url::parse("https://example.com").unwrap(), &Params::default()).is_ok_and(|x| x==true)); diff --git a/src/types/string_modification.rs b/src/types/string_modification.rs index 3d6a2f5..0d8f66e 100644 --- a/src/types/string_modification.rs +++ b/src/types/string_modification.rs @@ -4,13 +4,9 @@ use serde::{Serialize, Deserialize}; use thiserror::Error; use percent_encoding::{percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC}; -use super::*; -#[cfg(feature = "regex")] -use crate::glue::RegexWrapper; -#[cfg(feature = "commands")] -use crate::glue::{CommandWrapper, CommandError}; -use crate::glue::string_or_struct; -use crate::config::Params; +use crate::types::*; +use crate::glue::*; +use crate::util::*; /// A wrapper around [`str`]'s various substring modification functions. /// [`isize`] is used to allow Python-style negative indexing. @@ -70,7 +66,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::Set("ghi".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "ghi"); @@ -80,7 +76,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::Append("ghi".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "abcdefghi"); @@ -90,7 +86,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::Prepend("ghi".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "ghiabcdef"); @@ -100,7 +96,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcabc".to_string(); /// assert!(StringModification::Replace{find: "ab".to_string(), replace: "xy".to_string()}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "xycxyc"); @@ -117,7 +113,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::ReplaceRange{start: Some( 6), end: Some( 7), replace: "123" .to_string()}.apply(&mut x, &Params::default()).is_err()); /// assert_eq!(&x, "abcdef"); @@ -142,7 +138,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "ABCdef".to_string(); /// assert!(StringModification::Lowercase.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "abcdef"); @@ -152,7 +148,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcDEF".to_string(); /// assert!(StringModification::Uppercase.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "ABCDEF"); @@ -164,7 +160,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::StripPrefix("abc".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "def"); @@ -178,7 +174,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::StripSuffix("def".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "abc"); @@ -190,7 +186,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::StripMaybePrefix("abc".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "def"); @@ -202,7 +198,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::StripMaybeSuffix("def".to_string()).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "abc"); @@ -214,7 +210,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "aaaaa".to_string(); /// assert!(StringModification::Replacen{find: "a" .to_string(), replace: "x".to_string(), count: 2}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "xxaaa"); @@ -235,7 +231,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abc".to_string(); /// assert!(StringModification::Insert{r#where: 0, value: "def".to_string()}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "defabc"); @@ -256,7 +252,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdef".to_string(); /// assert!(StringModification::Remove( 1).apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "acdef"); @@ -270,7 +266,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "abcdefghi".to_string(); /// assert!(StringModification::KeepRange{start: Some( 1), end: Some( 8)}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "bcdefgh"); @@ -293,7 +289,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "a.b.c.d.e.f".to_string(); /// assert!(StringModification::SetNthSegment{split: ".".to_string(), n: 1, value: Some( "1".to_string())}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "a.1.c.d.e.f"); @@ -321,7 +317,7 @@ pub enum StringModification { /// # Examples /// ``` /// use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "a.b.c".to_string(); /// assert!(StringModification::InsertSegmentBefore{split: ".".to_string(), n: 1, value: "1".to_string()}.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "a.1.b.c"); @@ -384,7 +380,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "a/b/c".to_string(); /// assert!(StringModification::URLEncode.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "a%2Fb%2Fc"); @@ -396,7 +392,7 @@ pub enum StringModification { /// # Examples /// ``` /// # use url_cleaner::types::StringModification; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// let mut x = "a%2fb%2Fc".to_string(); /// assert!(StringModification::URLDecode.apply(&mut x, &Params::default()).is_ok()); /// assert_eq!(&x, "a/b/c"); @@ -407,7 +403,7 @@ pub enum StringModification { /// ``` /// # use url_cleaner::types::StringModification; /// # use url_cleaner::glue::CommandWrapper; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use std::str::FromStr; /// let mut x = "abc\n".to_string(); /// StringModification::CommandOutput(CommandWrapper::from_str("cat").unwrap()).apply(&mut x, &Params::default()); diff --git a/src/types/string_source.rs b/src/types/string_source.rs index 4cfcc91..c184623 100644 --- a/src/types/string_source.rs +++ b/src/types/string_source.rs @@ -8,8 +8,7 @@ use thiserror::Error; #[cfg(all(feature = "http", not(target_family = "wasm")))] use reqwest::{Error as ReqwestError, header::{HeaderMap, ToStrError}}; -use super::*; -use crate::config::Params; +use crate::types::*; use crate::glue::*; /// Allows conditions and mappers to get strings from various sources without requiring different conditions and mappers for each source. @@ -30,7 +29,7 @@ pub enum StringSource { /// ``` /// # use url_cleaner::types::StringSource; /// # use url::Url; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use std::borrow::Cow; /// let url = Url::parse("https://example.com").unwrap(); /// assert!(StringSource::String("abc".to_string()).get(&url, &Params::default(), false).is_ok_and(|x| x==Some(Cow::Borrowed("abc")))); @@ -41,7 +40,7 @@ pub enum StringSource { /// ``` /// # use url_cleaner::types::StringSource; /// # use url::Url; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use std::borrow::Cow; /// # use url_cleaner::types::UrlPart; /// let url = Url::parse("https://example.com").unwrap(); @@ -54,7 +53,7 @@ pub enum StringSource { /// ``` /// # use url_cleaner::types::StringSource; /// # use url::Url; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use std::borrow::Cow; /// # use std::collections::HashMap; /// let url = Url::parse("https://example.com").unwrap(); @@ -67,7 +66,7 @@ pub enum StringSource { /// ``` /// # use url_cleaner::types::StringSource; /// # use url::Url; - /// # use url_cleaner::config::Params; + /// # use url_cleaner::types::Params; /// # use std::borrow::Cow; /// # use url_cleaner::types::UrlPart; /// # use std::collections::HashSet; diff --git a/src/types/url_part.rs b/src/types/url_part.rs index fd06f35..0b0c9f4 100644 --- a/src/types/url_part.rs +++ b/src/types/url_part.rs @@ -5,7 +5,7 @@ use thiserror::Error; use serde::{Serialize, Deserialize}; use crate::types::*; -use crate::config::Params; +use crate::util::*; /// Getters and setters for various parts of a URL. /// In general (except for [`Self::DomainSegment`] and [`Self::PathSegment`]), setting a part to its own value is a no-op. @@ -592,8 +592,10 @@ pub enum UrlPart { /// The string to split and join the part on and with. split: String, /// The start of the range of segments to get. + #[serde(default)] start: Option, /// The end of the range of segments to get. + #[serde(default)] end: Option } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..234297d --- /dev/null +++ b/src/util.rs @@ -0,0 +1,78 @@ +use std::ops::Bound; + +/// Loops negative `index`es around similar to Python. +pub(crate) const fn neg_index(index: isize, len: usize) -> Option { + if 0<=index && (index as usize)<=len { + Some(index as usize) + } else if index<0 { + len.checked_sub(index.unsigned_abs()) + } else { + None + } +} + +/// Equivalent to how python handles indexing. Minus the panicking, of course. +pub(crate) fn neg_nth(iter: I, i: isize) -> Option { + if i<0 { + let temp=iter.into_iter().collect::>(); + let fixed_i=neg_index(i, temp.len())?; + temp.into_iter().nth(fixed_i) + } else { + iter.into_iter().nth(i as usize) + } +} + +/// `f` but allows for `None` to represent open range ends. +fn neg_maybe_index(index: Option, len: usize) -> Option> { + index.map(|index| neg_index(index, len)) +} + +/// A range that may or may not have one or both ends open. +pub(crate) fn neg_range(start: Option, end: Option, len: usize) -> Option<(Bound, Bound)> { + Some(( + match neg_maybe_index(start, len) { + Some(Some(start)) => Bound::Included(start), + Some(None ) => None?, + None => Bound::Unbounded + }, + match neg_maybe_index(end, len) { + Some(Some(end)) => Bound::Excluded(end), + Some(None ) => None?, + None => Bound::Unbounded + } + )) +} + +pub(crate) fn neg_vec_keep(iter: I, start: Option, end: Option) -> Option> { + let mut ret=iter.into_iter().collect::>(); + Some(ret.drain(neg_range(start, end, ret.len())?).collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn neg_index_test() { + assert_eq!(neg_index( 0, 5), Some(0)); + assert_eq!(neg_index( 4, 5), Some(4)); + assert_eq!(neg_index(-1, 5), Some(4)); + assert_eq!(neg_index( 6, 5), None ); + assert_eq!(neg_index(-6, 5), None ); + } + + #[test] + fn neg_nth_test() { + assert_eq!(neg_nth([1,2,3], 0), Some(1)); + assert_eq!(neg_nth([1,2,3], -1), Some(3)); + assert_eq!(neg_nth([1,2,3], -4), None); + } + + #[test] + fn neg_range_test() { + assert_eq!(neg_range(Some( 3), None , 10), Some((Bound::Included( 3), Bound::Unbounded))); + assert_eq!(neg_range(Some(10), None , 10), Some((Bound::Included(10), Bound::Unbounded))); + assert_eq!(neg_range(Some(11), None , 10), None); + assert_eq!(neg_range(Some( 3), Some(5), 10), Some((Bound::Included( 3), Bound::Excluded(5)))); + } +}