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.clone());
132 self.capability_delegation.push(key_ref);
133 self
134 }
135
136 pub fn conformance_level(mut self, level: ConformanceLevel) -> Self {
138 self.conformance_level = level;
139 self
140 }
141
142 pub fn name(mut self, name: &str) -> Self {
144 self.name = Some(name.to_string());
145 self
146 }
147
148 pub fn description(mut self, description: &str) -> Self {
150 self.description = Some(description.to_string());
151 self
152 }
153
154 pub fn lineage(mut self, lineage: LineageSection) -> Self {
156 self.lineage = Some(lineage);
157 self
158 }
159
160 pub fn governance(mut self, governance: GovernanceSection) -> Self {
162 self.governance = Some(governance);
163 self
164 }
165
166 pub fn lifecycle_status(mut self, status: LifecycleStatus) -> Self {
168 self.lifecycle_status = Some(status);
169 self
170 }
171
172 pub fn sequence(mut self, seq: u64) -> Self {
174 self.sequence = seq;
175 self
176 }
177
178 pub fn add_service(mut self, service: ServiceEndpoint) -> Self {
180 self.services.push(service);
181 self
182 }
183
184 pub fn profile(mut self, profile: ProfileSection) -> Self {
186 self.profile = Some(profile);
187 self
188 }
189
190 pub fn contact(mut self, contact: ContactSection) -> Self {
192 self.contact = Some(contact);
193 self
194 }
195
196 pub fn calendar(mut self, calendar: CalendarSection) -> Self {
198 self.calendar = Some(calendar);
199 self
200 }
201
202 pub fn operational(mut self, operational: OperationalSection) -> Self {
204 self.operational = Some(operational);
205 self
206 }
207
208 pub fn pricing(mut self, pricing: PricingSection) -> Self {
210 self.pricing = Some(pricing);
211 self
212 }
213
214 pub fn interoperability(mut self, interop: InteroperabilitySection) -> Self {
216 self.interoperability = Some(interop);
217 self
218 }
219
220 pub fn reputation(mut self, reputation: ReputationSection) -> Self {
222 self.reputation = Some(reputation);
223 self
224 }
225
226 pub fn compliance(mut self, compliance: ComplianceSection) -> Self {
228 self.compliance = Some(compliance);
229 self
230 }
231
232 pub fn relationships(mut self, relationships: RelationshipsSection) -> Self {
234 self.relationships = Some(relationships);
235 self
236 }
237
238 pub fn build_and_sign(
253 self,
254 signing_keypair: &OasKeyPair,
255 created: &str,
256 ) -> Result<OasDocument, DocumentError> {
257 let controller = self.controller.unwrap_or_else(|| self.id.clone());
258
259 let mut doc = OasDocument {
260 context: OasDocument::default_context(),
261 id: self.id,
262 controller,
263 verification_method: self.verification_methods,
264 authentication: self.authentication,
265 assertion_method: self.assertion_method,
266 capability_invocation: self.capability_invocation,
267 capability_delegation: self.capability_delegation,
268 service: self.services,
269 oas_version: crate::document::OAS_VERSION.to_string(),
270 kind: self.kind,
271 name: self.name,
272 description: self.description,
273 lineage: self.lineage,
274 governance: self.governance,
275 conformance_level: self.conformance_level,
276 lifecycle_status: self.lifecycle_status,
277 sequence: self.sequence,
278 metadata: DocumentMetadata {
279 created: created.to_string(),
280 updated: None,
281 version: None,
282 },
283 proof: None,
284 revoked: None,
285 revoked_at: None,
286 profile: self.profile,
287 contact: self.contact,
288 calendar: self.calendar,
289 operational: self.operational,
290 pricing: self.pricing,
291 interoperability: self.interoperability,
292 reputation: self.reputation,
293 compliance: self.compliance,
294 relationships: self.relationships,
295 };
296
297 let doc_json =
299 serde_json::to_value(&doc).map_err(|e| DocumentError::ProofGenerationFailed {
300 reason: format!("failed to serialize document: {e}"),
301 })?;
302
303 let vm_id = doc
304 .verification_method
305 .first()
306 .map(|vm| vm.id.as_str())
307 .ok_or_else(|| DocumentError::ProofGenerationFailed {
308 reason: "no verification method available for signing".to_string(),
309 })?
310 .to_string();
311
312 let proof = DocumentProof::generate(&doc_json, signing_keypair, &vm_id, created)?;
313 doc.proof = Some(proof);
314
315 Ok(doc)
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 #[test]
324 fn build_hmr_document() {
325 let keypair = OasKeyPair::generate();
326 let doc = DocumentBuilder::new("did:oas:test:hmr:alice", "hmr")
327 .controller("did:oas:test:hmr:alice")
328 .conformance_level(ConformanceLevel::L1)
329 .add_verification_method(&keypair)
330 .name("Alice")
331 .build_and_sign(&keypair, "2026-01-15T00:00:00Z");
332
333 assert!(doc.is_ok());
334 let doc = doc.unwrap_or_else(|_| unreachable_doc());
335 assert_eq!(doc.kind, "hmr");
336 assert!(doc.proof.is_some());
337 assert_eq!(doc.sequence, 1);
338 assert!(doc.is_root());
339 }
340
341 #[test]
342 fn build_agent_document_with_lineage() {
343 let keypair = OasKeyPair::generate();
344 let lineage = LineageSection {
345 human_root_did: "did:oas:test:hmr:alice".to_string(),
346 creator_did: "did:oas:test:hmr:alice".to_string(),
347 generation: 0,
348 derivation_proof: None,
349 human_root_chain: vec![
350 "did:oas:test:agent:bot".to_string(),
351 "did:oas:test:hmr:alice".to_string(),
352 ],
353 org_inclusion_proof: None,
354 anchor_refs: None,
355 };
356
357 let doc = DocumentBuilder::new("did:oas:test:agent:bot", "agent")
358 .controller("did:oas:test:hmr:alice")
359 .conformance_level(ConformanceLevel::L1)
360 .add_verification_method(&keypair)
361 .lineage(lineage)
362 .lifecycle_status(LifecycleStatus::Active)
363 .build_and_sign(&keypair, "2026-01-15T00:00:00Z");
364
365 assert!(doc.is_ok());
366 let doc = doc.unwrap_or_else(|_| unreachable_doc());
367 assert!(doc.lineage.is_some());
368 assert!(!doc.is_root());
369 }
370
371 fn unreachable_doc() -> OasDocument {
372 OasDocument {
373 context: vec![],
374 id: String::new(),
375 controller: String::new(),
376 verification_method: vec![],
377 authentication: vec![],
378 assertion_method: vec![],
379 capability_invocation: vec![],
380 capability_delegation: vec![],
381 service: vec![],
382 oas_version: String::new(),
383 kind: String::new(),
384 name: None,
385 description: None,
386 lineage: None,
387 governance: None,
388 conformance_level: ConformanceLevel::L0,
389 lifecycle_status: None,
390 sequence: 0,
391 metadata: DocumentMetadata {
392 created: String::new(),
393 updated: None,
394 version: None,
395 },
396 proof: None,
397 revoked: None,
398 revoked_at: None,
399 profile: None,
400 contact: None,
401 calendar: None,
402 operational: None,
403 pricing: None,
404 interoperability: None,
405 reputation: None,
406 compliance: None,
407 relationships: None,
408 }
409 }
410}