Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update ServiceEndpoint to support sets and maps #485

Merged
merged 8 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bindings/wasm/examples/src/resolve_history.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ async function resolveHistory(clientConfig) {
let serviceJSON2 = {
id: diffDoc2.id + "#linked-domain-2",
type: "LinkedDomains",
serviceEndpoint: "https://example.com",
serviceEndpoint: {
"origins": ["https://iota.org/", "https://example.com/"]
},
};
diffDoc2.insertService(Service.fromJSON(serviceJSON2));
diffDoc2.updated = Timestamp.nowUTC();
Expand Down
6 changes: 4 additions & 2 deletions examples/low-level-api/resolve_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async fn main() -> Result<()> {
let service: Service = Service::from_json_value(json!({
"id": diff_doc_1.id().to_url().join("#linked-domain-1")?,
"type": "LinkedDomains",
"serviceEndpoint": "https://iota.org"
"serviceEndpoint": "https://iota.org/"
}))?;
assert!(diff_doc_1.insert_service(service));
diff_doc_1.set_updated(Timestamp::now_utc());
Expand Down Expand Up @@ -120,7 +120,9 @@ async fn main() -> Result<()> {
let service: Service = Service::from_json_value(json!({
"id": diff_doc_2.id().to_url().join("#linked-domain-2")?,
"type": "LinkedDomains",
"serviceEndpoint": "https://example.com"
"serviceEndpoint": {
"origins": ["https://iota.org/", "https://example.com/"]
}
}))?;
diff_doc_2.insert_service(service);
diff_doc_2.set_updated(Timestamp::now_utc());
Expand Down
8 changes: 4 additions & 4 deletions identity-account/src/events/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crypto::signatures::ed25519;

use identity_core::common::Fragment;
use identity_core::common::Object;
use identity_core::common::Url;
use identity_core::crypto::PublicKey;
use identity_did::service::ServiceEndpoint;
use identity_did::verification::MethodData;
use identity_did::verification::MethodScope;
use identity_did::verification::MethodType;
Expand Down Expand Up @@ -60,7 +60,7 @@ pub(crate) enum Command {
CreateService {
fragment: String,
type_: String,
endpoint: Url,
endpoint: ServiceEndpoint,
properties: Option<Object>,
},
DeleteService {
Expand Down Expand Up @@ -358,12 +358,12 @@ impl_command_builder!(
/// # Parameters
/// - `type_`: the type of the service, e.g. `"LinkedDomains"`, required.
/// - `fragment`: the identifier of the service in the document, required.
/// - `endpoint`: the url of the service, required.
/// - `endpoint`: the `ServiceEndpoint` of the service, required.
/// - `properties`: additional properties of the service, optional.
CreateService {
@required fragment String,
@required type_ String,
@required endpoint Url,
@required endpoint ServiceEndpoint,
@optional properties Object,
});

Expand Down
5 changes: 3 additions & 2 deletions identity-account/src/identity/identity_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use identity_did::did::DID;
use identity_did::document::CoreDocument;
use identity_did::document::DocumentBuilder;
use identity_did::service::Service as CoreService;
use identity_did::service::ServiceEndpoint;
use identity_did::verifiable::Properties as VerifiableProperties;
use identity_did::verification::MethodData;
use identity_did::verification::MethodRef as CoreMethodRef;
Expand Down Expand Up @@ -569,14 +570,14 @@ pub struct TinyService {
#[serde(rename = "2")]
type_: String,
#[serde(rename = "3")]
endpoint: Url,
endpoint: ServiceEndpoint,
#[serde(rename = "4")]
properties: Option<Object>,
}

impl TinyService {
/// Creates a new `TinyService`.
pub fn new(fragment: String, type_: String, endpoint: Url, properties: Option<Object>) -> Self {
pub fn new(fragment: String, type_: String, endpoint: ServiceEndpoint, properties: Option<Object>) -> Self {
Self {
fragment: Fragment::new(fragment),
type_,
Expand Down
1 change: 1 addition & 0 deletions identity-did/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ async-trait = { version = "0.1", default-features = false }
did_url = { version = "0.1", default-features = false, features = ["std", "serde"] }
form_urlencoded = { version = "1.0.1", default-features = false }
identity-core = { version = "=0.4.0", path = "../identity-core" }
indexmap = { version = "1.7", default-features = false, features = ["std", "serde-1"] }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }
strum = { version = "0.21", features = ["derive"] }
thiserror = { version = "1.0", default-features = false }
Expand Down
3 changes: 2 additions & 1 deletion identity-did/src/diff/diff_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ mod test {
use identity_core::common::Value;

use crate::service::ServiceBuilder;
use crate::service::ServiceEndpoint;
use crate::verification::MethodBuilder;
use crate::verification::MethodData;
use crate::verification::MethodType;
Expand All @@ -340,7 +341,7 @@ mod test {
fn service(did_url: CoreDIDUrl) -> Service {
ServiceBuilder::default()
.id(did_url)
.service_endpoint(Url::parse("did:service:1234").unwrap())
.service_endpoint(ServiceEndpoint::One(Url::parse("did:service:1234").unwrap()))
.type_("test_service")
.build()
.unwrap()
Expand Down
117 changes: 105 additions & 12 deletions identity-did/src/diff/diff_service.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
// Copyright 2020-2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use serde::Deserialize;
use serde::Serialize;

use identity_core::common::Object;
use identity_core::common::Url;
use identity_core::convert::FromJson;
use identity_core::convert::ToJson;
use identity_core::diff::Diff;
use identity_core::diff::DiffString;
use identity_core::diff::Error;
use identity_core::diff::Result;
use serde::Deserialize;
use serde::Serialize;

use crate::did::CoreDIDUrl;
use crate::service::Service;
use crate::service::ServiceEndpoint;

#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct DiffService<T = Object>
Expand Down Expand Up @@ -72,7 +75,7 @@ where
.transpose()?
.unwrap_or_else(|| self.type_().to_string());

let service_endpoint: Url = diff
let service_endpoint: ServiceEndpoint = diff
.service_endpoint
.map(|value| self.service_endpoint().merge(value))
.transpose()?
Expand Down Expand Up @@ -105,9 +108,9 @@ where
.transpose()?
.ok_or_else(|| Error::convert("Missing field `service.type_`"))?;

let service_endpoint: Url = diff
let service_endpoint: ServiceEndpoint = diff
.service_endpoint
.map(Url::from_diff)
.map(ServiceEndpoint::from_diff)
.transpose()?
.ok_or_else(|| Error::convert("Missing field `service.service_endpoint`"))?;

Expand All @@ -131,10 +134,41 @@ where
}
}

impl Diff for ServiceEndpoint {
type Type = DiffString;

fn diff(&self, other: &Self) -> identity_core::diff::Result<Self::Type> {
self
.to_json()
.map_err(identity_core::diff::Error::diff)?
.diff(&other.to_string())
}

fn merge(&self, diff: Self::Type) -> identity_core::diff::Result<Self> {
self
.to_json()
.map_err(identity_core::diff::Error::diff)?
.merge(diff)
.and_then(|this| Self::from_json(&this).map_err(identity_core::diff::Error::merge))
}

fn from_diff(diff: Self::Type) -> identity_core::diff::Result<Self> {
String::from_diff(diff).and_then(|this| Self::from_json(&this).map_err(identity_core::diff::Error::convert))
}

fn into_diff(self) -> identity_core::diff::Result<Self::Type> {
self.to_json().map_err(identity_core::diff::Error::diff)?.into_diff()
}
}

#[cfg(test)]
mod test {
use super::*;
use indexmap::IndexMap;

use identity_core::common::Object;
use identity_core::common::Url;

use super::*;

fn controller() -> CoreDIDUrl {
"did:example:1234".parse().unwrap()
Expand All @@ -146,7 +180,7 @@ mod test {
properties.insert("key1".to_string(), "value1".into());
Service::builder(properties)
.id(controller)
.service_endpoint(Url::parse("did:service:1234").unwrap())
.service_endpoint(Url::parse("did:service:1234").unwrap().into())
.type_("test_service")
.build()
.unwrap()
Expand Down Expand Up @@ -183,17 +217,76 @@ mod test {
}

#[test]
fn test_service_endpoint() {
fn test_service_endpoint_one() {
let service = service();
let mut new = service.clone();
let new_url = "did:test:1234".to_string();
*new.service_endpoint_mut() = Url::parse(new_url.clone()).unwrap();
let new_url = "did:test:1234#service".to_string();
*new.service_endpoint_mut() = Url::parse(new_url).unwrap().into();

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some("\"did:test:1234#service\"".to_owned())))
);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
}

#[test]
fn test_service_endpoint_set() {
let service = service();

let mut new = service.clone();
let new_url_set = vec![
Url::parse("https://example.com/").unwrap(),
Url::parse("did:test:1234#service").unwrap(),
];
*new.service_endpoint_mut() = ServiceEndpoint::Set(new_url_set.try_into().unwrap());

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some(
r#"["https://example.com/","did:test:1234#service"]"#.to_owned()
)))
);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
}

#[test]
fn test_service_endpoint_map() {
let service = service();

let mut new = service.clone();
let mut new_url_map = IndexMap::new();
new_url_map.insert(
"origins".to_owned(),
vec![
Url::parse("https://example.com/").unwrap(),
Url::parse("did:test:1234#service").unwrap(),
]
.try_into()
.unwrap(),
);
*new.service_endpoint_mut() = ServiceEndpoint::Map(new_url_map);

let diff = service.diff(&new).unwrap();
assert!(diff.id.is_none());
assert!(diff.properties.is_none());
assert!(diff.type_.is_none());
assert_eq!(diff.service_endpoint, Some(DiffString(Some(new_url))));
assert_eq!(
diff.service_endpoint,
Some(DiffString(Some(
r#"{"origins":["https://example.com/","did:test:1234#service"]}"#.to_owned()
)))
);
let merge = service.merge(diff).unwrap();
assert_eq!(merge, new);
}
Expand Down
2 changes: 1 addition & 1 deletion identity-did/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ pub enum Error {
#[error("Invalid DID Resolution Fragment")]
InvalidDIDFragment,
#[error("Invalid DID Resolution Service")]
InvalidServiceProtocol,
InvalidResolutionService,
}
12 changes: 9 additions & 3 deletions identity-did/src/resolution/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::resolution::Resolution;
use crate::resolution::ResolverMethod;
use crate::resolution::Resource;
use crate::resolution::SecondaryResource;
use crate::service::ServiceEndpoint;
use crate::utils::OrderedSet;

/// Resolves a DID into a DID Document by using the "Read" operation of the DID method.
Expand Down Expand Up @@ -229,7 +230,12 @@ fn dereference_primary(document: CoreDocument, mut did_url: CoreDIDUrl) -> Resul
.find(|service| matches!(service.id().fragment(), Some(fragment) if fragment == target))
.map(|service| service.service_endpoint())
// 1.2. Execute the Service Endpoint Construction algorithm.
.map(|url| service_endpoint_ctor(did_url, url))
.map(|endpoint| match endpoint {
ServiceEndpoint::One(url) => service_endpoint_ctor(did_url, url),
// TODO: support service endpoint sets and map? Dereferencing spec does not address them.
ServiceEndpoint::Set(_) => Err(Error::InvalidResolutionService),
ServiceEndpoint::Map(_) => Err(Error::InvalidResolutionService),
})
.transpose()?
// 1.3. Return the output service endpoint URL.
.map(Into::into)
Expand Down Expand Up @@ -321,7 +327,7 @@ fn service_endpoint_ctor(did: CoreDIDUrl, url: &Url) -> Result<Url> {

// The input service endpoint URL MUST be an HTTP(S) URL.
if url.scheme() != "https" {
return Err(Error::InvalidServiceProtocol);
return Err(Error::InvalidResolutionService);
}

// 1. Initialize a string output service endpoint URL to the value of
Expand Down Expand Up @@ -455,7 +461,7 @@ mod test {
fn generate_service(did: &CoreDID, fragment: &str, url: &str) -> Service {
Service::builder(Default::default())
.id(did.to_url().join(fragment).unwrap())
.service_endpoint(Url::parse(url).unwrap())
.service_endpoint(Url::parse(url).unwrap().into())
.type_("LinkedDomains")
.build()
.unwrap()
Expand Down
12 changes: 7 additions & 5 deletions identity-did/src/service/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
// SPDX-License-Identifier: Apache-2.0

use identity_core::common::Object;
use identity_core::common::Url;

use crate::did::CoreDIDUrl;
use crate::error::Result;
use crate::service::Service;
use crate::service::ServiceEndpoint;

/// A `ServiceBuilder` is used to generate a customized `Service`.
#[derive(Clone, Debug, Default)]
pub struct ServiceBuilder<T = Object> {
pub(crate) id: Option<CoreDIDUrl>,
pub(crate) type_: Option<String>,
pub(crate) service_endpoint: Option<Url>,
pub(crate) service_endpoint: Option<ServiceEndpoint>,
pub(crate) properties: T,
}

Expand Down Expand Up @@ -44,7 +44,7 @@ impl<T> ServiceBuilder<T> {

/// Sets the `serviceEndpoint` value of the generated `Service`.
#[must_use]
pub fn service_endpoint(mut self, value: Url) -> Self {
pub fn service_endpoint(mut self, value: ServiceEndpoint) -> Self {
self.service_endpoint = Some(value);
self
}
Expand All @@ -57,14 +57,16 @@ impl<T> ServiceBuilder<T> {

#[cfg(test)]
mod tests {
use identity_core::common::Url;

use super::*;

#[test]
#[should_panic = "InvalidServiceId"]
fn test_missing_id() {
let _: Service = ServiceBuilder::default()
.type_("ServiceType")
.service_endpoint("https://example.com".parse().unwrap())
.service_endpoint(Url::parse("https://example.com").unwrap().into())
.build()
.unwrap();
}
Expand All @@ -74,7 +76,7 @@ mod tests {
fn test_missing_type_() {
let _: Service = ServiceBuilder::default()
.id("did:example:123".parse().unwrap())
.service_endpoint("https://example.com".parse().unwrap())
.service_endpoint(Url::parse("https://example.com").unwrap().into())
.build()
.unwrap();
}
Expand Down
Loading