vta_sdk/protocols/did_management/
create.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
27#[serde(tag = "mode", content = "path", rename_all = "snake_case")]
28#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
29pub enum WebvhPathMode {
30 WellKnown,
32 Explicit(String),
34 #[default]
36 AutoAssign,
37}
38
39impl WebvhPathMode {
40 pub fn to_request_path(&self) -> Option<&str> {
51 match self {
52 WebvhPathMode::WellKnown => Some(".well-known"),
53 WebvhPathMode::Explicit(p) => Some(p),
54 WebvhPathMode::AutoAssign => None,
55 }
56 }
57
58 pub fn resolve(path_mode: Option<WebvhPathMode>, legacy_path: Option<String>) -> Self {
63 path_mode.unwrap_or_else(|| WebvhPathMode::from(legacy_path))
64 }
65}
66
67impl From<Option<String>> for WebvhPathMode {
68 fn from(path: Option<String>) -> Self {
69 match path {
70 None => WebvhPathMode::AutoAssign,
71 Some(p) => WebvhPathMode::from(p),
72 }
73 }
74}
75
76impl From<String> for WebvhPathMode {
77 fn from(path: String) -> Self {
78 match path.trim() {
79 "" => WebvhPathMode::AutoAssign,
82 ".well-known" => WebvhPathMode::WellKnown,
83 trimmed => WebvhPathMode::Explicit(trimmed.to_string()),
84 }
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
91pub struct CreateDidWebvhBody {
92 #[serde(alias = "context_id")]
93 pub context_id: String,
94 #[serde(default, skip_serializing_if = "Option::is_none", alias = "server_id")]
95 pub server_id: Option<String>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub url: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub path: Option<String>,
106 #[serde(default, skip_serializing_if = "Option::is_none", alias = "path_mode")]
109 pub path_mode: Option<WebvhPathMode>,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub domain: Option<String>,
119 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub label: Option<String>,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub portable: Option<bool>,
123 #[serde(
124 default,
125 skip_serializing_if = "Option::is_none",
126 alias = "add_mediator_service"
127 )]
128 pub add_mediator_service: Option<bool>,
129 #[serde(
130 default,
131 skip_serializing_if = "Option::is_none",
132 alias = "additional_services"
133 )]
134 pub additional_services: Option<Vec<serde_json::Value>>,
135 #[serde(
136 default,
137 skip_serializing_if = "Option::is_none",
138 alias = "pre_rotation_count"
139 )]
140 pub pre_rotation_count: Option<u32>,
141 #[serde(
145 default,
146 skip_serializing_if = "Option::is_none",
147 alias = "did_document"
148 )]
149 pub did_document: Option<serde_json::Value>,
150 #[serde(default, skip_serializing_if = "Option::is_none", alias = "did_log")]
154 pub did_log: Option<String>,
155 #[serde(
158 default,
159 skip_serializing_if = "Option::is_none",
160 alias = "set_primary"
161 )]
162 pub set_primary: Option<bool>,
163 #[serde(
166 default,
167 skip_serializing_if = "Option::is_none",
168 alias = "signing_key_id"
169 )]
170 pub signing_key_id: Option<String>,
171 #[serde(default, skip_serializing_if = "Option::is_none", alias = "ka_key_id")]
175 pub ka_key_id: Option<String>,
176 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub template: Option<String>,
180 #[serde(
183 default,
184 skip_serializing_if = "Option::is_none",
185 alias = "template_context"
186 )]
187 pub template_context: Option<String>,
188 #[serde(
192 default,
193 skip_serializing_if = "Option::is_none",
194 alias = "template_vars"
195 )]
196 pub template_vars: Option<std::collections::HashMap<String, serde_json::Value>>,
197}
198
199#[derive(Clone, Serialize, Deserialize)]
200#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
201pub struct CreateDidWebvhResultBody {
202 pub did: String,
203 pub context_id: String,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub server_id: Option<String>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub mnemonic: Option<String>,
208 pub scid: String,
209 pub portable: bool,
210 pub signing_key_id: String,
211 pub ka_key_id: String,
212 pub pre_rotation_key_count: u32,
213 pub created_at: DateTime<Utc>,
214 #[serde(default, skip_serializing_if = "Option::is_none")]
215 pub did_document: Option<serde_json::Value>,
216 #[serde(default, skip_serializing_if = "Option::is_none")]
217 pub log_entry: Option<String>,
218}
219
220impl std::fmt::Debug for CreateDidWebvhResultBody {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 f.debug_struct("CreateDidWebvhResultBody")
227 .field("did", &self.did)
228 .field("context_id", &self.context_id)
229 .field("server_id", &self.server_id)
230 .field("mnemonic", &self.mnemonic.as_ref().map(|_| "<redacted>"))
231 .field("scid", &self.scid)
232 .field("portable", &self.portable)
233 .field("signing_key_id", &self.signing_key_id)
234 .field("ka_key_id", &self.ka_key_id)
235 .field("pre_rotation_key_count", &self.pre_rotation_key_count)
236 .field("created_at", &self.created_at)
237 .field("did_document", &self.did_document)
238 .field("log_entry", &self.log_entry)
239 .finish()
240 }
241}
242
243#[cfg(test)]
244mod webvh_path_mode_tests {
245 use super::WebvhPathMode;
246
247 #[test]
251 fn to_request_path_maps_each_mode() {
252 assert_eq!(
253 WebvhPathMode::WellKnown.to_request_path(),
254 Some(".well-known")
255 );
256 assert_eq!(
257 WebvhPathMode::Explicit("alice".into()).to_request_path(),
258 Some("alice")
259 );
260 assert_eq!(WebvhPathMode::AutoAssign.to_request_path(), None);
261 }
262
263 #[test]
266 fn default_is_auto_assign() {
267 assert_eq!(WebvhPathMode::default(), WebvhPathMode::AutoAssign);
268 }
269
270 #[test]
273 fn from_legacy_path() {
274 assert_eq!(WebvhPathMode::from(None), WebvhPathMode::AutoAssign);
275 assert_eq!(
276 WebvhPathMode::from(Some(" ".to_string())),
277 WebvhPathMode::AutoAssign
278 );
279 assert_eq!(
280 WebvhPathMode::from(Some(".well-known".to_string())),
281 WebvhPathMode::WellKnown
282 );
283 assert_eq!(
284 WebvhPathMode::from(Some(" alice ".to_string())),
285 WebvhPathMode::Explicit("alice".into())
286 );
287 }
288
289 #[test]
292 fn resolve_prefers_explicit_mode() {
293 assert_eq!(
295 WebvhPathMode::resolve(Some(WebvhPathMode::AutoAssign), Some("alice".into())),
296 WebvhPathMode::AutoAssign
297 );
298 assert_eq!(
300 WebvhPathMode::resolve(None, Some("alice".into())),
301 WebvhPathMode::Explicit("alice".into())
302 );
303 assert_eq!(
304 WebvhPathMode::resolve(None, None),
305 WebvhPathMode::AutoAssign
306 );
307 }
308
309 #[test]
311 fn serde_round_trips() {
312 for mode in [
313 WebvhPathMode::WellKnown,
314 WebvhPathMode::Explicit("alice".into()),
315 WebvhPathMode::AutoAssign,
316 ] {
317 let json = serde_json::to_value(&mode).unwrap();
318 let back: WebvhPathMode = serde_json::from_value(json).unwrap();
319 assert_eq!(mode, back);
320 }
321 assert_eq!(
323 serde_json::to_value(WebvhPathMode::Explicit("alice".into())).unwrap(),
324 serde_json::json!({ "mode": "explicit", "path": "alice" })
325 );
326 assert_eq!(
327 serde_json::to_value(WebvhPathMode::AutoAssign).unwrap(),
328 serde_json::json!({ "mode": "auto_assign" })
329 );
330 }
331}
332
333#[cfg(test)]
334mod casing_tests {
335 use super::*;
336
337 #[test]
345 fn create_body_reads_both_casings() {
346 let camel: CreateDidWebvhBody =
347 serde_json::from_str(r#"{"contextId":"default","preRotationCount":2}"#).unwrap();
348 assert_eq!(camel.context_id, "default");
349 assert_eq!(camel.pre_rotation_count, Some(2));
350
351 let snake: CreateDidWebvhBody =
352 serde_json::from_str(r#"{"context_id":"default","pre_rotation_count":2}"#).unwrap();
353 assert_eq!(snake.context_id, "default");
354 assert_eq!(snake.pre_rotation_count, Some(2));
355 }
356}