Skip to main content

aeo_protocol/
document.rs

1use serde::{Deserialize, Serialize};
2
3use crate::AeoError;
4
5/// The five entity types defined by AEO Protocol v0.1.
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub enum EntityType {
8    /// A natural person.
9    Person,
10    /// An organization, company, brand, or institution.
11    Organization,
12    /// A product or service.
13    Product,
14    /// A geographical place.
15    Place,
16    /// An abstract concept.
17    Concept,
18}
19
20/// The six verification types defined by AEO Protocol v0.1.
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(rename_all = "kebab-case")]
23pub enum VerificationType {
24    /// HTTP domain control.
25    Domain,
26    /// DNS TXT record.
27    Dns,
28    /// GitHub handle ownership.
29    Github,
30    /// LinkedIn profile ownership.
31    Linkedin,
32    /// GPG signature.
33    Gpg,
34    /// A separate well-known URI.
35    #[serde(rename = "well-known-uri")]
36    WellKnownUri,
37}
38
39/// Confidence level for a claim.
40#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "lowercase")]
42pub enum Confidence {
43    /// High confidence (default).
44    #[default]
45    High,
46    /// Medium confidence.
47    Medium,
48    /// Low confidence.
49    Low,
50}
51
52/// Audit mode for a declaration document.
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum AuditMode {
56    /// No audit surface.
57    None,
58    /// Signed declaration document.
59    Signature,
60    /// Audit-report endpoint.
61    Endpoint,
62}
63
64/// The entity the declaration is about.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct Entity {
68    /// Stable canonical identifier (URI).
69    pub id: String,
70    /// One of `Person`, `Organization`, `Product`, `Place`, `Concept`.
71    #[serde(rename = "type")]
72    pub entity_type: EntityType,
73    /// Primary display name.
74    pub name: String,
75    /// Optional alternative names, prior names, transliterations.
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub aliases: Option<Vec<String>>,
78    /// The canonical web presence URL.
79    pub canonical_url: String,
80}
81
82/// A proof of ownership or control over an identifier.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct Verification {
86    /// The verification mechanism.
87    #[serde(rename = "type")]
88    pub verification_type: VerificationType,
89    /// The identifier being proved.
90    pub value: String,
91    /// Optional URL to the proof artifact.
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub proof_uri: Option<String>,
94}
95
96/// Authority information for the entity.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98#[serde(deny_unknown_fields)]
99pub struct Authority {
100    /// Ordered list of canonical sources; earlier outranks later. MUST be non-empty.
101    pub primary_sources: Vec<String>,
102    /// Optional secondary corroborating sources.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub evidence_links: Option<Vec<String>>,
105    /// Optional verification proofs.
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub verifications: Option<Vec<Verification>>,
108}
109
110/// An asserted fact about the entity.
111#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(deny_unknown_fields)]
113pub struct Claim {
114    /// Local identifier unique within this document (kebab-case).
115    pub id: String,
116    /// Schema.org property name OR `aeo:<name>` namespaced predicate.
117    pub predicate: String,
118    /// The asserted value. May be any JSON value.
119    pub value: serde_json::Value,
120    /// Optional sources backing the claim.
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub evidence: Option<Vec<String>>,
123    /// Optional ISO-8601 date the claim became true.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub valid_from: Option<String>,
126    /// Optional ISO-8601 date the claim ceases to be true (or `null` for ongoing).
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub valid_until: Option<Option<String>>,
129    /// Confidence level for the claim. Defaults to `high`.
130    #[serde(default)]
131    pub confidence: Confidence,
132}
133
134/// Citation preferences for the entity.
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136#[serde(deny_unknown_fields)]
137pub struct CitationPreferences {
138    /// Short attribution template with placeholders.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub preferred_attribution: Option<String>,
141    /// Canonical links to include when surfacing claims.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub canonical_links: Option<Vec<String>>,
144    /// Sources known to be stale or incorrect.
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub do_not_cite: Option<Vec<String>>,
147}
148
149/// Soft constraints for answer engines synthesizing about this entity.
150#[derive(Debug, Clone, Default, Serialize, Deserialize)]
151#[serde(deny_unknown_fields)]
152pub struct AnswerConstraints {
153    /// Claim IDs that SHOULD appear in any answer.
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub must_include: Option<Vec<String>>,
156    /// Claim IDs or `topic:` tags that SHOULD NOT appear.
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub must_not_include: Option<Vec<String>>,
159    /// Maximum acceptable data age in days before re-fetching.
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub freshness_window_days: Option<u32>,
162}
163
164/// Audit configuration for the declaration.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166#[serde(deny_unknown_fields)]
167pub struct Audit {
168    /// Audit mode: `none`, `signature`, or `endpoint`.
169    pub mode: AuditMode,
170    /// Signature mode: URI of the signing JWK.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub signing_key_uri: Option<String>,
173    /// Signature mode: detached JWS over the canonical JSON form.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub signature: Option<String>,
176    /// Endpoint mode: audit-report POST endpoint.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub endpoint_uri: Option<String>,
179    /// Endpoint mode: schema URI for audit report payloads.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub endpoint_schema: Option<String>,
182}
183
184/// A complete AEO Protocol v0.1 declaration document.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186#[serde(deny_unknown_fields)]
187pub struct Document {
188    /// Protocol version. MUST be `"0.1"`.
189    pub aeo_version: String,
190    /// The entity the declaration is about.
191    pub entity: Entity,
192    /// Authority information.
193    pub authority: Authority,
194    /// At least one claim about the entity.
195    pub claims: Vec<Claim>,
196    /// Optional citation preferences.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub citation_preferences: Option<CitationPreferences>,
199    /// Optional answer-engine constraints.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub answer_constraints: Option<AnswerConstraints>,
202    /// Optional audit configuration.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub audit: Option<Audit>,
205}
206
207impl Document {
208    /// Parse a JSON string into a `Document`.
209    pub fn from_json(raw: &str) -> Result<Self, AeoError> {
210        serde_json::from_str(raw).map_err(AeoError::Parse)
211    }
212
213    /// Serialize to pretty-printed JSON.
214    pub fn to_json(&self) -> Result<String, AeoError> {
215        serde_json::to_string_pretty(self).map_err(AeoError::Parse)
216    }
217
218    /// Return references to the IDs of all claims in this document.
219    pub fn claim_ids(&self) -> Vec<&str> {
220        self.claims.iter().map(|c| c.id.as_str()).collect()
221    }
222
223    /// Find a claim by its ID.
224    pub fn find_claim(&self, id: &str) -> Option<&Claim> {
225        self.claims.iter().find(|c| c.id == id)
226    }
227}