Skip to main content

solid_pod_rs/wac/
serializer.rs

1//! Turtle serialiser for `AclDocument`.
2
3use crate::wac::conditions::Condition;
4use crate::wac::document::{AclDocument, IdOrIds};
5
6/// Serialise an [`AclDocument`] as Turtle.
7pub fn serialize_turtle_acl(doc: &AclDocument) -> String {
8    let mut out = String::new();
9    out.push_str("@prefix acl: <http://www.w3.org/ns/auth/acl#> .\n");
10    out.push_str("@prefix foaf: <http://xmlns.com/foaf/0.1/> .\n\n");
11    let graph = match &doc.graph {
12        Some(g) => g,
13        None => return out,
14    };
15    for (i, auth) in graph.iter().enumerate() {
16        let subject = format!("<#rule-{i}>");
17        out.push_str(&subject);
18        out.push_str(" a acl:Authorization");
19        emit_pairs(&mut out, "acl:agent", &auth.agent);
20        emit_pairs(&mut out, "acl:agentClass", &auth.agent_class);
21        emit_pairs(&mut out, "acl:agentGroup", &auth.agent_group);
22        emit_pairs(&mut out, "acl:origin", &auth.origin);
23        emit_pairs(&mut out, "acl:accessTo", &auth.access_to);
24        emit_pairs(&mut out, "acl:default", &auth.default);
25        emit_pairs(&mut out, "acl:mode", &auth.mode);
26        emit_conditions(&mut out, auth.condition.as_deref());
27        out.push_str(" .\n\n");
28    }
29    out
30}
31
32fn emit_pairs(out: &mut String, pred: &str, vals: &Option<IdOrIds>) {
33    if let Some(ids) = vals {
34        let refs: Vec<&str> = match ids {
35            IdOrIds::Single(r) => vec![r.id.as_str()],
36            IdOrIds::Multiple(v) => v.iter().map(|r| r.id.as_str()).collect(),
37        };
38        if refs.is_empty() {
39            return;
40        }
41        out.push_str(" ;\n    ");
42        out.push_str(pred);
43        out.push(' ');
44        let rendered: Vec<String> = refs
45            .iter()
46            .map(|r| {
47                if r.starts_with("http") {
48                    format!("<{r}>")
49                } else {
50                    r.to_string()
51                }
52            })
53            .collect();
54        out.push_str(&rendered.join(", "));
55    }
56}
57
58fn emit_conditions(out: &mut String, conds: Option<&[Condition]>) {
59    let conds = match conds {
60        Some(c) if !c.is_empty() => c,
61        _ => return,
62    };
63    for cond in conds {
64        out.push_str(" ;\n    acl:condition [\n        a ");
65        out.push_str(cond.type_iri());
66        match cond {
67            Condition::Client(body) => {
68                emit_body_pair(out, "acl:client", &body.client);
69                emit_body_pair(out, "acl:clientGroup", &body.client_group);
70                emit_body_pair(out, "acl:clientClass", &body.client_class);
71            }
72            Condition::Issuer(body) => {
73                emit_body_pair(out, "acl:issuer", &body.issuer);
74                emit_body_pair(out, "acl:issuerGroup", &body.issuer_group);
75                emit_body_pair(out, "acl:issuerClass", &body.issuer_class);
76            }
77            Condition::Unknown { .. } => {}
78        }
79        out.push_str("\n    ]");
80    }
81}
82
83fn emit_body_pair(out: &mut String, pred: &str, vals: &Option<IdOrIds>) {
84    if let Some(ids) = vals {
85        let refs: Vec<&str> = match ids {
86            IdOrIds::Single(r) => vec![r.id.as_str()],
87            IdOrIds::Multiple(v) => v.iter().map(|r| r.id.as_str()).collect(),
88        };
89        if refs.is_empty() {
90            return;
91        }
92        out.push_str(" ;\n        ");
93        out.push_str(pred);
94        out.push(' ');
95        let rendered: Vec<String> = refs
96            .iter()
97            .map(|r| {
98                if r.starts_with("http") {
99                    format!("<{r}>")
100                } else {
101                    r.to_string()
102                }
103            })
104            .collect();
105        out.push_str(&rendered.join(", "));
106    }
107}