1use oas_crypto::keypair::OasKeyPair;
5
6use crate::calendar::CalendarSection;
7use crate::compliance::ComplianceSection;
8use crate::conformance::ConformanceLevel;
9use crate::contact::ContactSection;
10use crate::document::{DocumentMetadata, OasDocument};
11use crate::error::DocumentError;
12use crate::governance::GovernanceSection;
13use crate::interop::InteroperabilitySection;
14use crate::lifecycle::LifecycleStatus;
15use crate::lineage_section::LineageSection;
16use crate::operational::OperationalSection;
17use crate::pricing::PricingSection;
18use crate::profile::ProfileSection;
19use crate::proof_format::DocumentProof;
20use crate::relationships::RelationshipsSection;
21use crate::reputation::ReputationSection;
22use crate::service::ServiceEndpoint;
23use crate::verification_method::VerificationMethod;
24
25pub struct DocumentBuilder {
49 id: String,
50 kind: String,
51 controller: Option<String>,
52 verification_methods: Vec<VerificationMethod>,
53 authentication: Vec<String>,
54 assertion_method: Vec<String>,
55 capability_invocation: Vec<String>,
56 capability_delegation: Vec<String>,
57 services: Vec<ServiceEndpoint>,
58 name: Option<String>,
59 description: Option<String>,
60 lineage: Option<LineageSection>,
61 governance: Option<GovernanceSection>,
62 conformance_level: ConformanceLevel,
63 lifecycle_status: Option<LifecycleStatus>,
64 sequence: u64,
65 profile: Option<ProfileSection>,
67 contact: Option<ContactSection>,
68 calendar: Option<CalendarSection>,
69 operational: Option<OperationalSection>,
70 pricing: Option<PricingSection>,
71 interoperability: Option<InteroperabilitySection>,
72 reputation: Option<ReputationSection>,
73 compliance: Option<ComplianceSection>,
74 relationships: Option<RelationshipsSection>,
75}
76
77impl DocumentBuilder {
78 pub fn new(id: &str, kind: &str) -> Self {
85 Self {
86 id: id.to_string(),
87 kind: kind.to_string(),
88 controller: None,
89 verification_methods: Vec::new(),
90 authentication: Vec::new(),
91 assertion_method: Vec::new(),
92 capability_invocation: Vec::new(),
93 capability_delegation: Vec::new(),
94 services: Vec::new(),
95 name: None,
96 description: None,
97 lineage: None,
98 governance: None,
99 conformance_level: ConformanceLevel::L0,
100 lifecycle_status: None,
101 sequence: 1,
102 profile: None,
103 contact: None,
104 calendar: None,
105 operational: None,
106 pricing: None,
107 interoperability: None,
108 reputation: None,
109 compliance: None,
110 relationships: None,
111 }
112 }
113
114 pub fn controller(mut self, controller: &str) -> Self {
116 self.controller = Some(controller.to_string());
117 self
118 }
119
120 pub fn add_verification_method(mut self, keypair: &OasKeyPair) -> Self {
125 let key_num = self.verification_methods.len() as u32 + 1;
126 let vm = VerificationMethod::new(&self.id, key_num, &keypair.public_key_multibase());
127 let key_ref = vm.id.clone();
128 self.verification_methods.push(vm);
129 self.authentication.push(key_ref.clone());
130 self.assertion_method.push(key_ref.clone());
131 self.capability_invocation.push(key_ref);
132 self
133 }
134
135 pub fn conformance_level(mut self, level: ConformanceLevel) -> Self {
137 self.conformance_level = level;
138 self
139 }
140
141 pub fn name(mut self, name: &str) -> Self {
143 self.name = Some(name.to_string());
144 self
145 }
146
147 pub fn description(mut self, description: &str) -> Self {
149 self.description = Some(description.to_string());
150 self
151 }
152
153 pub fn lineage(mut self, lineage: LineageSection) -> Self {
155 self.lineage = Some(lineage);
156 self
157 }
158
159 pub fn governance(mut self, governance: GovernanceSection) -> Self {
161 self.governance = Some(governance);
162 self
163 }
164
165 pub fn lifecycle_status(mut self, status: LifecycleStatus) -> Self {
167 self.lifecycle_status = Some(status);
168 self
169 }
170
171 pub fn sequence(mut self, seq: u64) -> Self {
173 self.sequence = seq;
174 self
175 }
176
177 pub fn add_service(mut self, service: ServiceEndpoint) -> Self {
179 self.services.push(service);
180 self
181 }
182
183 pub fn profile(mut self, profile: ProfileSection) -> Self {
185 self.profile = Some(profile);
186 self
187 }
188
189 pub fn contact(mut self, contact: ContactSection) -> Self {
191 self.contact = Some(contact);
192 self
193 }
194
195 pub fn calendar(mut self, calendar: CalendarSection) -> Self {
197 self.calendar = Some(calendar);
198 self
199 }
200
201 pub fn operational(mut self, operational: OperationalSection) -> Self {
203 self.operational = Some(operational);
204 self
205 }
206
207 pub fn pricing(mut self, pricing: PricingSection) -> Self {
209 self.pricing = Some(pricing);
210 self
211 }
212
213 pub fn interoperability(mut self, interop: InteroperabilitySection) -> Self {
215 self.interoperability = Some(interop);
216 self
217 }
218
219 pub fn reputation(mut self, reputation: ReputationSection) -> Self {
221 self.reputation = Some(reputation);
222 self
223 }
224
225 pub fn compliance(mut self, compliance: ComplianceSection) -> Self {
227 self.compliance = Some(compliance);
228 self
229 }
230
231 pub fn relationships(mut self, relationships: RelationshipsSection) -> Self {
233 self.relationships = Some(relationships);
234 self
235 }
236
237 pub fn build_and_sign(
252 self,
253 signing_keypair: &OasKeyPair,
254 created: &str,
255 ) -> Result<OasDocument, DocumentError> {
256 let controller = self.controller.unwrap_or_else(|| self.id.clone());
257
258 let mut doc = OasDocument {
259 context: OasDocument::default_context(),
260 id: self.id,
261 controller,
262 verification_method: self.verification_methods,
263 authentication: self.authentication,
264 assertion_method: self.assertion_method,
265 capability_invocation: self.capability_invocation,
266 capability_delegation: self.capability_delegation,
267 service: self.services,
268 oas_version: crate::document::OAS_VERSION.to_string(),
269 kind: self.kind,
270 name: self.name,
271 description: self.description,
272 lineage: self.lineage,
273 governance: self.governance,
274 conformance_level: self.conformance_level,
275 lifecycle_status: self.lifecycle_status,
276 sequence: self.sequence,
277 metadata: DocumentMetadata {
278 created: created.to_string(),
279 updated: None,
280 version: None,
281 },
282 proof: None,
283 revoked: None,
284 revoked_at: None,
285 profile: self.profile,
286 contact: self.contact,
287 calendar: self.calendar,
288 operational: self.operational,
289 pricing: self.pricing,
290 interoperability: self.interoperability,
291 reputation: self.reputation,
292 compliance: self.compliance,
293 relationships: self.relationships,
294 };
295
296 let doc_json =
298 serde_json::to_value(&doc).map_err(|e| DocumentError::ProofGenerationFailed {
299 reason: format!("failed to serialize document: {e}"),
300 })?;
301
302 let vm_id = doc
303 .verification_method
304 .first()
305 .map(|vm| vm.id.as_str())
306 .ok_or_else(|| DocumentError::ProofGenerationFailed {
307 reason: "no verification method available for signing".to_string(),
308 })?
309 .to_string();
310
311 let proof = DocumentProof::generate(&doc_json, signing_keypair, &vm_id, created)?;
312 doc.proof = Some(proof);
313
314 Ok(doc)
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn build_hmr_document() {
324 let keypair = OasKeyPair::generate();
325 let doc = DocumentBuilder::new("did:oas:test:hmr:alice", "hmr")
326 .controller("did:oas:test:hmr:alice")
327 .conformance_level(ConformanceLevel::L1)
328 .add_verification_method(&keypair)
329 .name("Alice")
330 .build_and_sign(&keypair, "2026-01-15T00:00:00Z");
331
332 assert!(doc.is_ok());
333 let doc = doc.unwrap_or_else(|_| unreachable_doc());
334 assert_eq!(doc.kind, "hmr");
335 assert!(doc.proof.is_some());
336 assert_eq!(doc.sequence, 1);
337 assert!(doc.is_root());
338 }
339
340 #[test]
341 fn build_agent_document_with_lineage() {
342 let keypair = OasKeyPair::generate();
343 let lineage = LineageSection {
344 human_root_did: "did:oas:test:hmr:alice".to_string(),
345 creator_did: "did:oas:test:hmr:alice".to_string(),
346 generation: 0,
347 derivation_proof: None,
348 human_root_chain: vec![
349 "did:oas:test:agent:bot".to_string(),
350 "did:oas:test:hmr:alice".to_string(),
351 ],
352 org_inclusion_proof: None,
353 anchor_refs: None,
354 };
355
356 let doc = DocumentBuilder::new("did:oas:test:agent:bot", "agent")
357 .controller("did:oas:test:hmr:alice")
358 .conformance_level(ConformanceLevel::L1)
359 .add_verification_method(&keypair)
360 .lineage(lineage)
361 .lifecycle_status(LifecycleStatus::Active)
362 .build_and_sign(&keypair, "2026-01-15T00:00:00Z");
363
364 assert!(doc.is_ok());
365 let doc = doc.unwrap_or_else(|_| unreachable_doc());
366 assert!(doc.lineage.is_some());
367 assert!(!doc.is_root());
368 }
369
370 fn unreachable_doc() -> OasDocument {
371 OasDocument {
372 context: vec![],
373 id: String::new(),
374 controller: String::new(),
375 verification_method: vec![],
376 authentication: vec![],
377 assertion_method: vec![],
378 capability_invocation: vec![],
379 capability_delegation: vec![],
380 service: vec![],
381 oas_version: String::new(),
382 kind: String::new(),
383 name: None,
384 description: None,
385 lineage: None,
386 governance: None,
387 conformance_level: ConformanceLevel::L0,
388 lifecycle_status: None,
389 sequence: 0,
390 metadata: DocumentMetadata {
391 created: String::new(),
392 updated: None,
393 version: None,
394 },
395 proof: None,
396 revoked: None,
397 revoked_at: None,
398 profile: None,
399 contact: None,
400 calendar: None,
401 operational: None,
402 pricing: None,
403 interoperability: None,
404 reputation: None,
405 compliance: None,
406 relationships: None,
407 }
408 }
409}