Skip to main content

codoseo_web/
crawl_policy.rs

1//! The plan rules for a manual crawl, shared by the Run crawl button and the API's `run_crawl`
2//! so both queue in the same lane and refuse with the same words.
3
4use codoseo_core::plan::{ManualAllowance, Plan};
5use time::Duration;
6
7/// Priority lane for a manual crawl on a paid plan (spec section 10).
8pub const PAID_MANUAL_PRIORITY: i16 = 2;
9/// Priority lane for a manual crawl on Free (spec section 10).
10pub const FREE_MANUAL_PRIORITY: i16 = 4;
11
12/// The queue lane for a manual crawl on `plan`.
13pub fn manual_priority(plan: Plan) -> i16 {
14    match plan {
15        Plan::Free => FREE_MANUAL_PRIORITY,
16        Plan::Pro | Plan::Agency | Plan::SelfHosted => PAID_MANUAL_PRIORITY,
17    }
18}
19
20pub fn plan_name(plan: Plan) -> &'static str {
21    match plan {
22        Plan::Free => "Free",
23        Plan::Pro => "Pro",
24        Plan::Agency => "Agency",
25        Plan::SelfHosted => "self-hosted",
26    }
27}
28
29/// `1 manual crawl a week`, `3 manual crawls a day`; `None` when unlimited.
30pub fn allowance_phrase(allowance: ManualAllowance) -> Option<String> {
31    let (n, per) = match allowance {
32        ManualAllowance::PerWeek(n) => (n, "week"),
33        ManualAllowance::PerDay(n) => (n, "day"),
34        ManualAllowance::Unlimited => return None,
35    };
36    let s = if n == 1 { "" } else { "s" };
37    Some(format!("{n} manual crawl{s} a {per}"))
38}
39
40/// The refusal when the allowance is used up, e.g. "Your Free plan includes 1 manual crawl a
41/// week. The next one is available in 3 days."
42pub fn limit_message(plan: Plan, allowance: ManualAllowance, wait: Duration) -> String {
43    let phrase = allowance_phrase(allowance).unwrap_or_else(|| "manual crawls".to_owned());
44    format!(
45        "Your {} plan includes {phrase}. The next one is available {}.",
46        plan_name(plan),
47        until(wait)
48    )
49}
50
51/// `in a minute`, `in 12 minutes`, `in 5 hours`, `in 3 days` (rounded up below a day, to the
52/// nearest day above).
53pub fn until(wait: Duration) -> String {
54    let minutes = (wait.whole_seconds().max(0) + 59) / 60;
55    if minutes <= 1 {
56        return "in a minute".to_owned();
57    }
58    if minutes < 60 {
59        return format!("in {minutes} minutes");
60    }
61    let hours = (minutes + 59) / 60;
62    if hours < 24 {
63        let s = if hours == 1 { "" } else { "s" };
64        return format!("in {hours} hour{s}");
65    }
66    let days = (hours + 12) / 24;
67    let s = if days == 1 { "" } else { "s" };
68    format!("in {days} day{s}")
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn manual_lanes() {
77        assert_eq!(manual_priority(Plan::Free), 4);
78        assert_eq!(manual_priority(Plan::Pro), 2);
79        assert_eq!(manual_priority(Plan::Agency), 2);
80        assert_eq!(manual_priority(Plan::SelfHosted), 2);
81    }
82
83    #[test]
84    fn waits() {
85        assert_eq!(until(Duration::seconds(-5)), "in a minute");
86        assert_eq!(until(Duration::seconds(50)), "in a minute");
87        assert_eq!(until(Duration::minutes(12)), "in 12 minutes");
88        assert_eq!(
89            until(Duration::minutes(59) + Duration::seconds(59)),
90            "in 1 hour"
91        );
92        assert_eq!(
93            until(Duration::hours(4) + Duration::minutes(10)),
94            "in 5 hours"
95        );
96        assert_eq!(until(Duration::hours(25)), "in 1 day");
97        assert_eq!(until(Duration::days(3) - Duration::seconds(1)), "in 3 days");
98        assert_eq!(until(Duration::days(6) + Duration::hours(23)), "in 7 days");
99    }
100
101    #[test]
102    fn limit_messages() {
103        assert_eq!(
104            limit_message(
105                Plan::Free,
106                ManualAllowance::PerWeek(1),
107                Duration::days(3) - Duration::seconds(1)
108            ),
109            "Your Free plan includes 1 manual crawl a week. The next one is available in 3 days."
110        );
111        assert_eq!(
112            limit_message(Plan::Pro, ManualAllowance::PerDay(2), Duration::minutes(30)),
113            "Your Pro plan includes 2 manual crawls a day. The next one is available in 30 minutes."
114        );
115    }
116}