url_cleaner_engine/types/
actions.rs

1//! Logic for how a [`TaskState`] should be modified.
2
3use std::str::{FromStr, Utf8Error};
4use std::collections::HashSet;
5use std::borrow::Cow;
6
7use serde::{Serialize, Deserialize};
8use serde_with::{serde_as, SetPreventDuplicates};
9use thiserror::Error;
10#[cfg(feature = "http")]
11use reqwest::header::HeaderMap;
12#[expect(unused_imports, reason = "Used in doc comment.")]
13use url::{Url, PathSegmentsMut};
14use ::percent_encoding::percent_decode_str as pds;
15
16use crate::glue::*;
17use crate::types::*;
18use crate::util::*;
19
20/// Actions are how [`TaskState`]s get manipulated to clean URLs.
21///
22/// Please note that, in general, when a [`Action`] returns an [`Err`], the [`TaskState`] may still be modified. For example:
23/// ```
24/// use url_cleaner_engine::types::*;
25/// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
26///
27/// Action::All(vec![
28///     Action::SetPath("/change".into()),
29///     Action::Error("This won't revert the above".into()),
30///     Action::SetPath("/this-wont-happen".into())
31/// ]).apply(&mut task_state).unwrap_err();
32///
33/// assert_eq!(task_state.url, "https://example.com/change");
34/// ```
35///
36/// This is because reverting on an error requires keeping a copy of the input state, which is very expensive and, if the error is just going to be returned as the result of the [`Task`], not useful.
37///
38/// If you need to revert the [`TaskState`] when an error is returned, use [`Self::RevertOnError`] to revert the effects but still return the error, and optionally [`Self::IgnoreError`] to ignore the error.
39#[serde_as]
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Suitability)]
41#[serde(deny_unknown_fields)]
42#[serde(remote = "Self")]
43pub enum Action {
44    /// Does nothing.
45    /// # Examples
46    /// ```
47    /// use url_cleaner_engine::types::*;
48    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
49    ///
50    /// Action::None.apply(&mut task_state).unwrap();
51    ///
52    /// assert_eq!(task_state.url, "https://example.com/");
53    /// ```
54    None,
55    /// Always returns the error [`ActionError::ExplicitError`] with the included message.
56    /// # Errors
57    /// Always returns the error [`ActionError::ExplicitError`].
58    /// # Examples
59    /// ```
60    /// use url_cleaner_engine::types::*;
61    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
62    ///
63    /// Action::Error("...".into()).apply(&mut task_state).unwrap_err();
64    ///
65    /// assert_eq!(task_state.url, "https://example.com/");
66    /// ```
67    Error(String),
68    /// Prints debug info about the contained [`Self`] and the current [`TaskStateView`], then returns its return value.
69    /// # Errors
70    /// If the call to [`Self::apply`] returns an error, that error is returned after the debug info is printed.
71    #[suitable(never)]
72    Debug(Box<Self>),
73
74    /// If the call to [`Self::If::if`] passes, apply [`Self::If::then`].
75    ///
76    /// If the call to [`Self::If::if`] fails and [`Self::If::else`] is [`Some`], apply [`Self::If::else`].
77    /// # Errors
78    #[doc = edoc!(checkerr(Condition), applyerr(Self))]
79    /// # Examples
80    /// ```
81    /// use url_cleaner_engine::types::*;
82    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
83    ///
84    /// Action::If {
85    ///     r#if  : Condition::Always,
86    ///     then  : Box::new(Action::None),
87    ///     r#else: Some(Box::new(Action::Error("...".into())))
88    /// }.apply(&mut task_state).unwrap();
89    ///
90    /// Action::If {
91    ///     r#if  : Condition::Never,
92    ///     then  : Box::new(Action::None),
93    ///     r#else: Some(Box::new(Action::Error("...".into())))
94    /// }.apply(&mut task_state).unwrap_err();
95    ///
96    /// Action::If {
97    ///     r#if  : Condition::Always,
98    ///     then  : Box::new(Action::None),
99    ///     r#else: None
100    /// }.apply(&mut task_state).unwrap();
101    ///
102    /// Action::If {
103    ///     r#if  : Condition::Never,
104    ///     then  : Box::new(Action::None),
105    ///     r#else: None
106    /// }.apply(&mut task_state).unwrap();
107    /// ```
108    If {
109        /// The [`Condition`] to decide between [`Self::If::then`] and [`Self::If::else`].
110        r#if: Condition,
111        /// The [`Self`] to apply if [`Self::If::if`] passes.
112        then: Box<Self>,
113        /// The [`Self`] to apply if [`Self::If::if`] fails.
114        ///
115        /// Defaults to [`None`].
116        #[serde(default, skip_serializing_if = "is_default")]
117        r#else: Option<Box<Self>>
118    },
119    /// Applies the contained [`Self`]s in order.
120    ///
121    /// Please note that if one of the contained [`Self`]s returns an error, previous calls to [`Self::apply`] aren't reverted.
122    /// # Errors
123    #[doc = edoc!(applyerr(Self, 3))]
124    /// # Examples
125    /// ```
126    /// use url_cleaner_engine::types::*;
127    /// url_cleaner_engine::task_state!(task_state);
128    ///
129    /// Action::All(vec![
130    ///     Action::SetHost("example2.com".into()),
131    ///     Action::Error("...".into()),
132    ///     Action::SetHost("example3.com".into()),
133    /// ]).apply(&mut task_state).unwrap_err();
134    ///
135    /// assert_eq!(task_state.url, "https://example2.com/");
136    /// ```
137    All(Vec<Self>),
138    /// Gets the value specified by [`Self::PartMap::part`], indexes [`Self::PartMap::map`], and applies the returned [`Self`]
139    ///
140    /// If the call to [`Map::get`] returns [`None`], does nothing..
141    /// # Errors
142    #[doc = edoc!(applyerr(Self))]
143    /// # Examples
144    /// ```
145    /// use url_cleaner_engine::types::*;
146    /// url_cleaner_engine::task_state!(task_state);
147    ///
148    /// Action::PartMap {
149    ///     part: UrlPart::Host,
150    ///     map: Map {
151    ///         map: [
152    ///             ("example.com".into(), Action::Error("...".into()))
153    ///         ].into(),
154    ///         if_none: None,
155    ///         r#else: None
156    ///     }
157    /// }.apply(&mut task_state).unwrap_err();
158    /// ```
159    PartMap {
160        /// The [`UrlPart`] to index [`Self::PartMap::map`] with.
161        part: UrlPart,
162        /// The [`Map`] to index with [`Self::PartMap::part`].
163        #[serde(flatten)]
164        map: Map<Self>
165    },
166    /// Gets the string specified by [`Self::StringMap::value`], indexes [`Self::StringMap::map`], and applies the returned [`Self`].
167    ///
168    /// If the call to [`Map::get`] returns [`None`], does nothing.
169    /// # Errors
170    #[doc = edoc!(geterr(StringSource), applyerr(Self))]
171    /// # Examples
172    /// ```
173    /// use url_cleaner_engine::types::*;
174    /// url_cleaner_engine::task_state!(task_state);
175    ///
176    /// Action::StringMap {
177    ///     value: StringSource::String("a".into()),
178    ///     map: Map {
179    ///         map: [
180    ///             ("a".into(), Action::Error("...".into()))
181    ///         ].into(),
182    ///         if_none: None,
183    ///         r#else: None
184    ///     }
185    /// }.apply(&mut task_state).unwrap_err();
186    /// ```
187    StringMap {
188        /// The [`StringSource`] to index [`Self::StringMap::map`] with.
189        value: StringSource,
190        /// The [`Map`] to index with [`Self::StringMap::value`].
191        #[serde(flatten)]
192        map: Map<Self>
193    },
194    /// Gets the name of the partition [`Self::PartNamedPartitioning::part`] is in in the specified [`NamedPartitioning`], indexes [`Self::PartNamedPartitioning::map`] with the partition name, and if the [`Map`] has a [`Self`] there, applies it.
195    /// # Errors
196    #[doc = edoc!(geterr(StringSource, 2), getnone(StringSource, Action, 2), notfound(NamedPartitioning, Action), applyerr(Self))]
197    PartNamedPartitioning {
198        /// The [`NamedPartitioning`] to search in.
199        named_partitioning: StringSource,
200        /// The [`UrlPart`] whose value to find in the [`NamedPartitioning`].
201        part: UrlPart,
202        /// The [`Map`] to index.
203        #[serde(flatten)]
204        map: Map<Self>
205    },
206    /// [`Self::PartNamedPartitioning`] but uses each [`UrlPart`] in [`Self::FirstMatchingPartNamedPartitioning`] until a match is found.
207    /// # Errors
208    #[doc = edoc!(geterr(StringSource, 2), getnone(StringSource, Action, 2), notfound(NamedPartitioning, Action), applyerr(Self))]
209    /// # Examples
210    /// ```
211    /// use std::borrow::Cow;
212    /// use url_cleaner_engine::types::*;
213    ///
214    /// url_cleaner_engine::task_state!(task_state, url = "https://abc.example.com", params = Params {
215    ///     named_partitionings: Cow::Owned([
216    ///         (
217    ///             "a".into(),
218    ///             NamedPartitioning::try_from_iter([
219    ///                 ("b".into(), vec![Some("example.com".into())])
220    ///             ]).unwrap(),
221    ///         )
222    ///     ].into()),
223    ///     ..Default::default()
224    /// });
225    ///
226    /// Action::FirstMatchingPartNamedPartitioning {
227    ///     named_partitioning: "a".into(),
228    ///     parts: vec![UrlPart::NormalizedHost, UrlPart::RegDomain],
229    ///     map: [
230    ///         ("b".to_string(), Action::SetPath("/123".into()))
231    ///     ].into(),
232    /// }.apply(&mut task_state).unwrap();
233    ///
234    /// assert_eq!(task_state.url.path(), "/123");
235    /// ```
236    FirstMatchingPartNamedPartitioning {
237        /// The [`NamedPartitioning`] to search in.
238        named_partitioning: StringSource,
239        /// The [`UrlPart`]s whose value to find in the [`NamedPartitioning`].
240        parts: Vec<UrlPart>,
241        /// The [`Map`] to index.
242        #[serde(flatten)]
243        map: Map<Self>
244    },
245    /// Gets the name of the partition [`Self::StringNamedPartitioning::value`] is in in the specified [`NamedPartitioning`], indexes [`Self::StringNamedPartitioning::map`] with the partition name, and if the [`Map`] has a [`Self`] there, applies it.
246    /// # Errors
247    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action), notfound(NamedPartitioning, Action), applyerr(Self))]
248    StringNamedPartitioning {
249        /// The [`NamedPartitioning`] to search in.
250        named_partitioning: StringSource,
251        /// The [`StringSource`] whose value to find in the [`NamedPartitioning`].
252        value: StringSource,
253        /// The [`Map`] to index.
254        #[serde(flatten)]
255        map: Map<Self>
256    },
257    /// [`Self::StringNamedPartitioning`] but uses each [`StringSource`] in [`Self::FirstMatchingStringNamedPartitioning`] until a match is found.
258    /// # Errors
259    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action), notfound(NamedPartitioning, Action), applyerr(Self))]
260    /// # Examples
261    /// ```
262    /// use std::borrow::Cow;
263    /// use url_cleaner_engine::types::*;
264    ///
265    /// url_cleaner_engine::task_state!(task_state, url = "https://abc.example.com", params = Params {
266    ///     named_partitionings: Cow::Owned([
267    ///         (
268    ///             "a".into(),
269    ///             NamedPartitioning::try_from_iter([
270    ///                 ("b".into(), vec![Some("example.com".into())])
271    ///             ]).unwrap(),
272    ///         )
273    ///     ].into()),
274    ///     ..Default::default()
275    /// });
276    ///
277    /// Action::FirstMatchingStringNamedPartitioning {
278    ///     named_partitioning: "a".into(),
279    ///     values: vec![StringSource::Part(UrlPart::NormalizedHost), StringSource::Part(UrlPart::RegDomain)],
280    ///     map: [
281    ///         ("b".to_string(), Action::SetPath("/123".into()))
282    ///     ].into(),
283    /// }.apply(&mut task_state).unwrap();
284    ///
285    /// assert_eq!(task_state.url.path(), "/123");
286    /// ```
287    FirstMatchingStringNamedPartitioning {
288        /// The [`NamedPartitioning`] to search in.
289        named_partitioning: StringSource,
290        /// The [`StringSource`] whose value to find in the [`NamedPartitioning`].
291        values: Vec<StringSource>,
292        /// The [`Map`] to index.
293        #[serde(flatten)]
294        map: Map<Self>
295    },
296
297
298
299    /// Repeat [`Self::Repeat::actions`] until either the [`TaskState::url`] and [`TaskState::scratchpad`] end up in the same state or the rules were executed [`Self::Repeat::limit`] times.
300    /// # Errors
301    #[doc = edoc!(applyerr(Self, 3))]
302    Repeat {
303        /// The [`Self`]s to repeat.
304        actions: Vec<Action>,
305        /// The maximum amount of times to repeat.
306        ///
307        /// Defaults to 10.
308        #[serde(default = "get_10_u64")]
309        limit: u64
310    },
311
312
313
314    /// If the contained [`Self`] returns an error, ignore it.
315    ///
316    /// Does not revert any successful calls to [`Self::apply`]. For that, also use [`Self::RevertOnError`].
317    /// # Examples
318    /// ```
319    /// use url_cleaner_engine::types::*;
320    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
321    ///
322    /// Action::IgnoreError(Box::new(
323    ///     Action::RevertOnError(Box::new(
324    ///         Action::All(vec![
325    ///             Action::SetPath("/change".into()),
326    ///             Action::Error("This won't revert the above".into()),
327    ///             Action::SetPath("/wont-happen".into())
328    ///         ])
329    ///     ))
330    /// )).apply(&mut task_state).unwrap(); // Error is ignored.
331    ///
332    /// assert_eq!(task_state.url, "https://example.com/"); // The first `Action::SetPath` is reverted.
333    /// ```
334    IgnoreError(Box<Self>),
335    /// If the contained [`Self`] returns an error, revert the [`TaskState`] to its previous state then return the error.
336    ///
337    /// To ignore errors, put this in a [`Self::IgnoreError`].
338    /// # Errors
339    #[doc = edoc!(applyerr(Self))]
340    /// # Examples
341    /// ```
342    /// use url_cleaner_engine::types::*;
343    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
344    ///
345    /// Action::RevertOnError(Box::new(
346    ///     Action::All(vec![
347    ///         Action::SetPath("/change".into()),
348    ///         Action::Error("This won't revert the above".into()),
349    ///         Action::SetPath("/wont-happen".into())
350    ///     ])
351    /// )).apply(&mut task_state).unwrap_err(); // Still returns an error.
352    ///
353    /// assert_eq!(task_state.url, "https://example.com/"); // The first `Action::SetPath` is reverted.
354    /// ```
355    RevertOnError(Box<Self>),
356    /// If [`Self::TryElse::try`]'s call to [`Self::apply`] returns an error, apply [`Self::TryElse::else`].
357    /// # Errors
358    #[doc = edoc!(applyerrte(Self, Action))]
359    TryElse {
360        /// The [`Self`] to try first.
361        r#try: Box<Self>,
362        /// The [`Self`] to try if [`Self::TryElse::try`] returns an error.
363        r#else: Box<Self>
364    },
365    /// Applies the contained [`Self`]s in order, stopping as soon as a call to [`Self::apply`] doesn't return an error.
366    /// # Errors
367    #[doc = edoc!(applyerrfne(Self, Action))]
368    FirstNotError(Vec<Self>),
369
370    // Whole
371
372    /// Sets [`UrlPart::Whole`].
373    SetWhole(StringSource),
374    /// [`Url::join`].
375    /// # Errors
376    #[doc = edoc!(geterr(StringSource), callerr(Url::join))]
377    /// # Examples
378    /// ```
379    /// use url_cleaner_engine::types::*;
380    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com/a/b/c");
381    ///
382    /// Action::Join("..".into()).apply(&mut task_state).unwrap();
383    /// assert_eq!(task_state.url, "https://example.com/a/");
384    ///
385    ///
386    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com/a/b/c/");
387    ///
388    /// Action::Join("..".into()).apply(&mut task_state).unwrap();
389    /// assert_eq!(task_state.url, "https://example.com/a/b/");
390    /// ```
391    Join(StringSource),
392
393    // Scheme
394
395    /// [`BetterUrl::set_scheme`].
396    /// # Errors
397    #[doc = edoc!(callerr(BetterUrl::set_scheme))]
398    SetScheme(StringSource),
399
400    // Host
401
402    /// [`BetterUrl::set_host`].
403    /// # Errors
404    #[doc = edoc!(callerr(BetterUrl::set_host))]
405    SetHost(StringSource),
406    /// [`BetterUrl::set_subdomain`].
407    /// # Errors
408    #[doc = edoc!(callerr(BetterUrl::set_subdomain))]
409    SetSubdomain(StringSource),
410    /// [`BetterUrl::set_reg_domain`].
411    /// # Errors
412    #[doc = edoc!(callerr(BetterUrl::set_reg_domain))]
413    SetRegDomain(StringSource),
414    /// [`BetterUrl::set_domain`].
415    /// # Errors
416    #[doc = edoc!(callerr(BetterUrl::set_domain))]
417    SetDomain(StringSource),
418    /// [`BetterUrl::set_domain_middle`].
419    /// # Errors
420    #[doc = edoc!(callerr(BetterUrl::set_domain_middle))]
421    SetDomainMiddle(StringSource),
422    /// [`BetterUrl::set_not_domain_suffix`].
423    /// # Errors
424    #[doc = edoc!(callerr(BetterUrl::set_not_domain_suffix))]
425    SetNotDomainSuffix(StringSource),
426    /// [`BetterUrl::set_domain_suffix`].
427    /// # Errors
428    #[doc = edoc!(callerr(BetterUrl::set_domain_suffix))]
429    SetDomainSuffix(StringSource),
430    /// [`BetterUrl::domain_segment`].
431    /// # Errors
432    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::domain_segment))]
433    SetDomainSegment {
434        /// The index to insert the segment at.
435        index: isize,
436        /// The value to insert.
437        value: StringSource
438    },
439    /// [`BetterUrl::subdomain_segment`].
440    /// # Errors
441    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::subdomain_segment))]
442    SetSubdomainSegment {
443        /// The index to insert the segment at.
444        index: isize,
445        /// The value to insert.
446        value: StringSource
447    },
448    /// [`BetterUrl::domain_suffix_segment`].
449    /// # Errors
450    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::domain_suffix_segment))]
451    SetDomainSuffixSegment {
452        /// The index to insert the segment at.
453        index: isize,
454        /// The value to insert.
455        value: StringSource
456    },
457    /// [`BetterUrl::insert_domain_segment`].
458    /// # Errors
459    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::insert_domain_segment))]
460    InsertDomainSegment {
461        /// The index to insert the segment at.
462        index: isize,
463        /// The value to insert.
464        value: StringSource
465    },
466    /// [`BetterUrl::insert_subdomain_segment`].
467    /// # Errors
468    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::insert_subdomain_segment))]
469    InsertSubdomainSegment {
470        /// The index to insert the segment at.
471        index: isize,
472        /// The value to insert.
473        value: StringSource
474    },
475    /// [`BetterUrl::insert_domain_suffix_segment`].
476    /// # Errors
477    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::insert_domain_suffix_segment))]
478    InsertDomainSuffixSegment {
479        /// The index to insert the segment at.
480        index: isize,
481        /// The value to insert.
482        value: StringSource
483    },
484
485    /// [`BetterUrl::set_fqdn`] to [`true`]
486    /// # Errors
487    #[doc = edoc!(callerr(BetterUrl::set_fqdn))]
488    EnsureFqdnPeriod,
489    /// [`BetterUrl::set_fqdn`] to [`false`]
490    /// # Errors
491    #[doc = edoc!(callerr(BetterUrl::set_fqdn))]
492    RemoveFqdnPeriod,
493
494    /// [`BetterUrl::set_path`].
495    SetPath(StringSource),
496    /// Removes the specified [`UrlPart::PathSegment`].
497    /// # Errors
498    #[doc = edoc!(callerr(BetterUrl::set_path_segment))]
499    RemovePathSegment(isize),
500    /// [`BetterUrl::set_path_segment`].
501    /// # Errors
502    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::set_path_segment))]
503    SetPathSegment {
504        /// The [`UrlPart::PathSegment`] to set.
505        index: isize,
506        /// The value to set it to.
507        value: StringSource
508    },
509    /// [`BetterUrl::insert_path_segment`].
510    /// # Errors
511    #[doc = edoc!(geterr(StringSource), getnone(StringSource, ActionError), callerr(BetterUrl::insert_path_segment))]
512    InsertPathSegment {
513        /// The index to insert it at.
514        index: isize,
515        /// The value to insert.
516        value: StringSource
517    },
518    /// [`BetterUrl::set_path_segment`].
519    /// # Errors
520    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::set_path_segment))]
521    SetRawPathSegment {
522        /// The [`UrlPart::PathSegment`] to set.
523        index: isize,
524        /// The value to set it to.
525        value: StringSource
526    },
527    /// [`BetterUrl::insert_path_segment`].
528    /// # Errors
529    #[doc = edoc!(geterr(StringSource), getnone(StringSource, ActionError), callerr(BetterUrl::insert_path_segment))]
530    InsertRawPathSegment {
531        /// The index to insert it at.
532        index: isize,
533        /// The value to insert.
534        value: StringSource
535    },
536    /// [`PathSegmentsMut::pop_if_empty`].
537    /// # Errors
538    #[doc = edoc!(callerr(BetterUrl::path_segments_mut))]
539    RemoveEmptyLastPathSegment,
540    /// [`PathSegmentsMut::pop_if_empty`] and [`PathSegmentsMut::push`].
541    /// # Errors
542    #[doc = edoc!(geterr(StringSource), getnone(StringSource, ActionError), callerr(BetterUrl::path_segments_mut))]
543    RemoveEmptyLastPathSegmentAndInsertNew(StringSource),
544    /// Remove the first `n` path segments.
545    ///
546    /// The number of path segments after this succeeds is equal to the number of path segments before this is applied minus `n`.
547    ///
548    /// Because a path can't have zero segments, this means trying to remove all segments counts as not having enough segments. If this is a serious ergonomics issue for you, I'll prioritize making a workaround.
549    /// # Errors
550    #[doc = edoc!(callerr(BetterUrl::remove_first_n_path_segments))]
551    RemoveFirstNPathSegments(usize),
552    /// Keep the first `n` path segments.
553    ///
554    /// The number of path segments after this succeeds is equal to `n`.
555    ///
556    /// Because a path can't have zero segments, this means trying to keep zero segments always errors. This is easy to just not do.
557    /// # Errors
558    #[doc = edoc!(callerr(BetterUrl::keep_first_n_path_segments))]
559    KeepFirstNPathSegments(usize),
560    /// Remove the last `n` path segments.
561    ///
562    /// The number of path segments after this succeeds is equal to the number of path segments before this is applied minus `n`.
563    ///
564    /// Because a path can't have zero segments, this means trying to remove all segments counts as not having enough segments. If this is a serious ergonomics issue for you, I'll prioritize making a workaround.
565    /// # Errors
566    #[doc = edoc!(callerr(BetterUrl::remove_last_n_path_segments))]
567    RemoveLastNPathSegments(usize),
568    /// Keep the last `n` path segments.
569    ///
570    /// The number of path segments after this succeeds is equal to `n`.
571    ///
572    /// Because a path can't have zero segments, this means trying to keep zero segments always errors. This is easy to just not do.
573    /// # Errors
574    #[doc = edoc!(callerr(BetterUrl::keep_last_n_path_segments))]
575    KeepLastNPathSegments(usize),
576
577
578
579    /// [`BetterUrl::set_query`].
580    /// # Errors
581    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::set_query))]
582    SetQuery(StringSource),
583    /// [`BetterUrl::set_query_param`]
584    /// # Errors
585    #[doc = edoc!(geterr(StringSource), callerr(BetterUrl::set_query_param))]
586    SetQueryParam {
587        /// The query param to set.
588        param: QueryParamSelector,
589        /// The value to set it to.
590        value: StringSource
591    },
592    /// Remove the entire [`UrlPart::Query`].
593    /// # Examples
594    /// ```
595    /// use url_cleaner_engine::types::*;
596    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2");
597    ///
598    /// Action::RemoveQuery.apply(&mut task_state).unwrap();
599    /// assert_eq!(task_state.url, "https://example.com/");
600    /// ```
601    RemoveQuery,
602    /// If the [`Url::query`] is `Some("")`, set it to [`None`].
603    RemoveEmptyQuery,
604    /// Removes all query parameters with the specified name.
605    ///
606    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
607    /// # Errors
608    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action))]
609    /// # Examples
610    /// ```
611    /// use url_cleaner_engine::types::*;
612    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2&b=3&a=4&c=5");
613    ///
614    /// Action::RemoveQueryParam("a".into()).apply(&mut task_state).unwrap();
615    /// assert_eq!(task_state.url.query(), Some("b=3&c=5"));
616    /// Action::RemoveQueryParam("b".into()).apply(&mut task_state).unwrap();
617    /// assert_eq!(task_state.url.query(), Some("c=5"));
618    /// Action::RemoveQueryParam("c".into()).apply(&mut task_state).unwrap();
619    /// assert_eq!(task_state.url.query(), None);
620    /// ```
621    RemoveQueryParam(StringSource),
622    /// Keeps all query parameters with the specified name.
623    ///
624    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
625    AllowQueryParam(StringSource),
626    /// Removes all query params with names in the specified [`HashSet`].
627    ///
628    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
629    /// # Errors
630    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action))]
631    /// # Examples
632    /// ```
633    /// use url_cleaner_engine::types::*;
634    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2&b=3&%61=4&c=5");
635    ///
636    /// Action::RemoveQueryParams(["a".to_string(), "b".to_string()].into()).apply(&mut task_state).unwrap();
637    /// assert_eq!(task_state.url.query(), Some("c=5"));
638    /// Action::RemoveQueryParams(["c".to_string()].into()).apply(&mut task_state).unwrap();
639    /// assert_eq!(task_state.url.query(), None);
640    /// ```
641    RemoveQueryParams(#[serde_as(as = "SetPreventDuplicates<_>")] HashSet<String>),
642    /// Keeps only query params with names in the specified [`HashSet`].
643    ///
644    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
645    /// # Examples
646    /// ```
647    /// use url_cleaner_engine::types::*;
648    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2&b=3&%61=4&c=5");
649    ///
650    /// Action::AllowQueryParams(["a".to_string(), "b".to_string()].into()).apply(&mut task_state).unwrap();
651    /// assert_eq!(task_state.url.query(), Some("a=2&b=3&%61=4"));
652    /// Action::AllowQueryParams(["c".to_string()].into()).apply(&mut task_state).unwrap();
653    /// assert_eq!(task_state.url.query(), None);
654    /// ```
655    AllowQueryParams(#[serde_as(as = "SetPreventDuplicates<_>")] HashSet<String>),
656    /// Removes all query params with names matching the specified [`StringMatcher`].
657    ///
658    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
659    /// # Errors
660    #[doc = edoc!(checkerr(StringMatcher))]
661    /// # Examples
662    /// ```
663    /// use url_cleaner_engine::types::*;
664    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2&b=3&%61=4&c=5");
665    ///
666    /// Action::RemoveQueryParamsMatching(StringMatcher::Is("a".into())).apply(&mut task_state).unwrap();
667    /// assert_eq!(task_state.url.query(), Some("b=3&c=5"));
668    /// Action::RemoveQueryParamsMatching(StringMatcher::Is("b".into())).apply(&mut task_state).unwrap();
669    /// assert_eq!(task_state.url.query(), Some("c=5"));
670    /// Action::RemoveQueryParamsMatching(StringMatcher::Is("c".into())).apply(&mut task_state).unwrap();
671    /// assert_eq!(task_state.url.query(), None);
672    /// ```
673    RemoveQueryParamsMatching(StringMatcher),
674    /// Keeps only query params with names matching the specified [`StringMatcher`].
675    ///
676    /// For performance reasons, if the resulting query is empty, this instead sets it to [`None`].
677    /// # Errors
678    #[doc = edoc!(checkerr(StringMatcher))]
679    /// # Examples
680    /// ```
681    /// use url_cleaner_engine::types::*;
682    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?a=2&b=3&%61=4&c=5");
683    ///
684    /// Action::AllowQueryParamsMatching(StringMatcher::Is("a".into())).apply(&mut task_state).unwrap();
685    /// assert_eq!(task_state.url.query(), Some("a=2&%61=4"));
686    /// Action::AllowQueryParamsMatching(StringMatcher::Is("b".into())).apply(&mut task_state).unwrap();
687    /// assert_eq!(task_state.url.query(), None);
688    /// ```
689    AllowQueryParamsMatching(StringMatcher),
690    /// Extreme shorthand for handling universal query parameters.
691    /// # Errors
692    #[doc = edoc!(notfound(Set, Action))]
693    ///
694    /// If the list isn't found, returns the error [`ActionError::ListNotFound`].
695    RemoveQueryParamsInSetOrStartingWithAnyInList {
696        /// The name of the [`Set`] in [`Params::sets`] to use.
697        set: String,
698        /// The name of the list in [`Params::lists`] to use.
699        list: String
700    },
701    /// Rename the specified query parameter to the specified name.
702    /// # Errors
703    #[doc = edoc!(callerr(BetterUrl::rename_query_param))]
704    RenameQueryParam {
705        /// The query parameter to rename.
706        from: QueryParamSelector,
707        /// The name to rename it to.
708        to: StringSource
709    },
710
711    /// Sets [`UrlPart::Whole`] to the value of the first query parameter with a name determined by the [`TaskState`].
712    /// # Errors
713    /// If the URL doesn't have a query, returns the error [`ActionError::NoQuery`].
714    ///
715    /// If the specified query param isn't found, returns the error [`ActionError::QueryParamNotFound`].
716    ///
717    /// If the specified query param doesn't have a value, returns the error [`ActionError::QueryParamNoValue`].
718    /// 
719    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action))]
720    ///
721    /// If no matching query parameter is found, returns the error [`ActionError::QueryParamNotFound`].
722    /// # Examples
723    /// ```
724    /// use url_cleaner_engine::types::*;
725    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com?redirect=https://example.com/2");
726    ///
727    /// Action::GetUrlFromQueryParam("redirect".into()).apply(&mut task_state).unwrap();
728    /// assert_eq!(task_state.url, "https://example.com/2");
729    ///
730    /// Action::GetUrlFromQueryParam("redirect".into()).apply(&mut task_state).unwrap_err();
731    /// ```
732    GetUrlFromQueryParam(StringSource),
733
734    // Fragment
735
736    /// Removes the [`UrlPart::Fragment`].
737    RemoveFragment,
738    /// If the [`Url::fragment`] is `Some("")`, set it to [`None`].
739    RemoveEmptyFragment,
740
741    // General parts
742
743    /// Sets the specified [`UrlPart`] to the specified value.
744    /// # Errors
745    #[doc = edoc!(geterr(StringSource), seterr(UrlPart))]
746    /// # Examples
747    /// ```
748    /// use url_cleaner_engine::types::*;
749    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
750    ///
751    /// Action::SetPart {part: UrlPart::Path, value: "abc".into()}.apply(&mut task_state).unwrap();
752    /// assert_eq!(task_state.url, "https://example.com/abc");
753    /// ```
754    SetPart {
755        /// The part to set the value of.
756        part: UrlPart,
757        /// The value to set the part to.
758        value: StringSource
759    },
760    /// If the specified [`UrlPart`] is [`Some`], applies [`Self::ModifyPart::modification`].
761    ///
762    /// If the part is [`None`], does nothing.
763    /// # Errors
764    #[doc = edoc!(applyerr(StringModification), seterr(UrlPart))]
765    /// # Examples
766    /// ```
767    /// use url_cleaner_engine::types::*;
768    ///
769    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com");
770    ///
771    /// Action::ModifyPart {part: UrlPart::Path, modification: StringModification::Set("abc".into())}.apply(&mut task_state).unwrap();
772    /// assert_eq!(task_state.url, "https://example.com/abc");
773    ///
774    /// Action::ModifyPart {part: UrlPart::Query, modification: StringModification::Set("abc".into())}.apply(&mut task_state).unwrap();
775    /// assert_eq!(task_state.url, "https://example.com/abc?abc");
776    /// ```
777    ModifyPart {
778        /// The part to modify.
779        part: UrlPart,
780        /// The modification to apply to the part.
781        modification: StringModification
782    },
783    /// If the specified [`UrlPart`] is [`Some`], apply [`Self::ModifyPartIfSome::modification`].
784    /// # Errors
785    #[doc = edoc!(applyerr(StringModification), seterr(UrlPart))]
786    ModifyPartIfSome {
787        /// The [`UrlPart`] to modify.
788        part: UrlPart,
789        /// The [`StringModification`] to apply.
790        modification: StringModification
791    },
792    /// Sets [`Self::CopyPart::to`] to the value of [`Self::CopyPart::from`], leaving [`Self::CopyPart::from`] unchanged.
793    /// # Errors
794    #[doc = edoc!(seterr(UrlPart))]
795    /// # Examples
796    /// ```
797    /// use url_cleaner_engine::types::*;
798    ///
799    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com/abc#def");
800    ///
801    /// Action::CopyPart {from: UrlPart::Fragment, to: UrlPart::Path}.apply(&mut task_state).unwrap();
802    /// assert_eq!(task_state.url, "https://example.com/def#def");
803    /// ```
804    CopyPart {
805        /// The part whose value to copy.
806        from: UrlPart,
807        /// The part whose value to set.
808        to: UrlPart
809    },
810    /// Sets [`Self::CopyPart::to`] to the value of [`Self::CopyPart::from`], then sets [`Self::CopyPart::from`] to [`None`].
811    /// # Errors
812    #[doc = edoc!(seterr(UrlPart, 2))]
813    /// # Examples
814    /// ```
815    /// use url_cleaner_engine::types::*;
816    ///
817    /// url_cleaner_engine::task_state!(task_state, url = "https://example.com/abc#def");
818    ///
819    /// Action::MovePart {from: UrlPart::Fragment, to: UrlPart::Path}.apply(&mut task_state).unwrap();
820    /// assert_eq!(task_state.url, "https://example.com/def");
821    /// ```
822    MovePart {
823        /// The part whose value to move.
824        from: UrlPart,
825        /// The part whose value to set.
826        to: UrlPart
827    },
828
829    // Misc.
830
831    /// Sends an HTTP GET request to the current [`TaskState::url`], and sets it either to the value of the response's `Location` header (if the response is a redirect) or the final URL after redirects.
832    ///
833    /// If the `cache` feature flag is enabled, caches the operation with the subject `redirect`, the key set to the input URL, and the value set to the returned URL.
834    /// # Errors
835    #[cfg_attr(feature = "cache", doc = edoc!(callerr(Cache::read), callnone(Cache::read, ActionError::CachedUrlIsNone), callerr(BetterUrl::parse)))]
836    #[cfg_attr(feature = "cache", doc = "")]
837    #[doc = edoc!(callerr(TaskStateView::http_client), callerr(reqwest::blocking::RequestBuilder::send))]
838    ///
839    /// If the response is a redirect:
840    ///
841    /// - If the `Location` header is missing, returns the error [`ActionError::LocationHeaderNotFound`].
842    ///
843    #[doc = edoc!(listitem, callerr(std::str::from_utf8), callerr(BetterUrl::parse))]
844    #[cfg_attr(feature = "cache", doc = "")]
845    #[cfg_attr(feature = "cache", doc = edoc!(callerr(Cache::write)))]
846    #[cfg(feature = "http")]
847    ExpandRedirect {
848        /// If [`Some`], expand this URL instead.
849        ///
850        /// Defaults to [`None`].
851        #[serde(default, skip_serializing_if = "is_default")]
852        url: Option<StringSource>,
853        /// The extra headers to send.
854        ///
855        /// Defaults to an empty [`HeaderMap`].
856        #[serde(default, skip_serializing_if = "is_default", with = "serde_headermap")]
857        headers: HeaderMap,
858        /// The [`HttpClientConfigDiff`] to apply.
859        ///
860        /// Defaults to [`None`].
861        ///
862        /// Boxed because it's massive.
863        #[serde(default, skip_serializing_if = "is_default")]
864        http_client_config_diff: Option<Box<HttpClientConfigDiff>>
865    },
866    /// Sets the specified [`Scratchpad::flags`] to [`Self::SetScratchpadFlag::value`].
867    /// # Errors
868    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action))]
869    /// # Examples
870    /// ```
871    /// use url_cleaner_engine::types::*;
872    ///
873    /// url_cleaner_engine::task_state!(task_state);
874    ///
875    /// assert_eq!(task_state.scratchpad.flags.contains("abc"), false);
876    /// Action::SetScratchpadFlag {name: "abc".into(), value: true}.apply(&mut task_state).unwrap();
877    /// assert_eq!(task_state.scratchpad.flags.contains("abc"), true);
878    /// ```
879    SetScratchpadFlag {
880        /// The name of the flag to set.
881        name: StringSource,
882        /// The value to set the flag to.
883        value: bool
884    },
885    /// Sets the specified [`Scratchpad::vars`] to [`Self::SetScratchpadVar::value`].
886    /// # Errors
887    #[doc = edoc!(geterr(StringSource))]
888    /// # Examples
889    /// ```
890    /// use url_cleaner_engine::types::*;
891    ///
892    /// url_cleaner_engine::task_state!(task_state);
893    ///
894    /// Action::SetScratchpadVar {name: "abc".into(), value: "def".into()}.apply(&mut task_state).unwrap();
895    /// assert_eq!(task_state.scratchpad.vars.get("abc").map(|x| &**x), Some("def"));
896    /// Action::SetScratchpadVar {name: "abc".into(), value: StringSource::None}.apply(&mut task_state).unwrap();
897    /// assert_eq!(task_state.scratchpad.vars.get("abc").map(|x| &**x), None);
898    /// ```
899    SetScratchpadVar {
900        /// The name of the var to set.
901        name: StringSource,
902        /// The value to set the var to.
903        value: StringSource
904    },
905    /// If the specified [`Scratchpad::vars`] is [`Some`], applies [`Self::ModifyScratchpadVar::modification`].
906    ///
907    /// If the part is [`None`], does nothing.
908    /// # Errors
909    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action), applyerr(StringModification))]
910    /// # Examples
911    /// ```
912    /// use url_cleaner_engine::types::*;
913    ///
914    /// url_cleaner_engine::task_state!(task_state);
915    ///
916    /// Action::ModifyScratchpadVar {name: "abc".into(), modification: StringModification::Set("123".into())}.apply(&mut task_state).unwrap();
917    /// assert_eq!(task_state.scratchpad.vars.get("abc").map(|x| &**x), Some("123"));
918    /// Action::ModifyScratchpadVar {name: "abc".into(), modification: StringModification::Set(StringSource::None)}.apply(&mut task_state).unwrap();
919    /// assert_eq!(task_state.scratchpad.vars.get("abc").map(|x| &**x), None);
920    /// ```
921    ModifyScratchpadVar {
922        /// The name of the var to modify.
923        name: StringSource,
924        /// The modification to apply.
925        modification: StringModification
926    },
927    /// If an entry with a subject of [`Self::CacheUrl::subject`] and a key of [`TaskState::url`] exists in the [`TaskState::cache`], sets the URL to the entry's value.
928    ///
929    /// If no such entry exists, applies [`Self::CacheUrl::action`] and inserts a new entry equivalent to applying it.
930    ///
931    /// Does not cache the [`TaskState::scratchpad`].
932    /// # Errors
933    #[doc = edoc!(callerr(Cache::read), callnone(Cache::read, ActionError::CachedUrlIsNone), callerr(BetterUrl::parse), applyerr(Self), callerr(Cache::write))]
934    #[cfg(feature = "cache")]
935    CacheUrl {
936        /// The subject for the cache entry.
937        subject: StringSource,
938        /// The action to apply and cache.
939        action: Box<Self>
940    },
941    /// Applies a [`Self`] from [`TaskState::commons`]'s [`Commons::actions`].
942    /// # Errors
943    #[doc = edoc!(geterr(StringSource), getnone(StringSource, Action), commonnotfound(Self, Action), callerr(CommonCallArgsSource::build), applyerr(Self))]
944    /// # Examples
945    /// ```
946    /// use url_cleaner_engine::types::*;
947    ///
948    /// url_cleaner_engine::task_state!(task_state, commons = Commons {
949    ///     actions: [("abc".into(), Action::None)].into(),
950    ///     ..Default::default()
951    /// });
952    ///
953    /// Action::Common(CommonCall {name: Box::new("abc".into()), args: Default::default()}).apply(&mut task_state).unwrap();
954    /// ```
955    Common(CommonCall),
956    /// Gets a [`Self`] from [`TaskStateView::common_args`]'s [`CommonCallArgs::actions`] and applies it.
957    /// # Errors
958    /// If [`TaskStateView::common_args`] is [`None`], returns the error [`ActionError::NotInCommonContext`].
959    ///
960    #[doc = edoc!(commoncallargnotfound(Self, Action), applyerr(Self))]
961    CommonCallArg(StringSource),
962    /// Calls the specified function and returns its value.
963    ///
964    /// Because this uses function pointers, this plays weirdly with [`PartialEq`]/[`Eq`].
965    /// # Errors
966    #[doc = edoc!(callerr(Self::Custom::0))]
967    /// # Examples
968    /// ```
969    /// use url_cleaner_engine::types::*;
970    ///
971    /// url_cleaner_engine::task_state!(task_state);
972    ///
973    /// fn some_complex_operation(task_state: &mut TaskState) -> Result<(), ActionError> {
974    ///     Ok(())
975    /// }
976    ///
977    /// Action::Custom(some_complex_operation).apply(&mut task_state).unwrap();
978    /// ```
979    #[cfg(feature = "custom")]
980    #[suitable(never)]
981    #[serde(skip)]
982    Custom(fn(&mut TaskState) -> Result<(), ActionError>)
983}
984
985string_or_struct_magic!(Action);
986
987/// The error returned when trying to deserialize a [`StringModification`] variant with fields that aren't all defaultable.
988#[derive(Debug, Error)]
989#[error("Tried deserializing undefaultable or Action unknown variant {0}.")]
990pub struct NonDefaultableActionVariant(String);
991
992impl From<&str> for NonDefaultableActionVariant {
993    fn from(value: &str) -> Self {
994        value.to_string().into()
995    }
996}
997
998impl From<String> for NonDefaultableActionVariant {
999    fn from(value: String) -> Self {
1000        Self(value)
1001    }
1002}
1003
1004impl FromStr for Action {
1005    type Err = NonDefaultableActionVariant;
1006
1007    fn from_str(s: &str) -> Result<Self, Self::Err> {
1008        Ok(match s {
1009            "None"                       => Action::None,
1010            "EnsureFqdnPeriod"           => Action::EnsureFqdnPeriod,
1011            "RemoveFqdnPeriod"           => Action::RemoveFqdnPeriod,
1012            "RemoveEmptyLastPathSegment" => Action::RemoveEmptyLastPathSegment,
1013            "RemoveQuery"                => Action::RemoveQuery,
1014            "RemoveEmptyQuery"           => Action::RemoveEmptyQuery,
1015            "RemoveFragment"             => Action::RemoveFragment,
1016            "RemoveEmptyFragment"        => Action::RemoveEmptyFragment,
1017            #[cfg(feature = "http")]
1018            "ExpandRedirect"             => Action::ExpandRedirect {url: Default::default(), headers: Default::default(), http_client_config_diff: Default::default()},
1019            _                            => return Err(s.into())
1020        })
1021    }
1022}
1023
1024/// Helper function to get the default [`Action::Repeat::limit`].
1025const fn get_10_u64() -> u64 {10}
1026
1027/// The enum of errors [`Action::apply`] can return.
1028#[derive(Debug, Error)]
1029pub enum ActionError {
1030    /// Returned when a [`Action::Error`] is used.
1031    #[error("Explicit error: {0}")]
1032    ExplicitError(String),
1033    /// Returned when both [`Action`]s in a [`Action::TryElse`] return errors.
1034    #[error("Both Actions in a Action::TryElse returned errors.")]
1035    TryElseError {
1036        /// The error returned by [`Action::TryElse::try`].
1037        try_error: Box<Self>,
1038        /// The error returned by [`Action::TryElse::else`].
1039        else_error: Box<Self>
1040    },
1041    /// Returned when all [`Action`]s in a [`Action::FirstNotError`] error.
1042    #[error("All Actions in a Action::FirstNotError errored.")]
1043    FirstNotErrorErrors(Vec<Self>),
1044
1045    /// Returned when a [`StringSourceError`] is encountered.
1046    #[error(transparent)]
1047    StringSourceError(#[from] StringSourceError),
1048    /// Returned when a part of the URL is [`None`] where it has to be [`Some`].
1049    #[error("A StringSource returned None where it had to return Some.")]
1050    StringSourceIsNone,
1051    /// Returned when a [`StringModificationError`] is encountered.
1052    #[error(transparent)]
1053    StringModificationError(#[from] StringModificationError),
1054    /// Returned when a [`StringMatcherError`] is encountered.
1055    #[error(transparent)]
1056    StringMatcherError(#[from] StringMatcherError),
1057    /// Returned when a [`StringLocationError`] is encountered.
1058    #[error(transparent)]
1059    StringLocationError(#[from] StringLocationError),
1060
1061    /// Returned when a [`SetSchemeError`] is encountered.
1062    #[error(transparent)]
1063    SetSchemeError(#[from] SetSchemeError),
1064    /// Returned when attempting to set a URL's scheme to [`None`].
1065    #[error("Attempted to set the URL's scheme to None.")]
1066    SchemeCannotBeNone,
1067
1068    /// Returned when a [`SetHostError`] is encountered.
1069    #[error(transparent)]
1070    SetHostError(#[from] SetHostError),
1071    /// Returned when a [`SetSubdomainError`] is encountered.
1072    #[error(transparent)]
1073    SetSubdomainError(#[from] SetSubdomainError),
1074    /// Returned when a [`SetRegDomainError`] is encountered.
1075    #[error(transparent)]
1076    SetRegDomainError(#[from] SetRegDomainError),
1077    /// Returned when a [`SetDomainError`] is encountered.
1078    #[error(transparent)]
1079    SetDomainError(#[from] SetDomainError),
1080    /// Returned when a [`SetDomainMiddleError`] is encountered.
1081    #[error(transparent)]
1082    SetDomainMiddleError(#[from] SetDomainMiddleError),
1083    /// Returned when a [`SetNotDomainSuffixError`] is encountered.
1084    #[error(transparent)]
1085    SetNotDomainSuffixError(#[from] SetNotDomainSuffixError),
1086    /// Returned when a [`SetDomainSuffixError`] is encountered.
1087    #[error(transparent)]
1088    SetDomainSuffixError(#[from] SetDomainSuffixError),
1089    /// Returned when a [`SetFqdnError`] is encountered.
1090    #[error(transparent)]
1091    SetFqdnError(#[from] SetFqdnError),
1092
1093    /// Returned when a [`InsertDomainSegmentError`] is encountered.
1094    #[error(transparent)]
1095    InsertDomainSegmentError(#[from] InsertDomainSegmentError),
1096    /// Returned when a [`InsertSubdomainSegmentError`] is encountered.
1097    #[error(transparent)]
1098    InsertSubdomainSegmentError(#[from] InsertSubdomainSegmentError),
1099    /// Returned when a [`InsertDomainSuffixSegmentError`] is encountered.
1100    #[error(transparent)]
1101    InsertDomainSuffixSegmentError(#[from] InsertDomainSuffixSegmentError),
1102    /// Returned when a [`SetDomainSegmentError`] is encountered.
1103    #[error(transparent)]
1104    SetDomainSegmentError(#[from] SetDomainSegmentError),
1105    /// Returned when a [`SetSubdomainSegmentError`] is encountered.
1106    #[error(transparent)]
1107    SetSubdomainSegmentError(#[from] SetSubdomainSegmentError),
1108    /// Returned when a [`SetDomainSuffixSegmentError`] is encountered.
1109    #[error(transparent)]
1110    SetDomainSuffixSegmentError(#[from] SetDomainSuffixSegmentError),
1111
1112    /// Returned when attempting to set a URL's path to [`None`].
1113    #[error("Attempted to set the URL's path to None.")]
1114    PathCannotBeNone,
1115    /// Returned when attempting to get the path segments of a URL with no path segments.
1116    #[error("Attempted to get the path segments of a URL with no path segments.")]
1117    UrlDoesNotHavePathSegments,
1118    /// Returned when a [`SetPathSegmentError`] is encountered.
1119    #[error(transparent)]
1120    SetPathSegmentError(#[from] SetPathSegmentError),
1121    /// Returned when a [`InsertPathSegmentError`] is encountered.
1122    #[error(transparent)]
1123    InsertPathSegmentError(#[from] InsertPathSegmentError),
1124    /// Returned when attempting to keep/remove more path segments than are available.
1125    #[error("Attempted to keep/remove more path segments than were available.")]
1126    NotEnoughPathSegments,
1127    /// Returned when a [`RemoveFirstNPathSegmentsError`] is encountered.
1128    #[error(transparent)]
1129    RemoveFirstNPathSegmentsError(#[from] RemoveFirstNPathSegmentsError),
1130    /// Returned when a [`KeepFirstNPathSegmentsError`] is encountered.
1131    #[error(transparent)]
1132    KeepFirstNPathSegmentsError(#[from] KeepFirstNPathSegmentsError),
1133    /// Returned when a [`RemoveLastNPathSegmentsError`] is encountered.
1134    #[error(transparent)]
1135    RemoveLastNPathSegmentsError(#[from] RemoveLastNPathSegmentsError),
1136    /// Returned when a [`KeepLastNPathSegmentsError`] is encountered.
1137    #[error(transparent)]
1138    KeepLastNPathSegmentsError(#[from] KeepLastNPathSegmentsError),
1139
1140    /// Returned when attempting to get the value of a query param from a URL with no query.
1141    #[error("Attempted to get the value of a query param from a URL with no query.")]
1142    NoQuery,
1143    /// Returned when attempting to get the value of a query param that wasn't found.
1144    #[error("Attempted to get the value of a query param that wasn't found.")]
1145    QueryParamNotFound,
1146    /// Returned when attempting to get the value of a query param that didn't have a value.
1147    #[error("Attempted to get the value of a query param that didn't have a value.")]
1148    QueryParamNoValue,
1149    /// Returned when a [`RenameQueryParamError`] is encountered.
1150    #[error(transparent)]
1151    RenameQueryParamError(#[from] RenameQueryParamError),
1152    /// Returned when a [`SetQueryParamError`] is encountered.
1153    #[error(transparent)]
1154    SetQueryParamError(#[from] SetQueryParamError),
1155
1156    /// Returned when a [`url::ParseError`] is encountered.
1157    #[error(transparent)]
1158    UrlParseError(#[from] url::ParseError),
1159    /// Returned when a [`Utf8Error`] is encountered.
1160    #[error(transparent)]
1161    Utf8Error(#[from] Utf8Error),
1162    /// Returned when a [`SetUrlPartError`] is encountered.
1163    #[error(transparent)]
1164    SetUrlPartError(#[from] SetUrlPartError),
1165
1166    /// Returned when a [`ConditionError`] is encountered.
1167    #[error(transparent)]
1168    ConditionError(#[from] ConditionError),
1169
1170    /// Returned when a [`NamedPartitioning`] with the specified name isn't found.
1171    #[error("A NamedPartitioning with the specified name wasn't found.")]
1172    NamedPartitioningNotFound,
1173    /// Returned when a [`Set`] with the specified name isn't found.
1174    #[error("A Set with the specified name wasn't found.")]
1175    SetNotFound,
1176    /// Returned when a list with the specified name isn't found.
1177    #[error("A list with the specified name wasn't found.")]
1178    ListNotFound,
1179
1180    /// Returned when a [`reqwest::Error`] is encountered.
1181    #[cfg(feature = "http")]
1182    #[error(transparent)]
1183    ReqwestError(#[from] reqwest::Error),
1184    /// Returned when a redirect's `Location` header isn't found.
1185    #[cfg(feature = "http")]
1186    #[error("The redirect's Location header wasn't found")]
1187    LocationHeaderNotFound,
1188    /// Returned when a [`reqwest::header::ToStrError`] is encountered.
1189    #[cfg(feature = "http")]
1190    #[error(transparent)]
1191    ToStrError(#[from] reqwest::header::ToStrError),
1192
1193    /// Returned when attempting to get a URL from the cache but its value is [`None`].
1194    #[cfg(feature = "cache")]
1195    #[error("Attempted to get a URL from the cache but its value was None.")]
1196    CachedUrlIsNone,
1197    /// Returned when a [`ReadFromCacheError`] is encountered.
1198    #[cfg(feature = "cache")]
1199    #[error(transparent)]
1200    ReadFromCacheError(#[from] ReadFromCacheError),
1201    /// Returned when a [`WriteToCacheError`] is encountered.
1202    #[cfg(feature = "cache")]
1203    #[error(transparent)]
1204    WriteToCacheError(#[from] WriteToCacheError),
1205
1206    /// Returned when a [`CommonCallArgsError`] is encountered.
1207    #[error(transparent)]
1208    CommonCallArgsError(#[from] CommonCallArgsError),
1209    /// Returned when a [`Action`] with the specified name isn't found in the [`Commons::actions`].
1210    #[error("An Action with the specified name wasn't found in the Commons::actions.")]
1211    CommonActionNotFound,
1212    /// Returned when trying to use [`Action::CommonCallArg`] outside of a common context.
1213    #[error("Tried to use Action::CommonCallArg outside of a common context.")]
1214    NotInCommonContext,
1215    /// Returned when the [`Action`] requested from an [`Action::CommonCallArg`] isn't found.
1216    #[error("The Action requested from an Action::CommonCallArg wasn't found.")]
1217    CommonCallArgActionNotFound,
1218    /// An arbitrary [`std::error::Error`] returned by [`Action::Custom`].
1219    #[error(transparent)]
1220    #[cfg(feature = "custom")]
1221    Custom(Box<dyn std::error::Error + Send>)
1222}
1223
1224impl Action {
1225    /// Applies the specified variant of [`Self`].
1226    ///
1227    /// If an error is returned, `task_state` may be left in a partially modified state.
1228    /// # Errors
1229    /// See each variant of [`Self`] for when each variant returns an error.
1230    #[allow(clippy::missing_panics_doc, reason = "Can't happen.")]
1231    pub fn apply(&self, task_state: &mut TaskState) -> Result<(), ActionError> {
1232        debug!(Action::apply, self, task_state.debug_helper());
1233
1234        match self {
1235            // Debug/constants
1236
1237            Self::None => {},
1238            Self::Error(msg) => Err(ActionError::ExplicitError(msg.clone()))?,
1239            Self::Debug(action) => {
1240                let old_task_state = format!("{:?}", task_state.debug_helper());
1241                let return_value=action.apply(task_state);
1242                eprintln!("=== Action::Debug ===\nOld task_state: {old_task_state}\nReturn value: {return_value:?}\nNew task_state: {:?}", task_state.debug_helper());
1243                return_value?
1244            },
1245
1246            // Error handling
1247
1248            Self::IgnoreError(action) => {let _ = action.apply(task_state);},
1249            Self::TryElse {r#try, r#else} => match r#try.apply(task_state) {
1250                Ok(x) => x,
1251                Err(try_error) => match r#else.apply(task_state) {
1252                    Ok(x) => x,
1253                    Err(else_error) => Err(ActionError::TryElseError {try_error: Box::new(try_error), else_error: Box::new(else_error)})?
1254                }
1255            },
1256            Self::FirstNotError(actions) => {
1257                let mut errors = Vec::new();
1258                for action in actions {
1259                    match action.apply(task_state) {
1260                        Ok(()) => return Ok(()),
1261                        Err(e) => errors.push(e)
1262                    }
1263                }
1264                Err(ActionError::FirstNotErrorErrors(errors))?
1265            },
1266            Self::RevertOnError(action) => {
1267                let old_url = task_state.url.clone();
1268                let old_scratchpad = task_state.scratchpad.clone();
1269                if let Err(e) = action.apply(task_state) {
1270                    *task_state.url = old_url;
1271                    *task_state.scratchpad = old_scratchpad;
1272                    Err(e)?;
1273                }
1274            },
1275
1276            // Logic
1277
1278            Self::If {r#if, then, r#else} => if r#if.check(&task_state.to_view())? {
1279                then.apply(task_state)?;
1280            } else if let Some(r#else) = r#else {
1281                r#else.apply(task_state)?;
1282            },
1283            Self::All(actions) => {
1284                for action in actions {
1285                    action.apply(task_state)?;
1286                }
1287            },
1288            Self::Repeat{actions, limit} => {
1289                let mut previous_url;
1290                let mut previous_scratchpad;
1291                for _ in 0..*limit {
1292                    previous_url = task_state.url.to_string();
1293                    previous_scratchpad = task_state.scratchpad.clone();
1294                    for action in actions {
1295                        action.apply(task_state)?;
1296                    }
1297                    if task_state.url == &previous_url && task_state.scratchpad == &previous_scratchpad {break;}
1298                }
1299            },
1300
1301            // Maps
1302
1303            Self::PartMap   {part , map} => if let Some(action) = map.get(part .get( task_state.url      ) ) {action.apply(task_state)?;},
1304            Self::StringMap {value, map} => if let Some(action) = map.get(value.get(&task_state.to_view())?) {action.apply(task_state)?;},
1305
1306            Self::PartNamedPartitioning   {named_partitioning, part , map} => if let Some(action) = map.get(task_state.params.named_partitionings.get(get_str!(named_partitioning, task_state, ActionError)).ok_or(ActionError::NamedPartitioningNotFound)?.get_partition_of(part.get(task_state.url).as_deref())) {action.apply(task_state)?;}
1307            Self::FirstMatchingPartNamedPartitioning {named_partitioning, parts, map} => {
1308                let named_partitioning = task_state.params.named_partitionings.get(get_str!(named_partitioning, task_state, ActionError)).ok_or(ActionError::NamedPartitioningNotFound)?;
1309                for part in parts.iter() {
1310                    if let Some(action) = map.get(named_partitioning.get_partition_of(part.get(task_state.url).as_deref())) {
1311                        return action.apply(task_state);
1312                    }
1313                }
1314            }
1315            Self::StringNamedPartitioning {named_partitioning, value, map} => if let Some(action) = map.get(task_state.params.named_partitionings.get(get_str!(named_partitioning, task_state, ActionError)).ok_or(ActionError::NamedPartitioningNotFound)?.get_partition_of(get_option_str!(value, task_state) )) {action.apply(task_state)?;}
1316            Self::FirstMatchingStringNamedPartitioning {named_partitioning, values, map} => {
1317                let named_partitioning = task_state.params.named_partitionings.get(get_str!(named_partitioning, task_state, ActionError)).ok_or(ActionError::NamedPartitioningNotFound)?;
1318                for value in values.iter() {
1319                    if let Some(action) = map.get(named_partitioning.get_partition_of(get_option_str!(value, task_state))) {
1320                        return action.apply(task_state);
1321                    }
1322                }
1323            }
1324
1325            // Whole
1326
1327            Self::SetWhole(new) => *task_state.url = BetterUrl::parse(get_new_str!(new, task_state, ActionError))?,
1328            Self::Join(with) => *task_state.url=task_state.url.join(get_str!(with, task_state, ActionError))?.into(),
1329
1330            // Scheme
1331
1332            Self::SetScheme(to) => task_state.url.set_scheme(get_new_str!(to, task_state, ActionError))?,
1333
1334            // Domain
1335
1336            Self::SetHost           (to) => task_state.url.set_host             (get_new_option_str!(to, task_state))?,
1337            Self::SetSubdomain      (to) => task_state.url.set_subdomain        (get_new_option_str!(to, task_state))?,
1338            Self::SetRegDomain      (to) => task_state.url.set_reg_domain       (get_new_option_str!(to, task_state))?,
1339            Self::SetDomain         (to) => task_state.url.set_domain           (get_new_option_str!(to, task_state))?,
1340            Self::SetDomainMiddle   (to) => task_state.url.set_domain_middle    (get_new_option_str!(to, task_state))?,
1341            Self::SetNotDomainSuffix(to) => task_state.url.set_not_domain_suffix(get_new_option_str!(to, task_state))?,
1342            Self::SetDomainSuffix   (to) => task_state.url.set_domain_suffix    (get_new_option_str!(to, task_state))?,
1343
1344            Self::SetDomainSegment               {index, value} => task_state.url.set_domain_segment          (*index, get_new_option_str!(value, task_state))?,
1345            Self::SetSubdomainSegment            {index, value} => task_state.url.set_subdomain_segment       (*index, get_new_option_str!(value, task_state))?,
1346            Self::SetDomainSuffixSegment         {index, value} => task_state.url.set_domain_suffix_segment   (*index, get_new_option_str!(value, task_state))?,
1347            Self::InsertDomainSegment            {index, value} => task_state.url.insert_domain_segment       (*index, get_new_str!(value, task_state, ActionError))?,
1348            Self::InsertSubdomainSegment         {index, value} => task_state.url.insert_subdomain_segment    (*index, get_new_str!(value, task_state, ActionError))?,
1349            Self::InsertDomainSuffixSegment      {index, value} => task_state.url.insert_domain_suffix_segment(*index, get_new_str!(value, task_state, ActionError))?,
1350
1351            Self::EnsureFqdnPeriod => task_state.url.set_fqdn(true)?,
1352            Self::RemoveFqdnPeriod => task_state.url.set_fqdn(false)?,
1353
1354            // Path
1355
1356            Self::SetPath(to) => task_state.url.set_path(get_new_str!(to, task_state, ActionError)),
1357
1358            Self::RemovePathSegment         (index) => task_state.url.set_path_segment(*index, None)?,
1359            Self::SetPathSegment            {index, value} => task_state.url.set_path_segment       (*index, get_new_option_str!(value, task_state))?,
1360            Self::InsertPathSegment         {index, value} => task_state.url.insert_path_segment    (*index, get_new_str!(value, task_state, ActionError))?,
1361            Self::SetRawPathSegment         {index, value} => task_state.url.set_raw_path_segment   (*index, get_new_option_str!(value, task_state))?,
1362            Self::InsertRawPathSegment      {index, value} => task_state.url.insert_raw_path_segment(*index, get_new_str!(value, task_state, ActionError))?,
1363            Self::RemoveEmptyLastPathSegment => {task_state.url.path_segments_mut().ok_or(ActionError::UrlDoesNotHavePathSegments)?.pop_if_empty();},
1364            Self::RemoveEmptyLastPathSegmentAndInsertNew(value) => {
1365                let value = get_new_str!(value, task_state, ActionError);
1366                let mut segments_mut = task_state.url.path_segments_mut().ok_or(ActionError::UrlDoesNotHavePathSegments)?;
1367                segments_mut.pop_if_empty();
1368                segments_mut.push(value);
1369            },
1370            Self::RemoveFirstNPathSegments(n) => task_state.url.remove_first_n_path_segments(*n)?,
1371            Self::KeepFirstNPathSegments  (n) => task_state.url.keep_first_n_path_segments  (*n)?,
1372            Self::RemoveLastNPathSegments (n) => task_state.url.remove_last_n_path_segments (*n)?,
1373            Self::KeepLastNPathSegments   (n) => task_state.url.keep_last_n_path_segments   (*n)?,
1374
1375            // Query
1376
1377            Self::SetQuery(to) => task_state.url.set_query(get_new_option_str!(to, task_state)),
1378            Self::SetQueryParam {param: QueryParamSelector {name, index}, value} => task_state.url.set_query_param(name, *index, get_new_option_str!(value, task_state).map(Some))?,
1379            Self::RemoveQuery => task_state.url.set_query(None),
1380            Self::RemoveEmptyQuery => if task_state.url.query() == Some("") {task_state.url.set_query(None)},
1381            Self::RemoveQueryParam(name) => if let Some(query) = task_state.url.query() {
1382                let mut new = String::with_capacity(query.len());
1383                let name = get_str!(name, task_state, ActionError);
1384                for param in query.split('&') {
1385                    if pds(param.split('=').next().expect("The first segment to always exist.")).ne(name.bytes()) {
1386                        if !new.is_empty() {new.push('&');}
1387                        new.push_str(param);
1388                    }
1389                }
1390                if new.len() != query.len() {
1391                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1392                }
1393            },
1394            Self::AllowQueryParam(name) => if let Some(query) = task_state.url.query() {
1395                let mut new = String::with_capacity(query.len());
1396                let name = get_str!(name, task_state, ActionError);
1397                for param in query.split('&') {
1398                    if pds(param.split('=').next().expect("The first segment to always exist.")).eq(name.bytes()) {
1399                        if !new.is_empty() {new.push('&');}
1400                        new.push_str(param);
1401                    }
1402                }
1403                if new.len() != query.len() {
1404                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1405                }
1406            },
1407            Self::RemoveQueryParams(names) => if let Some(query) = task_state.url.query() {
1408                let mut new = String::with_capacity(query.len());
1409                for param in query.split('&') {
1410                    if !names.contains(&*pds(param.split('=').next().expect("The first segment to always exist.")).decode_utf8_lossy()) {
1411                        if !new.is_empty() {new.push('&');}
1412                        new.push_str(param);
1413                    }
1414                }
1415                if new.len() != query.len() {
1416                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1417                }
1418            },
1419            Self::AllowQueryParams(names) => if let Some(query) = task_state.url.query() {
1420                let mut new = String::with_capacity(query.len());
1421                for param in query.split('&') {
1422                    if names.contains(&*pds(param.split('=').next().expect("The first segment to always exist.")).decode_utf8_lossy()) {
1423                        if !new.is_empty() {new.push('&');}
1424                        new.push_str(param);
1425                    }
1426                }
1427                if new.len() != query.len() {
1428                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1429                }
1430            },
1431            Self::RemoveQueryParamsMatching(matcher) => if let Some(query) = task_state.url.query() {
1432                let mut new = String::with_capacity(query.len());
1433                for param in query.split('&') {
1434                    if !matcher.check(Some(&*pds(param.split('=').next().expect("The first segment to always exist.")).decode_utf8_lossy()), &task_state.to_view())? {
1435                        if !new.is_empty() {new.push('&');}
1436                        new.push_str(param);
1437                    }
1438                }
1439                if new.len() != query.len() {
1440                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1441                }
1442            },
1443            Self::AllowQueryParamsMatching(matcher) => if let Some(query) = task_state.url.query() {
1444                let mut new = String::with_capacity(query.len());
1445                for param in query.split('&') {
1446                    if matcher.check(Some(&*pds(param.split('=').next().expect("The first segment to always exist.")).decode_utf8_lossy()), &task_state.to_view())? {
1447                        if !new.is_empty() {new.push('&');}
1448                        new.push_str(param);
1449                    }
1450                }
1451                if new.len() != query.len() {
1452                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1453                }
1454            },
1455            Self::RemoveQueryParamsInSetOrStartingWithAnyInList {set, list} => if let Some(query) = task_state.url.query() {
1456                let mut new = String::with_capacity(query.len());
1457                let set = task_state.params.sets.get(set).ok_or(ActionError::SetNotFound)?;
1458                let list = task_state.params.lists.get(list).ok_or(ActionError::ListNotFound)?;
1459                for param in query.split('&') {
1460                    let name = pds(param.split('=').next().expect("The first segment to always exist.")).decode_utf8_lossy();
1461                    if !(set.contains(Some(&*name)) || list.iter().any(|x| name.starts_with(x))) {
1462                        if !new.is_empty() {new.push('&');}
1463                        new.push_str(param);
1464                    }
1465                }
1466                if new.len() != query.len() {
1467                    task_state.url.set_query(Some(&*new).filter(|x| !x.is_empty()));
1468                }
1469            },
1470            Self::RenameQueryParam {from, to} => task_state.url.rename_query_param(&from.name, from.index, get_new_str!(to, task_state, ActionError))?,
1471
1472            Self::GetUrlFromQueryParam(name) => match task_state.url.query_param(get_str!(name, task_state, ActionError), 0) {
1473                Some(Some(Some(new_url))) => {*task_state.url = BetterUrl::parse(&new_url)?;},
1474                Some(Some(None))          => Err(ActionError::QueryParamNoValue)?,
1475                Some(None)                => Err(ActionError::QueryParamNotFound)?,
1476                None                      => Err(ActionError::NoQuery)?
1477            },
1478
1479            // Fragment
1480
1481            Self::RemoveFragment => task_state.url.set_fragment(None),
1482            Self::RemoveEmptyFragment => if task_state.url.fragment() == Some("") {task_state.url.set_fragment(None)},
1483
1484            // General parts
1485
1486            Self::SetPart {part, value} => part.set(task_state.url, get_new_option_str!(value, task_state))?,
1487
1488            Self::ModifyPart {part, modification} => {
1489                let mut temp = part.get(task_state.url);
1490                modification.apply(&mut temp, &task_state.to_view())?;
1491                part.set(task_state.url, temp.map(Cow::into_owned).as_deref())?;
1492            },
1493            Self::ModifyPartIfSome {part, modification} => {
1494                if let mut temp @ Some(_) = part.get(task_state.url) {
1495                    modification.apply(&mut temp, &task_state.to_view())?;
1496                    part.set(task_state.url, temp.map(Cow::into_owned).as_deref())?;
1497                }
1498            },
1499
1500            Self::CopyPart {from, to} => to.set(task_state.url, from.get(task_state.url).map(|x| x.into_owned()).as_deref())?,
1501            Self::MovePart {from, to} => {
1502                to.set(task_state.url, from.get(task_state.url).map(|x| x.into_owned()).as_deref())?;
1503                from.set(task_state.url, None)?;
1504            },
1505
1506            // Misc.
1507
1508            #[cfg(feature = "http")]
1509            Self::ExpandRedirect {url, headers, http_client_config_diff} => {
1510                let (url, use_task) = match url {
1511                    Some(url) => (get_cow!(url, task_state, ActionError), false),
1512                    None => (Cow::Borrowed(task_state.url.as_str()), true)
1513                };
1514                let _unthread_handle = task_state.unthreader.unthread();
1515                #[cfg(feature = "cache")]
1516                if let Some(entry) = task_state.cache.read(CacheEntryKeys {subject: "redirect", key: &url})? {
1517                    *task_state.url = BetterUrl::parse(&entry.value.ok_or(ActionError::CachedUrlIsNone)?)?;
1518                    return Ok(());
1519                }
1520                #[cfg(feature = "cache")]
1521                let start = std::time::Instant::now();
1522                let response = task_state.to_view().http_client(http_client_config_diff.as_deref())?.get(&*url).headers(headers.clone()).send()?;
1523                let new_url = if response.status().is_redirection() {
1524                    BetterParseOptions::default().base_url(Some(&*if use_task {Cow::Borrowed(&**task_state.url)} else {Cow::Owned(Url::parse(&url)?)}))
1525                        .parse(std::str::from_utf8(response.headers().get("location").ok_or(ActionError::LocationHeaderNotFound)?.as_bytes())?)?
1526                } else {
1527                    response.url().clone().into()
1528                };
1529                #[cfg(feature = "cache")]
1530                let duration = start.elapsed();
1531                #[cfg(feature = "cache")]
1532                task_state.cache.write(NewCacheEntry {
1533                    subject: "redirect",
1534                    key: &url,
1535                    value: Some(new_url.as_str()),
1536                    duration
1537                })?;
1538                *task_state.url=new_url;
1539            },
1540
1541            Self::SetScratchpadFlag {name, value} => {
1542                let name = get_string!(name, task_state, ActionError);
1543                match value {
1544                    true  => task_state.scratchpad.flags.insert( name),
1545                    false => task_state.scratchpad.flags.remove(&name)
1546                };
1547            },
1548            Self::SetScratchpadVar {name, value} => match get_option_string!(value, task_state) {
1549                Some(value) => {let _ = task_state.scratchpad.vars.insert( get_string!(name, task_state, ActionError), value);}
1550                None        => {let _ = task_state.scratchpad.vars.remove(&get_string!(name, task_state, ActionError));}
1551            },
1552            Self::ModifyScratchpadVar {name, modification} => {
1553                let name = get_string!(name, task_state, ActionError);
1554                let mut value = task_state.scratchpad.vars.get(&name).map(|x| Cow::Borrowed(&**x));
1555                modification.apply(&mut value, &task_state.to_view())?;
1556                match value {
1557                    Some(value) => {let _ = task_state.scratchpad.vars.insert( name, value.into_owned());},
1558                    None        => {let _ = task_state.scratchpad.vars.remove(&name);}
1559                }
1560            },
1561            #[cfg(feature = "cache")]
1562            Self::CacheUrl {subject, action} => {
1563                let _unthread_handle = task_state.unthreader.unthread();
1564                let subject = get_string!(subject, task_state, ActionError);
1565                if let Some(entry) = task_state.cache.read(CacheEntryKeys {subject: &subject, key: task_state.url.as_str()})? {
1566                    *task_state.url = BetterUrl::parse(&entry.value.ok_or(ActionError::CachedUrlIsNone)?)?;
1567                    return Ok(());
1568                }
1569                let old_url = task_state.url.to_string();
1570                let start = std::time::Instant::now();
1571                action.apply(task_state)?;
1572                let duration = start.elapsed();
1573                task_state.cache.write(NewCacheEntry {
1574                    subject: &subject,
1575                    key: &old_url,
1576                    value: Some(task_state.url.as_str()),
1577                    duration
1578                })?;
1579            },
1580            Self::Common(common_call) => {
1581                task_state.commons.actions.get(get_str!(common_call.name, task_state, ActionError)).ok_or(ActionError::CommonActionNotFound)?.apply(&mut TaskState {
1582                    common_args: Some(&common_call.args.build(&task_state.to_view())?),
1583                    url        : task_state.url,
1584                    scratchpad : task_state.scratchpad,
1585                    context    : task_state.context,
1586                    job_context: task_state.job_context,
1587                    params     : task_state.params,
1588                    commons    : task_state.commons,
1589                    #[cfg(feature = "cache")]
1590                    cache      : task_state.cache,
1591                    unthreader : task_state.unthreader
1592                })?
1593            },
1594            Self::CommonCallArg(name) => task_state.common_args.ok_or(ActionError::NotInCommonContext)?.actions.get(get_str!(name, task_state, ActionError)).ok_or(ActionError::CommonCallArgActionNotFound)?.apply(task_state)?,
1595            #[cfg(feature = "custom")]
1596            Self::Custom(function) => function(task_state)?
1597        };
1598        Ok(())
1599    }
1600}