vsmtp-rule-engine 2.0.1-rc.4

Next-gen MTA. Secured, Faster and Greener
Documentation
/*
 * vSMTP mail transfer agent
 * Copyright (C) 2022 viridIT SAS
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see https://www.gnu.org/licenses/.
 *
*/

use crate::{
    api::{Context, EngineResult, SharedObject},
    ExecutionStage,
};
use rhai::plugin::{
    mem, Dynamic, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, NativeCallContext,
    PluginFunction, RhaiResult, TypeId,
};
use vsmtp_common::transfer::{ForwardTarget, Transfer};

pub use transport::*;

/// Functions to configure delivery methods of emails.
#[allow(clippy::needless_pass_by_value)]
#[rhai::plugin::export_module]
mod transport {
    use crate::get_global;

    /// Set the delivery method to forwarding for a single recipient.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email to the recipient.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// const rules = #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward("john.doe@example.com", "mta-john.example.com"),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///     rcpt: [
    ///       action "forward (str/str)" || {
    ///         envelop::add_rcpt("my.address@foo.com");
    ///         transport::forward("my.address@foo.com", "127.0.0.1");
    ///       },
    ///       action "forward (obj/str)" || {
    ///         let rcpt = address("my.address@bar.com");
    ///         envelop::add_rcpt(rcpt);
    ///         transport::forward(rcpt, "127.0.0.2");
    ///       },
    ///       action "forward (str/obj)" || {
    ///         let target = ip6("::1");
    ///         envelop::add_rcpt("my.address@baz.com");
    ///         transport::forward("my.address@baz.com", target);
    ///       },
    ///       action "forward (obj/obj)" || {
    ///         let rcpt = address("my.address@boz.com");
    ///         envelop::add_rcpt(rcpt);
    ///         transport::forward(rcpt, ip4("127.0.0.4"));
    ///       },
    ///     ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{ForwardTarget, Transfer, EmailTransferStatus},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, (addr, target)) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     ("my.address@foo.com", "127.0.0.1"),
    /// #     ("my.address@bar.com", "127.0.0.2"),
    /// #     ("my.address@baz.com", "::1"),
    /// #     ("my.address@boz.com", "127.0.0.4")
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Forward(ForwardTarget::Ip(target.parse().unwrap()))
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(name = "forward", return_raw)]
    pub fn forward(ncc: NativeCallContext, rcpt: &str, forward: &str) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(forward)
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_for_one(&get_global!(ncc, ctx)?, rcpt, &Transfer::Forward(forward))
    }

    /// Set the delivery method to forwarding for a single recipient.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email to the recipient.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward("john.doe@example.com", "mta-john.example.com"),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "forward", return_raw)]
    pub fn forward_obj_str(
        ncc: NativeCallContext,
        rcpt: SharedObject,
        forward: &str,
    ) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(forward)
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_for_one(
            &get_global!(ncc, ctx)?,
            &rcpt.to_string(),
            &Transfer::Forward(forward),
        )
    }

    /// Set the delivery method to forwarding for a single recipient.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email to the recipient.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward("john.doe@example.com", "mta-john.example.com"),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "forward", return_raw)]
    pub fn forward_str_obj(
        ncc: NativeCallContext,
        rcpt: &str,
        forward: SharedObject,
    ) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(&forward.to_string())
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_for_one(&get_global!(ncc, ctx)?, rcpt, &Transfer::Forward(forward))
    }

    /// Set the delivery method to forwarding for a single recipient.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email to the recipient.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward("john.doe@example.com", "mta-john.example.com"),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "forward", return_raw)]
    pub fn forward_obj_obj(
        ncc: NativeCallContext,
        rcpt: SharedObject,
        forward: SharedObject,
    ) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(&forward.to_string())
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_for_one(
            &get_global!(ncc, ctx)?,
            &rcpt.to_string(),
            &Transfer::Forward(forward),
        )
    }

    /// Set the delivery method to forwarding for all recipients.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email.
    ///
    /// # Args
    ///
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward_all("mta-john.example.com"),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "forward_all" || {
    ///       envelop::add_rcpt("my.address@foo.com");
    ///       envelop::add_rcpt("my.address@bar.com");
    ///       transport::forward_all("127.0.0.1");
    ///     },
    ///     action "forward_all (obj)" || {
    ///       envelop::add_rcpt("my.address@foo2.com");
    ///       envelop::add_rcpt("my.address@bar2.com");
    ///       transport::forward_all(ip4("127.0.0.1"));
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{ForwardTarget, Transfer, EmailTransferStatus},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "my.address@foo.com",
    /// #     "my.address@bar.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Forward(ForwardTarget::Ip("127.0.0.1".parse().unwrap()))
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(name = "forward_all", return_raw)]
    pub fn forward_all(ncc: NativeCallContext, forward: &str) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(forward)
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_foreach(&get_global!(ncc, ctx)?, &Transfer::Forward(forward))
    }

    /// Set the delivery method to forwarding for all recipients.
    /// After all rules are evaluated, forwarding will be used to deliver
    /// the email.
    ///
    /// # Args
    ///
    /// * `target` - the target to forward the email to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup forwarding" || transport::forward_all(fqdn("mta-john.example.com")),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "forward_all", return_raw)]
    pub fn forward_all_obj(ncc: NativeCallContext, forward: SharedObject) -> EngineResult<()> {
        let forward = <ForwardTarget as std::str::FromStr>::from_str(&forward.to_string())
            .map_err::<Box<EvalAltResult>, _>(|err| err.to_string().into())?;

        set_transport_foreach(&get_global!(ncc, ctx)?, &Transfer::Forward(forward))
    }

    /// Set the delivery method to deliver for a single recipient.
    /// After all rules are evaluated, the email will be sent
    /// to the recipient using the domain of its address.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup delivery" || transport::deliver("john.doe@example.com"),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "deliver (str/str)" || {
    ///       envelop::add_rcpt("my.address@foo.com");
    ///       transport::deliver("my.address@foo.com");
    ///     },
    ///     action "deliver (obj/str)" || {
    ///       let rcpt = address("my.address@bar.com");
    ///       envelop::add_rcpt(rcpt);
    ///       transport::deliver(rcpt);
    ///     },
    ///     action "deliver (str/obj)" || {
    ///       let target = ip6("::1");
    ///       envelop::add_rcpt("my.address@baz.com");
    ///       transport::deliver("my.address@baz.com");
    ///     },
    ///     action "deliver (obj/obj)" || {
    ///       let rcpt = address("my.address@boz.com");
    ///       envelop::add_rcpt(rcpt);
    ///       transport::deliver(rcpt);
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{ForwardTarget, Transfer, EmailTransferStatus},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "my.address@foo.com",
    /// #     "my.address@bar.com",
    /// #     "my.address@baz.com",
    /// #     "my.address@boz.com"
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Deliver
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(name = "deliver", return_raw)]
    pub fn deliver(ncc: NativeCallContext, rcpt: &str) -> EngineResult<()> {
        set_transport_for_one(&get_global!(ncc, ctx)?, rcpt, &Transfer::Deliver)
    }

    /// Set the delivery method to deliver for a single recipient.
    /// After all rules are evaluated, the email will be sent
    /// to the recipient using the domain of its address.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Example
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup delivery" || transport::deliver(address("john.doe@example.com")),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "deliver", return_raw)]
    pub fn deliver_obj(ncc: NativeCallContext, rcpt: SharedObject) -> EngineResult<()> {
        set_transport_for_one(
            &get_global!(ncc, ctx)?,
            &rcpt.to_string(),
            &Transfer::Deliver,
        )
    }

    /// Set the delivery method to deliver for all recipients.
    /// After all rules are evaluated, the email will be sent
    /// to all recipients using the domain of their respective address.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup delivery" || transport::deliver_all(),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "deliver_all" || {
    ///       envelop::add_rcpt("my.address@foo.com");
    ///       envelop::add_rcpt("my.address@bar.com");
    ///       transport::deliver_all();
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{ForwardTarget, Transfer, EmailTransferStatus},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "my.address@foo.com",
    /// #     "my.address@bar.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Deliver
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(return_raw)]
    pub fn deliver_all(ncc: NativeCallContext) -> EngineResult<()> {
        set_transport_foreach(&get_global!(ncc, ctx)?, &Transfer::Deliver)
    }

    /// Set the delivery method to mbox for a recipient.
    /// After all rules are evaluated, the email will be stored
    /// locally in the mail box of the recipient if it exists on the server.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup mbox" || transport::mbox("john.doe@example.com"),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "setup mbox" || {
    ///         const doe = address("doe@example.com");
    ///         envelop::add_rcpt(doe);
    ///         envelop::add_rcpt("a@example.com");
    ///         transport::mbox(doe);
    ///         transport::mbox("a@example.com");
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{Transfer},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "doe@example.com",
    /// #     "a@example.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Mbox
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(name = "mbox", return_raw)]
    pub fn mbox(ncc: NativeCallContext, rcpt: &str) -> EngineResult<()> {
        set_transport_for_one(&get_global!(ncc, ctx)?, rcpt, &Transfer::Mbox)
    }

    /// Set the delivery method to mbox for a recipient.
    /// After all rules are evaluated, the email will be stored
    /// locally in the mail box of the recipient if it exists on the server.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Example
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup mbox" || transport::mbox(address("john.doe@example.com")),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "mbox", return_raw)]
    pub fn mbox_obj(ncc: NativeCallContext, rcpt: SharedObject) -> EngineResult<()> {
        set_transport_for_one(&get_global!(ncc, ctx)?, &rcpt.to_string(), &Transfer::Mbox)
    }

    /// Set the delivery method to mbox for all recipients.
    /// After all rules are evaluated, the email will be stored
    /// locally in the mail box of all recipients if they exists on the server.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup mbox" || transport::mbox_all(),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "setup mbox" || {
    ///         const doe = address("doe@example.com");
    ///         envelop::add_rcpt(doe);
    ///         envelop::add_rcpt("a@example.com");
    ///         transport::mbox_all();
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{Transfer},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "doe@example.com",
    /// #     "a@example.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Mbox
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(return_raw)]
    pub fn mbox_all(ncc: NativeCallContext) -> EngineResult<()> {
        set_transport_foreach(&get_global!(ncc, ctx)?, &Transfer::Mbox)
    }

    /// Set the delivery method to maildir for a recipient.
    /// After all rules are evaluated, the email will be stored
    /// locally in the `~/Maildir/new/` folder of the recipient's user if it exists on the server.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup maildir" || transport::maildir("john.doe@example.com"),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "setup maildir" || {
    ///         const doe = address("doe@example.com");
    ///         envelop::add_rcpt(doe);
    ///         envelop::add_rcpt("a@example.com");
    ///         transport::maildir(doe);
    ///         transport::maildir("a@example.com");
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{Transfer},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "doe@example.com",
    /// #     "a@example.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Maildir
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(name = "maildir", return_raw)]
    pub fn maildir(ncc: NativeCallContext, rcpt: &str) -> EngineResult<()> {
        set_transport_for_one(&get_global!(ncc, ctx)?, rcpt, &Transfer::Maildir)
    }

    /// Set the delivery method to maildir for a recipient.
    /// After all rules are evaluated, the email will be stored
    /// locally in the `~/Maildir/new/` folder of the recipient's user if it exists on the server.
    ///
    /// # Args
    ///
    /// * `rcpt` - the recipient to apply the method to.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Example
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup maildir" || transport::maildir(address("john.doe@example.com")),
    ///     ]
    /// }
    /// ```
    #[rhai_fn(name = "maildir", return_raw)]
    pub fn maildir_obj(ncc: NativeCallContext, rcpt: SharedObject) -> EngineResult<()> {
        set_transport_for_one(
            &get_global!(ncc, ctx)?,
            &rcpt.to_string(),
            &Transfer::Maildir,
        )
    }

    /// Set the delivery method to maildir for all recipients.
    /// After all rules are evaluated, the email will be stored
    /// locally in each `~/Maildir/new` folder of they respective recipient
    /// if they exists on the server.
    ///
    /// # Effective smtp stage
    ///
    /// All of them.
    ///
    /// # Examples
    ///
    /// ```ignore
    /// #{
    ///     delivery: [
    ///        action "setup maildir" || transport::maildir_all(),
    ///     ]
    /// }
    /// ```
    ///
    /// ```
    /// # let states = vsmtp_test::vsl::run(
    /// # |builder| Ok(builder.add_root_filter_rules(r#"
    /// #{
    ///   rcpt: [
    ///     action "setup maildir" || {
    ///         const doe = address("doe@example.com");
    ///         envelop::add_rcpt(doe);
    ///         envelop::add_rcpt("a@example.com");
    ///         transport::maildir_all();
    ///     },
    ///   ],
    /// }
    /// # "#)?.build()));
    ///
    /// # use vsmtp_common::{
    /// #   transfer::{Transfer},
    /// #   rcpt::Rcpt,
    /// #   Address,
    /// # };
    /// # for (rcpt, addr) in states[&vsmtp_rule_engine::ExecutionStage::RcptTo].0.forward_paths().unwrap().iter().zip([
    /// #     "doe@example.com",
    /// #     "a@example.com",
    /// # ]) {
    /// #   assert_eq!(
    /// #     rcpt.address,
    /// #     Address::new_unchecked(addr.to_string())
    /// #   );
    /// #   assert_eq!(
    /// #     rcpt.transfer_method,
    /// #     Transfer::Maildir
    /// #   );
    /// # }
    /// ```
    #[rhai_fn(return_raw)]
    pub fn maildir_all(ncc: NativeCallContext) -> EngineResult<()> {
        set_transport_foreach(&get_global!(ncc, ctx)?, &Transfer::Maildir)
    }
}

fn set_transport_for_one(context: &Context, search: &str, method: &Transfer) -> EngineResult<()> {
    vsl_missing_ok!(
        ref vsl_guard_ok!(context.write()).forward_paths_mut().ok(),
        "rcpt_list",
        ExecutionStage::RcptTo
    )
    .iter_mut()
    .find(|rcpt| rcpt.address.full() == search)
    .ok_or_else::<Box<EvalAltResult>, _>(|| format!("could not find rcpt '{search}'").into())
    .map(|rcpt| rcpt.transfer_method = method.clone())
}

fn set_transport_foreach(context: &Context, method: &Transfer) -> EngineResult<()> {
    vsl_missing_ok!(
        ref vsl_guard_ok!(context.write()).forward_paths_mut().ok(),
        "rcpt_list",
        ExecutionStage::RcptTo
    )
    .iter_mut()
    .for_each(|rcpt| rcpt.transfer_method = method.clone());

    Ok(())
}