Skip to content

Commit

Permalink
Make service generic over extra fields
Browse files Browse the repository at this point in the history
Signed-off-by: Miroslav Kovar <miroslav.kovar@absa.africa>
  • Loading branch information
mirgee committed Jun 1, 2023
1 parent 1222ef7 commit 6c610e4
Show file tree
Hide file tree
Showing 18 changed files with 314 additions and 100 deletions.
2 changes: 2 additions & 0 deletions did_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ extern crate serde_json;

pub mod error;
pub mod schema;

pub use did_parser;
40 changes: 29 additions & 11 deletions did_doc/src/schema/did_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use did_parser::{Did, DidUrl};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::error::DidDocumentBuilderError;

use super::{
service::Service,
types::uri::Uri,
Expand All @@ -16,7 +18,10 @@ type ControllerAlias = OneOrList<Did>;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
#[serde(default)]
#[serde(rename_all = "camelCase")]
pub struct DidDocument {
pub struct DidDocument<E>
where
E: Default,
{
id: Did,
#[serde(skip_serializing_if = "Vec::is_empty")]
also_known_as: Vec<Uri>,
Expand All @@ -35,14 +40,17 @@ pub struct DidDocument {
#[serde(skip_serializing_if = "Vec::is_empty")]
capability_delegation: Vec<VerificationMethodKind>,
#[serde(skip_serializing_if = "Vec::is_empty")]
service: Vec<Service>,
service: Vec<Service<E>>,
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(flatten)]
extra: HashMap<String, Value>,
}

impl DidDocument {
pub fn builder(id: Did) -> DidDocumentBuilder {
impl<E> DidDocument<E>
where
E: Default,
{
pub fn builder(id: Did) -> DidDocumentBuilder<E> {
DidDocumentBuilder::new(id)
}

Expand Down Expand Up @@ -82,17 +90,24 @@ impl DidDocument {
self.capability_delegation.as_ref()
}

pub fn service(&self) -> &[Service] {
pub fn service(&self) -> &[Service<E>] {
self.service.as_ref()
}

pub fn extra_field(&self, key: &str) -> Option<&Value> {
self.extra.get(key)
}

pub fn validate(&self) -> Result<(), DidDocumentBuilderError> {
Ok(())
}
}

#[derive(Debug, Default)]
pub struct DidDocumentBuilder {
pub struct DidDocumentBuilder<E>
where
E: Default,
{
id: Did,
also_known_as: Vec<Uri>,
controller: Vec<Did>,
Expand All @@ -102,11 +117,14 @@ pub struct DidDocumentBuilder {
key_agreement: Vec<VerificationMethodKind>,
capability_invocation: Vec<VerificationMethodKind>,
capability_delegation: Vec<VerificationMethodKind>,
service: Vec<Service>,
service: Vec<Service<E>>,
extra: HashMap<String, Value>,
}

impl DidDocumentBuilder {
impl<E> DidDocumentBuilder<E>
where
E: Default,
{
pub fn new(id: Did) -> Self {
Self {
id,
Expand Down Expand Up @@ -189,7 +207,7 @@ impl DidDocumentBuilder {
self
}

pub fn add_service(mut self, service: Service) -> Self {
pub fn add_service(mut self, service: Service<E>) -> Self {
self.service.push(service);
self
}
Expand All @@ -199,7 +217,7 @@ impl DidDocumentBuilder {
self
}

pub fn build(self) -> DidDocument {
pub fn build(self) -> DidDocument<E> {
let controller = if self.controller.is_empty() {
None
} else {
Expand Down Expand Up @@ -249,7 +267,7 @@ mod tests {
let service_id = Uri::new("did:example:123456789abcdefghi;service-1").unwrap();
let service_type = "test-service".to_string();
let service_endpoint = "https://example.com/service";
let service = ServiceBuilder::new(service_id, service_endpoint.try_into().unwrap())
let service = ServiceBuilder::<()>::new(service_id, service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type(service_type)
.unwrap()
Expand Down
103 changes: 61 additions & 42 deletions did_doc/src/schema/service.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::error::DidDocumentBuilderError;

Expand All @@ -14,22 +13,26 @@ type ServiceTypeAlias = OneOrList<String>;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Service {
pub struct Service<E>
where
E: Default,
{
id: Uri,
#[serde(rename = "type")]
service_type: ServiceTypeAlias,
service_endpoint: Url,
#[serde(flatten)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
#[serde(default)]
extra: HashMap<String, Value>,
extra: E,
}

impl Service {
impl<E> Service<E>
where
E: Default,
{
pub fn builder(
id: Uri,
service_endpoint: Url,
) -> Result<ServiceBuilder, DidDocumentBuilderError> {
) -> Result<ServiceBuilder<E>, DidDocumentBuilderError> {
ServiceBuilder::new(id, service_endpoint)
}

Expand All @@ -41,30 +44,33 @@ impl Service {
&self.service_type
}

pub fn service_endpoint(&self) -> &str {
self.service_endpoint.as_ref()
pub fn service_endpoint(&self) -> &Url {
&self.service_endpoint
}

pub fn extra_field(&self, key: &str) -> Option<&Value> {
self.extra.get(key)
pub fn extra(&self) -> &E {
&self.extra
}
}

#[derive(Debug)]
pub struct ServiceBuilder {
pub struct ServiceBuilder<E> {
id: Uri,
service_type: HashSet<String>,
service_endpoint: Url,
extra: HashMap<String, Value>,
extra: E,
}

impl ServiceBuilder {
impl<E> ServiceBuilder<E>
where
E: Default,
{
pub fn new(id: Uri, service_endpoint: Url) -> Result<Self, DidDocumentBuilderError> {
Ok(Self {
id,
service_endpoint,
service_type: HashSet::new(),
extra: HashMap::new(),
extra: E::default(),
})
}

Expand All @@ -79,12 +85,12 @@ impl ServiceBuilder {
Ok(self)
}

pub fn add_extra_field(mut self, key: String, value: Value) -> Self {
self.extra.insert(key, value);
pub fn add_extra(mut self, extra: E) -> Self {
self.extra = extra;
self
}

pub fn build(self) -> Result<Service, DidDocumentBuilderError> {
pub fn build(self) -> Result<Service<E>, DidDocumentBuilderError> {
if self.service_type.is_empty() {
Err(DidDocumentBuilderError::MissingField("type"))
} else {
Expand All @@ -106,21 +112,30 @@ mod tests {
Uri::new("http://example.com").unwrap()
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ExtraSov {
pub priority: u32,
pub recipient_keys: Vec<String>,
pub routing_keys: Vec<String>,
}

#[test]
fn test_service_builder_basic() {
let id = create_valid_uri();
let service_endpoint = "http://example.com/endpoint";
let service_type = "DIDCommMessaging".to_string();

let service = ServiceBuilder::new(id.clone(), service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type(service_type.clone())
.unwrap()
.build()
.unwrap();
let service =
ServiceBuilder::<ExtraSov>::new(id.clone(), service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type(service_type.clone())
.unwrap()
.build()
.unwrap();

assert_eq!(service.id(), &id);
assert_eq!(service.service_endpoint(), service_endpoint);
assert_eq!(service.service_endpoint().as_ref(), service_endpoint);
assert_eq!(service.service_type(), &OneOrList::List(vec![service_type]));
}

Expand All @@ -129,18 +144,24 @@ mod tests {
let id = create_valid_uri();
let service_endpoint = "http://example.com/endpoint";
let service_type = "DIDCommMessaging".to_string();
let extra_key = "foo".to_string();
let extra_value = Value::String("bar".to_string());

let service = ServiceBuilder::new(id, service_endpoint.try_into().unwrap())
let recipient_keys = vec!["foo".to_string()];
let routing_keys = vec!["bar".to_string()];
let extra = ExtraSov {
priority: 0,
recipient_keys: recipient_keys.clone(),
routing_keys: routing_keys.clone(),
};

let service = ServiceBuilder::<ExtraSov>::new(id, service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type(service_type)
.unwrap()
.add_extra_field(extra_key.clone(), extra_value.clone())
.add_extra(extra)
.build()
.unwrap();

assert_eq!(service.extra_field(&extra_key).unwrap(), &extra_value);
assert_eq!(service.extra().recipient_keys, recipient_keys);
assert_eq!(service.extra().routing_keys, routing_keys);
}

#[test]
Expand All @@ -149,7 +170,7 @@ mod tests {
let service_endpoint = "http://example.com/endpoint";
let service_type = "DIDCommMessaging".to_string();

let service = ServiceBuilder::new(id, service_endpoint.try_into().unwrap())
let service = ServiceBuilder::<ExtraSov>::new(id, service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type(service_type.clone())
.unwrap()
Expand All @@ -166,7 +187,7 @@ mod tests {
let id = create_valid_uri();
let service_endpoint = "http://example.com/endpoint";

let res = ServiceBuilder::new(id, service_endpoint.try_into().unwrap())
let res = ServiceBuilder::<ExtraSov>::new(id, service_endpoint.try_into().unwrap())
.unwrap()
.add_service_type("".to_string());
assert!(res.is_err());
Expand All @@ -188,7 +209,7 @@ mod tests {
"serviceEndpoint": "https://example.com/endpoint"
}"##;

let service: Service = serde_json::from_str(service_serialized).unwrap();
let service: Service<ExtraSov> = serde_json::from_str(service_serialized).unwrap();
assert_eq!(
service.id(),
&Uri::new("did:sov:HR6vs6GEZ8rHaVgjg2WodM#did-communication").unwrap()
Expand All @@ -197,16 +218,14 @@ mod tests {
service.service_type(),
&OneOrList::One("did-communication".to_string())
);
assert_eq!(service.service_endpoint(), "https://example.com/endpoint");
assert_eq!(
service.extra_field("priority").unwrap(),
&Value::Number(0.into())
service.service_endpoint().as_ref(),
"https://example.com/endpoint"
);
assert_eq!(service.extra().priority, 0);
assert_eq!(
service.extra_field("recipientKeys").unwrap(),
&Value::Array(vec![Value::String(
"did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1".to_string()
)])
service.extra().recipient_keys,
vec!["did:sov:HR6vs6GEZ8rHaVgjg2WodM#key-agreement-1".to_string()]
);
}
}
28 changes: 28 additions & 0 deletions did_doc/src/schema/types/url.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{fmt::Display, str::FromStr};

use serde::{Deserialize, Serialize};
use url::Url as UrlDep;

Expand All @@ -20,8 +22,34 @@ impl TryFrom<&str> for Url {
}
}

impl FromStr for Url {
type Err = DidDocumentBuilderError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(UrlDep::parse(s)?))
}
}

impl From<UrlDep> for Url {
fn from(url: UrlDep) -> Self {
Self(url)
}
}

impl From<Url> for UrlDep {
fn from(url: Url) -> Self {
url.0
}
}

impl AsRef<str> for Url {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}

impl Display for Url {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_str().fmt(f)
}
}
11 changes: 11 additions & 0 deletions did_doc/src/schema/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::{Debug, Display};

use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
Expand All @@ -6,3 +8,12 @@ pub enum OneOrList<T> {
One(T),
List(Vec<T>),
}

impl<T: Display + Debug> Display for OneOrList<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OneOrList::One(t) => write!(f, "{}", t),
OneOrList::List(t) => write!(f, "{:?}", t),
}
}
}
Loading

0 comments on commit 6c610e4

Please sign in to comment.