use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(tag = "mode", content = "path", rename_all = "snake_case")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum WebvhPathMode {
WellKnown,
Explicit(String),
#[default]
AutoAssign,
}
impl WebvhPathMode {
pub fn to_request_path(&self) -> Option<&str> {
match self {
WebvhPathMode::WellKnown => Some(".well-known"),
WebvhPathMode::Explicit(p) => Some(p),
WebvhPathMode::AutoAssign => None,
}
}
pub fn resolve(path_mode: Option<WebvhPathMode>, legacy_path: Option<String>) -> Self {
path_mode.unwrap_or_else(|| WebvhPathMode::from(legacy_path))
}
}
impl From<Option<String>> for WebvhPathMode {
fn from(path: Option<String>) -> Self {
match path {
None => WebvhPathMode::AutoAssign,
Some(p) => WebvhPathMode::from(p),
}
}
}
impl From<String> for WebvhPathMode {
fn from(path: String) -> Self {
match path.trim() {
"" => WebvhPathMode::AutoAssign,
".well-known" => WebvhPathMode::WellKnown,
trimmed => WebvhPathMode::Explicit(trimmed.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CreateDidWebvhBody {
pub context_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub server_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path_mode: Option<WebvhPathMode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub portable: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub add_mediator_service: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub additional_services: Option<Vec<serde_json::Value>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pre_rotation_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub did_document: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub did_log: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub set_primary: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub signing_key_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ka_key_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub template: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub template_context: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub template_vars: Option<std::collections::HashMap<String, serde_json::Value>>,
}
#[derive(Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CreateDidWebvhResultBody {
pub did: String,
pub context_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub server_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mnemonic: Option<String>,
pub scid: String,
pub portable: bool,
pub signing_key_id: String,
pub ka_key_id: String,
pub pre_rotation_key_count: u32,
pub created_at: DateTime<Utc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub did_document: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub log_entry: Option<String>,
}
impl std::fmt::Debug for CreateDidWebvhResultBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CreateDidWebvhResultBody")
.field("did", &self.did)
.field("context_id", &self.context_id)
.field("server_id", &self.server_id)
.field("mnemonic", &self.mnemonic.as_ref().map(|_| "<redacted>"))
.field("scid", &self.scid)
.field("portable", &self.portable)
.field("signing_key_id", &self.signing_key_id)
.field("ka_key_id", &self.ka_key_id)
.field("pre_rotation_key_count", &self.pre_rotation_key_count)
.field("created_at", &self.created_at)
.field("did_document", &self.did_document)
.field("log_entry", &self.log_entry)
.finish()
}
}
#[cfg(test)]
mod webvh_path_mode_tests {
use super::WebvhPathMode;
#[test]
fn to_request_path_maps_each_mode() {
assert_eq!(
WebvhPathMode::WellKnown.to_request_path(),
Some(".well-known")
);
assert_eq!(
WebvhPathMode::Explicit("alice".into()).to_request_path(),
Some("alice")
);
assert_eq!(WebvhPathMode::AutoAssign.to_request_path(), None);
}
#[test]
fn default_is_auto_assign() {
assert_eq!(WebvhPathMode::default(), WebvhPathMode::AutoAssign);
}
#[test]
fn from_legacy_path() {
assert_eq!(WebvhPathMode::from(None), WebvhPathMode::AutoAssign);
assert_eq!(
WebvhPathMode::from(Some(" ".to_string())),
WebvhPathMode::AutoAssign
);
assert_eq!(
WebvhPathMode::from(Some(".well-known".to_string())),
WebvhPathMode::WellKnown
);
assert_eq!(
WebvhPathMode::from(Some(" alice ".to_string())),
WebvhPathMode::Explicit("alice".into())
);
}
#[test]
fn resolve_prefers_explicit_mode() {
assert_eq!(
WebvhPathMode::resolve(Some(WebvhPathMode::AutoAssign), Some("alice".into())),
WebvhPathMode::AutoAssign
);
assert_eq!(
WebvhPathMode::resolve(None, Some("alice".into())),
WebvhPathMode::Explicit("alice".into())
);
assert_eq!(
WebvhPathMode::resolve(None, None),
WebvhPathMode::AutoAssign
);
}
#[test]
fn serde_round_trips() {
for mode in [
WebvhPathMode::WellKnown,
WebvhPathMode::Explicit("alice".into()),
WebvhPathMode::AutoAssign,
] {
let json = serde_json::to_value(&mode).unwrap();
let back: WebvhPathMode = serde_json::from_value(json).unwrap();
assert_eq!(mode, back);
}
assert_eq!(
serde_json::to_value(WebvhPathMode::Explicit("alice".into())).unwrap(),
serde_json::json!({ "mode": "explicit", "path": "alice" })
);
assert_eq!(
serde_json::to_value(WebvhPathMode::AutoAssign).unwrap(),
serde_json::json!({ "mode": "auto_assign" })
);
}
}