Skip to main content

sofie_model/
sign_in.rs

1use syn::{Expr, Item};
2
3use crate::areas::{constant_value, implemented_trait};
4use crate::syntax::{read_file, string_literal, type_last_name};
5use crate::{AreaDefinition, ModelError};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct SignInPaths {
9    pub sign_in: String,
10    pub sign_up: Option<String>,
11}
12
13impl AreaDefinition {
14    /// As the area's own file declares them: sign-up by a hand-written `MemberArea`'s
15    /// `SIGN_UP` or by sofie-identity's `SignUp`, which a generated `MemberArea` reads.
16    pub fn sign_in_paths(&self) -> Result<Option<SignInPaths>, ModelError> {
17        let Some(type_name) = self.type_path.last() else {
18            return Ok(None);
19        };
20        let file = read_file(&self.file)?;
21        let constant = |trait_name: &str, constant: &str| {
22            file.items.iter().find_map(|item| match item {
23                Item::Impl(implementation)
24                    if implemented_trait(implementation).as_deref() == Some(trait_name)
25                        && type_last_name(&implementation.self_ty).as_ref() == Some(type_name) =>
26                {
27                    constant_value(implementation, constant).cloned()
28                }
29                _ => None,
30            })
31        };
32        let Some(sign_in) = constant("Area", "SIGN_IN")
33            .as_ref()
34            .and_then(string_literal)
35        else {
36            return Ok(None);
37        };
38        let sign_up = constant("MemberArea", "SIGN_UP")
39            .as_ref()
40            .and_then(some_literal)
41            .or_else(|| constant("SignUp", "PATH").as_ref().and_then(string_literal));
42        Ok(Some(SignInPaths { sign_in, sign_up }))
43    }
44}
45
46fn some_literal(expression: &Expr) -> Option<String> {
47    let Expr::Call(call) = expression else {
48        return None;
49    };
50    let is_some = matches!(call.func.as_ref(), Expr::Path(path) if path.path.is_ident("Some"));
51    match call.args.first() {
52        Some(argument) if is_some && call.args.len() == 1 => string_literal(argument),
53        _ => None,
54    }
55}
56
57#[cfg(test)]
58mod tests {
59    use crate::testing::{TemporaryDirectory, define_area, module};
60
61    use super::SignInPaths;
62
63    const AREA: &str = "impl Area for Member {\n    const NAME: &'static str = \"member\";\n    \
64                        const SIGN_IN: &'static str = \"/sign-in\";\n}\n";
65
66    #[test]
67    fn reads_sign_in_and_an_opened_sign_up_from_the_areas_file() {
68        let directory = TemporaryDirectory::new("sofie-model-sign-in");
69        let mut order = module("order", directory.path("order"));
70        define_area(&mut order, "member");
71        let definition = &order.defined_areas[0];
72        let member = "impl MemberArea for Member {\n    const SIGN_UP: Option<&'static str> = Some(\"/join\");\n}\n";
73        directory.write("order/contract/src/area.rs", &format!("{AREA}{member}"));
74
75        let paths = definition.sign_in_paths().unwrap();
76
77        let expected = SignInPaths {
78            sign_in: "/sign-in".to_owned(),
79            sign_up: Some("/join".to_owned()),
80        };
81        assert_eq!(paths, Some(expected));
82    }
83
84    #[test]
85    fn reads_sign_up_declared_for_a_generated_member_area() {
86        let directory = TemporaryDirectory::new("sofie-model-sign-up");
87        let mut order = module("order", directory.path("order"));
88        define_area(&mut order, "member");
89        let sign_up = "impl SignUp for Member {\n    const PATH: &'static str = \"/join\";\n}\n";
90        directory.write("order/contract/src/area.rs", &format!("{AREA}{sign_up}"));
91
92        let paths = order.defined_areas[0].sign_in_paths().unwrap().unwrap();
93
94        assert_eq!(paths.sign_up.as_deref(), Some("/join"));
95    }
96
97    #[test]
98    fn a_member_area_without_sign_up_keeps_it_closed() {
99        let directory = TemporaryDirectory::new("sofie-model-sign-in");
100        let mut order = module("order", directory.path("order"));
101        define_area(&mut order, "member");
102        directory.write("order/contract/src/area.rs", AREA);
103
104        let paths = order.defined_areas[0].sign_in_paths().unwrap().unwrap();
105
106        assert_eq!(paths.sign_up, None);
107    }
108}