use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DidDocument {
#[serde(rename = "@context")]
pub context: Vec<String>,
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub controller: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub verification_method: Vec<VerificationMethod>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub authentication: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub assertion_method: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub key_agreement: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub service: Vec<DidService>,
}
impl DidDocument {
pub fn new(id: impl Into<String>) -> Self {
Self {
context: vec![
"https://www.w3.org/ns/did/v1".to_string(),
"https://w3id.org/security/suites/ed25519-2020/v1".to_string(),
"https://ns.tenzro.network/v1".to_string(),
],
id: id.into(),
controller: None,
verification_method: Vec::new(),
authentication: Vec::new(),
assertion_method: Vec::new(),
key_agreement: Vec::new(),
service: Vec::new(),
}
}
pub fn with_controller(mut self, controller: impl Into<String>) -> Self {
self.controller = Some(controller.into());
self
}
pub fn add_verification_method(&mut self, method: VerificationMethod) {
let method_id = method.id.clone();
for purpose in &method.purposes {
match purpose {
VerificationPurpose::Authentication => {
self.authentication.push(method_id.clone());
}
VerificationPurpose::AssertionMethod => {
self.assertion_method.push(method_id.clone());
}
VerificationPurpose::KeyAgreement => {
self.key_agreement.push(method_id.clone());
}
}
}
self.verification_method.push(method);
}
pub fn add_service(&mut self, service: DidService) {
self.service.push(service);
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerificationMethod {
pub id: String,
pub controller: String,
#[serde(rename = "type")]
pub method_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_multibase: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key_jwk: Option<serde_json::Value>,
#[serde(skip)]
pub purposes: Vec<VerificationPurpose>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerificationPurpose {
Authentication,
AssertionMethod,
KeyAgreement,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DidService {
pub id: String,
#[serde(rename = "type")]
pub service_type: String,
#[serde(rename = "serviceEndpoint")]
pub service_endpoint: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_did_document() {
let doc = DidDocument::new("did:tenzro:human:alice-uuid");
assert_eq!(doc.id, "did:tenzro:human:alice-uuid");
assert!(doc.context.contains(&"https://www.w3.org/ns/did/v1".to_string()));
assert!(doc.controller.is_none());
}
#[test]
fn test_did_document_with_controller() {
let doc = DidDocument::new("did:tenzro:machine:ctrl:bot1")
.with_controller("did:tenzro:human:ctrl");
assert_eq!(doc.controller, Some("did:tenzro:human:ctrl".to_string()));
}
#[test]
fn test_add_verification_method() {
let mut doc = DidDocument::new("did:tenzro:human:alice");
let method = VerificationMethod {
id: "did:tenzro:human:alice#key-1".to_string(),
controller: "did:tenzro:human:alice".to_string(),
method_type: "Ed25519VerificationKey2020".to_string(),
public_key_multibase: Some("z6Mk...".to_string()),
public_key_jwk: None,
purposes: vec![
VerificationPurpose::Authentication,
VerificationPurpose::AssertionMethod,
],
};
doc.add_verification_method(method);
assert_eq!(doc.verification_method.len(), 1);
assert_eq!(doc.authentication.len(), 1);
assert_eq!(doc.assertion_method.len(), 1);
assert_eq!(doc.key_agreement.len(), 0);
}
#[test]
fn test_add_service() {
let mut doc = DidDocument::new("did:tenzro:human:alice");
doc.add_service(DidService {
id: "did:tenzro:human:alice#inference".to_string(),
service_type: "InferenceEndpoint".to_string(),
service_endpoint: "https://node.tenzro.com/inference".to_string(),
});
assert_eq!(doc.service.len(), 1);
assert_eq!(doc.service[0].service_type, "InferenceEndpoint");
}
#[test]
fn test_serialize_did_document() {
let doc = DidDocument::new("did:tenzro:human:alice");
let json = serde_json::to_string_pretty(&doc).unwrap();
assert!(json.contains("@context"));
assert!(json.contains("did:tenzro:human:alice"));
}
#[test]
fn test_w3c_did_document_json_ld_compliance() {
let mut doc = DidDocument::new("did:tenzro:human:alice-uuid");
doc.add_verification_method(VerificationMethod {
id: "did:tenzro:human:alice-uuid#key-1".to_string(),
controller: "did:tenzro:human:alice-uuid".to_string(),
method_type: "Ed25519VerificationKey2020".to_string(),
public_key_multibase: Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()),
public_key_jwk: None,
purposes: vec![VerificationPurpose::Authentication],
});
let json = serde_json::to_string_pretty(&doc).unwrap();
assert!(json.contains("@context"), "Missing @context field");
assert!(json.contains("https://www.w3.org/ns/did/v1"), "Missing W3C DID context");
assert!(json.contains("\"id\""), "Missing id field");
assert!(json.contains("verificationMethod"), "Missing verificationMethod");
assert!(json.contains("authentication"), "Missing authentication relationship");
let parsed: DidDocument = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, "did:tenzro:human:alice-uuid");
assert_eq!(parsed.context.len(), 3);
assert_eq!(parsed.verification_method.len(), 1);
assert_eq!(parsed.authentication.len(), 1);
}
#[test]
fn test_did_document_roundtrip_with_all_fields() {
let mut doc = DidDocument::new("did:tenzro:machine:ctrl:bot")
.with_controller("did:tenzro:human:ctrl");
doc.add_verification_method(VerificationMethod {
id: "did:tenzro:machine:ctrl:bot#key-1".to_string(),
controller: "did:tenzro:machine:ctrl:bot".to_string(),
method_type: "Ed25519VerificationKey2020".to_string(),
public_key_multibase: Some("z6Mk...".to_string()),
public_key_jwk: None,
purposes: vec![
VerificationPurpose::Authentication,
VerificationPurpose::AssertionMethod,
VerificationPurpose::KeyAgreement,
],
});
doc.add_service(DidService {
id: "did:tenzro:machine:ctrl:bot#inference".to_string(),
service_type: "InferenceEndpoint".to_string(),
service_endpoint: "https://node.example.com/inference".to_string(),
});
let json = serde_json::to_string(&doc).unwrap();
let parsed: DidDocument = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.id, doc.id);
assert_eq!(parsed.controller, doc.controller);
assert_eq!(parsed.verification_method.len(), 1);
assert_eq!(parsed.authentication.len(), 1);
assert_eq!(parsed.assertion_method.len(), 1);
assert_eq!(parsed.key_agreement.len(), 1);
assert_eq!(parsed.service.len(), 1);
}
}