use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use crate::did::DID;
use crate::service::Service;
use crate::verification_method::VerificationMethod;
use crate::proof::Proof;
#[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 also_known_as: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub controller: Option<StringOrVec>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verification_method: Option<Vec<VerificationMethod>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authentication: Option<Vec<VerificationRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assertion_method: Option<Vec<VerificationRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_agreement: Option<Vec<VerificationRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capability_invocation: Option<Vec<VerificationRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub capability_delegation: Option<Vec<VerificationRelationship>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service: Option<Vec<Service>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof: Option<OneOrMany<Proof>>,
#[serde(flatten)]
pub additional_properties: HashMap<String, Value>,
}
impl DIDDocument {
pub fn new(did: &DID) -> Self {
Self {
context: vec![
"https://www.w3.org/ns/did/v1".to_string(),
"https://w3id.org/security/suites/ed25519-2020/v1".to_string(),
"https://w3id.org/security/suites/x25519-2020/v1".to_string(),
],
id: did.to_string(),
also_known_as: None,
controller: None,
verification_method: None,
authentication: None,
assertion_method: None,
key_agreement: None,
capability_invocation: None,
capability_delegation: None,
service: None,
created: Some(Utc::now()),
updated: None,
proof: None,
additional_properties: HashMap::new(),
}
}
pub fn add_verification_method(&mut self, method: VerificationMethod) {
match &mut self.verification_method {
Some(methods) => methods.push(method),
None => self.verification_method = Some(vec![method]),
}
}
pub fn add_authentication(&mut self, auth: VerificationRelationship) {
match &mut self.authentication {
Some(methods) => methods.push(auth),
None => self.authentication = Some(vec![auth]),
}
}
pub fn add_key_agreement(&mut self, agreement: VerificationRelationship) {
match &mut self.key_agreement {
Some(methods) => methods.push(agreement),
None => self.key_agreement = Some(vec![agreement]),
}
}
pub fn add_service(&mut self, service: Service) {
match &mut self.service {
Some(services) => services.push(service),
None => self.service = Some(vec![service]),
}
}
pub fn set_controller(&mut self, controller: impl Into<StringOrVec>) {
self.controller = Some(controller.into());
}
pub fn add_also_known_as(&mut self, identifier: String) {
match &mut self.also_known_as {
Some(ids) => ids.push(identifier),
None => self.also_known_as = Some(vec![identifier]),
}
}
pub fn find_verification_method(&self, id: &str) -> Option<&VerificationMethod> {
self.verification_method.as_ref()?.iter()
.find(|m| m.id == id || m.id.ends_with(&format!("#{id}")))
}
pub fn get_authentication_methods(&self) -> Vec<&VerificationMethod> {
let mut methods = Vec::new();
if let Some(auth_refs) = &self.authentication {
for auth_ref in auth_refs {
match auth_ref {
VerificationRelationship::Reference(id) => {
if let Some(method) = self.find_verification_method(id) {
methods.push(method);
}
}
VerificationRelationship::Embedded(method) => {
methods.push(method);
}
}
}
}
methods
}
pub fn validate(&self) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
if self.id.is_empty() {
errors.push("DID Document must have an id".to_string());
}
if self.context.is_empty() {
errors.push("DID Document must have at least one context".to_string());
}
if DID::from_str(&self.id).is_err() {
errors.push(format!("Invalid DID format: {}", self.id));
}
if let Some(auth) = &self.authentication {
for auth_ref in auth {
if let VerificationRelationship::Reference(id) = auth_ref {
if self.find_verification_method(id).is_none() {
errors.push(format!("Authentication references non-existent verification method: {id}"));
}
}
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum StringOrVec {
String(String),
Vec(Vec<String>),
}
impl From<String> for StringOrVec {
fn from(s: String) -> Self {
StringOrVec::String(s)
}
}
impl From<Vec<String>> for StringOrVec {
fn from(v: Vec<String>) -> Self {
StringOrVec::Vec(v)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum VerificationRelationship {
Reference(String),
Embedded(VerificationMethod),
}
impl From<String> for VerificationRelationship {
fn from(s: String) -> Self {
if s.starts_with('#') {
VerificationRelationship::Reference(s)
} else {
VerificationRelationship::Reference(s)
}
}
}
impl From<&str> for VerificationRelationship {
fn from(s: &str) -> Self {
VerificationRelationship::Reference(s.to_string())
}
}
impl From<VerificationMethod> for VerificationRelationship {
fn from(method: VerificationMethod) -> Self {
VerificationRelationship::Embedded(method)
}
}
use std::str::FromStr;
#[cfg(test)]
mod tests {
use super::*;
use crate::verification_method::VerificationMethodType;
#[test]
fn test_did_document_creation() {
let did = DID::hanzo_eth("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7");
let mut doc = DIDDocument::new(&did);
assert_eq!(doc.id, did.to_string());
assert!(!doc.context.is_empty());
let vm = VerificationMethod {
id: format!("{}#key-1", did),
type_: VerificationMethodType::Ed25519VerificationKey2020,
controller: did.to_string(),
public_key_multibase: Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()),
..Default::default()
};
doc.add_verification_method(vm.clone());
doc.add_authentication(format!("{}#key-1", did).into());
assert!(doc.verification_method.is_some());
assert!(doc.authentication.is_some());
assert!(doc.validate().is_ok());
}
#[test]
fn test_find_verification_method() {
let did = DID::hanzo_eth("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb7");
let mut doc = DIDDocument::new(&did);
let vm_id = format!("{}#key-1", did);
let vm = VerificationMethod {
id: vm_id.clone(),
type_: VerificationMethodType::Ed25519VerificationKey2020,
controller: did.to_string(),
public_key_multibase: Some("z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK".to_string()),
..Default::default()
};
doc.add_verification_method(vm);
assert!(doc.find_verification_method(&vm_id).is_some());
assert!(doc.find_verification_method("key-1").is_some());
}
}