use oas_crypto::keypair::OasKeyPair;
use crate::calendar::CalendarSection;
use crate::compliance::ComplianceSection;
use crate::conformance::ConformanceLevel;
use crate::contact::ContactSection;
use crate::document::{DocumentMetadata, OasDocument};
use crate::error::DocumentError;
use crate::governance::GovernanceSection;
use crate::interop::InteroperabilitySection;
use crate::lifecycle::LifecycleStatus;
use crate::lineage_section::LineageSection;
use crate::operational::OperationalSection;
use crate::pricing::PricingSection;
use crate::profile::ProfileSection;
use crate::proof_format::DocumentProof;
use crate::relationships::RelationshipsSection;
use crate::reputation::ReputationSection;
use crate::service::ServiceEndpoint;
use crate::verification_method::VerificationMethod;
pub struct DocumentBuilder {
id: String,
kind: String,
controller: Option<String>,
verification_methods: Vec<VerificationMethod>,
authentication: Vec<String>,
assertion_method: Vec<String>,
capability_invocation: Vec<String>,
capability_delegation: Vec<String>,
services: Vec<ServiceEndpoint>,
name: Option<String>,
description: Option<String>,
lineage: Option<LineageSection>,
governance: Option<GovernanceSection>,
conformance_level: ConformanceLevel,
lifecycle_status: Option<LifecycleStatus>,
sequence: u64,
profile: Option<ProfileSection>,
contact: Option<ContactSection>,
calendar: Option<CalendarSection>,
operational: Option<OperationalSection>,
pricing: Option<PricingSection>,
interoperability: Option<InteroperabilitySection>,
reputation: Option<ReputationSection>,
compliance: Option<ComplianceSection>,
relationships: Option<RelationshipsSection>,
}
impl DocumentBuilder {
pub fn new(id: &str, kind: &str) -> Self {
Self {
id: id.to_string(),
kind: kind.to_string(),
controller: None,
verification_methods: Vec::new(),
authentication: Vec::new(),
assertion_method: Vec::new(),
capability_invocation: Vec::new(),
capability_delegation: Vec::new(),
services: Vec::new(),
name: None,
description: None,
lineage: None,
governance: None,
conformance_level: ConformanceLevel::L0,
lifecycle_status: None,
sequence: 1,
profile: None,
contact: None,
calendar: None,
operational: None,
pricing: None,
interoperability: None,
reputation: None,
compliance: None,
relationships: None,
}
}
pub fn controller(mut self, controller: &str) -> Self {
self.controller = Some(controller.to_string());
self
}
pub fn add_verification_method(mut self, keypair: &OasKeyPair) -> Self {
let key_num = self.verification_methods.len() as u32 + 1;
let vm = VerificationMethod::new(&self.id, key_num, &keypair.public_key_multibase());
let key_ref = vm.id.clone();
self.verification_methods.push(vm);
self.authentication.push(key_ref.clone());
self.assertion_method.push(key_ref.clone());
self.capability_invocation.push(key_ref);
self
}
pub fn conformance_level(mut self, level: ConformanceLevel) -> Self {
self.conformance_level = level;
self
}
pub fn name(mut self, name: &str) -> Self {
self.name = Some(name.to_string());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_string());
self
}
pub fn lineage(mut self, lineage: LineageSection) -> Self {
self.lineage = Some(lineage);
self
}
pub fn governance(mut self, governance: GovernanceSection) -> Self {
self.governance = Some(governance);
self
}
pub fn lifecycle_status(mut self, status: LifecycleStatus) -> Self {
self.lifecycle_status = Some(status);
self
}
pub fn sequence(mut self, seq: u64) -> Self {
self.sequence = seq;
self
}
pub fn add_service(mut self, service: ServiceEndpoint) -> Self {
self.services.push(service);
self
}
pub fn profile(mut self, profile: ProfileSection) -> Self {
self.profile = Some(profile);
self
}
pub fn contact(mut self, contact: ContactSection) -> Self {
self.contact = Some(contact);
self
}
pub fn calendar(mut self, calendar: CalendarSection) -> Self {
self.calendar = Some(calendar);
self
}
pub fn operational(mut self, operational: OperationalSection) -> Self {
self.operational = Some(operational);
self
}
pub fn pricing(mut self, pricing: PricingSection) -> Self {
self.pricing = Some(pricing);
self
}
pub fn interoperability(mut self, interop: InteroperabilitySection) -> Self {
self.interoperability = Some(interop);
self
}
pub fn reputation(mut self, reputation: ReputationSection) -> Self {
self.reputation = Some(reputation);
self
}
pub fn compliance(mut self, compliance: ComplianceSection) -> Self {
self.compliance = Some(compliance);
self
}
pub fn relationships(mut self, relationships: RelationshipsSection) -> Self {
self.relationships = Some(relationships);
self
}
pub fn build_and_sign(
self,
signing_keypair: &OasKeyPair,
created: &str,
) -> Result<OasDocument, DocumentError> {
let controller = self.controller.unwrap_or_else(|| self.id.clone());
let mut doc = OasDocument {
context: OasDocument::default_context(),
id: self.id,
controller,
verification_method: self.verification_methods,
authentication: self.authentication,
assertion_method: self.assertion_method,
capability_invocation: self.capability_invocation,
capability_delegation: self.capability_delegation,
service: self.services,
oas_version: crate::document::OAS_VERSION.to_string(),
kind: self.kind,
name: self.name,
description: self.description,
lineage: self.lineage,
governance: self.governance,
conformance_level: self.conformance_level,
lifecycle_status: self.lifecycle_status,
sequence: self.sequence,
metadata: DocumentMetadata {
created: created.to_string(),
updated: None,
version: None,
},
proof: None,
revoked: None,
revoked_at: None,
profile: self.profile,
contact: self.contact,
calendar: self.calendar,
operational: self.operational,
pricing: self.pricing,
interoperability: self.interoperability,
reputation: self.reputation,
compliance: self.compliance,
relationships: self.relationships,
};
let doc_json =
serde_json::to_value(&doc).map_err(|e| DocumentError::ProofGenerationFailed {
reason: format!("failed to serialize document: {e}"),
})?;
let vm_id = doc
.verification_method
.first()
.map(|vm| vm.id.as_str())
.ok_or_else(|| DocumentError::ProofGenerationFailed {
reason: "no verification method available for signing".to_string(),
})?
.to_string();
let proof = DocumentProof::generate(&doc_json, signing_keypair, &vm_id, created)?;
doc.proof = Some(proof);
Ok(doc)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_hmr_document() {
let keypair = OasKeyPair::generate();
let doc = DocumentBuilder::new("did:oas:test:hmr:alice", "hmr")
.controller("did:oas:test:hmr:alice")
.conformance_level(ConformanceLevel::L1)
.add_verification_method(&keypair)
.name("Alice")
.build_and_sign(&keypair, "2026-01-15T00:00:00Z");
assert!(doc.is_ok());
let doc = doc.unwrap_or_else(|_| unreachable_doc());
assert_eq!(doc.kind, "hmr");
assert!(doc.proof.is_some());
assert_eq!(doc.sequence, 1);
assert!(doc.is_root());
}
#[test]
fn build_agent_document_with_lineage() {
let keypair = OasKeyPair::generate();
let lineage = LineageSection {
human_root_did: "did:oas:test:hmr:alice".to_string(),
creator_did: "did:oas:test:hmr:alice".to_string(),
generation: 0,
derivation_proof: None,
human_root_chain: vec![
"did:oas:test:agent:bot".to_string(),
"did:oas:test:hmr:alice".to_string(),
],
org_inclusion_proof: None,
anchor_refs: None,
};
let doc = DocumentBuilder::new("did:oas:test:agent:bot", "agent")
.controller("did:oas:test:hmr:alice")
.conformance_level(ConformanceLevel::L1)
.add_verification_method(&keypair)
.lineage(lineage)
.lifecycle_status(LifecycleStatus::Active)
.build_and_sign(&keypair, "2026-01-15T00:00:00Z");
assert!(doc.is_ok());
let doc = doc.unwrap_or_else(|_| unreachable_doc());
assert!(doc.lineage.is_some());
assert!(!doc.is_root());
}
fn unreachable_doc() -> OasDocument {
OasDocument {
context: vec![],
id: String::new(),
controller: String::new(),
verification_method: vec![],
authentication: vec![],
assertion_method: vec![],
capability_invocation: vec![],
capability_delegation: vec![],
service: vec![],
oas_version: String::new(),
kind: String::new(),
name: None,
description: None,
lineage: None,
governance: None,
conformance_level: ConformanceLevel::L0,
lifecycle_status: None,
sequence: 0,
metadata: DocumentMetadata {
created: String::new(),
updated: None,
version: None,
},
proof: None,
revoked: None,
revoked_at: None,
profile: None,
contact: None,
calendar: None,
operational: None,
pricing: None,
interoperability: None,
reputation: None,
compliance: None,
relationships: None,
}
}
}