Skip to main content

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}