-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
843 additions
and
6 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
use std::{fmt::Display, str::FromStr}; | ||
|
||
use serde::{Deserialize, Deserializer, Serialize, Serializer}; | ||
use serde_json::Value; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct Did { | ||
pub method_name: MethodName, | ||
pub method_id: MethodId, | ||
} | ||
|
||
impl Serialize for Did { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
let did_string = format!("did:{}:{}", self.method_name.0, self.method_id.0); | ||
serializer.serialize_str(&did_string) | ||
} | ||
} | ||
|
||
impl<'de> Deserialize<'de> for Did { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let s = String::deserialize(deserializer)?; | ||
let mut parts = s.splitn(3, ':'); | ||
|
||
if parts.next() != Some("did") { | ||
return Err(serde::de::Error::custom("DID must start with 'did:'")); | ||
} | ||
|
||
let method_name = parts | ||
.next() | ||
.ok_or_else(|| serde::de::Error::custom("Missing method name"))?; | ||
let method_specific_id = parts | ||
.next() | ||
.ok_or_else(|| serde::de::Error::custom("Missing method-specific ID"))?; | ||
|
||
let method_name = MethodName::from_str(method_name).map_err(serde::de::Error::custom)?; | ||
let method_id = MethodId::from_str(method_specific_id).map_err(serde::de::Error::custom)?; | ||
|
||
Ok(Did { | ||
method_name, | ||
method_id, | ||
}) | ||
} | ||
} | ||
|
||
impl FromStr for Did { | ||
type Err = serde_json::Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
serde_json::from_value(Value::String(s.to_string())) | ||
} | ||
} | ||
|
||
impl Display for Did { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
let value = serde_json::to_value(self).map_err(|_| std::fmt::Error)?; | ||
match value { | ||
Value::String(s) => write!(f, "{}", s), | ||
_ => Err(std::fmt::Error), | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct MethodName(pub String); | ||
|
||
impl FromStr for MethodName { | ||
type Err = String; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
if s.chars() | ||
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit()) | ||
{ | ||
Ok(MethodName(s.to_string())) | ||
} else { | ||
Err("Method name must contain only lowercase letters and digits".into()) | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct MethodId(pub String); | ||
|
||
impl FromStr for MethodId { | ||
type Err = String; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
if s.split(':').all(is_valid_idchar) { | ||
Ok(MethodId(s.to_string())) | ||
} else { | ||
Err("Method-specific ID contains invalid characters".into()) | ||
} | ||
} | ||
} | ||
|
||
fn is_valid_idchar(s: &str) -> bool { | ||
s.chars().all(|c| { | ||
c.is_ascii_alphanumeric() | ||
|| c == '.' | ||
|| c == '-' | ||
|| c == '_' | ||
|| c == '%' | ||
|| c.is_ascii_hexdigit() | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_did_example() { | ||
let did = Did { | ||
method_name: MethodName("example".to_string()), | ||
method_id: MethodId("1234-5678-abcdef".to_string()), | ||
}; | ||
|
||
let serialized = did.to_string(); | ||
assert_eq!(serialized, "did:example:1234-5678-abcdef"); | ||
|
||
let deserialized = Did::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
use std::{fmt::Display, str::FromStr}; | ||
|
||
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; | ||
use serde_json::Value; | ||
|
||
use crate::did::Did; | ||
|
||
#[derive(Debug, Clone, PartialEq, Eq)] | ||
pub struct DidUrl { | ||
pub did: Did, | ||
pub path_abempty: String, | ||
pub query: Option<String>, | ||
pub fragment: Option<String>, | ||
} | ||
|
||
impl Serialize for DidUrl { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
let mut url = format!("{}{}", self.did, self.path_abempty); | ||
|
||
if let Some(ref query) = self.query { | ||
url.push('?'); | ||
url.push_str(query); | ||
} | ||
|
||
if let Some(ref fragment) = self.fragment { | ||
url.push('#'); | ||
url.push_str(fragment); | ||
} | ||
|
||
serializer.serialize_str(&url) | ||
} | ||
} | ||
|
||
impl<'de> Deserialize<'de> for DidUrl { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
struct DidUrlVisitor; | ||
|
||
impl<'de> Visitor<'de> for DidUrlVisitor { | ||
type Value = DidUrl; | ||
|
||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { | ||
formatter.write_str("a valid DID URL") | ||
} | ||
|
||
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> | ||
where | ||
E: serde::de::Error, | ||
{ | ||
let (did_str, _) = value.split_once('/').unwrap_or_else(|| { | ||
value | ||
.split_once('?') | ||
.unwrap_or_else(|| value.split_once('#').unwrap_or((value, ""))) | ||
}); | ||
|
||
let did = Did::from_str(did_str).map_err(serde::de::Error::custom)?; | ||
|
||
let mut path_abempty = String::new(); | ||
let mut query = None; | ||
let mut fragment = None; | ||
|
||
let mut rest = value.strip_prefix(did_str).unwrap(); | ||
if let Some((before_fragment, frag)) = rest.split_once('#') { | ||
fragment = Some(frag.to_string()); | ||
rest = before_fragment; | ||
} | ||
|
||
if let Some((before_query, qry)) = rest.split_once('?') { | ||
query = Some(qry.to_string()); | ||
rest = before_query; | ||
} | ||
|
||
path_abempty.push_str(rest); | ||
|
||
Ok(DidUrl { | ||
did, | ||
path_abempty, | ||
query, | ||
fragment, | ||
}) | ||
} | ||
} | ||
|
||
deserializer.deserialize_str(DidUrlVisitor) | ||
} | ||
} | ||
|
||
impl FromStr for DidUrl { | ||
type Err = serde_json::Error; | ||
|
||
fn from_str(s: &str) -> Result<Self, Self::Err> { | ||
serde_json::from_value(Value::String(s.to_string())) | ||
} | ||
} | ||
|
||
impl Display for DidUrl { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
let value = serde_json::to_value(self).map_err(|_| std::fmt::Error)?; | ||
match value { | ||
Value::String(s) => write!(f, "{}", s), | ||
_ => Err(std::fmt::Error), | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_did_url_full() { | ||
let did_url = DidUrl { | ||
did: Did::from_str("did:example:123").unwrap(), | ||
path_abempty: "/path/to/resource".to_string(), | ||
query: Some("key=value".to_string()), | ||
fragment: Some("section".to_string()), | ||
}; | ||
|
||
let serialized = did_url.to_string(); | ||
assert_eq!( | ||
serialized, | ||
"did:example:123/path/to/resource?key=value#section" | ||
); | ||
|
||
let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did_url); | ||
} | ||
|
||
#[test] | ||
fn test_did_url_no_path() { | ||
let did_url = DidUrl { | ||
did: Did::from_str("did:example:123").unwrap(), | ||
path_abempty: "".to_string(), | ||
query: Some("key=value".to_string()), | ||
fragment: Some("section".to_string()), | ||
}; | ||
|
||
let serialized = did_url.to_string(); | ||
assert_eq!(serialized, "did:example:123?key=value#section"); | ||
|
||
let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did_url); | ||
} | ||
|
||
#[test] | ||
fn test_did_url_no_query() { | ||
let did_url = DidUrl { | ||
did: Did::from_str("did:example:123").unwrap(), | ||
path_abempty: "/path/to/resource".to_string(), | ||
query: None, | ||
fragment: Some("section".to_string()), | ||
}; | ||
|
||
let serialized = did_url.to_string(); | ||
assert_eq!(serialized, "did:example:123/path/to/resource#section"); | ||
|
||
let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did_url); | ||
} | ||
|
||
#[test] | ||
fn test_did_url_no_fragment() { | ||
let did_url = DidUrl { | ||
did: Did::from_str("did:example:123").unwrap(), | ||
path_abempty: "/path/to/resource".to_string(), | ||
query: Some("key=value".to_string()), | ||
fragment: None, | ||
}; | ||
|
||
let serialized = did_url.to_string(); | ||
assert_eq!(serialized, "did:example:123/path/to/resource?key=value"); | ||
|
||
let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did_url); | ||
} | ||
|
||
#[test] | ||
fn test_did_url_none() { | ||
let did_url = DidUrl { | ||
did: Did::from_str("did:example:123").unwrap(), | ||
path_abempty: "".to_string(), | ||
query: None, | ||
fragment: None, | ||
}; | ||
|
||
let serialized = did_url.to_string(); | ||
assert_eq!(serialized, "did:example:123"); | ||
|
||
let deserialized = DidUrl::from_str(&serialized).expect("deserialize failed"); | ||
assert_eq!(deserialized, did_url); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use serde::{Deserialize, Serialize}; | ||
use serde_with::{serde_as, skip_serializing_none}; | ||
|
||
use crate::{did::Did, did_url::DidUrl}; | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde_as] | ||
#[skip_serializing_none] | ||
pub struct Document { | ||
pub id: Did, | ||
pub also_known_as: Option<Vec<String>>, | ||
#[serde_as(as = "Option<OneOrMany<_>>")] | ||
pub controller: Option<Vec<Did>>, | ||
pub verification_method: Option<Vec<VerificationMethodMap>>, | ||
pub authentication: Option<Vec<VerificationMethod>>, | ||
pub assertion_method: Option<Vec<VerificationMethod>>, | ||
pub key_agreement: Option<Vec<VerificationMethod>>, | ||
pub capability_invocation: Option<Vec<VerificationMethod>>, | ||
pub capability_delegation: Option<Vec<VerificationMethod>>, | ||
pub service: Option<Vec<ServiceEndpoint>>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub enum VerificationMethod { | ||
Map(VerificationMethodMap), | ||
URL(String), | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
#[skip_serializing_none] | ||
pub struct VerificationMethodMap { | ||
pub id: DidUrl, | ||
pub controller: Did, | ||
#[serde(rename = "type")] | ||
pub typ: String, | ||
pub public_key_jwk: Option<Jwk>, | ||
/// Multibase encoded public key. | ||
pub public_key_multibase: Option<String>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
#[serde_as] | ||
pub struct ServiceEndpoint { | ||
pub id: String, | ||
#[serde(rename = "type")] | ||
#[serde_as(as = "OneOrMany<_>")] | ||
pub typ: Vec<String>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize, Debug)] | ||
pub struct Jwk {} |
Oops, something went wrong.