solid_pod_rs/wac/document.rs
1//! ACL document AST — JSON-LD deserialisation shape.
2//!
3//! The same struct shape is produced by the Turtle parser in
4//! `wac::parser` so downstream consumers work with a single canonical
5//! representation regardless of wire format.
6
7use serde::{Deserialize, Serialize};
8
9use crate::wac::conditions::Condition;
10
11/// Parsed ACL document containing zero or more authorization rules.
12#[derive(Debug, Clone, Default, Deserialize, Serialize)]
13pub struct AclDocument {
14 /// JSON-LD `@context` value, if the document was parsed from JSON-LD.
15 #[serde(rename = "@context", skip_serializing_if = "Option::is_none")]
16 pub context: Option<serde_json::Value>,
17
18 /// The list of authorization rules in this document.
19 #[serde(rename = "@graph", skip_serializing_if = "Option::is_none")]
20 pub graph: Option<Vec<AclAuthorization>>,
21
22 /// `true` when this ACL was resolved from an ANCESTOR container's
23 /// `.acl` sidecar rather than the resource's own sidecar (the WAC
24 /// walk-up found it one or more levels up). An inherited document
25 /// must honour ONLY `acl:default` rules — `acl:accessTo` rules apply
26 /// to the exact resource they name and MUST NOT leak to descendants
27 /// (WAC §4.2). Defaults to `false`; never serialised (it is a
28 /// resolution-context flag, not part of the ACL on the wire).
29 #[serde(skip, default)]
30 pub inherited: bool,
31}
32
33/// A single WAC authorization rule describing who may access what, and how.
34#[derive(Debug, Clone, Deserialize, Serialize)]
35pub struct AclAuthorization {
36 /// Node identifier for this authorization (e.g. `#owner`).
37 #[serde(rename = "@id", skip_serializing_if = "Option::is_none")]
38 pub id: Option<String>,
39
40 /// RDF type, typically `acl:Authorization`.
41 #[serde(rename = "@type", skip_serializing_if = "Option::is_none")]
42 pub r#type: Option<String>,
43
44 /// WebID(s) of the agent(s) this rule grants access to.
45 #[serde(rename = "acl:agent", skip_serializing_if = "Option::is_none")]
46 pub agent: Option<IdOrIds>,
47
48 /// Agent class (`foaf:Agent` for public, `acl:AuthenticatedAgent` for logged-in).
49 #[serde(rename = "acl:agentClass", skip_serializing_if = "Option::is_none")]
50 pub agent_class: Option<IdOrIds>,
51
52 /// IRI(s) of `vcard:Group` documents whose members are granted access.
53 #[serde(rename = "acl:agentGroup", skip_serializing_if = "Option::is_none")]
54 pub agent_group: Option<IdOrIds>,
55
56 /// Permitted request origin(s) per WAC section 4.3.
57 #[serde(rename = "acl:origin", skip_serializing_if = "Option::is_none")]
58 pub origin: Option<IdOrIds>,
59
60 /// Resource path(s) this rule applies to (exact match + direct children).
61 #[serde(rename = "acl:accessTo", skip_serializing_if = "Option::is_none")]
62 pub access_to: Option<IdOrIds>,
63
64 /// Container path(s) whose descendants inherit this rule recursively.
65 #[serde(rename = "acl:default", skip_serializing_if = "Option::is_none")]
66 pub default: Option<IdOrIds>,
67
68 /// Access mode(s) granted: `acl:Read`, `acl:Write`, `acl:Append`, `acl:Control`.
69 #[serde(rename = "acl:mode", skip_serializing_if = "Option::is_none")]
70 pub mode: Option<IdOrIds>,
71
72 /// WAC 2.0 conjunctive conditions. Every listed condition must be
73 /// `Satisfied` for this authorisation to grant access. An unknown
74 /// condition type parses as `Condition::Unknown` and evaluates to
75 /// `NotApplicable`, which fails closed — see
76 /// [`crate::wac::conditions`].
77 #[serde(
78 rename = "acl:condition",
79 default,
80 skip_serializing_if = "Option::is_none"
81 )]
82 pub condition: Option<Vec<Condition>>,
83}
84
85/// One or more JSON-LD `@id` references, modelling both singular and array ACL values.
86#[derive(Debug, Clone, Deserialize, Serialize)]
87#[serde(untagged)]
88pub enum IdOrIds {
89 /// A single `@id` reference.
90 Single(IdRef),
91 /// An array of `@id` references.
92 Multiple(Vec<IdRef>),
93}
94
95/// A JSON-LD node reference carrying a single `@id` IRI.
96#[derive(Debug, Clone, Deserialize, Serialize)]
97pub struct IdRef {
98 /// The IRI value.
99 #[serde(rename = "@id")]
100 pub id: String,
101}
102
103pub(crate) fn get_ids(val: &Option<IdOrIds>) -> Vec<&str> {
104 match val {
105 None => Vec::new(),
106 Some(IdOrIds::Single(r)) => vec![r.id.as_str()],
107 Some(IdOrIds::Multiple(refs)) => refs.iter().map(|r| r.id.as_str()).collect(),
108 }
109}
110
111pub(crate) fn ids_of(items: Vec<String>) -> IdOrIds {
112 let mut iter = items.into_iter();
113 let first = iter.next();
114 let second = iter.next();
115 match (first, second) {
116 // Single-element path: the len==1 guard previously ensured this.
117 (Some(id), None) => IdOrIds::Single(IdRef { id }),
118 // Multi-element: reconstruct the full iterator.
119 (Some(first), Some(second)) => IdOrIds::Multiple(
120 std::iter::once(first)
121 .chain(std::iter::once(second))
122 .chain(iter)
123 .map(|id| IdRef { id })
124 .collect(),
125 ),
126 // Empty: shouldn't happen per call-site invariants but handle
127 // defensively — return an empty Multiple rather than panicking.
128 _ => IdOrIds::Multiple(Vec::new()),
129 }
130}