From 75803cbbf28dcd41592cb0e744c0c38f8bc7c83c Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 07:55:51 -0700 Subject: [PATCH 01/44] Add basic structures for verifiable credentials --- identity_vc/Cargo.toml | 17 +++- identity_vc/bin/vc_test_suite.rs | 30 ++++++ identity_vc/src/common/context.rs | 35 +++++++ identity_vc/src/common/issuer.rs | 24 +++++ identity_vc/src/common/mod.rs | 8 ++ identity_vc/src/common/object.rs | 43 ++++++++ identity_vc/src/common/oom.rs | 109 +++++++++++++++++++++ identity_vc/src/common/uri.rs | 39 ++++++++ identity_vc/src/common/value.rs | 102 +++++++++++++++++++ identity_vc/src/credential.rs | 62 ++++++++++++ identity_vc/src/lib.rs | 26 +++-- identity_vc/src/presentation.rs | 48 +++++++++ identity_vc/src/verifiable/credential.rs | 46 +++++++++ identity_vc/src/verifiable/mod.rs | 4 + identity_vc/src/verifiable/presentation.rs | 46 +++++++++ identity_vc/tests/basic.rs | 32 ++++++ identity_vc/tests/input/example-01.json | 17 ++++ identity_vc/tests/input/example-02.json | 20 ++++ identity_vc/tests/input/example-03.json | 20 ++++ identity_vc/tests/input/example-04.json | 20 ++++ identity_vc/tests/input/example-05.json | 24 +++++ identity_vc/tests/input/example-06.json | 21 ++++ identity_vc/tests/input/example-07.json | 24 +++++ identity_vc/tests/input/example-08.json | 31 ++++++ identity_vc/tests/input/example-09.json | 24 +++++ identity_vc/tests/input/example-10.json | 30 ++++++ identity_vc/tests/input/example-11.json | 24 +++++ identity_vc/tests/input/example-12.json | 37 +++++++ identity_vc/tests/input/example-13.json | 35 +++++++ 29 files changed, 990 insertions(+), 8 deletions(-) create mode 100644 identity_vc/bin/vc_test_suite.rs create mode 100644 identity_vc/src/common/context.rs create mode 100644 identity_vc/src/common/issuer.rs create mode 100644 identity_vc/src/common/mod.rs create mode 100644 identity_vc/src/common/object.rs create mode 100644 identity_vc/src/common/oom.rs create mode 100644 identity_vc/src/common/uri.rs create mode 100644 identity_vc/src/common/value.rs create mode 100644 identity_vc/src/credential.rs create mode 100644 identity_vc/src/presentation.rs create mode 100644 identity_vc/src/verifiable/credential.rs create mode 100644 identity_vc/src/verifiable/mod.rs create mode 100644 identity_vc/src/verifiable/presentation.rs create mode 100644 identity_vc/tests/basic.rs create mode 100644 identity_vc/tests/input/example-01.json create mode 100644 identity_vc/tests/input/example-02.json create mode 100644 identity_vc/tests/input/example-03.json create mode 100644 identity_vc/tests/input/example-04.json create mode 100644 identity_vc/tests/input/example-05.json create mode 100644 identity_vc/tests/input/example-06.json create mode 100644 identity_vc/tests/input/example-07.json create mode 100644 identity_vc/tests/input/example-08.json create mode 100644 identity_vc/tests/input/example-09.json create mode 100644 identity_vc/tests/input/example-10.json create mode 100644 identity_vc/tests/input/example-11.json create mode 100644 identity_vc/tests/input/example-12.json create mode 100644 identity_vc/tests/input/example-13.json diff --git a/identity_vc/Cargo.toml b/identity_vc/Cargo.toml index ffcce5f5b3..dd9c5f5c61 100644 --- a/identity_vc/Cargo.toml +++ b/identity_vc/Cargo.toml @@ -3,13 +3,26 @@ name = "identity_vc" version = "0.1.0" authors = ["IOTA Identity"] edition = "2018" -description = "A library for Verifiable Credentials" +description = "An implementation of the Verifiable Credentials (VC) standard" readme = "../README.md" repository = "https://github.com/iotaledger/identity.rs" license = "Apache-2.0" keywords = ["iota", "tangle", "identity"] homepage = "https://www.iota.org" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "vc_test_suite" +path = "bin/vc_test_suite.rs" [dependencies] +# error handling +thiserror = "1.0" +anyhow = "1.0" + +# serialization +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" +serde_cbor = "0.11" + +# timestamps +chrono = { version = "0.4", features = ["serde"] } diff --git a/identity_vc/bin/vc_test_suite.rs b/identity_vc/bin/vc_test_suite.rs new file mode 100644 index 0000000000..01117eaca8 --- /dev/null +++ b/identity_vc/bin/vc_test_suite.rs @@ -0,0 +1,30 @@ +use anyhow::Result; +use identity_vc::prelude::*; +use serde_json::{from_reader, to_string}; +use std::{env::args, fs::File, path::Path}; + +fn main() -> Result<()> { + let args: Vec = args().collect(); + + match args[1].as_str() { + "test-credential" => { + let path: &Path = Path::new(&args[2]); + let file: File = File::open(path)?; + let data: VerifiableCredential = from_reader(file)?; + + println!("{}", to_string(&data)?); + } + "test-presentation" => { + let path: &Path = Path::new(&args[2]); + let file: File = File::open(path)?; + let data: VerifiablePresentation = from_reader(file)?; + + println!("{}", to_string(&data)?); + } + test => { + panic!("Unknown Test: {:?}", test); + } + } + + Ok(()) +} diff --git a/identity_vc/src/common/context.rs b/identity_vc/src/common/context.rs new file mode 100644 index 0000000000..33b1c5f79b --- /dev/null +++ b/identity_vc/src/common/context.rs @@ -0,0 +1,35 @@ +use std::fmt; + +use crate::common::{Object, URI}; + +/// A reference to a JSON-LD context +#[derive(Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Context { + URI(URI), + OBJ(Object), +} + +impl fmt::Debug for Context { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::URI(inner) => fmt::Debug::fmt(inner, f), + Self::OBJ(inner) => fmt::Debug::fmt(inner, f), + } + } +} + +impl From for Context +where + T: Into, +{ + fn from(other: T) -> Self { + Self::URI(other.into()) + } +} + +impl From for Context { + fn from(other: Object) -> Self { + Self::OBJ(other) + } +} diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs new file mode 100644 index 0000000000..b6bbe5b21f --- /dev/null +++ b/identity_vc/src/common/issuer.rs @@ -0,0 +1,24 @@ +use crate::common::{Object, URI}; + +/// TODO: +/// - Deserialize single URI into object-style layout +/// - Replace Enum with plain struct +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Issuer { + URI(URI), + OBJ { + id: URI, + #[serde(flatten)] + object: Object, + }, +} + +impl Issuer { + pub fn uri(&self) -> &URI { + match self { + Self::URI(uri) => uri, + Self::OBJ { id, .. } => id, + } + } +} diff --git a/identity_vc/src/common/mod.rs b/identity_vc/src/common/mod.rs new file mode 100644 index 0000000000..6699fe58ea --- /dev/null +++ b/identity_vc/src/common/mod.rs @@ -0,0 +1,8 @@ +mod context; +mod issuer; +mod object; +mod oom; +mod uri; +mod value; + +pub use self::{context::*, issuer::*, object::*, oom::*, uri::*, value::*}; diff --git a/identity_vc/src/common/object.rs b/identity_vc/src/common/object.rs new file mode 100644 index 0000000000..56abd007d6 --- /dev/null +++ b/identity_vc/src/common/object.rs @@ -0,0 +1,43 @@ +use std::{collections::HashMap, fmt, ops::Deref}; + +use crate::common::Value; + +type Inner = HashMap; + +// An immutable String -> Value `HashMap` +#[derive(Clone, Default, PartialEq, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Object(Inner); + +impl Object { + pub fn into_inner(self) -> Inner { + self.0 + } +} + +impl fmt::Debug for Object { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl Deref for Object { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for Object { + fn from(other: Inner) -> Self { + Self(other) + } +} + +impl From for Inner { + fn from(other: Object) -> Self { + other.into_inner() + } +} diff --git a/identity_vc/src/common/oom.rs b/identity_vc/src/common/oom.rs new file mode 100644 index 0000000000..ef6db486f5 --- /dev/null +++ b/identity_vc/src/common/oom.rs @@ -0,0 +1,109 @@ +use std::fmt; + +/// A generic container that stores one or many values of a given type. +#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[serde(untagged)] +pub enum OneOrMany { + One(T), + Many(Vec), +} + +impl OneOrMany { + /// Returns the number of elements in the collection + pub fn len(&self) -> usize { + match self { + Self::One(_) => 1, + Self::Many(inner) => inner.len(), + } + } + + /// Returns `true` if the collection is empty + pub fn is_empty(&self) -> bool { + match self { + Self::One(_) => false, + Self::Many(inner) => inner.is_empty(), + } + } + + /// Returns a reference to the element at the given index. + pub fn get(&self, index: usize) -> Option<&T> { + match self { + Self::One(inner) if index == 0 => Some(inner), + Self::One(_) => None, + Self::Many(inner) => inner.get(index), + } + } + + /// Returns `true` if the given value is represented in the collection. + pub fn contains(&self, value: &T) -> bool + where + T: PartialEq, + { + match self { + Self::One(inner) => inner == value, + Self::Many(inner) => inner.contains(value), + } + } + + pub fn iter(&self) -> impl Iterator + '_ { + OneOrManyIter::new(self) + } + + /// Consumes the `OneOrMany`, returning the contents as a `Vec`. + pub fn into_vec(self) -> Vec { + match self { + Self::One(inner) => vec![inner], + Self::Many(inner) => inner, + } + } +} + +impl fmt::Debug for OneOrMany +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::One(inner) => fmt::Debug::fmt(inner, f), + Self::Many(inner) => fmt::Debug::fmt(inner, f), + } + } +} + +impl From for OneOrMany { + fn from(other: T) -> Self { + Self::One(other) + } +} + +impl From> for OneOrMany { + fn from(other: Vec) -> Self { + Self::Many(other) + } +} + +impl From> for Vec { + fn from(other: OneOrMany) -> Self { + other.into_vec() + } +} + +struct OneOrManyIter<'a, T> { + inner: &'a OneOrMany, + index: usize, +} + +impl<'a, T> OneOrManyIter<'a, T> { + pub fn new(inner: &'a OneOrMany) -> Self { + Self { inner, index: 0 } + } +} + +impl<'a, T> Iterator for OneOrManyIter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option { + self.index += 1; + self.inner.get(self.index - 1) + } +} diff --git a/identity_vc/src/common/uri.rs b/identity_vc/src/common/uri.rs new file mode 100644 index 0000000000..5097421f14 --- /dev/null +++ b/identity_vc/src/common/uri.rs @@ -0,0 +1,39 @@ +use std::{fmt, ops::Deref}; + +/// A "spec-compliant" URI - possibly a DID. +/// +/// TODO: Parse/validate and represent this an *ACTUAL* URI. +/// TODO: impl From for URI +#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct URI(String); + +impl fmt::Debug for URI { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "URI({:?})", self.0) + } +} + +impl Deref for URI { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for URI +where + T: Into, +{ + fn from(other: T) -> Self { + Self(other.into()) + } +} + +impl PartialEq for URI { + fn eq(&self, other: &str) -> bool { + self.0.eq(other) + } +} diff --git a/identity_vc/src/common/value.rs b/identity_vc/src/common/value.rs new file mode 100644 index 0000000000..64b9f8ce6e --- /dev/null +++ b/identity_vc/src/common/value.rs @@ -0,0 +1,102 @@ +use std::fmt; + +use crate::common::Object; + +#[derive(Clone, PartialEq, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Value { + Null, + Boolean(bool), + Number(Number), + String(String), + Array(Vec), + Object(Object), +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Null => write!(f, "Null"), + Self::Boolean(inner) => fmt::Debug::fmt(inner, f), + Self::Number(inner) => fmt::Debug::fmt(inner, f), + Self::String(inner) => fmt::Debug::fmt(inner, f), + Self::Array(inner) => fmt::Debug::fmt(inner, f), + Self::Object(inner) => fmt::Debug::fmt(inner, f), + } + } +} + +impl From<()> for Value { + fn from(_: ()) -> Self { + Self::Null + } +} + +impl From for Value { + fn from(other: bool) -> Self { + Self::Boolean(other) + } +} + +impl From for Value { + fn from(other: u64) -> Self { + Self::Number(Number::UInt(other)) + } +} + +impl From for Value { + fn from(other: i64) -> Self { + Self::Number(Number::SInt(other)) + } +} + +impl From for Value { + fn from(other: f64) -> Self { + Self::Number(Number::Float(other)) + } +} + +impl From<&'_ str> for Value { + fn from(other: &'_ str) -> Self { + Self::String(other.into()) + } +} + +impl From for Value { + fn from(other: String) -> Self { + Self::String(other) + } +} + +impl From> for Value +where + T: Into, +{ + fn from(other: Vec) -> Self { + Self::Array(other.into_iter().map(Into::into).collect()) + } +} + +impl From for Value { + fn from(other: Object) -> Self { + Self::Object(other) + } +} + +#[derive(Clone, Copy, PartialEq, PartialOrd, Deserialize, Serialize)] +#[serde(untagged)] +pub enum Number { + UInt(u64), + SInt(i64), + Float(f64), +} + +impl fmt::Debug for Number { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::UInt(inner) => fmt::Debug::fmt(inner, f), + Self::SInt(inner) => fmt::Debug::fmt(inner, f), + Self::Float(inner) => fmt::Debug::fmt(inner, f), + } + } +} diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs new file mode 100644 index 0000000000..1d11687de3 --- /dev/null +++ b/identity_vc/src/credential.rs @@ -0,0 +1,62 @@ +use anyhow::Result; +use chrono::DateTime; + +use crate::common::{Context, Issuer, Object, OneOrMany, URI}; + +/// A `Credential` represents a set of claims describing an entity. +/// +/// `Credential`s can be combined with `Proof`s to create `VerifiableCredential`s. +#[derive(Debug, Deserialize, Serialize)] +pub struct Credential { + /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. + /// + /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of credential. + /// + /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly + /// passed as non-`URI` strings and expected to be processed with JSON-LD. + /// We're using a `String` here since we don't currently use JSON-LD and + /// don't have any immediate plans to do so. + #[serde(rename = "type")] + pub types: OneOrMany, + /// One or more `Object`s representing the credential subject(s). + #[serde(rename = "credentialSubject")] + pub credential_subject: OneOrMany, + /// A reference to the issuer of the credential. + pub issuer: Issuer, + /// The date and time the credential becomes valid. + #[serde(rename = "issuanceDate")] + pub issuance_date: String, + /// The date and time the credential is no longer considered valid. + #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + /// TODO + #[serde(rename = "credentialStatus", skip_serializing_if = "Option::is_none")] + pub credential_status: Option>, + /// TODO + #[serde(rename = "credentialSchema", skip_serializing_if = "Option::is_none")] + pub credential_schema: Option>, + /// TODO + #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] + pub refresh_service: Option>, + /// The terms of use issued by the credential issuer + #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] + pub terms_of_use: Option>, + /// TODO + #[serde(skip_serializing_if = "Option::is_none")] + pub evidence: Option>, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: Object, +} + +impl Credential { + pub const BASE_CONTEXT: &'static str = "https://www.w3.org/2018/credentials/v1"; + + pub const BASE_TYPE: &'static str = "VerifiableCredential"; +} diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 31e1bb209f..3c68ac6eaa 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -1,7 +1,21 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } +#[macro_use] +extern crate anyhow; + +#[macro_use] +extern crate serde; + +pub mod common; +pub mod credential; +pub mod presentation; +pub mod verifiable; + +pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; + +pub mod prelude { + pub use crate::{ + common::{Context, Issuer, Number, Object, OneOrMany, Value, URI}, + credential::Credential, + presentation::Presentation, + verifiable::{VerifiableCredential, VerifiablePresentation}, + }; } diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs new file mode 100644 index 0000000000..ab384011f9 --- /dev/null +++ b/identity_vc/src/presentation.rs @@ -0,0 +1,48 @@ +use anyhow::Result; + +use crate::{ + common::{Context, Object, OneOrMany, URI}, + verifiable::VerifiableCredential, +}; + +/// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. +/// +/// `Presentation`s can be combined with `Proof`s to create `VerifiablePresentation`s. +#[derive(Debug, Deserialize, Serialize)] +pub struct Presentation { + /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. + /// + /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of presentation. + /// + /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly + /// passed as non-`URI` strings and expected to be processed with JSON-LD. + /// We're using a `String` here since we don't currently use JSON-LD and + /// don't have any immediate plans to do so. + #[serde(rename = "type")] + pub types: OneOrMany, + /// TODO + #[serde(rename = "verifiableCredential")] + pub verifiable_credential: OneOrMany, + /// The entity that generated the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, + /// TODO + #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] + pub refresh_service: Option>, + /// The terms of use issued by the presentation holder + #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] + pub terms_of_use: Option>, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: Object, +} + +impl Presentation { + pub const BASE_TYPE: &'static str = "VerifiablePresentation"; +} diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs new file mode 100644 index 0000000000..856d8a2705 --- /dev/null +++ b/identity_vc/src/verifiable/credential.rs @@ -0,0 +1,46 @@ +use std::ops::Deref; + +use crate::{ + common::{Object, OneOrMany}, + credential::Credential, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct VerifiableCredential { + #[serde(flatten)] + credential: Credential, + proof: OneOrMany, +} + +impl VerifiableCredential { + pub fn new(credential: Credential, proof: impl Into>) -> Self { + Self { + credential, + proof: proof.into(), + } + } + + pub fn credential(&self) -> &Credential { + &self.credential + } + + pub fn credential_mut(&mut self) -> &mut Credential { + &mut self.credential + } + + pub fn proof(&self) -> &OneOrMany { + &self.proof + } + + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } +} + +impl Deref for VerifiableCredential { + type Target = Credential; + + fn deref(&self) -> &Self::Target { + &self.credential + } +} diff --git a/identity_vc/src/verifiable/mod.rs b/identity_vc/src/verifiable/mod.rs new file mode 100644 index 0000000000..6368af51f9 --- /dev/null +++ b/identity_vc/src/verifiable/mod.rs @@ -0,0 +1,4 @@ +mod credential; +mod presentation; + +pub use self::{credential::*, presentation::*}; diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs new file mode 100644 index 0000000000..4037fc0318 --- /dev/null +++ b/identity_vc/src/verifiable/presentation.rs @@ -0,0 +1,46 @@ +use std::ops::Deref; + +use crate::{ + common::{Object, OneOrMany}, + presentation::Presentation, +}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct VerifiablePresentation { + #[serde(flatten)] + presentation: Presentation, + proof: OneOrMany, +} + +impl VerifiablePresentation { + pub fn new(presentation: Presentation, proof: impl Into>) -> Self { + Self { + presentation, + proof: proof.into(), + } + } + + pub fn presentation(&self) -> &Presentation { + &self.presentation + } + + pub fn presentation_mut(&mut self) -> &mut Presentation { + &mut self.presentation + } + + pub fn proof(&self) -> &OneOrMany { + &self.proof + } + + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } +} + +impl Deref for VerifiablePresentation { + type Target = Presentation; + + fn deref(&self) -> &Self::Target { + &self.presentation + } +} diff --git a/identity_vc/tests/basic.rs b/identity_vc/tests/basic.rs new file mode 100644 index 0000000000..90ec3a9db5 --- /dev/null +++ b/identity_vc/tests/basic.rs @@ -0,0 +1,32 @@ +use identity_vc::prelude::*; +use serde_json::from_str; + +fn try_credential(data: &(impl AsRef + ?Sized)) { + dbg!(from_str::(data.as_ref()).unwrap()); +} + +fn try_presentation(data: &(impl AsRef + ?Sized)) { + dbg!(from_str::(data.as_ref()).unwrap()); +} + +#[test] +fn test_parse_credential() { + try_credential(include_str!("input/example-01.json")); + try_credential(include_str!("input/example-02.json")); + try_credential(include_str!("input/example-03.json")); + try_credential(include_str!("input/example-04.json")); + try_credential(include_str!("input/example-05.json")); + try_credential(include_str!("input/example-06.json")); + try_credential(include_str!("input/example-07.json")); + + try_credential(include_str!("input/example-09.json")); + try_credential(include_str!("input/example-10.json")); + try_credential(include_str!("input/example-11.json")); + try_credential(include_str!("input/example-12.json")); + try_credential(include_str!("input/example-13.json")); +} + +#[test] +fn test_parse_presentation() { + try_presentation(include_str!("input/example-08.json")); +} diff --git a/identity_vc/tests/input/example-01.json b/identity_vc/tests/input/example-01.json new file mode 100644 index 0000000000..d708f394ad --- /dev/null +++ b/identity_vc/tests/input/example-01.json @@ -0,0 +1,17 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/58473", + "type": ["VerifiableCredential", "AlumniCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "alumniOf": "Example University" + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-02.json b/identity_vc/tests/input/example-02.json new file mode 100644 index 0000000000..4f3ce82ef9 --- /dev/null +++ b/identity_vc/tests/input/example-02.json @@ -0,0 +1,20 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-03.json b/identity_vc/tests/input/example-03.json new file mode 100644 index 0000000000..4f3ce82ef9 --- /dev/null +++ b/identity_vc/tests/input/example-03.json @@ -0,0 +1,20 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-04.json b/identity_vc/tests/input/example-04.json new file mode 100644 index 0000000000..4f3ce82ef9 --- /dev/null +++ b/identity_vc/tests/input/example-04.json @@ -0,0 +1,20 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-05.json b/identity_vc/tests/input/example-05.json new file mode 100644 index 0000000000..84ab4498cb --- /dev/null +++ b/identity_vc/tests/input/example-05.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.gov/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu", + "issuanceDate": "2017-06-18T21:19:00Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": { + "type": "RsaSignature2018", + "created": "2017-06-18T21:19:10Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "https://example.com/jdoe/keys/1", + "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..TCYt5XsITJX1CxPCT8yAV-TVkIEq_PbChOMqsLfRoPsnsgw5WEuts01mq-pQy7UJiN5mgRxD-WUcX16dUEMGlv50aqzpqh4Qktb3rk-BuQy72IFLOqV0G_zS245-kronKb78cPN25DGlcTwLtjPAYuNzVBAh4vGHSrQyHUdBBPM" + } +} diff --git a/identity_vc/tests/input/example-06.json b/identity_vc/tests/input/example-06.json new file mode 100644 index 0000000000..cd6e39a7d4 --- /dev/null +++ b/identity_vc/tests/input/example-06.json @@ -0,0 +1,21 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "expirationDate": "2020-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-07.json b/identity_vc/tests/input/example-07.json new file mode 100644 index 0000000000..1a361816e4 --- /dev/null +++ b/identity_vc/tests/input/example-07.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "credentialStatus": { + "id": "https://example.edu/status/24", + "type": "CredentialStatusList2017" + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-08.json b/identity_vc/tests/input/example-08.json new file mode 100644 index 0000000000..333da44d17 --- /dev/null +++ b/identity_vc/tests/input/example-08.json @@ -0,0 +1,31 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "urn:uuid:3978344f-8596-4c3a-a978-8fcaba3903c5", + "type": ["VerifiablePresentation", "CredentialManagerPresentation"], + "verifiableCredential": [{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "proof": [{ + "type": "RsaSignature2018" + }] + }], + "proof": [{ + "type": "RsaSignature2018" + }] +} diff --git a/identity_vc/tests/input/example-09.json b/identity_vc/tests/input/example-09.json new file mode 100644 index 0000000000..7ec3e8b0c1 --- /dev/null +++ b/identity_vc/tests/input/example-09.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "credentialSchema": { + "id": "https://example.org/examples/degree.json", + "type": "JsonSchemaValidator2018" + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-10.json b/identity_vc/tests/input/example-10.json new file mode 100644 index 0000000000..5280c353eb --- /dev/null +++ b/identity_vc/tests/input/example-10.json @@ -0,0 +1,30 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "credentialSchema": [ + { + "id": "https://example.org/examples/degree.json", + "type": "JsonSchemaValidator2018" + }, + { + "id": "https://example.org/examples/degree.zkp", + "type": "ZkpExampleSchema2018" + } + ], + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-11.json b/identity_vc/tests/input/example-11.json new file mode 100644 index 0000000000..382fda9f5b --- /dev/null +++ b/identity_vc/tests/input/example-11.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "refreshService": { + "id": "https://example.edu/refresh/3732", + "type": "ManualRefreshService2018" + }, + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-12.json b/identity_vc/tests/input/example-12.json new file mode 100644 index 0000000000..11dbd92484 --- /dev/null +++ b/identity_vc/tests/input/example-12.json @@ -0,0 +1,37 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "termsOfUse": [ + { + "type": "IssuerPolicy", + "id": "http://example.com/policies/credential/4", + "profile": "http://example.com/profiles/credential", + "prohibition": [ + { + "assigner": "https://example.edu/issuers/14", + "assignee": "AllVerifiers", + "target": "http://example.edu/credentials/3732", + "action": [ + "Archival" + ] + } + ] + } + ], + "proof": { + "type": "RsaSignature2018" + } +} diff --git a/identity_vc/tests/input/example-13.json b/identity_vc/tests/input/example-13.json new file mode 100644 index 0000000000..5a33e926e1 --- /dev/null +++ b/identity_vc/tests/input/example-13.json @@ -0,0 +1,35 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1" + ], + "id": "http://example.edu/credentials/3732", + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "issuer": "https://example.edu/issuers/14", + "issuanceDate": "2010-01-01T19:23:24Z", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "degree": { + "type": "BachelorDegree", + "name": "Bachelor of Science in Mechanical Engineering" + } + }, + "evidence": [{ + "id": "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192d4231", + "type": ["DocumentVerification"], + "verifier": "https://example.edu/issuers/14", + "evidenceDocument": "DriversLicense", + "subjectPresence": "Physical", + "documentPresence": "Physical" + },{ + "id": "https://example.edu/evidence/f2aeec97-fc0d-42bf-8ca7-0548192dxyzab", + "type": ["SupportingActivity"], + "verifier": "https://example.edu/issuers/14", + "evidenceDocument": "Fluid Dynamics Focus", + "subjectPresence": "Digital", + "documentPresence": "Digital" + }], + "proof": { + "type": "RsaSignature2018" + } +} From ac968fe3b597092a507465efa72b66b53a081a7b Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 12:41:28 -0700 Subject: [PATCH 02/44] Basic validation of credential structure --- identity_vc/bin/vc_test_suite.rs | 4 ++ identity_vc/src/credential.rs | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/identity_vc/bin/vc_test_suite.rs b/identity_vc/bin/vc_test_suite.rs index 01117eaca8..37b306143c 100644 --- a/identity_vc/bin/vc_test_suite.rs +++ b/identity_vc/bin/vc_test_suite.rs @@ -12,6 +12,8 @@ fn main() -> Result<()> { let file: File = File::open(path)?; let data: VerifiableCredential = from_reader(file)?; + data.validate()?; + println!("{}", to_string(&data)?); } "test-presentation" => { @@ -19,6 +21,8 @@ fn main() -> Result<()> { let file: File = File::open(path)?; let data: VerifiablePresentation = from_reader(file)?; + data.validate()?; + println!("{}", to_string(&data)?); } test => { diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 1d11687de3..c5956b1013 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -59,4 +59,82 @@ impl Credential { pub const BASE_CONTEXT: &'static str = "https://www.w3.org/2018/credentials/v1"; pub const BASE_TYPE: &'static str = "VerifiableCredential"; + + pub fn validate(&self) -> Result<()> { + validate_context(&self.context)?; + + if let Some(ref id) = self.id { + validate_uri(id)?; + } + + validate_types(&self.types, Self::BASE_TYPE)?; + validate_subject(&self.credential_subject)?; + validate_uri(self.issuer.uri())?; + validate_timestamp("issuance date", &self.issuance_date)?; + + if let Some(ref timestamp) = self.expiration_date { + validate_timestamp("expiration date", timestamp)?; + } + + Ok(()) + } +} + +pub fn validate_context(context: &OneOrMany) -> Result<()> { + // Credentials/Presentations MUST have at least one context item + ensure!(!context.is_empty(), "Not enough context items"); + + // The first item MUST be a URI with the value of the base context + match context.get(0) { + Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), + Some(_) => bail!("Invalid base context"), + None => unreachable!(), + } +} + +pub fn validate_types(types: &OneOrMany, base: &'static str) -> Result<()> { + // Credentials/Presentations MUST have at least one type + ensure!(!types.is_empty(), "Not enough types"); + + // The set of types MUST contain the base type + ensure!(types.contains(&base.into()), "Missing base type"); + + Ok(()) +} + +pub fn validate_subject(subjects: &OneOrMany) -> Result<()> { + // A credential MUST have at least one subject + ensure!(!subjects.is_empty(), "Not enough subjects"); + + // Each subject is defined as one or more properties - no empty objects + for subject in subjects.iter() { + ensure!(!subject.is_empty(), "Invalid credential subject (empty)"); + } + + Ok(()) +} + +pub fn validate_uri(uri: &URI) -> Result<()> { + const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; + + // TODO: Proper URI validation + ensure!( + KNOWN.iter().any(|scheme| uri.starts_with(scheme)), + "Invalid URI `{}`", + uri.as_str(), + ); + + Ok(()) +} + +// Validate the timestamp format according to RFC 3339 +// +// Ref: https://tools.ietf.org/html/rfc3339 +pub fn validate_timestamp(name: &'static str, timestamp: &str) -> Result<()> { + ensure!(!timestamp.is_empty(), "Invalid {} (empty)", name); + + match DateTime::parse_from_rfc3339(timestamp) { + Ok(_) => Ok(()), + Err(error) => bail!("Invalid {} ({})", name, error), + } } From a6d19fd59924b1a1885b22ca229e2c20fe54ad9a Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 12:47:29 -0700 Subject: [PATCH 03/44] Basic presentation validations --- identity_vc/src/credential.rs | 17 ++++++++++++++++- identity_vc/src/presentation.rs | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index c5956b1013..89049a0488 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,7 +1,10 @@ use anyhow::Result; use chrono::DateTime; -use crate::common::{Context, Issuer, Object, OneOrMany, URI}; +use crate::{ + common::{Context, Issuer, Object, OneOrMany, URI}, + verifiable::VerifiableCredential, +}; /// A `Credential` represents a set of claims describing an entity. /// @@ -114,6 +117,18 @@ pub fn validate_subject(subjects: &OneOrMany) -> Result<()> { Ok(()) } +pub fn validate_credential(credentials: &OneOrMany) -> Result<()> { + // Presentations MUST have an least one verifiable credential + ensure!(!credentials.is_empty(), "Not enough credentials"); + + // All verifiable credentials MUST be valid (structurally) + for credential in credentials.iter() { + credential.validate()?; + } + + Ok(()) +} + pub fn validate_uri(uri: &URI) -> Result<()> { const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index ab384011f9..35efe3f38a 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -2,6 +2,7 @@ use anyhow::Result; use crate::{ common::{Context, Object, OneOrMany, URI}, + credential::{validate_context, validate_credential, validate_types, validate_uri}, verifiable::VerifiableCredential, }; @@ -45,4 +46,21 @@ pub struct Presentation { impl Presentation { pub const BASE_TYPE: &'static str = "VerifiablePresentation"; + + pub fn validate(&self) -> Result<()> { + validate_context(&self.context)?; + + if let Some(ref id) = self.id { + validate_uri(id)?; + } + + validate_types(&self.types, Self::BASE_TYPE)?; + validate_credential(&self.verifiable_credential)?; + + if let Some(ref holder) = self.holder { + validate_uri(holder)?; + } + + Ok(()) + } } From 4d8c14eaa184b8cf433cf70b3f555191b39c7d19 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 12:55:48 -0700 Subject: [PATCH 04/44] Add a builder for easier creation of credentials --- identity_vc/src/common/issuer.rs | 9 ++ identity_vc/src/credential.rs | 170 +++++++++++++++++++++++++++++++ identity_vc/src/lib.rs | 2 +- identity_vc/tests/basic.rs | 122 +++++++++++++++++++++- 4 files changed, 300 insertions(+), 3 deletions(-) diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs index b6bbe5b21f..34cd81234e 100644 --- a/identity_vc/src/common/issuer.rs +++ b/identity_vc/src/common/issuer.rs @@ -22,3 +22,12 @@ impl Issuer { } } } + +impl From for Issuer +where + T: Into, +{ + fn from(other: T) -> Self { + Self::URI(other.into()) + } +} diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 89049a0488..778400792a 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -153,3 +153,173 @@ pub fn validate_timestamp(name: &'static str, timestamp: &str) -> Result<()> { Err(error) => bail!("Invalid {} ({})", name, error), } } + +// ============================================================================= +// Credential Builder +// ============================================================================= + +/// A convenience for constructing a `Credential` or `VerifiableCredential` +/// from dynamic data. +/// +/// NOTE: Base context and type are automatically included. +#[derive(Clone, Debug)] +pub struct CredentialBuilder { + context: Vec, + id: Option, + types: Vec, + credential_subject: Vec, + issuer: Issuer, + issuance_date: String, + expiration_date: Option, + credential_status: Vec, + credential_schema: Vec, + refresh_service: Vec, + terms_of_use: Vec, + evidence: Vec, + properties: Object, +} + +impl CredentialBuilder { + pub fn new(issuer: impl Into) -> Self { + Self { + context: vec![Credential::BASE_CONTEXT.into()], + id: None, + types: vec![Credential::BASE_TYPE.into()], + credential_subject: Vec::new(), + issuer: issuer.into(), + issuance_date: String::new(), + expiration_date: None, + credential_status: Vec::new(), + credential_schema: Vec::new(), + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + evidence: Vec::new(), + properties: Default::default(), + } + } + + pub fn context(mut self, value: impl Into) -> Self { + let value: Context = value.into(); + + if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + self.context.push(value); + } + + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn type_(mut self, value: impl Into) -> Self { + let value: String = value.into(); + + if value != Credential::BASE_TYPE { + self.types.push(value); + } + + self + } + + pub fn subject(mut self, value: impl Into) -> Self { + self.credential_subject.push(value.into()); + self + } + + pub fn issuer(mut self, value: impl Into) -> Self { + self.issuer = value.into(); + self + } + + pub fn issuance_date(mut self, value: impl Into) -> Self { + self.issuance_date = value.into(); + self + } + + pub fn expiration_date(mut self, value: impl Into) -> Self { + self.expiration_date = Some(value.into()); + self + } + + pub fn credential_status(mut self, value: impl Into) -> Self { + self.credential_status.push(value.into()); + self + } + + pub fn credential_schema(mut self, value: impl Into) -> Self { + self.credential_schema.push(value.into()); + self + } + + pub fn refresh_service(mut self, value: impl Into) -> Self { + self.refresh_service.push(value.into()); + self + } + + pub fn terms_of_use(mut self, value: impl Into) -> Self { + self.terms_of_use.push(value.into()); + self + } + + pub fn evidence(mut self, value: impl Into) -> Self { + self.evidence.push(value.into()); + self + } + + pub fn properties(mut self, value: impl Into) -> Self { + self.properties = value.into(); + self + } + + /// Consumes the `CredentialBuilder`, returning a valid `Credential` + pub fn build(self) -> Result { + let mut credential: Credential = Credential { + context: self.context.into(), + id: self.id, + types: self.types.into(), + credential_subject: self.credential_subject.into(), + issuer: self.issuer, + issuance_date: self.issuance_date, + expiration_date: self.expiration_date, + credential_status: None, + credential_schema: None, + refresh_service: None, + terms_of_use: None, + evidence: None, + properties: self.properties, + }; + + if !self.credential_status.is_empty() { + credential.credential_status = Some(self.credential_status.into()); + } + + if !self.credential_schema.is_empty() { + credential.credential_schema = Some(self.credential_schema.into()); + } + + if !self.refresh_service.is_empty() { + credential.refresh_service = Some(self.refresh_service.into()); + } + + if !self.terms_of_use.is_empty() { + credential.terms_of_use = Some(self.terms_of_use.into()); + } + + if !self.evidence.is_empty() { + credential.evidence = Some(self.evidence.into()); + } + + credential.validate()?; + + Ok(credential) + } + + /// Consumes the `CredentialBuilder`, returning a valid `VerifiableCredential` + pub fn build_verifiable(self, proof: impl Into>) -> Result { + self + .build() + .map(|credential| VerifiableCredential::new(credential, proof)) + } +} diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 3c68ac6eaa..e6d0d68931 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -14,7 +14,7 @@ pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; pub mod prelude { pub use crate::{ common::{Context, Issuer, Number, Object, OneOrMany, Value, URI}, - credential::Credential, + credential::{Credential, CredentialBuilder}, presentation::Presentation, verifiable::{VerifiableCredential, VerifiablePresentation}, }; diff --git a/identity_vc/tests/basic.rs b/identity_vc/tests/basic.rs index 90ec3a9db5..06f75610cc 100644 --- a/identity_vc/tests/basic.rs +++ b/identity_vc/tests/basic.rs @@ -1,12 +1,44 @@ use identity_vc::prelude::*; use serde_json::from_str; +macro_rules! object { + () => { + ::identity_vc::common::Object::default() + }; + ($($key:ident : $value:expr),* $(,)*) => { + { + let mut object = ::std::collections::HashMap::new(); + + $( + object.insert( + stringify!($key).to_string(), + ::identity_vc::common::Value::from($value), + ); + )* + + ::identity_vc::common::Object::from(object) + } + }; +} + +macro_rules! assert_matches { + ($($tt:tt)*) => { + assert!(matches!($($tt)*)) + }; +} + fn try_credential(data: &(impl AsRef + ?Sized)) { - dbg!(from_str::(data.as_ref()).unwrap()); + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() } fn try_presentation(data: &(impl AsRef + ?Sized)) { - dbg!(from_str::(data.as_ref()).unwrap()); + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() } #[test] @@ -30,3 +62,89 @@ fn test_parse_credential() { fn test_parse_presentation() { try_presentation(include_str!("input/example-08.json")); } + +#[test] +fn test_credential_builder() { + let credential: Credential = CredentialBuilder::new("did:example:i55u3r") + .context("https://www.w3.org/2018/credentials/examples/v1") + .context(object!(id: "did:context:1234", type: "CustomContext2020")) + .id("did:example:1234567890") + .type_("RelationshipCredential") + .subject(object!(id: "did:iota:alice", spouse: "did:iota:bob")) + .subject(object!(id: "did:iota:bob", spouse: "did:iota:alice")) + .issuance_date("2010-01-01T19:23:24Z") + .expiration_date("2020-01-01T19:23:24Z") + .build() + .unwrap(); + + assert_eq!(credential.issuer.uri(), "did:example:i55u3r"); + + assert_eq!(credential.context.len(), 3); + assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); + + assert_eq!(credential.id, Some("did:example:1234567890".into())); + + assert_eq!(credential.types.len(), 2); + assert_eq!(credential.types.get(0).unwrap(), Credential::BASE_TYPE); + + assert_eq!(credential.credential_subject.len(), 2); + + assert_eq!( + credential.credential_subject.get(0).unwrap()["id"], + "did:iota:alice".into() + ); + + assert_eq!( + credential.credential_subject.get(1).unwrap()["id"], + "did:iota:bob".into() + ); +} + +#[test] +#[should_panic = "Not enough subjects"] +fn test_validate_credential_subject() { + CredentialBuilder::new("did:issuer").build().unwrap(); +} + +#[test] +#[should_panic = "Invalid credential subject (empty)"] +fn test_validate_credential_subject_empty() { + CredentialBuilder::new("did:issuer").subject(object!()).build().unwrap(); +} + +#[test] +#[should_panic = "Invalid URI `foo`"] +fn test_validate_issuer_bad_uri_1() { + CredentialBuilder::new("foo") + .subject(object!(id: "did:sub")) + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Invalid URI `did123`"] +fn test_validate_issuer_bad_uri_2() { + CredentialBuilder::new("did123") + .subject(object!(id: "did:sub")) + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Invalid issuance date (empty)"] +fn test_validate_issuance_date_1() { + CredentialBuilder::new("did:issuer") + .subject(object!(id: "did:sub")) + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Invalid issuance date (premature end of input)"] +fn test_validate_issuance_date_2() { + CredentialBuilder::new("did:issuer") + .subject(object!(id: "did:sub")) + .issuance_date("woo") + .build() + .unwrap(); +} From 33fd7170beef1d96a4c6a4f0fcaee5dece9ee561 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 13:19:35 -0700 Subject: [PATCH 05/44] Add builder for presentations, similar to credentials --- identity_vc/src/credential.rs | 18 +++-- identity_vc/src/presentation.rs | 120 +++++++++++++++++++++++++++++++- identity_vc/tests/basic.rs | 38 +++++++--- 3 files changed, 158 insertions(+), 18 deletions(-) diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 778400792a..1db4407984 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -162,13 +162,13 @@ pub fn validate_timestamp(name: &'static str, timestamp: &str) -> Result<()> { /// from dynamic data. /// /// NOTE: Base context and type are automatically included. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct CredentialBuilder { context: Vec, id: Option, types: Vec, credential_subject: Vec, - issuer: Issuer, + issuer: Option, issuance_date: String, expiration_date: Option, credential_status: Vec, @@ -180,13 +180,13 @@ pub struct CredentialBuilder { } impl CredentialBuilder { - pub fn new(issuer: impl Into) -> Self { + pub fn new() -> Self { Self { context: vec![Credential::BASE_CONTEXT.into()], id: None, types: vec![Credential::BASE_TYPE.into()], credential_subject: Vec::new(), - issuer: issuer.into(), + issuer: None, issuance_date: String::new(), expiration_date: None, credential_status: Vec::new(), @@ -229,7 +229,7 @@ impl CredentialBuilder { } pub fn issuer(mut self, value: impl Into) -> Self { - self.issuer = value.into(); + self.issuer = Some(value.into()); self } @@ -280,7 +280,7 @@ impl CredentialBuilder { id: self.id, types: self.types.into(), credential_subject: self.credential_subject.into(), - issuer: self.issuer, + issuer: self.issuer.ok_or_else(|| anyhow!("Missing issuer"))?, issuance_date: self.issuance_date, expiration_date: self.expiration_date, credential_status: None, @@ -323,3 +323,9 @@ impl CredentialBuilder { .map(|credential| VerifiableCredential::new(credential, proof)) } } + +impl Default for CredentialBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 35efe3f38a..428a020b66 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -2,8 +2,8 @@ use anyhow::Result; use crate::{ common::{Context, Object, OneOrMany, URI}, - credential::{validate_context, validate_credential, validate_types, validate_uri}, - verifiable::VerifiableCredential, + credential::{validate_context, validate_credential, validate_types, validate_uri, Credential}, + verifiable::{VerifiableCredential, VerifiablePresentation}, }; /// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. @@ -64,3 +64,119 @@ impl Presentation { Ok(()) } } + +// ============================================================================= +// Presentation Builder +// ============================================================================= + +/// A convenience for constructing a `Presentation` or `VerifiablePresentation` +/// from dynamic data. +/// +/// NOTE: Base context and type are automatically included. +#[derive(Debug)] +pub struct PresentationBuilder { + context: Vec, + id: Option, + types: Vec, + verifiable_credential: Vec, + holder: Option, + refresh_service: Vec, + terms_of_use: Vec, + properties: Object, +} + +impl PresentationBuilder { + pub fn new() -> Self { + Self { + context: vec![Credential::BASE_CONTEXT.into()], + id: None, + types: vec![Presentation::BASE_TYPE.into()], + verifiable_credential: Vec::new(), + holder: None, + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + properties: Default::default(), + } + } + + pub fn context(mut self, value: impl Into) -> Self { + let value: Context = value.into(); + + if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + self.context.push(value); + } + + self + } + + pub fn id(mut self, value: impl Into) -> Self { + self.id = Some(value.into()); + self + } + + pub fn type_(mut self, value: impl Into) -> Self { + let value: String = value.into(); + + if value != Presentation::BASE_TYPE { + self.types.push(value); + } + + self + } + + pub fn credential(mut self, value: impl Into) -> Self { + self.verifiable_credential.push(value.into()); + self + } + + pub fn holder(mut self, value: impl Into) -> Self { + self.holder = Some(value.into()); + self + } + + pub fn refresh_service(mut self, value: impl Into) -> Self { + self.refresh_service.push(value.into()); + self + } + + pub fn terms_of_use(mut self, value: impl Into) -> Self { + self.terms_of_use.push(value.into()); + self + } + + pub fn properties(mut self, value: impl Into) -> Self { + self.properties = value.into(); + self + } + + /// Consumes the `PresentationBuilder`, returning a valid `Presentation` + pub fn build(self) -> Result { + let mut presentation: Presentation = Presentation { + context: self.context.into(), + id: self.id, + types: self.types.into(), + verifiable_credential: self.verifiable_credential.into(), + holder: self.holder, + refresh_service: None, + terms_of_use: None, + properties: self.properties, + }; + + presentation.validate()?; + + Ok(presentation) + } + + /// Consumes the `PresentationBuilder`, returning a valid `VerifiablePresentation` + pub fn build_verifiable(self, proof: impl Into>) -> Result { + self + .build() + .map(|credential| VerifiablePresentation::new(credential, proof)) + } +} + +impl Default for PresentationBuilder { + fn default() -> Self { + Self::new() + } +} diff --git a/identity_vc/tests/basic.rs b/identity_vc/tests/basic.rs index 06f75610cc..46498bd3aa 100644 --- a/identity_vc/tests/basic.rs +++ b/identity_vc/tests/basic.rs @@ -65,7 +65,8 @@ fn test_parse_presentation() { #[test] fn test_credential_builder() { - let credential: Credential = CredentialBuilder::new("did:example:i55u3r") + let credential: Credential = CredentialBuilder::new() + .issuer("did:example:i55u3r") .context("https://www.w3.org/2018/credentials/examples/v1") .context(object!(id: "did:context:1234", type: "CustomContext2020")) .id("did:example:1234567890") @@ -102,21 +103,35 @@ fn test_credential_builder() { #[test] #[should_panic = "Not enough subjects"] -fn test_validate_credential_subject() { - CredentialBuilder::new("did:issuer").build().unwrap(); +fn test_validate_credential_subject_none() { + CredentialBuilder::new().issuer("did:issuer").build().unwrap(); } #[test] #[should_panic = "Invalid credential subject (empty)"] fn test_validate_credential_subject_empty() { - CredentialBuilder::new("did:issuer").subject(object!()).build().unwrap(); + CredentialBuilder::new() + .issuer("did:issuer") + .subject(object!()) + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Missing issuer"] +fn test_validate_issuer_none() { + CredentialBuilder::new() + .subject(object!(id: "did:sub")) + .build() + .unwrap(); } #[test] #[should_panic = "Invalid URI `foo`"] fn test_validate_issuer_bad_uri_1() { - CredentialBuilder::new("foo") + CredentialBuilder::new() .subject(object!(id: "did:sub")) + .issuer("foo") .build() .unwrap(); } @@ -124,16 +139,18 @@ fn test_validate_issuer_bad_uri_1() { #[test] #[should_panic = "Invalid URI `did123`"] fn test_validate_issuer_bad_uri_2() { - CredentialBuilder::new("did123") + CredentialBuilder::new() .subject(object!(id: "did:sub")) + .issuer("did123") .build() .unwrap(); } #[test] #[should_panic = "Invalid issuance date (empty)"] -fn test_validate_issuance_date_1() { - CredentialBuilder::new("did:issuer") +fn test_validate_issuance_date_empty() { + CredentialBuilder::new() + .issuer("did:issuer") .subject(object!(id: "did:sub")) .build() .unwrap(); @@ -141,8 +158,9 @@ fn test_validate_issuance_date_1() { #[test] #[should_panic = "Invalid issuance date (premature end of input)"] -fn test_validate_issuance_date_2() { - CredentialBuilder::new("did:issuer") +fn test_validate_issuance_date_bad_fmt() { + CredentialBuilder::new() + .issuer("did:issuer") .subject(object!(id: "did:sub")) .issuance_date("woo") .build() From 28bd9a5d16090f92e3c33cc090f0f51ed317dc5b Mon Sep 17 00:00:00 2001 From: l1h3r Date: Wed, 26 Aug 2020 13:21:25 -0700 Subject: [PATCH 06/44] Missed refresh_service/terms_of_use --- identity_vc/src/presentation.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 428a020b66..420e20d45e 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -162,6 +162,14 @@ impl PresentationBuilder { properties: self.properties, }; + if !self.refresh_service.is_empty() { + presentation.refresh_service = Some(self.refresh_service.into()); + } + + if !self.terms_of_use.is_empty() { + presentation.terms_of_use = Some(self.terms_of_use.into()); + } + presentation.validate()?; Ok(presentation) From 71af45d0c5eb1d93d15272c20a867ebe34d5d6cd Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 27 Aug 2020 00:04:20 +0200 Subject: [PATCH 07/44] prepared meeting notes --- docs/meeting-notes/2020-09-02.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 docs/meeting-notes/2020-09-02.md diff --git a/docs/meeting-notes/2020-09-02.md b/docs/meeting-notes/2020-09-02.md new file mode 100644 index 0000000000..c3c6bcecc7 --- /dev/null +++ b/docs/meeting-notes/2020-09-02.md @@ -0,0 +1,19 @@ +# 🗓️ Team Identity Meeting Notes - 2020-09-02 + +## 👥 Participants +- @Thoralf-M +- @nothingismagick +- @l1h3r +- @tensor-programming +- @JelleMillenaar +- @huhn511 +- @vidalattias + +## 💬 Discussion topics +- +- +- + + +## ⏭️ Next Meeting +Wednesday, 2020-09-09 - 17:00 to 18:00 (CEST) \ No newline at end of file From 265dfd388173c0a3af6dd476ce0f55230303d1fc Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:13:28 -0700 Subject: [PATCH 08/44] Add timestamp type --- identity_vc/src/common/timestamp.rs | 57 +++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 identity_vc/src/common/timestamp.rs diff --git a/identity_vc/src/common/timestamp.rs b/identity_vc/src/common/timestamp.rs new file mode 100644 index 0000000000..8ca678fc99 --- /dev/null +++ b/identity_vc/src/common/timestamp.rs @@ -0,0 +1,57 @@ +use chrono::{DateTime, Utc}; +use std::{ + ops::{Deref, DerefMut}, + convert::TryFrom +}; + +use crate::error::Error; + +type Inner = DateTime; + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Timestamp(Inner); + +impl Timestamp { + pub fn into_inner(self) -> Inner { + self.0 + } +} + +impl Default for Timestamp { + fn default() -> Self { + Self(Utc::now()) + } +} + +impl From for Timestamp { + fn from(other: Inner) -> Self { + Self(other) + } +} + +impl From for Inner { + fn from(other: Timestamp) -> Self { + other.into_inner() + } +} + +impl Deref for Timestamp { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom<&'_ str> for Timestamp { + type Error = Error; + + fn try_from(string: &'_ str) -> Result { + match DateTime::parse_from_rfc3339(string) { + Ok(datetime) => Ok(Self(datetime.into())), + Err(error) => Err(Error::InvalidTimestamp(error)), + } + } +} From b4000b85521490c0188e6c8b1a6c8c9d62723916 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:14:33 -0700 Subject: [PATCH 09/44] Traits for convenience --- identity_vc/src/common/oom.rs | 6 ++++++ identity_vc/src/common/uri.rs | 12 +++++++++--- identity_vc/src/common/value.rs | 8 +++++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/identity_vc/src/common/oom.rs b/identity_vc/src/common/oom.rs index ef6db486f5..8b1a39e8b6 100644 --- a/identity_vc/src/common/oom.rs +++ b/identity_vc/src/common/oom.rs @@ -70,6 +70,12 @@ where } } +impl Default for OneOrMany { + fn default() -> Self { + Self::Many(Vec::new()) + } +} + impl From for OneOrMany { fn from(other: T) -> Self { Self::One(other) diff --git a/identity_vc/src/common/uri.rs b/identity_vc/src/common/uri.rs index 5097421f14..4b2211f465 100644 --- a/identity_vc/src/common/uri.rs +++ b/identity_vc/src/common/uri.rs @@ -1,13 +1,19 @@ use std::{fmt, ops::Deref}; -/// A "spec-compliant" URI - possibly a DID. +/// A simple wrapper for URIs adhering to RFC 3986 /// -/// TODO: Parse/validate and represent this an *ACTUAL* URI. +/// TODO: Parse/Validate according to RFC 3986 /// TODO: impl From for URI #[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] -pub struct URI(String); +pub struct URI(pub(crate) String); + +impl URI { + pub fn into_inner(self) -> String { + self.0 + } +} impl fmt::Debug for URI { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/identity_vc/src/common/value.rs b/identity_vc/src/common/value.rs index 64b9f8ce6e..c87415eefc 100644 --- a/identity_vc/src/common/value.rs +++ b/identity_vc/src/common/value.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::common::Object; +use crate::common::{Object, URI}; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] @@ -68,6 +68,12 @@ impl From for Value { } } +impl From for Value { + fn from(other: URI) -> Self { + Self::String(other.0) + } +} + impl From> for Value where T: Into, From 6b777ce006562fdf900dcbf9a74caba239f99508 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:24:15 -0700 Subject: [PATCH 10/44] Explicit types for known credential fields --- .../common/credential/credential_schema.rs | 40 +++++++++++++++++++ .../common/credential/credential_status.rs | 40 +++++++++++++++++++ .../common/credential/credential_subject.rs | 30 ++++++++++++++ identity_vc/src/common/credential/evidence.rs | 38 ++++++++++++++++++ identity_vc/src/common/credential/mod.rs | 40 +++++++++++++++++++ .../src/common/credential/refresh_service.rs | 40 +++++++++++++++++++ .../src/common/credential/terms_of_use.rs | 39 ++++++++++++++++++ identity_vc/src/common/mod.rs | 4 +- identity_vc/src/common/object.rs | 14 ++++++- 9 files changed, 282 insertions(+), 3 deletions(-) create mode 100644 identity_vc/src/common/credential/credential_schema.rs create mode 100644 identity_vc/src/common/credential/credential_status.rs create mode 100644 identity_vc/src/common/credential/credential_subject.rs create mode 100644 identity_vc/src/common/credential/evidence.rs create mode 100644 identity_vc/src/common/credential/mod.rs create mode 100644 identity_vc/src/common/credential/refresh_service.rs create mode 100644 identity_vc/src/common/credential/terms_of_use.rs diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs new file mode 100644 index 0000000000..c5ad3892e9 --- /dev/null +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -0,0 +1,40 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + error::Error, +}; + +/// Information used to validate the structure of a `Credential`. +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#data-schemas +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct CredentialSchema { + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for CredentialSchema { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = match take_object_id(&mut other) { + Some(id) => id.into(), + None => return Err(Error::BadObjectConversion("CredentialSchema")), + }; + + this.types = match take_object_type(&mut other) { + Some(types) => types, + None => return Err(Error::BadObjectConversion("CredentialSchema")), + }; + + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs new file mode 100644 index 0000000000..eb370eb489 --- /dev/null +++ b/identity_vc/src/common/credential/credential_status.rs @@ -0,0 +1,40 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + error::Error, +}; + +/// Information used to determine the current status of a `Credential`. +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#status +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct CredentialStatus { + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for CredentialStatus { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = match take_object_id(&mut other) { + Some(id) => id.into(), + None => return Err(Error::BadObjectConversion("CredentialStatus")), + }; + + this.types = match take_object_type(&mut other) { + Some(types) => types, + None => return Err(Error::BadObjectConversion("CredentialStatus")), + }; + + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/credential/credential_subject.rs b/identity_vc/src/common/credential/credential_subject.rs new file mode 100644 index 0000000000..5869f8d86e --- /dev/null +++ b/identity_vc/src/common/credential/credential_subject.rs @@ -0,0 +1,30 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, Object, URI}, + error::Error, +}; + +/// An entity who is the target of a set of claims. +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#credential-subject +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct CredentialSubject { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for CredentialSubject { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = take_object_id(&mut other).map(Into::into); + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs new file mode 100644 index 0000000000..00b2d49ad6 --- /dev/null +++ b/identity_vc/src/common/credential/evidence.rs @@ -0,0 +1,38 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, take_object_type, Object, OneOrMany}, + error::Error, +}; + +/// Information used to increase confidence in the claims of a `Credential` +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#evidence +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct Evidence { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for Evidence { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = take_object_id(&mut other); + + this.types = match take_object_type(&mut other) { + Some(types) => types, + None => return Err(Error::BadObjectConversion("Evidence")), + }; + + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/credential/mod.rs b/identity_vc/src/common/credential/mod.rs new file mode 100644 index 0000000000..09d0acabc6 --- /dev/null +++ b/identity_vc/src/common/credential/mod.rs @@ -0,0 +1,40 @@ +mod credential_schema; +mod credential_status; +mod credential_subject; +mod evidence; +mod refresh_service; +mod terms_of_use; + +pub use self::{ + credential_schema::*, credential_status::*, credential_subject::*, evidence::*, refresh_service::*, terms_of_use::*, +}; + +use crate::common::{Object, OneOrMany, Value}; + +pub fn take_object_id(object: &mut Object) -> Option { + match object.remove("id") { + Some(Value::String(id)) => Some(id), + Some(_) => None, + None => None, + } +} + +pub fn take_object_type(object: &mut Object) -> Option> { + match object.remove("type") { + Some(Value::String(value)) => Some(value.into()), + Some(Value::Array(values)) => Some(collect_types(values)), + Some(_) | None => None, + } +} + +fn collect_types(values: Vec) -> OneOrMany { + let mut types: Vec = Vec::with_capacity(values.len()); + + for value in values { + if let Value::String(value) = value { + types.push(value); + } + } + + types.into() +} diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs new file mode 100644 index 0000000000..a4516dc804 --- /dev/null +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -0,0 +1,40 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + error::Error, +}; + +/// Information used to refresh or assert the status of a `Credential`. +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#refreshing +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct RefreshService { + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for RefreshService { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = match take_object_id(&mut other) { + Some(id) => id.into(), + None => return Err(Error::BadObjectConversion("RefreshService")), + }; + + this.types = match take_object_type(&mut other) { + Some(types) => types, + None => return Err(Error::BadObjectConversion("RefreshService")), + }; + + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs new file mode 100644 index 0000000000..37e26b427a --- /dev/null +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -0,0 +1,39 @@ +use std::convert::TryFrom; + +use crate::{ + common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + error::Error, +}; + +/// Information used to express obligations, prohibitions, and permissions about +/// a `Credential` or `Presentation`. +/// +/// Ref: https://www.w3.org/TR/vc-data-model/#terms-of-use +#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] +pub struct TermsOfUse { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, +} + +impl TryFrom for TermsOfUse { + type Error = Error; + + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); + + this.id = take_object_id(&mut other).map(Into::into); + + this.types = match take_object_type(&mut other) { + Some(types) => types, + None => return Err(Error::BadObjectConversion("TermsOfUse")), + }; + + this.properties = other; + + Ok(this) + } +} diff --git a/identity_vc/src/common/mod.rs b/identity_vc/src/common/mod.rs index 6699fe58ea..c021d0c122 100644 --- a/identity_vc/src/common/mod.rs +++ b/identity_vc/src/common/mod.rs @@ -1,8 +1,10 @@ mod context; +mod credential; mod issuer; mod object; mod oom; +mod timestamp; mod uri; mod value; -pub use self::{context::*, issuer::*, object::*, oom::*, uri::*, value::*}; +pub use self::{context::*, credential::*, issuer::*, object::*, oom::*, timestamp::*, uri::*, value::*}; diff --git a/identity_vc/src/common/object.rs b/identity_vc/src/common/object.rs index 56abd007d6..9ebc7a05b9 100644 --- a/identity_vc/src/common/object.rs +++ b/identity_vc/src/common/object.rs @@ -1,10 +1,14 @@ -use std::{collections::HashMap, fmt, ops::Deref}; +use std::{ + collections::HashMap, + fmt, + ops::{Deref, DerefMut}, +}; use crate::common::Value; type Inner = HashMap; -// An immutable String -> Value `HashMap` +// An String -> Value `HashMap` wrapper #[derive(Clone, Default, PartialEq, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] @@ -30,6 +34,12 @@ impl Deref for Object { } } +impl DerefMut for Object { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + impl From for Object { fn from(other: Inner) -> Self { Self(other) From 67f4489dfa921fd9179b61290404eca0adb261c2 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:24:38 -0700 Subject: [PATCH 11/44] Custom error type --- identity_vc/src/error.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 identity_vc/src/error.rs diff --git a/identity_vc/src/error.rs b/identity_vc/src/error.rs new file mode 100644 index 0000000000..0f24e31e3c --- /dev/null +++ b/identity_vc/src/error.rs @@ -0,0 +1,25 @@ +use std::result::Result as StdResult; +use thiserror::Error as ThisError; +use chrono::ParseError as ChronoError; + +#[derive(Debug, ThisError)] +pub enum Error { + #[error("Can't convert `Object` to `{0}`")] + BadObjectConversion(&'static str), + #[error("Missing base type for {0}")] + MissingBaseType(&'static str), + #[error("Missing base context for {0}")] + MissingBaseContext(&'static str), + #[error("Invalid base context for {0}")] + InvalidBaseContext(&'static str), + #[error("Invalid URI for {0}")] + InvalidURI(&'static str), + #[error("Invalid timestamp format ({0})")] + InvalidTimestamp(ChronoError), + #[error("Missing `Credential` subject")] + MissingCredentialSubject, + #[error("Invalid `Credential` subject")] + InvalidCredentialSubject, +} + +pub type Result = StdResult; From 1a2475fa3c3e4ef8e0172883595dcf48d9afc395 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:26:41 -0700 Subject: [PATCH 12/44] Clean up validation --- identity_vc/src/utils/mod.rs | 3 + identity_vc/src/utils/validation.rs | 93 +++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 identity_vc/src/utils/mod.rs create mode 100644 identity_vc/src/utils/validation.rs diff --git a/identity_vc/src/utils/mod.rs b/identity_vc/src/utils/mod.rs new file mode 100644 index 0000000000..f630594d03 --- /dev/null +++ b/identity_vc/src/utils/mod.rs @@ -0,0 +1,3 @@ +mod validation; + +pub use self::validation::*; diff --git a/identity_vc/src/utils/validation.rs b/identity_vc/src/utils/validation.rs new file mode 100644 index 0000000000..4896951af6 --- /dev/null +++ b/identity_vc/src/utils/validation.rs @@ -0,0 +1,93 @@ +use anyhow::Result; + +use crate::{ + common::{Context, OneOrMany, URI}, + credential::Credential, + error::Error, + presentation::Presentation, +}; + +pub fn validate_credential_structure(credential: &Credential) -> Result<()> { + // Ensure the base context is present and in the correct location + validate_context("Credential", &credential.context)?; + + // The set of types MUST contain the base type + validate_types("Credential", Credential::BASE_TYPE, &credential.types)?; + + // Ensure the id URI (if provided) adheres to the correct format + validate_opt_uri("Credential id", credential.id.as_ref())?; + + // Ensure the issuer URI adheres to the correct format + validate_uri("Credential issuer", credential.issuer.uri())?; + + // Credentials MUST have at least one subject + ensure!( + !credential.credential_subject.is_empty(), + Error::MissingCredentialSubject + ); + + // Each subject is defined as one or more properties - no empty objects + for subject in credential.credential_subject.iter() { + ensure!( + subject.id.is_some() || !subject.properties.is_empty(), + Error::InvalidCredentialSubject + ); + } + + Ok(()) +} + +pub fn validate_presentation_structure(presentation: &Presentation) -> Result<()> { + // Ensure the base context is present and in the correct location + validate_context("Presentation", &presentation.context)?; + + // The set of types MUST contain the base type + validate_types("Presentation", Presentation::BASE_TYPE, &presentation.types)?; + + // Ensure the id URI (if provided) adheres to the correct format + validate_opt_uri("Presentation id", presentation.id.as_ref())?; + + // Ensure the holder URI (if provided) adheres to the correct format + validate_opt_uri("Presentation holder", presentation.holder.as_ref())?; + + // Validate all verifiable credentials + for credential in presentation.verifiable_credential.iter() { + credential.validate()?; + } + + Ok(()) +} + +pub fn validate_types(name: &'static str, base: &str, types: &OneOrMany) -> Result<()> { + ensure!(types.contains(&base.into()), Error::MissingBaseType(name)); + + Ok(()) +} + +pub fn validate_context(name: &'static str, context: &OneOrMany) -> Result<()> { + // The first Credential/Presentation context MUST be a URI representing the base context + match context.get(0) { + Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), + Some(_) => bail!(Error::InvalidBaseContext(name)), + None => bail!(Error::MissingBaseContext(name)), + } +} + +pub fn validate_uri(name: &'static str, uri: &URI) -> Result<()> { + const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; + + // TODO: Proper URI validation + ensure!( + KNOWN.iter().any(|scheme| uri.starts_with(scheme)), + Error::InvalidURI(name), + ); + + Ok(()) +} + +pub fn validate_opt_uri(name: &'static str, uri: Option<&URI>) -> Result<()> { + match uri { + Some(uri) => validate_uri(name, uri), + None => Ok(()), + } +} From e3d76f624a15539793d06c1db544e75ee671b562 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:50:30 -0700 Subject: [PATCH 13/44] Use common types in Credentials --- identity_vc/src/credential.rs | 59 ++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 1db4407984..bc48821bf5 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,15 +1,17 @@ use anyhow::Result; -use chrono::DateTime; use crate::{ - common::{Context, Issuer, Object, OneOrMany, URI}, + common::{ + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, Object, OneOrMany, + RefreshService, TermsOfUse, Timestamp, Value, URI, + }, verifiable::VerifiableCredential, }; /// A `Credential` represents a set of claims describing an entity. /// /// `Credential`s can be combined with `Proof`s to create `VerifiableCredential`s. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Credential { /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. /// @@ -27,32 +29,36 @@ pub struct Credential { /// don't have any immediate plans to do so. #[serde(rename = "type")] pub types: OneOrMany, - /// One or more `Object`s representing the credential subject(s). + /// One or more `Object`s representing the `Credential` subject(s). #[serde(rename = "credentialSubject")] - pub credential_subject: OneOrMany, - /// A reference to the issuer of the credential. + pub credential_subject: OneOrMany, + /// A reference to the issuer of the `Credential`. pub issuer: Issuer, - /// The date and time the credential becomes valid. + /// The date and time the `Credential` becomes valid. #[serde(rename = "issuanceDate")] - pub issuance_date: String, - /// The date and time the credential is no longer considered valid. + pub issuance_date: Timestamp, + /// The date and time the `Credential` is no longer considered valid. #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] - pub expiration_date: Option, + pub expiration_date: Option, /// TODO #[serde(rename = "credentialStatus", skip_serializing_if = "Option::is_none")] - pub credential_status: Option>, + pub credential_status: Option>, /// TODO #[serde(rename = "credentialSchema", skip_serializing_if = "Option::is_none")] - pub credential_schema: Option>, + pub credential_schema: Option>, /// TODO #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] - pub refresh_service: Option>, - /// The terms of use issued by the credential issuer + pub refresh_service: Option>, + /// The terms of use issued by the `Credential` issuer #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] - pub terms_of_use: Option>, + pub terms_of_use: Option>, /// TODO #[serde(skip_serializing_if = "Option::is_none")] - pub evidence: Option>, + pub evidence: Option>, + /// Indicates that the `Credential` must only be contained within a + /// `Presentation` with a proof issued from the `Credential` subject. + #[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")] + pub non_transferable: Option, /// Miscellaneous properties. #[serde(flatten)] pub properties: Object, @@ -167,15 +173,16 @@ pub struct CredentialBuilder { context: Vec, id: Option, types: Vec, - credential_subject: Vec, + credential_subject: Vec, issuer: Option, - issuance_date: String, - expiration_date: Option, - credential_status: Vec, - credential_schema: Vec, - refresh_service: Vec, - terms_of_use: Vec, - evidence: Vec, + issuance_date: Timestamp, + expiration_date: Option, + credential_status: Vec, + credential_schema: Vec, + refresh_service: Vec, + terms_of_use: Vec, + evidence: Vec, + non_transferable: Option, properties: Object, } @@ -187,13 +194,14 @@ impl CredentialBuilder { types: vec![Credential::BASE_TYPE.into()], credential_subject: Vec::new(), issuer: None, - issuance_date: String::new(), + issuance_date: Default::default(), expiration_date: None, credential_status: Vec::new(), credential_schema: Vec::new(), refresh_service: Vec::new(), terms_of_use: Vec::new(), evidence: Vec::new(), + non_transferable: None, properties: Default::default(), } } @@ -288,6 +296,7 @@ impl CredentialBuilder { refresh_service: None, terms_of_use: None, evidence: None, + non_transferable: self.non_transferable, properties: self.properties, }; From 332447037a1979236396c01b3de0831df563b1a4 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:50:51 -0700 Subject: [PATCH 14/44] Use new validation --- identity_vc/src/credential.rs | 90 ++--------------------------------- 1 file changed, 3 insertions(+), 87 deletions(-) diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index bc48821bf5..45376a0a67 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -5,6 +5,8 @@ use crate::{ Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, Object, OneOrMany, RefreshService, TermsOfUse, Timestamp, Value, URI, }, + error::Error, + utils::validate_credential_structure, verifiable::VerifiableCredential, }; @@ -70,93 +72,7 @@ impl Credential { pub const BASE_TYPE: &'static str = "VerifiableCredential"; pub fn validate(&self) -> Result<()> { - validate_context(&self.context)?; - - if let Some(ref id) = self.id { - validate_uri(id)?; - } - - validate_types(&self.types, Self::BASE_TYPE)?; - validate_subject(&self.credential_subject)?; - validate_uri(self.issuer.uri())?; - validate_timestamp("issuance date", &self.issuance_date)?; - - if let Some(ref timestamp) = self.expiration_date { - validate_timestamp("expiration date", timestamp)?; - } - - Ok(()) - } -} - -pub fn validate_context(context: &OneOrMany) -> Result<()> { - // Credentials/Presentations MUST have at least one context item - ensure!(!context.is_empty(), "Not enough context items"); - - // The first item MUST be a URI with the value of the base context - match context.get(0) { - Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), - Some(_) => bail!("Invalid base context"), - None => unreachable!(), - } -} - -pub fn validate_types(types: &OneOrMany, base: &'static str) -> Result<()> { - // Credentials/Presentations MUST have at least one type - ensure!(!types.is_empty(), "Not enough types"); - - // The set of types MUST contain the base type - ensure!(types.contains(&base.into()), "Missing base type"); - - Ok(()) -} - -pub fn validate_subject(subjects: &OneOrMany) -> Result<()> { - // A credential MUST have at least one subject - ensure!(!subjects.is_empty(), "Not enough subjects"); - - // Each subject is defined as one or more properties - no empty objects - for subject in subjects.iter() { - ensure!(!subject.is_empty(), "Invalid credential subject (empty)"); - } - - Ok(()) -} - -pub fn validate_credential(credentials: &OneOrMany) -> Result<()> { - // Presentations MUST have an least one verifiable credential - ensure!(!credentials.is_empty(), "Not enough credentials"); - - // All verifiable credentials MUST be valid (structurally) - for credential in credentials.iter() { - credential.validate()?; - } - - Ok(()) -} - -pub fn validate_uri(uri: &URI) -> Result<()> { - const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; - - // TODO: Proper URI validation - ensure!( - KNOWN.iter().any(|scheme| uri.starts_with(scheme)), - "Invalid URI `{}`", - uri.as_str(), - ); - - Ok(()) -} - -// Validate the timestamp format according to RFC 3339 -// -// Ref: https://tools.ietf.org/html/rfc3339 -pub fn validate_timestamp(name: &'static str, timestamp: &str) -> Result<()> { - ensure!(!timestamp.is_empty(), "Invalid {} (empty)", name); - - match DateTime::parse_from_rfc3339(timestamp) { - Ok(_) => Ok(()), - Err(error) => bail!("Invalid {} ({})", name, error), + validate_credential_structure(self) } } From bcd9b3ba2b65a1bd966683ecb32d774d67b9d98f Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:51:59 -0700 Subject: [PATCH 15/44] Use common types in Presentation --- identity_vc/src/presentation.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 420e20d45e..2fa0578192 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crate::{ - common::{Context, Object, OneOrMany, URI}, + common::{Context, Object, OneOrMany, RefreshService, TermsOfUse, URI}, credential::{validate_context, validate_credential, validate_types, validate_uri, Credential}, verifiable::{VerifiableCredential, VerifiablePresentation}, }; @@ -9,7 +9,7 @@ use crate::{ /// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. /// /// `Presentation`s can be combined with `Proof`s to create `VerifiablePresentation`s. -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Presentation { /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. /// @@ -35,10 +35,10 @@ pub struct Presentation { pub holder: Option, /// TODO #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] - pub refresh_service: Option>, + pub refresh_service: Option>, /// The terms of use issued by the presentation holder #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] - pub terms_of_use: Option>, + pub terms_of_use: Option>, /// Miscellaneous properties. #[serde(flatten)] pub properties: Object, @@ -80,8 +80,8 @@ pub struct PresentationBuilder { types: Vec, verifiable_credential: Vec, holder: Option, - refresh_service: Vec, - terms_of_use: Vec, + refresh_service: Vec, + terms_of_use: Vec, properties: Object, } From 2236bde478b46366bc66e51738af692b7ba65f88 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:52:48 -0700 Subject: [PATCH 16/44] Use new validation for Presentations --- identity_vc/src/presentation.rs | 19 ++++--------------- identity_vc/src/verifiable/credential.rs | 2 +- identity_vc/src/verifiable/presentation.rs | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 2fa0578192..c519afca31 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -2,7 +2,9 @@ use anyhow::Result; use crate::{ common::{Context, Object, OneOrMany, RefreshService, TermsOfUse, URI}, - credential::{validate_context, validate_credential, validate_types, validate_uri, Credential}, + credential::Credential, + error::Error, + utils::validate_presentation_structure, verifiable::{VerifiableCredential, VerifiablePresentation}, }; @@ -48,20 +50,7 @@ impl Presentation { pub const BASE_TYPE: &'static str = "VerifiablePresentation"; pub fn validate(&self) -> Result<()> { - validate_context(&self.context)?; - - if let Some(ref id) = self.id { - validate_uri(id)?; - } - - validate_types(&self.types, Self::BASE_TYPE)?; - validate_credential(&self.verifiable_credential)?; - - if let Some(ref holder) = self.holder { - validate_uri(holder)?; - } - - Ok(()) + validate_presentation_structure(self) } } diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs index 856d8a2705..f239f65ea8 100644 --- a/identity_vc/src/verifiable/credential.rs +++ b/identity_vc/src/verifiable/credential.rs @@ -5,7 +5,7 @@ use crate::{ credential::Credential, }; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiableCredential { #[serde(flatten)] credential: Credential, diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs index 4037fc0318..b2a6df8af5 100644 --- a/identity_vc/src/verifiable/presentation.rs +++ b/identity_vc/src/verifiable/presentation.rs @@ -5,7 +5,7 @@ use crate::{ presentation::Presentation, }; -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiablePresentation { #[serde(flatten)] presentation: Presentation, From 9d1b143043ff56797eb7eccdaa183afcd07bab7d Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:55:14 -0700 Subject: [PATCH 17/44] Builder convenience for types supporting TryFrom --- identity_vc/src/common/timestamp.rs | 2 +- identity_vc/src/credential.rs | 75 ++++++++--------------------- identity_vc/src/macros.rs | 65 +++++++++++++++++++++++++ identity_vc/src/presentation.rs | 36 +++----------- 4 files changed, 95 insertions(+), 83 deletions(-) create mode 100644 identity_vc/src/macros.rs diff --git a/identity_vc/src/common/timestamp.rs b/identity_vc/src/common/timestamp.rs index 8ca678fc99..687f4a1c4a 100644 --- a/identity_vc/src/common/timestamp.rs +++ b/identity_vc/src/common/timestamp.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use std::{ - ops::{Deref, DerefMut}, + ops::Deref, convert::TryFrom }; diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 45376a0a67..db4157be91 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -132,11 +132,6 @@ impl CredentialBuilder { self } - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - pub fn type_(mut self, value: impl Into) -> Self { let value: String = value.into(); @@ -147,55 +142,27 @@ impl CredentialBuilder { self } - pub fn subject(mut self, value: impl Into) -> Self { - self.credential_subject.push(value.into()); - self - } - - pub fn issuer(mut self, value: impl Into) -> Self { - self.issuer = Some(value.into()); - self - } - - pub fn issuance_date(mut self, value: impl Into) -> Self { - self.issuance_date = value.into(); - self - } - - pub fn expiration_date(mut self, value: impl Into) -> Self { - self.expiration_date = Some(value.into()); - self - } - - pub fn credential_status(mut self, value: impl Into) -> Self { - self.credential_status.push(value.into()); - self - } - - pub fn credential_schema(mut self, value: impl Into) -> Self { - self.credential_schema.push(value.into()); - self - } - - pub fn refresh_service(mut self, value: impl Into) -> Self { - self.refresh_service.push(value.into()); - self - } - - pub fn terms_of_use(mut self, value: impl Into) -> Self { - self.terms_of_use.push(value.into()); - self - } - - pub fn evidence(mut self, value: impl Into) -> Self { - self.evidence.push(value.into()); - self - } - - pub fn properties(mut self, value: impl Into) -> Self { - self.properties = value.into(); - self - } + impl_builder_setter!(id, id, Option); + impl_builder_setter!(subject, credential_subject, Vec); + impl_builder_setter!(issuer, issuer, Option); + impl_builder_setter!(issuance_date, issuance_date, Timestamp); + impl_builder_setter!(expiration_date, expiration_date, Option); + impl_builder_setter!(status, credential_status, Vec); + impl_builder_setter!(schema, credential_schema, Vec); + impl_builder_setter!(refresh, refresh_service, Vec); + impl_builder_setter!(terms_of_use, terms_of_use, Vec); + impl_builder_setter!(evidence, evidence, Vec); + impl_builder_setter!(non_transferable, non_transferable, Option); + impl_builder_setter!(properties, properties, Object); + + impl_builder_try_setter!(try_subject, credential_subject, Vec); + impl_builder_try_setter!(try_issuance_date, issuance_date, Timestamp); + impl_builder_try_setter!(try_expiration_date, expiration_date, Option); + impl_builder_try_setter!(try_status, credential_status, Vec); + impl_builder_try_setter!(try_schema, credential_schema, Vec); + impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); + impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); + impl_builder_try_setter!(try_evidence, evidence, Vec); /// Consumes the `CredentialBuilder`, returning a valid `Credential` pub fn build(self) -> Result { diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs new file mode 100644 index 0000000000..a9fc5c110d --- /dev/null +++ b/identity_vc/src/macros.rs @@ -0,0 +1,65 @@ +macro_rules! impl_builder_setter { + ($fn:ident, $field:ident, Option<$ty:ty>) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field = Some(value.into()); + self + } + }; + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field.push(value.into()); + self + } + }; + ($fn:ident, $field:ident, $ty:ty) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field = value.into(); + self + } + }; +} + +macro_rules! impl_builder_try_setter { + ($fn:ident, $field:ident, Option<$ty:ty>) => { + pub fn $fn(mut self, value: T) -> ::anyhow::Result + where + $ty: ::std::convert::TryFrom, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field = Some(value); + self + }) + .map_err(Into::into) + } + }; + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + pub fn $fn(mut self, value: T) -> ::anyhow::Result + where + $ty: ::std::convert::TryFrom, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field.push(value); + self + }) + .map_err(Into::into) + } + }; + ($fn:ident, $field:ident, $ty:ty) => { + pub fn $fn(mut self, value: T) -> ::anyhow::Result + where + $ty: ::std::convert::TryFrom, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field = value; + self + }) + .map_err(Into::into) + } + }; +} diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index c519afca31..8631bddc18 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -98,11 +98,6 @@ impl PresentationBuilder { self } - pub fn id(mut self, value: impl Into) -> Self { - self.id = Some(value.into()); - self - } - pub fn type_(mut self, value: impl Into) -> Self { let value: String = value.into(); @@ -113,30 +108,15 @@ impl PresentationBuilder { self } - pub fn credential(mut self, value: impl Into) -> Self { - self.verifiable_credential.push(value.into()); - self - } - - pub fn holder(mut self, value: impl Into) -> Self { - self.holder = Some(value.into()); - self - } - - pub fn refresh_service(mut self, value: impl Into) -> Self { - self.refresh_service.push(value.into()); - self - } - - pub fn terms_of_use(mut self, value: impl Into) -> Self { - self.terms_of_use.push(value.into()); - self - } + impl_builder_setter!(id, id, Option); + impl_builder_setter!(credential, verifiable_credential, Vec); + impl_builder_setter!(holder, holder, Option); + impl_builder_setter!(refresh, refresh_service, Vec); + impl_builder_setter!(terms_of_use, terms_of_use, Vec); + impl_builder_setter!(properties, properties, Object); - pub fn properties(mut self, value: impl Into) -> Self { - self.properties = value.into(); - self - } + impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); + impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); /// Consumes the `PresentationBuilder`, returning a valid `Presentation` pub fn build(self) -> Result { From 68f7f72374df677f6949c795a250ef01593a6927 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 11:59:29 -0700 Subject: [PATCH 18/44] More tests --- identity_vc/src/common/timestamp.rs | 5 +- identity_vc/src/error.rs | 2 +- identity_vc/src/lib.rs | 12 +- identity_vc/src/macros.rs | 21 ++++ identity_vc/tests/basic.rs | 168 ---------------------------- identity_vc/tests/credential.rs | 88 +++++++++++++++ identity_vc/tests/macros.rs | 12 ++ identity_vc/tests/presentation.rs | 103 +++++++++++++++++ identity_vc/tests/serde.rs | 38 +++++++ 9 files changed, 274 insertions(+), 175 deletions(-) delete mode 100644 identity_vc/tests/basic.rs create mode 100644 identity_vc/tests/credential.rs create mode 100644 identity_vc/tests/macros.rs create mode 100644 identity_vc/tests/presentation.rs create mode 100644 identity_vc/tests/serde.rs diff --git a/identity_vc/src/common/timestamp.rs b/identity_vc/src/common/timestamp.rs index 687f4a1c4a..8353c76b03 100644 --- a/identity_vc/src/common/timestamp.rs +++ b/identity_vc/src/common/timestamp.rs @@ -1,8 +1,5 @@ use chrono::{DateTime, Utc}; -use std::{ - ops::Deref, - convert::TryFrom -}; +use std::{convert::TryFrom, ops::Deref}; use crate::error::Error; diff --git a/identity_vc/src/error.rs b/identity_vc/src/error.rs index 0f24e31e3c..c558cb7e13 100644 --- a/identity_vc/src/error.rs +++ b/identity_vc/src/error.rs @@ -1,6 +1,6 @@ +use chrono::ParseError as ChronoError; use std::result::Result as StdResult; use thiserror::Error as ThisError; -use chrono::ParseError as ChronoError; #[derive(Debug, ThisError)] pub enum Error { diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index e6d0d68931..d57d5257f2 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -4,18 +4,26 @@ extern crate anyhow; #[macro_use] extern crate serde; +#[macro_use] +mod macros; + pub mod common; pub mod credential; +pub mod error; pub mod presentation; +pub mod utils; pub mod verifiable; pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; pub mod prelude { pub use crate::{ - common::{Context, Issuer, Number, Object, OneOrMany, Value, URI}, + common::{ + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, Number, Object, OneOrMany, + RefreshService, TermsOfUse, Timestamp, Value, URI, + }, credential::{Credential, CredentialBuilder}, - presentation::Presentation, + presentation::{Presentation, PresentationBuilder}, verifiable::{VerifiableCredential, VerifiablePresentation}, }; } diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs index a9fc5c110d..289f330bc7 100644 --- a/identity_vc/src/macros.rs +++ b/identity_vc/src/macros.rs @@ -1,3 +1,24 @@ +#[macro_export] +macro_rules! object { + () => { + $crate::common::Object::default() + }; + ($($key:ident : $value:expr),* $(,)*) => { + { + let mut object = ::std::collections::HashMap::new(); + + $( + object.insert( + stringify!($key).to_string(), + $crate::common::Value::from($value), + ); + )* + + $crate::common::Object::from(object) + } + }; +} + macro_rules! impl_builder_setter { ($fn:ident, $field:ident, Option<$ty:ty>) => { pub fn $fn(mut self, value: impl Into<$ty>) -> Self { diff --git a/identity_vc/tests/basic.rs b/identity_vc/tests/basic.rs deleted file mode 100644 index 46498bd3aa..0000000000 --- a/identity_vc/tests/basic.rs +++ /dev/null @@ -1,168 +0,0 @@ -use identity_vc::prelude::*; -use serde_json::from_str; - -macro_rules! object { - () => { - ::identity_vc::common::Object::default() - }; - ($($key:ident : $value:expr),* $(,)*) => { - { - let mut object = ::std::collections::HashMap::new(); - - $( - object.insert( - stringify!($key).to_string(), - ::identity_vc::common::Value::from($value), - ); - )* - - ::identity_vc::common::Object::from(object) - } - }; -} - -macro_rules! assert_matches { - ($($tt:tt)*) => { - assert!(matches!($($tt)*)) - }; -} - -fn try_credential(data: &(impl AsRef + ?Sized)) { - from_str::(data.as_ref()) - .unwrap() - .validate() - .unwrap() -} - -fn try_presentation(data: &(impl AsRef + ?Sized)) { - from_str::(data.as_ref()) - .unwrap() - .validate() - .unwrap() -} - -#[test] -fn test_parse_credential() { - try_credential(include_str!("input/example-01.json")); - try_credential(include_str!("input/example-02.json")); - try_credential(include_str!("input/example-03.json")); - try_credential(include_str!("input/example-04.json")); - try_credential(include_str!("input/example-05.json")); - try_credential(include_str!("input/example-06.json")); - try_credential(include_str!("input/example-07.json")); - - try_credential(include_str!("input/example-09.json")); - try_credential(include_str!("input/example-10.json")); - try_credential(include_str!("input/example-11.json")); - try_credential(include_str!("input/example-12.json")); - try_credential(include_str!("input/example-13.json")); -} - -#[test] -fn test_parse_presentation() { - try_presentation(include_str!("input/example-08.json")); -} - -#[test] -fn test_credential_builder() { - let credential: Credential = CredentialBuilder::new() - .issuer("did:example:i55u3r") - .context("https://www.w3.org/2018/credentials/examples/v1") - .context(object!(id: "did:context:1234", type: "CustomContext2020")) - .id("did:example:1234567890") - .type_("RelationshipCredential") - .subject(object!(id: "did:iota:alice", spouse: "did:iota:bob")) - .subject(object!(id: "did:iota:bob", spouse: "did:iota:alice")) - .issuance_date("2010-01-01T19:23:24Z") - .expiration_date("2020-01-01T19:23:24Z") - .build() - .unwrap(); - - assert_eq!(credential.issuer.uri(), "did:example:i55u3r"); - - assert_eq!(credential.context.len(), 3); - assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); - - assert_eq!(credential.id, Some("did:example:1234567890".into())); - - assert_eq!(credential.types.len(), 2); - assert_eq!(credential.types.get(0).unwrap(), Credential::BASE_TYPE); - - assert_eq!(credential.credential_subject.len(), 2); - - assert_eq!( - credential.credential_subject.get(0).unwrap()["id"], - "did:iota:alice".into() - ); - - assert_eq!( - credential.credential_subject.get(1).unwrap()["id"], - "did:iota:bob".into() - ); -} - -#[test] -#[should_panic = "Not enough subjects"] -fn test_validate_credential_subject_none() { - CredentialBuilder::new().issuer("did:issuer").build().unwrap(); -} - -#[test] -#[should_panic = "Invalid credential subject (empty)"] -fn test_validate_credential_subject_empty() { - CredentialBuilder::new() - .issuer("did:issuer") - .subject(object!()) - .build() - .unwrap(); -} - -#[test] -#[should_panic = "Missing issuer"] -fn test_validate_issuer_none() { - CredentialBuilder::new() - .subject(object!(id: "did:sub")) - .build() - .unwrap(); -} - -#[test] -#[should_panic = "Invalid URI `foo`"] -fn test_validate_issuer_bad_uri_1() { - CredentialBuilder::new() - .subject(object!(id: "did:sub")) - .issuer("foo") - .build() - .unwrap(); -} - -#[test] -#[should_panic = "Invalid URI `did123`"] -fn test_validate_issuer_bad_uri_2() { - CredentialBuilder::new() - .subject(object!(id: "did:sub")) - .issuer("did123") - .build() - .unwrap(); -} - -#[test] -#[should_panic = "Invalid issuance date (empty)"] -fn test_validate_issuance_date_empty() { - CredentialBuilder::new() - .issuer("did:issuer") - .subject(object!(id: "did:sub")) - .build() - .unwrap(); -} - -#[test] -#[should_panic = "Invalid issuance date (premature end of input)"] -fn test_validate_issuance_date_bad_fmt() { - CredentialBuilder::new() - .issuer("did:issuer") - .subject(object!(id: "did:sub")) - .issuance_date("woo") - .build() - .unwrap(); -} diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs new file mode 100644 index 0000000000..b7396ffcc0 --- /dev/null +++ b/identity_vc/tests/credential.rs @@ -0,0 +1,88 @@ +#[macro_use] +extern crate identity_vc; + +#[macro_use] +mod macros; + +use identity_vc::prelude::*; + +#[test] +fn test_builder_valid() { + let issuance = timestamp!("2010-01-01T00:00:00Z"); + + let credential = CredentialBuilder::new() + .issuer("did:example:issuer") + .context("https://www.w3.org/2018/credentials/examples/v1") + .context(object!(id: "did:context:1234", type: "CustomContext2020")) + .id("did:example:123") + .type_("RelationshipCredential") + .try_subject(object!(id: "did:iota:alice", spouse: "did:iota:bob")) + .unwrap() + .try_subject(object!(id: "did:iota:bob", spouse: "did:iota:alice")) + .unwrap() + .issuance_date(issuance) + .build() + .unwrap(); + + assert_eq!(credential.context.len(), 3); + assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(credential.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + + assert_eq!(credential.id, Some("did:example:123".into())); + + assert_eq!(credential.types.len(), 2); + assert_eq!(credential.types.get(0).unwrap(), Credential::BASE_TYPE); + assert_eq!(credential.types.get(1).unwrap(), "RelationshipCredential"); + + assert_eq!(credential.credential_subject.len(), 2); + assert_eq!( + credential.credential_subject.get(0).unwrap().id, + Some("did:iota:alice".into()) + ); + assert_eq!( + credential.credential_subject.get(1).unwrap().id, + Some("did:iota:bob".into()) + ); + + assert_eq!(credential.issuer.uri(), "did:example:issuer"); + + assert_eq!(credential.issuance_date, issuance); +} + +#[test] +#[should_panic = "Missing `Credential` subject"] +fn test_builder_missing_subjects() { + CredentialBuilder::new().issuer("did:issuer").build().unwrap(); +} + +#[test] +#[should_panic = "Invalid `Credential` subject"] +fn test_builder_invalid_subjects() { + CredentialBuilder::new() + .issuer("did:issuer") + .try_subject(object!()) + .unwrap() + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Missing issuer"] +fn test_builder_missing_issuer() { + CredentialBuilder::new() + .try_subject(object!(id: "did:sub")) + .unwrap() + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Invalid URI for Credential issuer"] +fn test_builder_invalid_issuer() { + CredentialBuilder::new() + .try_subject(object!(id: "did:sub")) + .unwrap() + .issuer("foo") + .build() + .unwrap(); +} diff --git a/identity_vc/tests/macros.rs b/identity_vc/tests/macros.rs new file mode 100644 index 0000000000..890233fc60 --- /dev/null +++ b/identity_vc/tests/macros.rs @@ -0,0 +1,12 @@ +macro_rules! assert_matches { + ($($tt:tt)*) => { + assert!(matches!($($tt)*)) + }; +} + +macro_rules! timestamp { + ($expr:expr) => {{ + use ::std::convert::TryFrom; + ::identity_vc::prelude::Timestamp::try_from($expr).unwrap() + }}; +} diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs new file mode 100644 index 0000000000..c3f65f527a --- /dev/null +++ b/identity_vc/tests/presentation.rs @@ -0,0 +1,103 @@ +#[macro_use] +extern crate identity_vc; + +#[macro_use] +mod macros; + +use identity_vc::prelude::*; + +#[test] +fn test_builder_valid() { + let issuance = timestamp!("2010-01-01T00:00:00Z"); + + let credential = CredentialBuilder::new() + .issuer("did:example:issuer") + .context("https://www.w3.org/2018/credentials/examples/v1") + .type_("PrescriptionCredential") + .try_subject(object!(id: "did:iota:alice")) + .unwrap() + .issuance_date(issuance) + .build() + .unwrap(); + + let verifiable = VerifiableCredential::new(credential, object!()); + + let presentation = PresentationBuilder::new() + .context("https://www.w3.org/2018/credentials/examples/v1") + .id("did:example:id:123") + .type_("PrescriptionCredential") + .credential(verifiable.clone()) + .try_refresh_service(object!(id: "", type: "Refresh2020")) + .unwrap() + .try_terms_of_use(object!(type: "Policy2019")) + .unwrap() + .try_terms_of_use(object!(type: "Policy2020")) + .unwrap() + .build() + .unwrap(); + + assert_eq!(presentation.context.len(), 2); + assert_matches!(presentation.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(presentation.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + + assert_eq!(presentation.id, Some("did:example:id:123".into())); + + assert_eq!(presentation.types.len(), 2); + assert_eq!(presentation.types.get(0).unwrap(), Presentation::BASE_TYPE); + assert_eq!(presentation.types.get(1).unwrap(), "PrescriptionCredential"); + + assert_eq!(presentation.verifiable_credential.len(), 1); + assert_eq!(presentation.verifiable_credential.get(0).unwrap(), &verifiable); + + assert_eq!(presentation.refresh_service.unwrap().len(), 1); + assert_eq!(presentation.terms_of_use.unwrap().len(), 2); +} + +#[test] +#[should_panic = "Invalid URI for Presentation id"] +fn test_builder_invalid_id_fmt() { + PresentationBuilder::new().id("foo").build().unwrap(); +} + +#[test] +#[should_panic = "Invalid URI for Presentation holder"] +fn test_builder_invalid_holder_fmt() { + PresentationBuilder::new() + .id("did:iota:123") + .holder("d00m") + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Can't convert `Object` to `RefreshService`"] +fn test_builder_invalid_refresh_service_missing_id() { + PresentationBuilder::new() + .id("did:iota:123") + .try_refresh_service(object!(type: "RefreshServiceType")) + .unwrap() + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Can't convert `Object` to `RefreshService`"] +fn test_builder_invalid_refresh_service_missing_type() { + PresentationBuilder::new() + .id("did:iota:123") + .try_refresh_service(object!(id: "did:iota:rsv:123")) + .unwrap() + .build() + .unwrap(); +} + +#[test] +#[should_panic = "Can't convert `Object` to `TermsOfUse`"] +fn test_builder_invalid_terms_of_use_missing_type() { + PresentationBuilder::new() + .id("did:iota:123") + .try_terms_of_use(object!(id: "did:iota:rsv:123")) + .unwrap() + .build() + .unwrap(); +} diff --git a/identity_vc/tests/serde.rs b/identity_vc/tests/serde.rs new file mode 100644 index 0000000000..38aa248c27 --- /dev/null +++ b/identity_vc/tests/serde.rs @@ -0,0 +1,38 @@ +use identity_vc::prelude::*; +use serde_json::from_str; + +fn try_credential(data: &(impl AsRef + ?Sized)) { + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() +} + +fn try_presentation(data: &(impl AsRef + ?Sized)) { + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() +} + +#[test] +fn test_parse_credential_examples() { + try_credential(include_str!("input/example-01.json")); + try_credential(include_str!("input/example-02.json")); + try_credential(include_str!("input/example-03.json")); + try_credential(include_str!("input/example-04.json")); + try_credential(include_str!("input/example-05.json")); + try_credential(include_str!("input/example-06.json")); + try_credential(include_str!("input/example-07.json")); + + try_credential(include_str!("input/example-09.json")); + try_credential(include_str!("input/example-10.json")); + try_credential(include_str!("input/example-11.json")); + try_credential(include_str!("input/example-12.json")); + try_credential(include_str!("input/example-13.json")); +} + +#[test] +fn test_parse_presentation_examples() { + try_presentation(include_str!("input/example-08.json")); +} From 3bd65d11a6e949b9b9ba5c4e0260cd10d1c3e7d8 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 28 Aug 2020 12:29:06 -0700 Subject: [PATCH 19/44] More flexible Credential/Presentation construction --- identity_vc/src/common/value.rs | 46 ++++++++++++++++++++------------- identity_vc/src/credential.rs | 5 ++++ identity_vc/src/presentation.rs | 7 ++++- 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/identity_vc/src/common/value.rs b/identity_vc/src/common/value.rs index c87415eefc..88434ac797 100644 --- a/identity_vc/src/common/value.rs +++ b/identity_vc/src/common/value.rs @@ -38,24 +38,6 @@ impl From for Value { } } -impl From for Value { - fn from(other: u64) -> Self { - Self::Number(Number::UInt(other)) - } -} - -impl From for Value { - fn from(other: i64) -> Self { - Self::Number(Number::SInt(other)) - } -} - -impl From for Value { - fn from(other: f64) -> Self { - Self::Number(Number::Float(other)) - } -} - impl From<&'_ str> for Value { fn from(other: &'_ str) -> Self { Self::String(other.into()) @@ -74,6 +56,12 @@ impl From for Value { } } +impl From for Value where T: Into { + fn from(other: T) -> Self { + Self::Number(other.into()) + } +} + impl From> for Value where T: Into, @@ -106,3 +94,25 @@ impl fmt::Debug for Number { } } } + +macro_rules! impl_number_primitive { + ($src:ty, $ident:ident) => { + impl_number_primitive!($src as $src, $ident); + }; + ($src:ty as $dst:ty, $ident:ident) => { + impl From<$src> for Number { + fn from(other: $src) -> Self { + Self::$ident(other as $dst) + } + } + }; + ($($src:ty),* as $dst:ty, $ident:ident) => { + $( + impl_number_primitive!($src as $dst, $ident); + )* + }; +} + +impl_number_primitive!(u8, u16, u32, u64 as u64, UInt); +impl_number_primitive!(i8, i16, i32, i64 as i64, SInt); +impl_number_primitive!(f32, f64 as f64, Float); diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index db4157be91..ab1f5d4a0d 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -142,6 +142,11 @@ impl CredentialBuilder { self } + pub fn property(mut self, key: impl Into, value: impl Into) -> Self { + self.properties.insert(key.into(), value.into()); + self + } + impl_builder_setter!(id, id, Option); impl_builder_setter!(subject, credential_subject, Vec); impl_builder_setter!(issuer, issuer, Option); diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 8631bddc18..817616d761 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,7 +1,7 @@ use anyhow::Result; use crate::{ - common::{Context, Object, OneOrMany, RefreshService, TermsOfUse, URI}, + common::{Context, Object, OneOrMany, RefreshService, TermsOfUse, URI, Value}, credential::Credential, error::Error, utils::validate_presentation_structure, @@ -108,6 +108,11 @@ impl PresentationBuilder { self } + pub fn property(mut self, key: impl Into, value: impl Into) -> Self { + self.properties.insert(key.into(), value.into()); + self + } + impl_builder_setter!(id, id, Option); impl_builder_setter!(credential, verifiable_credential, Vec); impl_builder_setter!(holder, holder, Option); From ce5c563ec73aff94500c3370ff7c372e77a65374 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Sun, 30 Aug 2020 14:27:11 -0700 Subject: [PATCH 20/44] Display timestamps consistently --- identity_vc/src/common/timestamp.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/identity_vc/src/common/timestamp.rs b/identity_vc/src/common/timestamp.rs index 8353c76b03..c1973ac410 100644 --- a/identity_vc/src/common/timestamp.rs +++ b/identity_vc/src/common/timestamp.rs @@ -1,24 +1,32 @@ -use chrono::{DateTime, Utc}; -use std::{convert::TryFrom, ops::Deref}; +use chrono::{DateTime, SecondsFormat, Utc}; +use std::{convert::TryFrom, fmt, ops::Deref}; use crate::error::Error; type Inner = DateTime; -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] +#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] pub struct Timestamp(Inner); impl Timestamp { + pub fn now() -> Self { + Self(Utc::now()) + } + pub fn into_inner(self) -> Inner { self.0 } + + pub fn to_rfc3339(&self) -> String { + self.0.to_rfc3339_opts(SecondsFormat::Secs, true) + } } impl Default for Timestamp { fn default() -> Self { - Self(Utc::now()) + Self::now() } } @@ -52,3 +60,15 @@ impl TryFrom<&'_ str> for Timestamp { } } } + +impl fmt::Debug for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.to_rfc3339()) + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_rfc3339()) + } +} From 49b7606e5de0a25aac3e87667e744260d0b1ecba Mon Sep 17 00:00:00 2001 From: l1h3r Date: Sun, 30 Aug 2020 14:28:02 -0700 Subject: [PATCH 21/44] Use OneOrMany::One when explicitly converting from single item Vec --- identity_vc/src/common/oom.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/identity_vc/src/common/oom.rs b/identity_vc/src/common/oom.rs index 8b1a39e8b6..0f322dfc61 100644 --- a/identity_vc/src/common/oom.rs +++ b/identity_vc/src/common/oom.rs @@ -83,8 +83,12 @@ impl From for OneOrMany { } impl From> for OneOrMany { - fn from(other: Vec) -> Self { - Self::Many(other) + fn from(mut other: Vec) -> Self { + if other.len() == 1 { + Self::One(other.pop().expect("infallible")) + } else { + Self::Many(other) + } } } From fc7fe3fd2dc2b8f722738a812ec0d6b7af0ee95e Mon Sep 17 00:00:00 2001 From: l1h3r Date: Sun, 30 Aug 2020 14:29:34 -0700 Subject: [PATCH 22/44] Replace custom Value with serde_json::Value --- identity_vc/src/common/value.rs | 113 ++++---------------------------- 1 file changed, 11 insertions(+), 102 deletions(-) diff --git a/identity_vc/src/common/value.rs b/identity_vc/src/common/value.rs index 88434ac797..238a1bf41b 100644 --- a/identity_vc/src/common/value.rs +++ b/identity_vc/src/common/value.rs @@ -1,54 +1,12 @@ -use std::fmt; +use std::iter::FromIterator; +use serde_json::Map; -use crate::common::{Object, URI}; - -#[derive(Clone, PartialEq, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Value { - Null, - Boolean(bool), - Number(Number), - String(String), - Array(Vec), - Object(Object), -} - -impl fmt::Debug for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Null => write!(f, "Null"), - Self::Boolean(inner) => fmt::Debug::fmt(inner, f), - Self::Number(inner) => fmt::Debug::fmt(inner, f), - Self::String(inner) => fmt::Debug::fmt(inner, f), - Self::Array(inner) => fmt::Debug::fmt(inner, f), - Self::Object(inner) => fmt::Debug::fmt(inner, f), - } - } -} - -impl From<()> for Value { - fn from(_: ()) -> Self { - Self::Null - } -} - -impl From for Value { - fn from(other: bool) -> Self { - Self::Boolean(other) - } -} - -impl From<&'_ str> for Value { - fn from(other: &'_ str) -> Self { - Self::String(other.into()) - } -} +/// Re-export `Value` from `serde_json` as the catch-all value type. +/// +/// It's not ONLY compatible with JSON (implements Deserialize/Serialize) +pub use serde_json::Value; -impl From for Value { - fn from(other: String) -> Self { - Self::String(other) - } -} +use crate::common::{Object, URI}; impl From for Value { fn from(other: URI) -> Self { @@ -56,63 +14,14 @@ impl From for Value { } } -impl From for Value where T: Into { - fn from(other: T) -> Self { - Self::Number(other.into()) - } -} - -impl From> for Value -where - T: Into, -{ - fn from(other: Vec) -> Self { - Self::Array(other.into_iter().map(Into::into).collect()) +impl From for Map { + fn from(other: Object) -> Self { + Map::from_iter(other.into_inner().into_iter()) } } impl From for Value { fn from(other: Object) -> Self { - Self::Object(other) + Value::Object(other.into()) } } - -#[derive(Clone, Copy, PartialEq, PartialOrd, Deserialize, Serialize)] -#[serde(untagged)] -pub enum Number { - UInt(u64), - SInt(i64), - Float(f64), -} - -impl fmt::Debug for Number { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::UInt(inner) => fmt::Debug::fmt(inner, f), - Self::SInt(inner) => fmt::Debug::fmt(inner, f), - Self::Float(inner) => fmt::Debug::fmt(inner, f), - } - } -} - -macro_rules! impl_number_primitive { - ($src:ty, $ident:ident) => { - impl_number_primitive!($src as $src, $ident); - }; - ($src:ty as $dst:ty, $ident:ident) => { - impl From<$src> for Number { - fn from(other: $src) -> Self { - Self::$ident(other as $dst) - } - } - }; - ($($src:ty),* as $dst:ty, $ident:ident) => { - $( - impl_number_primitive!($src as $dst, $ident); - )* - }; -} - -impl_number_primitive!(u8, u16, u32, u64 as u64, UInt); -impl_number_primitive!(i8, i16, i32, i64 as i64, SInt); -impl_number_primitive!(f32, f64 as f64, Float); From 19e76286820200381147a75f30fd36cf2ef0f793 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:17:20 -0700 Subject: [PATCH 23/44] Use identity_core types --- identity_vc/Cargo.toml | 2 + identity_vc/src/common/context.rs | 3 +- .../common/credential/credential_schema.rs | 3 +- .../common/credential/credential_status.rs | 3 +- .../common/credential/credential_subject.rs | 3 +- identity_vc/src/common/credential/evidence.rs | 3 +- identity_vc/src/common/credential/mod.rs | 32 +------- .../src/common/credential/refresh_service.rs | 3 +- .../src/common/credential/terms_of_use.rs | 3 +- identity_vc/src/common/credential/utils.rs | 31 ++++++++ identity_vc/src/common/issuer.rs | 4 +- identity_vc/src/common/mod.rs | 5 +- identity_vc/src/common/object.rs | 53 ------------- identity_vc/src/common/timestamp.rs | 74 ------------------- identity_vc/src/common/uri.rs | 13 ++-- identity_vc/src/common/value.rs | 27 ------- identity_vc/src/credential.rs | 10 +-- identity_vc/src/error.rs | 4 +- identity_vc/src/lib.rs | 7 +- identity_vc/src/macros.rs | 33 ++------- identity_vc/src/presentation.rs | 6 +- identity_vc/src/utils/validation.rs | 33 ++++----- identity_vc/src/verifiable/credential.rs | 6 +- identity_vc/src/verifiable/presentation.rs | 6 +- identity_vc/tests/credential.rs | 2 +- identity_vc/tests/macros.rs | 4 +- identity_vc/tests/presentation.rs | 2 +- 27 files changed, 104 insertions(+), 271 deletions(-) create mode 100644 identity_vc/src/common/credential/utils.rs delete mode 100644 identity_vc/src/common/object.rs delete mode 100644 identity_vc/src/common/timestamp.rs delete mode 100644 identity_vc/src/common/value.rs diff --git a/identity_vc/Cargo.toml b/identity_vc/Cargo.toml index dd9c5f5c61..fc997aeea2 100644 --- a/identity_vc/Cargo.toml +++ b/identity_vc/Cargo.toml @@ -26,3 +26,5 @@ serde_cbor = "0.11" # timestamps chrono = { version = "0.4", features = ["serde"] } + +identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "feat/common-core-types" } diff --git a/identity_vc/src/common/context.rs b/identity_vc/src/common/context.rs index 33b1c5f79b..3fd9be1535 100644 --- a/identity_vc/src/common/context.rs +++ b/identity_vc/src/common/context.rs @@ -1,6 +1,7 @@ +use identity_core::common::Object; use std::fmt; -use crate::common::{Object, URI}; +use crate::common::URI; /// A reference to a JSON-LD context #[derive(Clone, PartialEq, Deserialize, Serialize)] diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index c5ad3892e9..368307eaf6 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + common::{take_object_id, take_object_type, OneOrMany, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index eb370eb489..4cf73d3e2c 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + common::{take_object_id, take_object_type, OneOrMany, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/credential_subject.rs b/identity_vc/src/common/credential/credential_subject.rs index 5869f8d86e..12c6fd415c 100644 --- a/identity_vc/src/common/credential/credential_subject.rs +++ b/identity_vc/src/common/credential/credential_subject.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, Object, URI}, + common::{take_object_id, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index 00b2d49ad6..436efe5052 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, Object, OneOrMany}, + common::{take_object_id, take_object_type, OneOrMany}, error::Error, }; diff --git a/identity_vc/src/common/credential/mod.rs b/identity_vc/src/common/credential/mod.rs index 09d0acabc6..d58a602c86 100644 --- a/identity_vc/src/common/credential/mod.rs +++ b/identity_vc/src/common/credential/mod.rs @@ -4,37 +4,9 @@ mod credential_subject; mod evidence; mod refresh_service; mod terms_of_use; +mod utils; pub use self::{ credential_schema::*, credential_status::*, credential_subject::*, evidence::*, refresh_service::*, terms_of_use::*, + utils::*, }; - -use crate::common::{Object, OneOrMany, Value}; - -pub fn take_object_id(object: &mut Object) -> Option { - match object.remove("id") { - Some(Value::String(id)) => Some(id), - Some(_) => None, - None => None, - } -} - -pub fn take_object_type(object: &mut Object) -> Option> { - match object.remove("type") { - Some(Value::String(value)) => Some(value.into()), - Some(Value::Array(values)) => Some(collect_types(values)), - Some(_) | None => None, - } -} - -fn collect_types(values: Vec) -> OneOrMany { - let mut types: Vec = Vec::with_capacity(values.len()); - - for value in values { - if let Value::String(value) = value { - types.push(value); - } - } - - types.into() -} diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index a4516dc804..a0a9bc6030 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + common::{take_object_id, take_object_type, OneOrMany, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index 37e26b427a..154bd4cf75 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -1,7 +1,8 @@ +use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, Object, OneOrMany, URI}, + common::{take_object_id, take_object_type, OneOrMany, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs new file mode 100644 index 0000000000..66fb089986 --- /dev/null +++ b/identity_vc/src/common/credential/utils.rs @@ -0,0 +1,31 @@ +use identity_core::common::{Object, Value}; + +use crate::common::OneOrMany; + +pub fn take_object_id(object: &mut Object) -> Option { + match object.remove("id") { + Some(Value::String(id)) => Some(id), + Some(_) => None, + None => None, + } +} + +pub fn take_object_type(object: &mut Object) -> Option> { + match object.remove("type") { + Some(Value::String(value)) => Some(value.into()), + Some(Value::Array(values)) => Some(collect_types(values)), + Some(_) | None => None, + } +} + +fn collect_types(values: Vec) -> OneOrMany { + let mut types: Vec = Vec::with_capacity(values.len()); + + for value in values { + if let Value::String(value) = value { + types.push(value); + } + } + + types.into() +} diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs index 34cd81234e..e6ff967a48 100644 --- a/identity_vc/src/common/issuer.rs +++ b/identity_vc/src/common/issuer.rs @@ -1,4 +1,6 @@ -use crate::common::{Object, URI}; +use identity_core::common::Object; + +use crate::common::URI; /// TODO: /// - Deserialize single URI into object-style layout diff --git a/identity_vc/src/common/mod.rs b/identity_vc/src/common/mod.rs index c021d0c122..cd4c9edf99 100644 --- a/identity_vc/src/common/mod.rs +++ b/identity_vc/src/common/mod.rs @@ -1,10 +1,7 @@ mod context; mod credential; mod issuer; -mod object; mod oom; -mod timestamp; mod uri; -mod value; -pub use self::{context::*, credential::*, issuer::*, object::*, oom::*, timestamp::*, uri::*, value::*}; +pub use self::{context::*, credential::*, issuer::*, oom::*, uri::*}; diff --git a/identity_vc/src/common/object.rs b/identity_vc/src/common/object.rs deleted file mode 100644 index 9ebc7a05b9..0000000000 --- a/identity_vc/src/common/object.rs +++ /dev/null @@ -1,53 +0,0 @@ -use std::{ - collections::HashMap, - fmt, - ops::{Deref, DerefMut}, -}; - -use crate::common::Value; - -type Inner = HashMap; - -// An String -> Value `HashMap` wrapper -#[derive(Clone, Default, PartialEq, Deserialize, Serialize)] -#[repr(transparent)] -#[serde(transparent)] -pub struct Object(Inner); - -impl Object { - pub fn into_inner(self) -> Inner { - self.0 - } -} - -impl fmt::Debug for Object { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } -} - -impl Deref for Object { - type Target = Inner; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Object { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for Object { - fn from(other: Inner) -> Self { - Self(other) - } -} - -impl From for Inner { - fn from(other: Object) -> Self { - other.into_inner() - } -} diff --git a/identity_vc/src/common/timestamp.rs b/identity_vc/src/common/timestamp.rs deleted file mode 100644 index c1973ac410..0000000000 --- a/identity_vc/src/common/timestamp.rs +++ /dev/null @@ -1,74 +0,0 @@ -use chrono::{DateTime, SecondsFormat, Utc}; -use std::{convert::TryFrom, fmt, ops::Deref}; - -use crate::error::Error; - -type Inner = DateTime; - -#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] -#[repr(transparent)] -#[serde(transparent)] -pub struct Timestamp(Inner); - -impl Timestamp { - pub fn now() -> Self { - Self(Utc::now()) - } - - pub fn into_inner(self) -> Inner { - self.0 - } - - pub fn to_rfc3339(&self) -> String { - self.0.to_rfc3339_opts(SecondsFormat::Secs, true) - } -} - -impl Default for Timestamp { - fn default() -> Self { - Self::now() - } -} - -impl From for Timestamp { - fn from(other: Inner) -> Self { - Self(other) - } -} - -impl From for Inner { - fn from(other: Timestamp) -> Self { - other.into_inner() - } -} - -impl Deref for Timestamp { - type Target = Inner; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl TryFrom<&'_ str> for Timestamp { - type Error = Error; - - fn try_from(string: &'_ str) -> Result { - match DateTime::parse_from_rfc3339(string) { - Ok(datetime) => Ok(Self(datetime.into())), - Err(error) => Err(Error::InvalidTimestamp(error)), - } - } -} - -impl fmt::Debug for Timestamp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self.to_rfc3339()) - } -} - -impl fmt::Display for Timestamp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.to_rfc3339()) - } -} diff --git a/identity_vc/src/common/uri.rs b/identity_vc/src/common/uri.rs index 4b2211f465..72afc72c1c 100644 --- a/identity_vc/src/common/uri.rs +++ b/identity_vc/src/common/uri.rs @@ -29,15 +29,18 @@ impl Deref for URI { } } -impl From for URI -where - T: Into, -{ - fn from(other: T) -> Self { +impl From<&'_ str> for URI { + fn from(other: &'_ str) -> Self { Self(other.into()) } } +impl From for URI { + fn from(other: String) -> Self { + Self(other) + } +} + impl PartialEq for URI { fn eq(&self, other: &str) -> bool { self.0.eq(other) diff --git a/identity_vc/src/common/value.rs b/identity_vc/src/common/value.rs deleted file mode 100644 index 238a1bf41b..0000000000 --- a/identity_vc/src/common/value.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::iter::FromIterator; -use serde_json::Map; - -/// Re-export `Value` from `serde_json` as the catch-all value type. -/// -/// It's not ONLY compatible with JSON (implements Deserialize/Serialize) -pub use serde_json::Value; - -use crate::common::{Object, URI}; - -impl From for Value { - fn from(other: URI) -> Self { - Self::String(other.0) - } -} - -impl From for Map { - fn from(other: Object) -> Self { - Map::from_iter(other.into_inner().into_iter()) - } -} - -impl From for Value { - fn from(other: Object) -> Self { - Value::Object(other.into()) - } -} diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index ab1f5d4a0d..b4859ef50b 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,11 +1,11 @@ -use anyhow::Result; +use identity_core::common::{Object, Timestamp, Value}; use crate::{ common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, Object, OneOrMany, - RefreshService, TermsOfUse, Timestamp, Value, URI, + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, + TermsOfUse, URI, }, - error::Error, + error::{Error, Result}, utils::validate_credential_structure, verifiable::VerifiableCredential, }; @@ -176,7 +176,7 @@ impl CredentialBuilder { id: self.id, types: self.types.into(), credential_subject: self.credential_subject.into(), - issuer: self.issuer.ok_or_else(|| anyhow!("Missing issuer"))?, + issuer: self.issuer.ok_or_else(|| Error::MissingCredentialIssuer)?, issuance_date: self.issuance_date, expiration_date: self.expiration_date, credential_status: None, diff --git a/identity_vc/src/error.rs b/identity_vc/src/error.rs index c558cb7e13..95959c7545 100644 --- a/identity_vc/src/error.rs +++ b/identity_vc/src/error.rs @@ -4,7 +4,7 @@ use thiserror::Error as ThisError; #[derive(Debug, ThisError)] pub enum Error { - #[error("Can't convert `Object` to `{0}`")] + #[error("Cannot convert `Object` to `{0}`")] BadObjectConversion(&'static str), #[error("Missing base type for {0}")] MissingBaseType(&'static str), @@ -20,6 +20,8 @@ pub enum Error { MissingCredentialSubject, #[error("Invalid `Credential` subject")] InvalidCredentialSubject, + #[error("Missing `Credential` issuer")] + MissingCredentialIssuer, } pub type Result = StdResult; diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index d57d5257f2..2f2bd437e3 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate anyhow; - #[macro_use] extern crate serde; @@ -19,8 +16,8 @@ pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; pub mod prelude { pub use crate::{ common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, Number, Object, OneOrMany, - RefreshService, TermsOfUse, Timestamp, Value, URI, + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, + TermsOfUse, URI, }, credential::{Credential, CredentialBuilder}, presentation::{Presentation, PresentationBuilder}, diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs index 289f330bc7..4b62408d19 100644 --- a/identity_vc/src/macros.rs +++ b/identity_vc/src/macros.rs @@ -1,24 +1,3 @@ -#[macro_export] -macro_rules! object { - () => { - $crate::common::Object::default() - }; - ($($key:ident : $value:expr),* $(,)*) => { - { - let mut object = ::std::collections::HashMap::new(); - - $( - object.insert( - stringify!($key).to_string(), - $crate::common::Value::from($value), - ); - )* - - $crate::common::Object::from(object) - } - }; -} - macro_rules! impl_builder_setter { ($fn:ident, $field:ident, Option<$ty:ty>) => { pub fn $fn(mut self, value: impl Into<$ty>) -> Self { @@ -42,9 +21,9 @@ macro_rules! impl_builder_setter { macro_rules! impl_builder_try_setter { ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> ::anyhow::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, { use ::std::convert::TryFrom; <$ty>::try_from(value) @@ -56,9 +35,9 @@ macro_rules! impl_builder_try_setter { } }; ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> ::anyhow::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, { use ::std::convert::TryFrom; <$ty>::try_from(value) @@ -70,9 +49,9 @@ macro_rules! impl_builder_try_setter { } }; ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: T) -> ::anyhow::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, { use ::std::convert::TryFrom; <$ty>::try_from(value) diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 817616d761..962fce721c 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,9 +1,9 @@ -use anyhow::Result; +use identity_core::common::{Object, Value}; use crate::{ - common::{Context, Object, OneOrMany, RefreshService, TermsOfUse, URI, Value}, + common::{Context, OneOrMany, RefreshService, TermsOfUse, URI}, credential::Credential, - error::Error, + error::Result, utils::validate_presentation_structure, verifiable::{VerifiableCredential, VerifiablePresentation}, }; diff --git a/identity_vc/src/utils/validation.rs b/identity_vc/src/utils/validation.rs index 4896951af6..99bdde0d92 100644 --- a/identity_vc/src/utils/validation.rs +++ b/identity_vc/src/utils/validation.rs @@ -1,9 +1,7 @@ -use anyhow::Result; - use crate::{ common::{Context, OneOrMany, URI}, credential::Credential, - error::Error, + error::{Error, Result}, presentation::Presentation, }; @@ -21,17 +19,15 @@ pub fn validate_credential_structure(credential: &Credential) -> Result<()> { validate_uri("Credential issuer", credential.issuer.uri())?; // Credentials MUST have at least one subject - ensure!( - !credential.credential_subject.is_empty(), - Error::MissingCredentialSubject - ); + if credential.credential_subject.is_empty() { + return Err(Error::MissingCredentialSubject); + } // Each subject is defined as one or more properties - no empty objects for subject in credential.credential_subject.iter() { - ensure!( - subject.id.is_some() || !subject.properties.is_empty(), - Error::InvalidCredentialSubject - ); + if subject.id.is_none() && subject.properties.is_empty() { + return Err(Error::InvalidCredentialSubject); + } } Ok(()) @@ -59,7 +55,9 @@ pub fn validate_presentation_structure(presentation: &Presentation) -> Result<() } pub fn validate_types(name: &'static str, base: &str, types: &OneOrMany) -> Result<()> { - ensure!(types.contains(&base.into()), Error::MissingBaseType(name)); + if !types.contains(&base.into()) { + return Err(Error::MissingBaseType(name)); + } Ok(()) } @@ -68,8 +66,8 @@ pub fn validate_context(name: &'static str, context: &OneOrMany) -> Res // The first Credential/Presentation context MUST be a URI representing the base context match context.get(0) { Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), - Some(_) => bail!(Error::InvalidBaseContext(name)), - None => bail!(Error::MissingBaseContext(name)), + Some(_) => Err(Error::InvalidBaseContext(name)), + None => Err(Error::MissingBaseContext(name)), } } @@ -77,10 +75,9 @@ pub fn validate_uri(name: &'static str, uri: &URI) -> Result<()> { const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; // TODO: Proper URI validation - ensure!( - KNOWN.iter().any(|scheme| uri.starts_with(scheme)), - Error::InvalidURI(name), - ); + if !KNOWN.iter().any(|scheme| uri.starts_with(scheme)) { + return Err(Error::InvalidURI(name)); + } Ok(()) } diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs index f239f65ea8..2c5d23df8b 100644 --- a/identity_vc/src/verifiable/credential.rs +++ b/identity_vc/src/verifiable/credential.rs @@ -1,9 +1,7 @@ +use identity_core::common::Object; use std::ops::Deref; -use crate::{ - common::{Object, OneOrMany}, - credential::Credential, -}; +use crate::{common::OneOrMany, credential::Credential}; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiableCredential { diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs index b2a6df8af5..cea4615605 100644 --- a/identity_vc/src/verifiable/presentation.rs +++ b/identity_vc/src/verifiable/presentation.rs @@ -1,9 +1,7 @@ +use identity_core::common::Object; use std::ops::Deref; -use crate::{ - common::{Object, OneOrMany}, - presentation::Presentation, -}; +use crate::{common::OneOrMany, presentation::Presentation}; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiablePresentation { diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs index b7396ffcc0..00399ffc9c 100644 --- a/identity_vc/tests/credential.rs +++ b/identity_vc/tests/credential.rs @@ -1,5 +1,5 @@ #[macro_use] -extern crate identity_vc; +extern crate identity_core; #[macro_use] mod macros; diff --git a/identity_vc/tests/macros.rs b/identity_vc/tests/macros.rs index 890233fc60..b554ed28c1 100644 --- a/identity_vc/tests/macros.rs +++ b/identity_vc/tests/macros.rs @@ -1,12 +1,14 @@ +#[macro_export] macro_rules! assert_matches { ($($tt:tt)*) => { assert!(matches!($($tt)*)) }; } +#[macro_export] macro_rules! timestamp { ($expr:expr) => {{ use ::std::convert::TryFrom; - ::identity_vc::prelude::Timestamp::try_from($expr).unwrap() + ::identity_core::common::Timestamp::try_from($expr).unwrap() }}; } diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs index c3f65f527a..aaf80bff98 100644 --- a/identity_vc/tests/presentation.rs +++ b/identity_vc/tests/presentation.rs @@ -1,5 +1,5 @@ #[macro_use] -extern crate identity_vc; +extern crate identity_core; #[macro_use] mod macros; From 119d75f5a48504dde93271b8b9e646e44ffe8c35 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:17:59 -0700 Subject: [PATCH 24/44] Add to/from_json fns to credentials/presentations --- identity_vc/src/credential.rs | 9 +++++++++ identity_vc/src/error.rs | 4 ++++ identity_vc/src/presentation.rs | 11 ++++++++++- 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index b4859ef50b..7bc69fafc7 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, Timestamp, Value}; +use serde_json::{from_str, to_string}; use crate::{ common::{ @@ -74,6 +75,14 @@ impl Credential { pub fn validate(&self) -> Result<()> { validate_credential_structure(self) } + + pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + from_str(json.as_ref()).map_err(Error::DecodeJSON) + } + + pub fn to_json(&self) -> Result { + to_string(self).map_err(Error::EncodeJSON) + } } // ============================================================================= diff --git a/identity_vc/src/error.rs b/identity_vc/src/error.rs index 95959c7545..d35cfc6b2c 100644 --- a/identity_vc/src/error.rs +++ b/identity_vc/src/error.rs @@ -22,6 +22,10 @@ pub enum Error { InvalidCredentialSubject, #[error("Missing `Credential` issuer")] MissingCredentialIssuer, + #[error("Failed to decode JSON: {0}")] + DecodeJSON(serde_json::Error), + #[error("Failed to encode JSON: {0}")] + EncodeJSON(serde_json::Error), } pub type Result = StdResult; diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 962fce721c..5308705f5f 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,9 +1,10 @@ use identity_core::common::{Object, Value}; +use serde_json::{from_str, to_string}; use crate::{ common::{Context, OneOrMany, RefreshService, TermsOfUse, URI}, credential::Credential, - error::Result, + error::{Error, Result}, utils::validate_presentation_structure, verifiable::{VerifiableCredential, VerifiablePresentation}, }; @@ -52,6 +53,14 @@ impl Presentation { pub fn validate(&self) -> Result<()> { validate_presentation_structure(self) } + + pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + from_str(json.as_ref()).map_err(Error::DecodeJSON) + } + + pub fn to_json(&self) -> Result { + to_string(self).map_err(Error::EncodeJSON) + } } // ============================================================================= From e87fed4e4b0726c6646eb19ae28b88427e76e4de Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:18:16 -0700 Subject: [PATCH 25/44] Fix tests --- identity_vc/tests/credential.rs | 19 +++++++++++-------- identity_vc/tests/presentation.rs | 25 ++++++++++++++----------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs index 00399ffc9c..13f4b957c2 100644 --- a/identity_vc/tests/credential.rs +++ b/identity_vc/tests/credential.rs @@ -52,7 +52,10 @@ fn test_builder_valid() { #[test] #[should_panic = "Missing `Credential` subject"] fn test_builder_missing_subjects() { - CredentialBuilder::new().issuer("did:issuer").build().unwrap(); + CredentialBuilder::new() + .issuer("did:issuer") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] @@ -61,19 +64,19 @@ fn test_builder_invalid_subjects() { CredentialBuilder::new() .issuer("did:issuer") .try_subject(object!()) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } #[test] -#[should_panic = "Missing issuer"] +#[should_panic = "Missing `Credential` issuer"] fn test_builder_missing_issuer() { CredentialBuilder::new() .try_subject(object!(id: "did:sub")) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } #[test] @@ -81,8 +84,8 @@ fn test_builder_missing_issuer() { fn test_builder_invalid_issuer() { CredentialBuilder::new() .try_subject(object!(id: "did:sub")) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .issuer("foo") .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs index aaf80bff98..6923107953 100644 --- a/identity_vc/tests/presentation.rs +++ b/identity_vc/tests/presentation.rs @@ -56,7 +56,10 @@ fn test_builder_valid() { #[test] #[should_panic = "Invalid URI for Presentation id"] fn test_builder_invalid_id_fmt() { - PresentationBuilder::new().id("foo").build().unwrap(); + PresentationBuilder::new() + .id("foo") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] @@ -66,38 +69,38 @@ fn test_builder_invalid_holder_fmt() { .id("did:iota:123") .holder("d00m") .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } #[test] -#[should_panic = "Can't convert `Object` to `RefreshService`"] +#[should_panic = "Cannot convert `Object` to `RefreshService`"] fn test_builder_invalid_refresh_service_missing_id() { PresentationBuilder::new() .id("did:iota:123") .try_refresh_service(object!(type: "RefreshServiceType")) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } #[test] -#[should_panic = "Can't convert `Object` to `RefreshService`"] +#[should_panic = "Cannot convert `Object` to `RefreshService`"] fn test_builder_invalid_refresh_service_missing_type() { PresentationBuilder::new() .id("did:iota:123") .try_refresh_service(object!(id: "did:iota:rsv:123")) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } #[test] -#[should_panic = "Can't convert `Object` to `TermsOfUse`"] +#[should_panic = "Cannot convert `Object` to `TermsOfUse`"] fn test_builder_invalid_terms_of_use_missing_type() { PresentationBuilder::new() .id("did:iota:123") .try_terms_of_use(object!(id: "did:iota:rsv:123")) - .unwrap() + .unwrap_or_else(|error| panic!("{}", error)) .build() - .unwrap(); + .unwrap_or_else(|error| panic!("{}", error)); } From 62971cd040f41216c72de92ad952798cce81fc93 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:36:12 -0700 Subject: [PATCH 26/44] Expose Error/Result in prelude --- identity_vc/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 2f2bd437e3..0e7f21a9bb 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -19,6 +19,7 @@ pub mod prelude { Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, TermsOfUse, URI, }, + error::{Error, Result}, credential::{Credential, CredentialBuilder}, presentation::{Presentation, PresentationBuilder}, verifiable::{VerifiableCredential, VerifiablePresentation}, From 6b0690c5ab16a2022bcee925c1f8b62bd568fdfc Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:36:48 -0700 Subject: [PATCH 27/44] More generic TryFrom for builder fns --- identity_vc/src/macros.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs index 4b62408d19..a3fd40d3b3 100644 --- a/identity_vc/src/macros.rs +++ b/identity_vc/src/macros.rs @@ -21,9 +21,10 @@ macro_rules! impl_builder_setter { macro_rules! impl_builder_try_setter { ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, { use ::std::convert::TryFrom; <$ty>::try_from(value) @@ -35,9 +36,10 @@ macro_rules! impl_builder_try_setter { } }; ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, { use ::std::convert::TryFrom; <$ty>::try_from(value) @@ -49,9 +51,10 @@ macro_rules! impl_builder_try_setter { } }; ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result + pub fn $fn(mut self, value: T) -> $crate::error::Result where - $ty: ::std::convert::TryFrom, + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, { use ::std::convert::TryFrom; <$ty>::try_from(value) From e6af56fd4a66a9eb7eb9e2e720bc39663e2ea720 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:54:43 -0700 Subject: [PATCH 28/44] Less-repetitive object conversions --- .../src/common/credential/credential_schema.rs | 14 +++----------- .../src/common/credential/credential_status.rs | 14 +++----------- identity_vc/src/common/credential/evidence.rs | 9 ++------- .../src/common/credential/refresh_service.rs | 14 +++----------- identity_vc/src/common/credential/terms_of_use.rs | 9 ++------- identity_vc/src/common/credential/utils.rs | 13 ++++++++++++- identity_vc/src/lib.rs | 2 +- 7 files changed, 26 insertions(+), 49 deletions(-) diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index 368307eaf6..6da2706cca 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, error::Error, }; @@ -24,16 +24,8 @@ impl TryFrom for CredentialSchema { fn try_from(mut other: Object) -> Result { let mut this: Self = Default::default(); - this.id = match take_object_id(&mut other) { - Some(id) => id.into(), - None => return Err(Error::BadObjectConversion("CredentialSchema")), - }; - - this.types = match take_object_type(&mut other) { - Some(types) => types, - None => return Err(Error::BadObjectConversion("CredentialSchema")), - }; - + this.id = try_take_object_id("CredentialSchema", &mut other)?.into(); + this.types = try_take_object_type("CredentialSchema", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index 4cf73d3e2c..88a2238e1f 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, error::Error, }; @@ -24,16 +24,8 @@ impl TryFrom for CredentialStatus { fn try_from(mut other: Object) -> Result { let mut this: Self = Default::default(); - this.id = match take_object_id(&mut other) { - Some(id) => id.into(), - None => return Err(Error::BadObjectConversion("CredentialStatus")), - }; - - this.types = match take_object_type(&mut other) { - Some(types) => types, - None => return Err(Error::BadObjectConversion("CredentialStatus")), - }; - + this.id = try_take_object_id("CredentialStatus", &mut other)?.into(); + this.types = try_take_object_type("CredentialStatus", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index 436efe5052..9f2b17d7c4 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, OneOrMany}, + common::{take_object_id, try_take_object_type, OneOrMany}, error::Error, }; @@ -26,12 +26,7 @@ impl TryFrom for Evidence { let mut this: Self = Default::default(); this.id = take_object_id(&mut other); - - this.types = match take_object_type(&mut other) { - Some(types) => types, - None => return Err(Error::BadObjectConversion("Evidence")), - }; - + this.types = try_take_object_type("Evidence", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index a0a9bc6030..b132242755 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, error::Error, }; @@ -24,16 +24,8 @@ impl TryFrom for RefreshService { fn try_from(mut other: Object) -> Result { let mut this: Self = Default::default(); - this.id = match take_object_id(&mut other) { - Some(id) => id.into(), - None => return Err(Error::BadObjectConversion("RefreshService")), - }; - - this.types = match take_object_type(&mut other) { - Some(types) => types, - None => return Err(Error::BadObjectConversion("RefreshService")), - }; - + this.id = try_take_object_id("RefreshService", &mut other)?.into(); + this.types = try_take_object_type("RefreshService", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index 154bd4cf75..b9e2483977 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, take_object_type, OneOrMany, URI}, + common::{take_object_id, try_take_object_type, OneOrMany, URI}, error::Error, }; @@ -27,12 +27,7 @@ impl TryFrom for TermsOfUse { let mut this: Self = Default::default(); this.id = take_object_id(&mut other).map(Into::into); - - this.types = match take_object_type(&mut other) { - Some(types) => types, - None => return Err(Error::BadObjectConversion("TermsOfUse")), - }; - + this.types = try_take_object_type("TermsOfUse", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs index 66fb089986..1598b5d7eb 100644 --- a/identity_vc/src/common/credential/utils.rs +++ b/identity_vc/src/common/credential/utils.rs @@ -1,6 +1,9 @@ use identity_core::common::{Object, Value}; -use crate::common::OneOrMany; +use crate::{ + common::OneOrMany, + error::{Error, Result}, +}; pub fn take_object_id(object: &mut Object) -> Option { match object.remove("id") { @@ -10,6 +13,10 @@ pub fn take_object_id(object: &mut Object) -> Option { } } +pub fn try_take_object_id(name: &'static str, object: &mut Object) -> Result { + take_object_id(object).ok_or_else(|| Error::BadObjectConversion(name)) +} + pub fn take_object_type(object: &mut Object) -> Option> { match object.remove("type") { Some(Value::String(value)) => Some(value.into()), @@ -18,6 +25,10 @@ pub fn take_object_type(object: &mut Object) -> Option> { } } +pub fn try_take_object_type(name: &'static str, object: &mut Object) -> Result> { + take_object_type(object).ok_or_else(|| Error::BadObjectConversion(name)) +} + fn collect_types(values: Vec) -> OneOrMany { let mut types: Vec = Vec::with_capacity(values.len()); diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 0e7f21a9bb..b5247ef13b 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -19,8 +19,8 @@ pub mod prelude { Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, TermsOfUse, URI, }, - error::{Error, Result}, credential::{Credential, CredentialBuilder}, + error::{Error, Result}, presentation::{Presentation, PresentationBuilder}, verifiable::{VerifiableCredential, VerifiablePresentation}, }; From 46cb9e382a7308fef2359cbf4fc8d2f2b672b724 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 31 Aug 2020 12:55:35 -0700 Subject: [PATCH 29/44] cargo fmt --- identity_vc/bin/vc_test_suite.rs | 40 +- identity_vc/src/common/context.rs | 28 +- .../common/credential/credential_schema.rs | 30 +- .../common/credential/credential_status.rs | 30 +- .../common/credential/credential_subject.rs | 26 +- identity_vc/src/common/credential/evidence.rs | 32 +- identity_vc/src/common/credential/mod.rs | 4 +- .../src/common/credential/refresh_service.rs | 30 +- .../src/common/credential/terms_of_use.rs | 32 +- identity_vc/src/common/credential/utils.rs | 40 +- identity_vc/src/common/issuer.rs | 30 +- identity_vc/src/common/oom.rs | 144 +++---- identity_vc/src/common/uri.rs | 38 +- identity_vc/src/credential.rs | 371 +++++++++--------- identity_vc/src/error.rs | 44 +-- identity_vc/src/lib.rs | 20 +- identity_vc/src/macros.rs | 126 +++--- identity_vc/src/presentation.rs | 247 ++++++------ identity_vc/src/utils/validation.rs | 110 +++--- identity_vc/src/verifiable/credential.rs | 48 +-- identity_vc/src/verifiable/presentation.rs | 48 +-- identity_vc/tests/credential.rs | 106 ++--- identity_vc/tests/macros.rs | 8 +- identity_vc/tests/presentation.rs | 140 +++---- identity_vc/tests/serde.rs | 42 +- 25 files changed, 906 insertions(+), 908 deletions(-) diff --git a/identity_vc/bin/vc_test_suite.rs b/identity_vc/bin/vc_test_suite.rs index 37b306143c..0695eb32c0 100644 --- a/identity_vc/bin/vc_test_suite.rs +++ b/identity_vc/bin/vc_test_suite.rs @@ -4,31 +4,31 @@ use serde_json::{from_reader, to_string}; use std::{env::args, fs::File, path::Path}; fn main() -> Result<()> { - let args: Vec = args().collect(); + let args: Vec = args().collect(); - match args[1].as_str() { - "test-credential" => { - let path: &Path = Path::new(&args[2]); - let file: File = File::open(path)?; - let data: VerifiableCredential = from_reader(file)?; + match args[1].as_str() { + "test-credential" => { + let path: &Path = Path::new(&args[2]); + let file: File = File::open(path)?; + let data: VerifiableCredential = from_reader(file)?; - data.validate()?; + data.validate()?; - println!("{}", to_string(&data)?); - } - "test-presentation" => { - let path: &Path = Path::new(&args[2]); - let file: File = File::open(path)?; - let data: VerifiablePresentation = from_reader(file)?; + println!("{}", to_string(&data)?); + } + "test-presentation" => { + let path: &Path = Path::new(&args[2]); + let file: File = File::open(path)?; + let data: VerifiablePresentation = from_reader(file)?; - data.validate()?; + data.validate()?; - println!("{}", to_string(&data)?); - } - test => { - panic!("Unknown Test: {:?}", test); + println!("{}", to_string(&data)?); + } + test => { + panic!("Unknown Test: {:?}", test); + } } - } - Ok(()) + Ok(()) } diff --git a/identity_vc/src/common/context.rs b/identity_vc/src/common/context.rs index 3fd9be1535..126843bc82 100644 --- a/identity_vc/src/common/context.rs +++ b/identity_vc/src/common/context.rs @@ -7,30 +7,30 @@ use crate::common::URI; #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Context { - URI(URI), - OBJ(Object), + URI(URI), + OBJ(Object), } impl fmt::Debug for Context { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::URI(inner) => fmt::Debug::fmt(inner, f), - Self::OBJ(inner) => fmt::Debug::fmt(inner, f), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::URI(inner) => fmt::Debug::fmt(inner, f), + Self::OBJ(inner) => fmt::Debug::fmt(inner, f), + } } - } } impl From for Context where - T: Into, + T: Into, { - fn from(other: T) -> Self { - Self::URI(other.into()) - } + fn from(other: T) -> Self { + Self::URI(other.into()) + } } impl From for Context { - fn from(other: Object) -> Self { - Self::OBJ(other) - } + fn from(other: Object) -> Self { + Self::OBJ(other) + } } diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index 6da2706cca..f4ae397b36 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, - error::Error, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + error::Error, }; /// Information used to validate the structure of a `Credential`. @@ -11,23 +11,23 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#data-schemas #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialSchema { - pub id: URI, - #[serde(rename = "type")] - pub types: OneOrMany, - #[serde(flatten)] - pub properties: Object, + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for CredentialSchema { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = try_take_object_id("CredentialSchema", &mut other)?.into(); - this.types = try_take_object_type("CredentialSchema", &mut other)?; - this.properties = other; + this.id = try_take_object_id("CredentialSchema", &mut other)?.into(); + this.types = try_take_object_type("CredentialSchema", &mut other)?; + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index 88a2238e1f..c928a7b5be 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, - error::Error, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + error::Error, }; /// Information used to determine the current status of a `Credential`. @@ -11,23 +11,23 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#status #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialStatus { - pub id: URI, - #[serde(rename = "type")] - pub types: OneOrMany, - #[serde(flatten)] - pub properties: Object, + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for CredentialStatus { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = try_take_object_id("CredentialStatus", &mut other)?.into(); - this.types = try_take_object_type("CredentialStatus", &mut other)?; - this.properties = other; + this.id = try_take_object_id("CredentialStatus", &mut other)?.into(); + this.types = try_take_object_type("CredentialStatus", &mut other)?; + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/credential_subject.rs b/identity_vc/src/common/credential/credential_subject.rs index 12c6fd415c..bcbd0f7666 100644 --- a/identity_vc/src/common/credential/credential_subject.rs +++ b/identity_vc/src/common/credential/credential_subject.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, URI}, - error::Error, + common::{take_object_id, URI}, + error::Error, }; /// An entity who is the target of a set of claims. @@ -11,21 +11,21 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#credential-subject #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialSubject { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(flatten)] - pub properties: Object, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for CredentialSubject { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = take_object_id(&mut other).map(Into::into); - this.properties = other; + this.id = take_object_id(&mut other).map(Into::into); + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index 9f2b17d7c4..e03afe0b9f 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_type, OneOrMany}, - error::Error, + common::{take_object_id, try_take_object_type, OneOrMany}, + error::Error, }; /// Information used to increase confidence in the claims of a `Credential` @@ -11,24 +11,24 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#evidence #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct Evidence { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(rename = "type")] - pub types: OneOrMany, - #[serde(flatten)] - pub properties: Object, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for Evidence { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = take_object_id(&mut other); - this.types = try_take_object_type("Evidence", &mut other)?; - this.properties = other; + this.id = take_object_id(&mut other); + this.types = try_take_object_type("Evidence", &mut other)?; + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/mod.rs b/identity_vc/src/common/credential/mod.rs index d58a602c86..eb6ff2336e 100644 --- a/identity_vc/src/common/credential/mod.rs +++ b/identity_vc/src/common/credential/mod.rs @@ -7,6 +7,6 @@ mod terms_of_use; mod utils; pub use self::{ - credential_schema::*, credential_status::*, credential_subject::*, evidence::*, refresh_service::*, terms_of_use::*, - utils::*, + credential_schema::*, credential_status::*, credential_subject::*, evidence::*, refresh_service::*, + terms_of_use::*, utils::*, }; diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index b132242755..df0ee06b17 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, - error::Error, + common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + error::Error, }; /// Information used to refresh or assert the status of a `Credential`. @@ -11,23 +11,23 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#refreshing #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct RefreshService { - pub id: URI, - #[serde(rename = "type")] - pub types: OneOrMany, - #[serde(flatten)] - pub properties: Object, + pub id: URI, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for RefreshService { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = try_take_object_id("RefreshService", &mut other)?.into(); - this.types = try_take_object_type("RefreshService", &mut other)?; - this.properties = other; + this.id = try_take_object_id("RefreshService", &mut other)?.into(); + this.types = try_take_object_type("RefreshService", &mut other)?; + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index b9e2483977..a71fb116b4 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -2,8 +2,8 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_type, OneOrMany, URI}, - error::Error, + common::{take_object_id, try_take_object_type, OneOrMany, URI}, + error::Error, }; /// Information used to express obligations, prohibitions, and permissions about @@ -12,24 +12,24 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#terms-of-use #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct TermsOfUse { - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - #[serde(rename = "type")] - pub types: OneOrMany, - #[serde(flatten)] - pub properties: Object, + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(rename = "type")] + pub types: OneOrMany, + #[serde(flatten)] + pub properties: Object, } impl TryFrom for TermsOfUse { - type Error = Error; + type Error = Error; - fn try_from(mut other: Object) -> Result { - let mut this: Self = Default::default(); + fn try_from(mut other: Object) -> Result { + let mut this: Self = Default::default(); - this.id = take_object_id(&mut other).map(Into::into); - this.types = try_take_object_type("TermsOfUse", &mut other)?; - this.properties = other; + this.id = take_object_id(&mut other).map(Into::into); + this.types = try_take_object_type("TermsOfUse", &mut other)?; + this.properties = other; - Ok(this) - } + Ok(this) + } } diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs index 1598b5d7eb..bfae56f2b1 100644 --- a/identity_vc/src/common/credential/utils.rs +++ b/identity_vc/src/common/credential/utils.rs @@ -1,42 +1,42 @@ use identity_core::common::{Object, Value}; use crate::{ - common::OneOrMany, - error::{Error, Result}, + common::OneOrMany, + error::{Error, Result}, }; pub fn take_object_id(object: &mut Object) -> Option { - match object.remove("id") { - Some(Value::String(id)) => Some(id), - Some(_) => None, - None => None, - } + match object.remove("id") { + Some(Value::String(id)) => Some(id), + Some(_) => None, + None => None, + } } pub fn try_take_object_id(name: &'static str, object: &mut Object) -> Result { - take_object_id(object).ok_or_else(|| Error::BadObjectConversion(name)) + take_object_id(object).ok_or_else(|| Error::BadObjectConversion(name)) } pub fn take_object_type(object: &mut Object) -> Option> { - match object.remove("type") { - Some(Value::String(value)) => Some(value.into()), - Some(Value::Array(values)) => Some(collect_types(values)), - Some(_) | None => None, - } + match object.remove("type") { + Some(Value::String(value)) => Some(value.into()), + Some(Value::Array(values)) => Some(collect_types(values)), + Some(_) | None => None, + } } pub fn try_take_object_type(name: &'static str, object: &mut Object) -> Result> { - take_object_type(object).ok_or_else(|| Error::BadObjectConversion(name)) + take_object_type(object).ok_or_else(|| Error::BadObjectConversion(name)) } fn collect_types(values: Vec) -> OneOrMany { - let mut types: Vec = Vec::with_capacity(values.len()); + let mut types: Vec = Vec::with_capacity(values.len()); - for value in values { - if let Value::String(value) = value { - types.push(value); + for value in values { + if let Value::String(value) = value { + types.push(value); + } } - } - types.into() + types.into() } diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs index e6ff967a48..a723e41c19 100644 --- a/identity_vc/src/common/issuer.rs +++ b/identity_vc/src/common/issuer.rs @@ -8,28 +8,28 @@ use crate::common::URI; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Issuer { - URI(URI), - OBJ { - id: URI, - #[serde(flatten)] - object: Object, - }, + URI(URI), + OBJ { + id: URI, + #[serde(flatten)] + object: Object, + }, } impl Issuer { - pub fn uri(&self) -> &URI { - match self { - Self::URI(uri) => uri, - Self::OBJ { id, .. } => id, + pub fn uri(&self) -> &URI { + match self { + Self::URI(uri) => uri, + Self::OBJ { id, .. } => id, + } } - } } impl From for Issuer where - T: Into, + T: Into, { - fn from(other: T) -> Self { - Self::URI(other.into()) - } + fn from(other: T) -> Self { + Self::URI(other.into()) + } } diff --git a/identity_vc/src/common/oom.rs b/identity_vc/src/common/oom.rs index 0f322dfc61..5d67ae41db 100644 --- a/identity_vc/src/common/oom.rs +++ b/identity_vc/src/common/oom.rs @@ -4,116 +4,116 @@ use std::fmt; #[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[serde(untagged)] pub enum OneOrMany { - One(T), - Many(Vec), + One(T), + Many(Vec), } impl OneOrMany { - /// Returns the number of elements in the collection - pub fn len(&self) -> usize { - match self { - Self::One(_) => 1, - Self::Many(inner) => inner.len(), + /// Returns the number of elements in the collection + pub fn len(&self) -> usize { + match self { + Self::One(_) => 1, + Self::Many(inner) => inner.len(), + } } - } - /// Returns `true` if the collection is empty - pub fn is_empty(&self) -> bool { - match self { - Self::One(_) => false, - Self::Many(inner) => inner.is_empty(), + /// Returns `true` if the collection is empty + pub fn is_empty(&self) -> bool { + match self { + Self::One(_) => false, + Self::Many(inner) => inner.is_empty(), + } } - } - - /// Returns a reference to the element at the given index. - pub fn get(&self, index: usize) -> Option<&T> { - match self { - Self::One(inner) if index == 0 => Some(inner), - Self::One(_) => None, - Self::Many(inner) => inner.get(index), + + /// Returns a reference to the element at the given index. + pub fn get(&self, index: usize) -> Option<&T> { + match self { + Self::One(inner) if index == 0 => Some(inner), + Self::One(_) => None, + Self::Many(inner) => inner.get(index), + } } - } - - /// Returns `true` if the given value is represented in the collection. - pub fn contains(&self, value: &T) -> bool - where - T: PartialEq, - { - match self { - Self::One(inner) => inner == value, - Self::Many(inner) => inner.contains(value), + + /// Returns `true` if the given value is represented in the collection. + pub fn contains(&self, value: &T) -> bool + where + T: PartialEq, + { + match self { + Self::One(inner) => inner == value, + Self::Many(inner) => inner.contains(value), + } } - } - pub fn iter(&self) -> impl Iterator + '_ { - OneOrManyIter::new(self) - } + pub fn iter(&self) -> impl Iterator + '_ { + OneOrManyIter::new(self) + } - /// Consumes the `OneOrMany`, returning the contents as a `Vec`. - pub fn into_vec(self) -> Vec { - match self { - Self::One(inner) => vec![inner], - Self::Many(inner) => inner, + /// Consumes the `OneOrMany`, returning the contents as a `Vec`. + pub fn into_vec(self) -> Vec { + match self { + Self::One(inner) => vec![inner], + Self::Many(inner) => inner, + } } - } } impl fmt::Debug for OneOrMany where - T: fmt::Debug, + T: fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::One(inner) => fmt::Debug::fmt(inner, f), - Self::Many(inner) => fmt::Debug::fmt(inner, f), + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::One(inner) => fmt::Debug::fmt(inner, f), + Self::Many(inner) => fmt::Debug::fmt(inner, f), + } } - } } impl Default for OneOrMany { - fn default() -> Self { - Self::Many(Vec::new()) - } + fn default() -> Self { + Self::Many(Vec::new()) + } } impl From for OneOrMany { - fn from(other: T) -> Self { - Self::One(other) - } + fn from(other: T) -> Self { + Self::One(other) + } } impl From> for OneOrMany { - fn from(mut other: Vec) -> Self { - if other.len() == 1 { - Self::One(other.pop().expect("infallible")) - } else { - Self::Many(other) + fn from(mut other: Vec) -> Self { + if other.len() == 1 { + Self::One(other.pop().expect("infallible")) + } else { + Self::Many(other) + } } - } } impl From> for Vec { - fn from(other: OneOrMany) -> Self { - other.into_vec() - } + fn from(other: OneOrMany) -> Self { + other.into_vec() + } } struct OneOrManyIter<'a, T> { - inner: &'a OneOrMany, - index: usize, + inner: &'a OneOrMany, + index: usize, } impl<'a, T> OneOrManyIter<'a, T> { - pub fn new(inner: &'a OneOrMany) -> Self { - Self { inner, index: 0 } - } + pub fn new(inner: &'a OneOrMany) -> Self { + Self { inner, index: 0 } + } } impl<'a, T> Iterator for OneOrManyIter<'a, T> { - type Item = &'a T; + type Item = &'a T; - fn next(&mut self) -> Option { - self.index += 1; - self.inner.get(self.index - 1) - } + fn next(&mut self) -> Option { + self.index += 1; + self.inner.get(self.index - 1) + } } diff --git a/identity_vc/src/common/uri.rs b/identity_vc/src/common/uri.rs index 72afc72c1c..de534a6679 100644 --- a/identity_vc/src/common/uri.rs +++ b/identity_vc/src/common/uri.rs @@ -10,39 +10,39 @@ use std::{fmt, ops::Deref}; pub struct URI(pub(crate) String); impl URI { - pub fn into_inner(self) -> String { - self.0 - } + pub fn into_inner(self) -> String { + self.0 + } } impl fmt::Debug for URI { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "URI({:?})", self.0) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "URI({:?})", self.0) + } } impl Deref for URI { - type Target = String; + type Target = String; - fn deref(&self) -> &Self::Target { - &self.0 - } + fn deref(&self) -> &Self::Target { + &self.0 + } } impl From<&'_ str> for URI { - fn from(other: &'_ str) -> Self { - Self(other.into()) - } + fn from(other: &'_ str) -> Self { + Self(other.into()) + } } impl From for URI { - fn from(other: String) -> Self { - Self(other) - } + fn from(other: String) -> Self { + Self(other) + } } impl PartialEq for URI { - fn eq(&self, other: &str) -> bool { - self.0.eq(other) - } + fn eq(&self, other: &str) -> bool { + self.0.eq(other) + } } diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 7bc69fafc7..bade70faee 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -2,13 +2,13 @@ use identity_core::common::{Object, Timestamp, Value}; use serde_json::{from_str, to_string}; use crate::{ - common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, - TermsOfUse, URI, - }, - error::{Error, Result}, - utils::validate_credential_structure, - verifiable::VerifiableCredential, + common::{ + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, + TermsOfUse, URI, + }, + error::{Error, Result}, + utils::validate_credential_structure, + verifiable::VerifiableCredential, }; /// A `Credential` represents a set of claims describing an entity. @@ -16,73 +16,73 @@ use crate::{ /// `Credential`s can be combined with `Proof`s to create `VerifiableCredential`s. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Credential { - /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. - /// - /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` - #[serde(rename = "@context")] - pub context: OneOrMany, - /// A unique `URI` referencing the subject of the credential. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// One or more URIs defining the type of credential. - /// - /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly - /// passed as non-`URI` strings and expected to be processed with JSON-LD. - /// We're using a `String` here since we don't currently use JSON-LD and - /// don't have any immediate plans to do so. - #[serde(rename = "type")] - pub types: OneOrMany, - /// One or more `Object`s representing the `Credential` subject(s). - #[serde(rename = "credentialSubject")] - pub credential_subject: OneOrMany, - /// A reference to the issuer of the `Credential`. - pub issuer: Issuer, - /// The date and time the `Credential` becomes valid. - #[serde(rename = "issuanceDate")] - pub issuance_date: Timestamp, - /// The date and time the `Credential` is no longer considered valid. - #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] - pub expiration_date: Option, - /// TODO - #[serde(rename = "credentialStatus", skip_serializing_if = "Option::is_none")] - pub credential_status: Option>, - /// TODO - #[serde(rename = "credentialSchema", skip_serializing_if = "Option::is_none")] - pub credential_schema: Option>, - /// TODO - #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] - pub refresh_service: Option>, - /// The terms of use issued by the `Credential` issuer - #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] - pub terms_of_use: Option>, - /// TODO - #[serde(skip_serializing_if = "Option::is_none")] - pub evidence: Option>, - /// Indicates that the `Credential` must only be contained within a - /// `Presentation` with a proof issued from the `Credential` subject. - #[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")] - pub non_transferable: Option, - /// Miscellaneous properties. - #[serde(flatten)] - pub properties: Object, + /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. + /// + /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the credential. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of credential. + /// + /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly + /// passed as non-`URI` strings and expected to be processed with JSON-LD. + /// We're using a `String` here since we don't currently use JSON-LD and + /// don't have any immediate plans to do so. + #[serde(rename = "type")] + pub types: OneOrMany, + /// One or more `Object`s representing the `Credential` subject(s). + #[serde(rename = "credentialSubject")] + pub credential_subject: OneOrMany, + /// A reference to the issuer of the `Credential`. + pub issuer: Issuer, + /// The date and time the `Credential` becomes valid. + #[serde(rename = "issuanceDate")] + pub issuance_date: Timestamp, + /// The date and time the `Credential` is no longer considered valid. + #[serde(rename = "expirationDate", skip_serializing_if = "Option::is_none")] + pub expiration_date: Option, + /// TODO + #[serde(rename = "credentialStatus", skip_serializing_if = "Option::is_none")] + pub credential_status: Option>, + /// TODO + #[serde(rename = "credentialSchema", skip_serializing_if = "Option::is_none")] + pub credential_schema: Option>, + /// TODO + #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] + pub refresh_service: Option>, + /// The terms of use issued by the `Credential` issuer + #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] + pub terms_of_use: Option>, + /// TODO + #[serde(skip_serializing_if = "Option::is_none")] + pub evidence: Option>, + /// Indicates that the `Credential` must only be contained within a + /// `Presentation` with a proof issued from the `Credential` subject. + #[serde(rename = "nonTransferable", skip_serializing_if = "Option::is_none")] + pub non_transferable: Option, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: Object, } impl Credential { - pub const BASE_CONTEXT: &'static str = "https://www.w3.org/2018/credentials/v1"; + pub const BASE_CONTEXT: &'static str = "https://www.w3.org/2018/credentials/v1"; - pub const BASE_TYPE: &'static str = "VerifiableCredential"; + pub const BASE_TYPE: &'static str = "VerifiableCredential"; - pub fn validate(&self) -> Result<()> { - validate_credential_structure(self) - } + pub fn validate(&self) -> Result<()> { + validate_credential_structure(self) + } - pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { - from_str(json.as_ref()).map_err(Error::DecodeJSON) - } + pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + from_str(json.as_ref()).map_err(Error::DecodeJSON) + } - pub fn to_json(&self) -> Result { - to_string(self).map_err(Error::EncodeJSON) - } + pub fn to_json(&self) -> Result { + to_string(self).map_err(Error::EncodeJSON) + } } // ============================================================================= @@ -95,143 +95,142 @@ impl Credential { /// NOTE: Base context and type are automatically included. #[derive(Debug)] pub struct CredentialBuilder { - context: Vec, - id: Option, - types: Vec, - credential_subject: Vec, - issuer: Option, - issuance_date: Timestamp, - expiration_date: Option, - credential_status: Vec, - credential_schema: Vec, - refresh_service: Vec, - terms_of_use: Vec, - evidence: Vec, - non_transferable: Option, - properties: Object, + context: Vec, + id: Option, + types: Vec, + credential_subject: Vec, + issuer: Option, + issuance_date: Timestamp, + expiration_date: Option, + credential_status: Vec, + credential_schema: Vec, + refresh_service: Vec, + terms_of_use: Vec, + evidence: Vec, + non_transferable: Option, + properties: Object, } impl CredentialBuilder { - pub fn new() -> Self { - Self { - context: vec![Credential::BASE_CONTEXT.into()], - id: None, - types: vec![Credential::BASE_TYPE.into()], - credential_subject: Vec::new(), - issuer: None, - issuance_date: Default::default(), - expiration_date: None, - credential_status: Vec::new(), - credential_schema: Vec::new(), - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - evidence: Vec::new(), - non_transferable: None, - properties: Default::default(), - } - } - - pub fn context(mut self, value: impl Into) -> Self { - let value: Context = value.into(); - - if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { - self.context.push(value); + pub fn new() -> Self { + Self { + context: vec![Credential::BASE_CONTEXT.into()], + id: None, + types: vec![Credential::BASE_TYPE.into()], + credential_subject: Vec::new(), + issuer: None, + issuance_date: Default::default(), + expiration_date: None, + credential_status: Vec::new(), + credential_schema: Vec::new(), + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + evidence: Vec::new(), + non_transferable: None, + properties: Default::default(), + } } - self - } + pub fn context(mut self, value: impl Into) -> Self { + let value: Context = value.into(); - pub fn type_(mut self, value: impl Into) -> Self { - let value: String = value.into(); + if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + self.context.push(value); + } - if value != Credential::BASE_TYPE { - self.types.push(value); + self } - self - } - - pub fn property(mut self, key: impl Into, value: impl Into) -> Self { - self.properties.insert(key.into(), value.into()); - self - } - - impl_builder_setter!(id, id, Option); - impl_builder_setter!(subject, credential_subject, Vec); - impl_builder_setter!(issuer, issuer, Option); - impl_builder_setter!(issuance_date, issuance_date, Timestamp); - impl_builder_setter!(expiration_date, expiration_date, Option); - impl_builder_setter!(status, credential_status, Vec); - impl_builder_setter!(schema, credential_schema, Vec); - impl_builder_setter!(refresh, refresh_service, Vec); - impl_builder_setter!(terms_of_use, terms_of_use, Vec); - impl_builder_setter!(evidence, evidence, Vec); - impl_builder_setter!(non_transferable, non_transferable, Option); - impl_builder_setter!(properties, properties, Object); - - impl_builder_try_setter!(try_subject, credential_subject, Vec); - impl_builder_try_setter!(try_issuance_date, issuance_date, Timestamp); - impl_builder_try_setter!(try_expiration_date, expiration_date, Option); - impl_builder_try_setter!(try_status, credential_status, Vec); - impl_builder_try_setter!(try_schema, credential_schema, Vec); - impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); - impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); - impl_builder_try_setter!(try_evidence, evidence, Vec); - - /// Consumes the `CredentialBuilder`, returning a valid `Credential` - pub fn build(self) -> Result { - let mut credential: Credential = Credential { - context: self.context.into(), - id: self.id, - types: self.types.into(), - credential_subject: self.credential_subject.into(), - issuer: self.issuer.ok_or_else(|| Error::MissingCredentialIssuer)?, - issuance_date: self.issuance_date, - expiration_date: self.expiration_date, - credential_status: None, - credential_schema: None, - refresh_service: None, - terms_of_use: None, - evidence: None, - non_transferable: self.non_transferable, - properties: self.properties, - }; - - if !self.credential_status.is_empty() { - credential.credential_status = Some(self.credential_status.into()); - } + pub fn type_(mut self, value: impl Into) -> Self { + let value: String = value.into(); - if !self.credential_schema.is_empty() { - credential.credential_schema = Some(self.credential_schema.into()); - } + if value != Credential::BASE_TYPE { + self.types.push(value); + } - if !self.refresh_service.is_empty() { - credential.refresh_service = Some(self.refresh_service.into()); + self } - if !self.terms_of_use.is_empty() { - credential.terms_of_use = Some(self.terms_of_use.into()); + pub fn property(mut self, key: impl Into, value: impl Into) -> Self { + self.properties.insert(key.into(), value.into()); + self } - if !self.evidence.is_empty() { - credential.evidence = Some(self.evidence.into()); + impl_builder_setter!(id, id, Option); + impl_builder_setter!(subject, credential_subject, Vec); + impl_builder_setter!(issuer, issuer, Option); + impl_builder_setter!(issuance_date, issuance_date, Timestamp); + impl_builder_setter!(expiration_date, expiration_date, Option); + impl_builder_setter!(status, credential_status, Vec); + impl_builder_setter!(schema, credential_schema, Vec); + impl_builder_setter!(refresh, refresh_service, Vec); + impl_builder_setter!(terms_of_use, terms_of_use, Vec); + impl_builder_setter!(evidence, evidence, Vec); + impl_builder_setter!(non_transferable, non_transferable, Option); + impl_builder_setter!(properties, properties, Object); + + impl_builder_try_setter!(try_subject, credential_subject, Vec); + impl_builder_try_setter!(try_issuance_date, issuance_date, Timestamp); + impl_builder_try_setter!(try_expiration_date, expiration_date, Option); + impl_builder_try_setter!(try_status, credential_status, Vec); + impl_builder_try_setter!(try_schema, credential_schema, Vec); + impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); + impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); + impl_builder_try_setter!(try_evidence, evidence, Vec); + + /// Consumes the `CredentialBuilder`, returning a valid `Credential` + pub fn build(self) -> Result { + let mut credential: Credential = Credential { + context: self.context.into(), + id: self.id, + types: self.types.into(), + credential_subject: self.credential_subject.into(), + issuer: self.issuer.ok_or_else(|| Error::MissingCredentialIssuer)?, + issuance_date: self.issuance_date, + expiration_date: self.expiration_date, + credential_status: None, + credential_schema: None, + refresh_service: None, + terms_of_use: None, + evidence: None, + non_transferable: self.non_transferable, + properties: self.properties, + }; + + if !self.credential_status.is_empty() { + credential.credential_status = Some(self.credential_status.into()); + } + + if !self.credential_schema.is_empty() { + credential.credential_schema = Some(self.credential_schema.into()); + } + + if !self.refresh_service.is_empty() { + credential.refresh_service = Some(self.refresh_service.into()); + } + + if !self.terms_of_use.is_empty() { + credential.terms_of_use = Some(self.terms_of_use.into()); + } + + if !self.evidence.is_empty() { + credential.evidence = Some(self.evidence.into()); + } + + credential.validate()?; + + Ok(credential) } - credential.validate()?; - - Ok(credential) - } - - /// Consumes the `CredentialBuilder`, returning a valid `VerifiableCredential` - pub fn build_verifiable(self, proof: impl Into>) -> Result { - self - .build() - .map(|credential| VerifiableCredential::new(credential, proof)) - } + /// Consumes the `CredentialBuilder`, returning a valid `VerifiableCredential` + pub fn build_verifiable(self, proof: impl Into>) -> Result { + self.build() + .map(|credential| VerifiableCredential::new(credential, proof)) + } } impl Default for CredentialBuilder { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity_vc/src/error.rs b/identity_vc/src/error.rs index d35cfc6b2c..81e48b4e90 100644 --- a/identity_vc/src/error.rs +++ b/identity_vc/src/error.rs @@ -4,28 +4,28 @@ use thiserror::Error as ThisError; #[derive(Debug, ThisError)] pub enum Error { - #[error("Cannot convert `Object` to `{0}`")] - BadObjectConversion(&'static str), - #[error("Missing base type for {0}")] - MissingBaseType(&'static str), - #[error("Missing base context for {0}")] - MissingBaseContext(&'static str), - #[error("Invalid base context for {0}")] - InvalidBaseContext(&'static str), - #[error("Invalid URI for {0}")] - InvalidURI(&'static str), - #[error("Invalid timestamp format ({0})")] - InvalidTimestamp(ChronoError), - #[error("Missing `Credential` subject")] - MissingCredentialSubject, - #[error("Invalid `Credential` subject")] - InvalidCredentialSubject, - #[error("Missing `Credential` issuer")] - MissingCredentialIssuer, - #[error("Failed to decode JSON: {0}")] - DecodeJSON(serde_json::Error), - #[error("Failed to encode JSON: {0}")] - EncodeJSON(serde_json::Error), + #[error("Cannot convert `Object` to `{0}`")] + BadObjectConversion(&'static str), + #[error("Missing base type for {0}")] + MissingBaseType(&'static str), + #[error("Missing base context for {0}")] + MissingBaseContext(&'static str), + #[error("Invalid base context for {0}")] + InvalidBaseContext(&'static str), + #[error("Invalid URI for {0}")] + InvalidURI(&'static str), + #[error("Invalid timestamp format ({0})")] + InvalidTimestamp(ChronoError), + #[error("Missing `Credential` subject")] + MissingCredentialSubject, + #[error("Invalid `Credential` subject")] + InvalidCredentialSubject, + #[error("Missing `Credential` issuer")] + MissingCredentialIssuer, + #[error("Failed to decode JSON: {0}")] + DecodeJSON(serde_json::Error), + #[error("Failed to encode JSON: {0}")] + EncodeJSON(serde_json::Error), } pub type Result = StdResult; diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index b5247ef13b..360bbb2962 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -14,14 +14,14 @@ pub mod verifiable; pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; pub mod prelude { - pub use crate::{ - common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, - TermsOfUse, URI, - }, - credential::{Credential, CredentialBuilder}, - error::{Error, Result}, - presentation::{Presentation, PresentationBuilder}, - verifiable::{VerifiableCredential, VerifiablePresentation}, - }; + pub use crate::{ + common::{ + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, + RefreshService, TermsOfUse, URI, + }, + credential::{Credential, CredentialBuilder}, + error::{Error, Result}, + presentation::{Presentation, PresentationBuilder}, + verifiable::{VerifiableCredential, VerifiablePresentation}, + }; } diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs index a3fd40d3b3..dd017a1acd 100644 --- a/identity_vc/src/macros.rs +++ b/identity_vc/src/macros.rs @@ -1,68 +1,68 @@ macro_rules! impl_builder_setter { - ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field = Some(value.into()); - self - } - }; - ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field.push(value.into()); - self - } - }; - ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field = value.into(); - self - } - }; + ($fn:ident, $field:ident, Option<$ty:ty>) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field = Some(value.into()); + self + } + }; + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field.push(value.into()); + self + } + }; + ($fn:ident, $field:ident, $ty:ty) => { + pub fn $fn(mut self, value: impl Into<$ty>) -> Self { + self.$field = value.into(); + self + } + }; } macro_rules! impl_builder_try_setter { - ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field = Some(value); - self - }) - .map_err(Into::into) - } - }; - ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field.push(value); - self - }) - .map_err(Into::into) - } - }; - ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field = value; - self - }) - .map_err(Into::into) - } - }; + ($fn:ident, $field:ident, Option<$ty:ty>) => { + pub fn $fn(mut self, value: T) -> $crate::error::Result + where + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field = Some(value); + self + }) + .map_err(Into::into) + } + }; + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + pub fn $fn(mut self, value: T) -> $crate::error::Result + where + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field.push(value); + self + }) + .map_err(Into::into) + } + }; + ($fn:ident, $field:ident, $ty:ty) => { + pub fn $fn(mut self, value: T) -> $crate::error::Result + where + $ty: ::std::convert::TryFrom, + U: Into<$crate::error::Error>, + { + use ::std::convert::TryFrom; + <$ty>::try_from(value) + .map(|value| { + self.$field = value; + self + }) + .map_err(Into::into) + } + }; } diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 5308705f5f..ad6953c02c 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -2,11 +2,11 @@ use identity_core::common::{Object, Value}; use serde_json::{from_str, to_string}; use crate::{ - common::{Context, OneOrMany, RefreshService, TermsOfUse, URI}, - credential::Credential, - error::{Error, Result}, - utils::validate_presentation_structure, - verifiable::{VerifiableCredential, VerifiablePresentation}, + common::{Context, OneOrMany, RefreshService, TermsOfUse, URI}, + credential::Credential, + error::{Error, Result}, + utils::validate_presentation_structure, + verifiable::{VerifiableCredential, VerifiablePresentation}, }; /// A `Presentation` represents a bundle of one or more `VerifiableCredential`s. @@ -14,53 +14,53 @@ use crate::{ /// `Presentation`s can be combined with `Proof`s to create `VerifiablePresentation`s. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct Presentation { - /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. - /// - /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` - #[serde(rename = "@context")] - pub context: OneOrMany, - /// A unique `URI` referencing the subject of the presentation. - #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, - /// One or more URIs defining the type of presentation. - /// - /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly - /// passed as non-`URI` strings and expected to be processed with JSON-LD. - /// We're using a `String` here since we don't currently use JSON-LD and - /// don't have any immediate plans to do so. - #[serde(rename = "type")] - pub types: OneOrMany, - /// TODO - #[serde(rename = "verifiableCredential")] - pub verifiable_credential: OneOrMany, - /// The entity that generated the presentation. - #[serde(skip_serializing_if = "Option::is_none")] - pub holder: Option, - /// TODO - #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] - pub refresh_service: Option>, - /// The terms of use issued by the presentation holder - #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] - pub terms_of_use: Option>, - /// Miscellaneous properties. - #[serde(flatten)] - pub properties: Object, + /// A set of URIs or `Object`s describing the applicable JSON-LD contexts. + /// + /// NOTE: The first URI MUST be `https://www.w3.org/2018/credentials/v1` + #[serde(rename = "@context")] + pub context: OneOrMany, + /// A unique `URI` referencing the subject of the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + /// One or more URIs defining the type of presentation. + /// + /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly + /// passed as non-`URI` strings and expected to be processed with JSON-LD. + /// We're using a `String` here since we don't currently use JSON-LD and + /// don't have any immediate plans to do so. + #[serde(rename = "type")] + pub types: OneOrMany, + /// TODO + #[serde(rename = "verifiableCredential")] + pub verifiable_credential: OneOrMany, + /// The entity that generated the presentation. + #[serde(skip_serializing_if = "Option::is_none")] + pub holder: Option, + /// TODO + #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] + pub refresh_service: Option>, + /// The terms of use issued by the presentation holder + #[serde(rename = "termsOfUse", skip_serializing_if = "Option::is_none")] + pub terms_of_use: Option>, + /// Miscellaneous properties. + #[serde(flatten)] + pub properties: Object, } impl Presentation { - pub const BASE_TYPE: &'static str = "VerifiablePresentation"; + pub const BASE_TYPE: &'static str = "VerifiablePresentation"; - pub fn validate(&self) -> Result<()> { - validate_presentation_structure(self) - } + pub fn validate(&self) -> Result<()> { + validate_presentation_structure(self) + } - pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { - from_str(json.as_ref()).map_err(Error::DecodeJSON) - } + pub fn from_json(json: &(impl AsRef + ?Sized)) -> Result { + from_str(json.as_ref()).map_err(Error::DecodeJSON) + } - pub fn to_json(&self) -> Result { - to_string(self).map_err(Error::EncodeJSON) - } + pub fn to_json(&self) -> Result { + to_string(self).map_err(Error::EncodeJSON) + } } // ============================================================================= @@ -73,101 +73,100 @@ impl Presentation { /// NOTE: Base context and type are automatically included. #[derive(Debug)] pub struct PresentationBuilder { - context: Vec, - id: Option, - types: Vec, - verifiable_credential: Vec, - holder: Option, - refresh_service: Vec, - terms_of_use: Vec, - properties: Object, + context: Vec, + id: Option, + types: Vec, + verifiable_credential: Vec, + holder: Option, + refresh_service: Vec, + terms_of_use: Vec, + properties: Object, } impl PresentationBuilder { - pub fn new() -> Self { - Self { - context: vec![Credential::BASE_CONTEXT.into()], - id: None, - types: vec![Presentation::BASE_TYPE.into()], - verifiable_credential: Vec::new(), - holder: None, - refresh_service: Vec::new(), - terms_of_use: Vec::new(), - properties: Default::default(), + pub fn new() -> Self { + Self { + context: vec![Credential::BASE_CONTEXT.into()], + id: None, + types: vec![Presentation::BASE_TYPE.into()], + verifiable_credential: Vec::new(), + holder: None, + refresh_service: Vec::new(), + terms_of_use: Vec::new(), + properties: Default::default(), + } } - } - pub fn context(mut self, value: impl Into) -> Self { - let value: Context = value.into(); + pub fn context(mut self, value: impl Into) -> Self { + let value: Context = value.into(); + + if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + self.context.push(value); + } - if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { - self.context.push(value); + self } - self - } + pub fn type_(mut self, value: impl Into) -> Self { + let value: String = value.into(); - pub fn type_(mut self, value: impl Into) -> Self { - let value: String = value.into(); + if value != Presentation::BASE_TYPE { + self.types.push(value); + } - if value != Presentation::BASE_TYPE { - self.types.push(value); + self } - self - } - - pub fn property(mut self, key: impl Into, value: impl Into) -> Self { - self.properties.insert(key.into(), value.into()); - self - } - - impl_builder_setter!(id, id, Option); - impl_builder_setter!(credential, verifiable_credential, Vec); - impl_builder_setter!(holder, holder, Option); - impl_builder_setter!(refresh, refresh_service, Vec); - impl_builder_setter!(terms_of_use, terms_of_use, Vec); - impl_builder_setter!(properties, properties, Object); - - impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); - impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); - - /// Consumes the `PresentationBuilder`, returning a valid `Presentation` - pub fn build(self) -> Result { - let mut presentation: Presentation = Presentation { - context: self.context.into(), - id: self.id, - types: self.types.into(), - verifiable_credential: self.verifiable_credential.into(), - holder: self.holder, - refresh_service: None, - terms_of_use: None, - properties: self.properties, - }; - - if !self.refresh_service.is_empty() { - presentation.refresh_service = Some(self.refresh_service.into()); + pub fn property(mut self, key: impl Into, value: impl Into) -> Self { + self.properties.insert(key.into(), value.into()); + self } - if !self.terms_of_use.is_empty() { - presentation.terms_of_use = Some(self.terms_of_use.into()); + impl_builder_setter!(id, id, Option); + impl_builder_setter!(credential, verifiable_credential, Vec); + impl_builder_setter!(holder, holder, Option); + impl_builder_setter!(refresh, refresh_service, Vec); + impl_builder_setter!(terms_of_use, terms_of_use, Vec); + impl_builder_setter!(properties, properties, Object); + + impl_builder_try_setter!(try_refresh_service, refresh_service, Vec); + impl_builder_try_setter!(try_terms_of_use, terms_of_use, Vec); + + /// Consumes the `PresentationBuilder`, returning a valid `Presentation` + pub fn build(self) -> Result { + let mut presentation: Presentation = Presentation { + context: self.context.into(), + id: self.id, + types: self.types.into(), + verifiable_credential: self.verifiable_credential.into(), + holder: self.holder, + refresh_service: None, + terms_of_use: None, + properties: self.properties, + }; + + if !self.refresh_service.is_empty() { + presentation.refresh_service = Some(self.refresh_service.into()); + } + + if !self.terms_of_use.is_empty() { + presentation.terms_of_use = Some(self.terms_of_use.into()); + } + + presentation.validate()?; + + Ok(presentation) } - presentation.validate()?; - - Ok(presentation) - } - - /// Consumes the `PresentationBuilder`, returning a valid `VerifiablePresentation` - pub fn build_verifiable(self, proof: impl Into>) -> Result { - self - .build() - .map(|credential| VerifiablePresentation::new(credential, proof)) - } + /// Consumes the `PresentationBuilder`, returning a valid `VerifiablePresentation` + pub fn build_verifiable(self, proof: impl Into>) -> Result { + self.build() + .map(|credential| VerifiablePresentation::new(credential, proof)) + } } impl Default for PresentationBuilder { - fn default() -> Self { - Self::new() - } + fn default() -> Self { + Self::new() + } } diff --git a/identity_vc/src/utils/validation.rs b/identity_vc/src/utils/validation.rs index 99bdde0d92..a86c21fec3 100644 --- a/identity_vc/src/utils/validation.rs +++ b/identity_vc/src/utils/validation.rs @@ -1,90 +1,90 @@ use crate::{ - common::{Context, OneOrMany, URI}, - credential::Credential, - error::{Error, Result}, - presentation::Presentation, + common::{Context, OneOrMany, URI}, + credential::Credential, + error::{Error, Result}, + presentation::Presentation, }; pub fn validate_credential_structure(credential: &Credential) -> Result<()> { - // Ensure the base context is present and in the correct location - validate_context("Credential", &credential.context)?; + // Ensure the base context is present and in the correct location + validate_context("Credential", &credential.context)?; - // The set of types MUST contain the base type - validate_types("Credential", Credential::BASE_TYPE, &credential.types)?; + // The set of types MUST contain the base type + validate_types("Credential", Credential::BASE_TYPE, &credential.types)?; - // Ensure the id URI (if provided) adheres to the correct format - validate_opt_uri("Credential id", credential.id.as_ref())?; + // Ensure the id URI (if provided) adheres to the correct format + validate_opt_uri("Credential id", credential.id.as_ref())?; - // Ensure the issuer URI adheres to the correct format - validate_uri("Credential issuer", credential.issuer.uri())?; + // Ensure the issuer URI adheres to the correct format + validate_uri("Credential issuer", credential.issuer.uri())?; - // Credentials MUST have at least one subject - if credential.credential_subject.is_empty() { - return Err(Error::MissingCredentialSubject); - } + // Credentials MUST have at least one subject + if credential.credential_subject.is_empty() { + return Err(Error::MissingCredentialSubject); + } - // Each subject is defined as one or more properties - no empty objects - for subject in credential.credential_subject.iter() { - if subject.id.is_none() && subject.properties.is_empty() { - return Err(Error::InvalidCredentialSubject); + // Each subject is defined as one or more properties - no empty objects + for subject in credential.credential_subject.iter() { + if subject.id.is_none() && subject.properties.is_empty() { + return Err(Error::InvalidCredentialSubject); + } } - } - Ok(()) + Ok(()) } pub fn validate_presentation_structure(presentation: &Presentation) -> Result<()> { - // Ensure the base context is present and in the correct location - validate_context("Presentation", &presentation.context)?; + // Ensure the base context is present and in the correct location + validate_context("Presentation", &presentation.context)?; - // The set of types MUST contain the base type - validate_types("Presentation", Presentation::BASE_TYPE, &presentation.types)?; + // The set of types MUST contain the base type + validate_types("Presentation", Presentation::BASE_TYPE, &presentation.types)?; - // Ensure the id URI (if provided) adheres to the correct format - validate_opt_uri("Presentation id", presentation.id.as_ref())?; + // Ensure the id URI (if provided) adheres to the correct format + validate_opt_uri("Presentation id", presentation.id.as_ref())?; - // Ensure the holder URI (if provided) adheres to the correct format - validate_opt_uri("Presentation holder", presentation.holder.as_ref())?; + // Ensure the holder URI (if provided) adheres to the correct format + validate_opt_uri("Presentation holder", presentation.holder.as_ref())?; - // Validate all verifiable credentials - for credential in presentation.verifiable_credential.iter() { - credential.validate()?; - } + // Validate all verifiable credentials + for credential in presentation.verifiable_credential.iter() { + credential.validate()?; + } - Ok(()) + Ok(()) } pub fn validate_types(name: &'static str, base: &str, types: &OneOrMany) -> Result<()> { - if !types.contains(&base.into()) { - return Err(Error::MissingBaseType(name)); - } + if !types.contains(&base.into()) { + return Err(Error::MissingBaseType(name)); + } - Ok(()) + Ok(()) } pub fn validate_context(name: &'static str, context: &OneOrMany) -> Result<()> { - // The first Credential/Presentation context MUST be a URI representing the base context - match context.get(0) { - Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), - Some(_) => Err(Error::InvalidBaseContext(name)), - None => Err(Error::MissingBaseContext(name)), - } + // The first Credential/Presentation context MUST be a URI representing the base context + match context.get(0) { + Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), + Some(_) => Err(Error::InvalidBaseContext(name)), + None => Err(Error::MissingBaseContext(name)), + } } pub fn validate_uri(name: &'static str, uri: &URI) -> Result<()> { - const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; + const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; - // TODO: Proper URI validation - if !KNOWN.iter().any(|scheme| uri.starts_with(scheme)) { - return Err(Error::InvalidURI(name)); - } + // TODO: Proper URI validation + if !KNOWN.iter().any(|scheme| uri.starts_with(scheme)) { + return Err(Error::InvalidURI(name)); + } - Ok(()) + Ok(()) } pub fn validate_opt_uri(name: &'static str, uri: Option<&URI>) -> Result<()> { - match uri { - Some(uri) => validate_uri(name, uri), - None => Ok(()), - } + match uri { + Some(uri) => validate_uri(name, uri), + None => Ok(()), + } } diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs index 2c5d23df8b..df45e69ffb 100644 --- a/identity_vc/src/verifiable/credential.rs +++ b/identity_vc/src/verifiable/credential.rs @@ -5,40 +5,40 @@ use crate::{common::OneOrMany, credential::Credential}; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiableCredential { - #[serde(flatten)] - credential: Credential, - proof: OneOrMany, + #[serde(flatten)] + credential: Credential, + proof: OneOrMany, } impl VerifiableCredential { - pub fn new(credential: Credential, proof: impl Into>) -> Self { - Self { - credential, - proof: proof.into(), + pub fn new(credential: Credential, proof: impl Into>) -> Self { + Self { + credential, + proof: proof.into(), + } } - } - pub fn credential(&self) -> &Credential { - &self.credential - } + pub fn credential(&self) -> &Credential { + &self.credential + } - pub fn credential_mut(&mut self) -> &mut Credential { - &mut self.credential - } + pub fn credential_mut(&mut self) -> &mut Credential { + &mut self.credential + } - pub fn proof(&self) -> &OneOrMany { - &self.proof - } + pub fn proof(&self) -> &OneOrMany { + &self.proof + } - pub fn proof_mut(&mut self) -> &mut OneOrMany { - &mut self.proof - } + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } } impl Deref for VerifiableCredential { - type Target = Credential; + type Target = Credential; - fn deref(&self) -> &Self::Target { - &self.credential - } + fn deref(&self) -> &Self::Target { + &self.credential + } } diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs index cea4615605..577a42787d 100644 --- a/identity_vc/src/verifiable/presentation.rs +++ b/identity_vc/src/verifiable/presentation.rs @@ -5,40 +5,40 @@ use crate::{common::OneOrMany, presentation::Presentation}; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiablePresentation { - #[serde(flatten)] - presentation: Presentation, - proof: OneOrMany, + #[serde(flatten)] + presentation: Presentation, + proof: OneOrMany, } impl VerifiablePresentation { - pub fn new(presentation: Presentation, proof: impl Into>) -> Self { - Self { - presentation, - proof: proof.into(), + pub fn new(presentation: Presentation, proof: impl Into>) -> Self { + Self { + presentation, + proof: proof.into(), + } } - } - pub fn presentation(&self) -> &Presentation { - &self.presentation - } + pub fn presentation(&self) -> &Presentation { + &self.presentation + } - pub fn presentation_mut(&mut self) -> &mut Presentation { - &mut self.presentation - } + pub fn presentation_mut(&mut self) -> &mut Presentation { + &mut self.presentation + } - pub fn proof(&self) -> &OneOrMany { - &self.proof - } + pub fn proof(&self) -> &OneOrMany { + &self.proof + } - pub fn proof_mut(&mut self) -> &mut OneOrMany { - &mut self.proof - } + pub fn proof_mut(&mut self) -> &mut OneOrMany { + &mut self.proof + } } impl Deref for VerifiablePresentation { - type Target = Presentation; + type Target = Presentation; - fn deref(&self) -> &Self::Target { - &self.presentation - } + fn deref(&self) -> &Self::Target { + &self.presentation + } } diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs index 13f4b957c2..29076f9708 100644 --- a/identity_vc/tests/credential.rs +++ b/identity_vc/tests/credential.rs @@ -8,84 +8,84 @@ use identity_vc::prelude::*; #[test] fn test_builder_valid() { - let issuance = timestamp!("2010-01-01T00:00:00Z"); + let issuance = timestamp!("2010-01-01T00:00:00Z"); - let credential = CredentialBuilder::new() - .issuer("did:example:issuer") - .context("https://www.w3.org/2018/credentials/examples/v1") - .context(object!(id: "did:context:1234", type: "CustomContext2020")) - .id("did:example:123") - .type_("RelationshipCredential") - .try_subject(object!(id: "did:iota:alice", spouse: "did:iota:bob")) - .unwrap() - .try_subject(object!(id: "did:iota:bob", spouse: "did:iota:alice")) - .unwrap() - .issuance_date(issuance) - .build() - .unwrap(); + let credential = CredentialBuilder::new() + .issuer("did:example:issuer") + .context("https://www.w3.org/2018/credentials/examples/v1") + .context(object!(id: "did:context:1234", type: "CustomContext2020")) + .id("did:example:123") + .type_("RelationshipCredential") + .try_subject(object!(id: "did:iota:alice", spouse: "did:iota:bob")) + .unwrap() + .try_subject(object!(id: "did:iota:bob", spouse: "did:iota:alice")) + .unwrap() + .issuance_date(issuance) + .build() + .unwrap(); - assert_eq!(credential.context.len(), 3); - assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); - assert_matches!(credential.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + assert_eq!(credential.context.len(), 3); + assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(credential.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); - assert_eq!(credential.id, Some("did:example:123".into())); + assert_eq!(credential.id, Some("did:example:123".into())); - assert_eq!(credential.types.len(), 2); - assert_eq!(credential.types.get(0).unwrap(), Credential::BASE_TYPE); - assert_eq!(credential.types.get(1).unwrap(), "RelationshipCredential"); + assert_eq!(credential.types.len(), 2); + assert_eq!(credential.types.get(0).unwrap(), Credential::BASE_TYPE); + assert_eq!(credential.types.get(1).unwrap(), "RelationshipCredential"); - assert_eq!(credential.credential_subject.len(), 2); - assert_eq!( - credential.credential_subject.get(0).unwrap().id, - Some("did:iota:alice".into()) - ); - assert_eq!( - credential.credential_subject.get(1).unwrap().id, - Some("did:iota:bob".into()) - ); + assert_eq!(credential.credential_subject.len(), 2); + assert_eq!( + credential.credential_subject.get(0).unwrap().id, + Some("did:iota:alice".into()) + ); + assert_eq!( + credential.credential_subject.get(1).unwrap().id, + Some("did:iota:bob".into()) + ); - assert_eq!(credential.issuer.uri(), "did:example:issuer"); + assert_eq!(credential.issuer.uri(), "did:example:issuer"); - assert_eq!(credential.issuance_date, issuance); + assert_eq!(credential.issuance_date, issuance); } #[test] #[should_panic = "Missing `Credential` subject"] fn test_builder_missing_subjects() { - CredentialBuilder::new() - .issuer("did:issuer") - .build() - .unwrap_or_else(|error| panic!("{}", error)); + CredentialBuilder::new() + .issuer("did:issuer") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Invalid `Credential` subject"] fn test_builder_invalid_subjects() { - CredentialBuilder::new() - .issuer("did:issuer") - .try_subject(object!()) - .unwrap_or_else(|error| panic!("{}", error)) - .build() - .unwrap_or_else(|error| panic!("{}", error)); + CredentialBuilder::new() + .issuer("did:issuer") + .try_subject(object!()) + .unwrap_or_else(|error| panic!("{}", error)) + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Missing `Credential` issuer"] fn test_builder_missing_issuer() { - CredentialBuilder::new() - .try_subject(object!(id: "did:sub")) - .unwrap_or_else(|error| panic!("{}", error)) - .build() - .unwrap_or_else(|error| panic!("{}", error)); + CredentialBuilder::new() + .try_subject(object!(id: "did:sub")) + .unwrap_or_else(|error| panic!("{}", error)) + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Invalid URI for Credential issuer"] fn test_builder_invalid_issuer() { - CredentialBuilder::new() - .try_subject(object!(id: "did:sub")) - .unwrap_or_else(|error| panic!("{}", error)) - .issuer("foo") - .build() - .unwrap_or_else(|error| panic!("{}", error)); + CredentialBuilder::new() + .try_subject(object!(id: "did:sub")) + .unwrap_or_else(|error| panic!("{}", error)) + .issuer("foo") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } diff --git a/identity_vc/tests/macros.rs b/identity_vc/tests/macros.rs index b554ed28c1..b91aa108ee 100644 --- a/identity_vc/tests/macros.rs +++ b/identity_vc/tests/macros.rs @@ -7,8 +7,8 @@ macro_rules! assert_matches { #[macro_export] macro_rules! timestamp { - ($expr:expr) => {{ - use ::std::convert::TryFrom; - ::identity_core::common::Timestamp::try_from($expr).unwrap() - }}; + ($expr:expr) => {{ + use ::std::convert::TryFrom; + ::identity_core::common::Timestamp::try_from($expr).unwrap() + }}; } diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs index 6923107953..d1debc17fb 100644 --- a/identity_vc/tests/presentation.rs +++ b/identity_vc/tests/presentation.rs @@ -8,99 +8,99 @@ use identity_vc::prelude::*; #[test] fn test_builder_valid() { - let issuance = timestamp!("2010-01-01T00:00:00Z"); - - let credential = CredentialBuilder::new() - .issuer("did:example:issuer") - .context("https://www.w3.org/2018/credentials/examples/v1") - .type_("PrescriptionCredential") - .try_subject(object!(id: "did:iota:alice")) - .unwrap() - .issuance_date(issuance) - .build() - .unwrap(); - - let verifiable = VerifiableCredential::new(credential, object!()); - - let presentation = PresentationBuilder::new() - .context("https://www.w3.org/2018/credentials/examples/v1") - .id("did:example:id:123") - .type_("PrescriptionCredential") - .credential(verifiable.clone()) - .try_refresh_service(object!(id: "", type: "Refresh2020")) - .unwrap() - .try_terms_of_use(object!(type: "Policy2019")) - .unwrap() - .try_terms_of_use(object!(type: "Policy2020")) - .unwrap() - .build() - .unwrap(); - - assert_eq!(presentation.context.len(), 2); - assert_matches!(presentation.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); - assert_matches!(presentation.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); - - assert_eq!(presentation.id, Some("did:example:id:123".into())); - - assert_eq!(presentation.types.len(), 2); - assert_eq!(presentation.types.get(0).unwrap(), Presentation::BASE_TYPE); - assert_eq!(presentation.types.get(1).unwrap(), "PrescriptionCredential"); - - assert_eq!(presentation.verifiable_credential.len(), 1); - assert_eq!(presentation.verifiable_credential.get(0).unwrap(), &verifiable); - - assert_eq!(presentation.refresh_service.unwrap().len(), 1); - assert_eq!(presentation.terms_of_use.unwrap().len(), 2); + let issuance = timestamp!("2010-01-01T00:00:00Z"); + + let credential = CredentialBuilder::new() + .issuer("did:example:issuer") + .context("https://www.w3.org/2018/credentials/examples/v1") + .type_("PrescriptionCredential") + .try_subject(object!(id: "did:iota:alice")) + .unwrap() + .issuance_date(issuance) + .build() + .unwrap(); + + let verifiable = VerifiableCredential::new(credential, object!()); + + let presentation = PresentationBuilder::new() + .context("https://www.w3.org/2018/credentials/examples/v1") + .id("did:example:id:123") + .type_("PrescriptionCredential") + .credential(verifiable.clone()) + .try_refresh_service(object!(id: "", type: "Refresh2020")) + .unwrap() + .try_terms_of_use(object!(type: "Policy2019")) + .unwrap() + .try_terms_of_use(object!(type: "Policy2020")) + .unwrap() + .build() + .unwrap(); + + assert_eq!(presentation.context.len(), 2); + assert_matches!(presentation.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(presentation.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + + assert_eq!(presentation.id, Some("did:example:id:123".into())); + + assert_eq!(presentation.types.len(), 2); + assert_eq!(presentation.types.get(0).unwrap(), Presentation::BASE_TYPE); + assert_eq!(presentation.types.get(1).unwrap(), "PrescriptionCredential"); + + assert_eq!(presentation.verifiable_credential.len(), 1); + assert_eq!(presentation.verifiable_credential.get(0).unwrap(), &verifiable); + + assert_eq!(presentation.refresh_service.unwrap().len(), 1); + assert_eq!(presentation.terms_of_use.unwrap().len(), 2); } #[test] #[should_panic = "Invalid URI for Presentation id"] fn test_builder_invalid_id_fmt() { - PresentationBuilder::new() - .id("foo") - .build() - .unwrap_or_else(|error| panic!("{}", error)); + PresentationBuilder::new() + .id("foo") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Invalid URI for Presentation holder"] fn test_builder_invalid_holder_fmt() { - PresentationBuilder::new() - .id("did:iota:123") - .holder("d00m") - .build() - .unwrap_or_else(|error| panic!("{}", error)); + PresentationBuilder::new() + .id("did:iota:123") + .holder("d00m") + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Cannot convert `Object` to `RefreshService`"] fn test_builder_invalid_refresh_service_missing_id() { - PresentationBuilder::new() - .id("did:iota:123") - .try_refresh_service(object!(type: "RefreshServiceType")) - .unwrap_or_else(|error| panic!("{}", error)) - .build() - .unwrap_or_else(|error| panic!("{}", error)); + PresentationBuilder::new() + .id("did:iota:123") + .try_refresh_service(object!(type: "RefreshServiceType")) + .unwrap_or_else(|error| panic!("{}", error)) + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Cannot convert `Object` to `RefreshService`"] fn test_builder_invalid_refresh_service_missing_type() { - PresentationBuilder::new() - .id("did:iota:123") - .try_refresh_service(object!(id: "did:iota:rsv:123")) - .unwrap_or_else(|error| panic!("{}", error)) - .build() - .unwrap_or_else(|error| panic!("{}", error)); + PresentationBuilder::new() + .id("did:iota:123") + .try_refresh_service(object!(id: "did:iota:rsv:123")) + .unwrap_or_else(|error| panic!("{}", error)) + .build() + .unwrap_or_else(|error| panic!("{}", error)); } #[test] #[should_panic = "Cannot convert `Object` to `TermsOfUse`"] fn test_builder_invalid_terms_of_use_missing_type() { - PresentationBuilder::new() - .id("did:iota:123") - .try_terms_of_use(object!(id: "did:iota:rsv:123")) - .unwrap_or_else(|error| panic!("{}", error)) - .build() - .unwrap_or_else(|error| panic!("{}", error)); + PresentationBuilder::new() + .id("did:iota:123") + .try_terms_of_use(object!(id: "did:iota:rsv:123")) + .unwrap_or_else(|error| panic!("{}", error)) + .build() + .unwrap_or_else(|error| panic!("{}", error)); } diff --git a/identity_vc/tests/serde.rs b/identity_vc/tests/serde.rs index 38aa248c27..505fc1f32c 100644 --- a/identity_vc/tests/serde.rs +++ b/identity_vc/tests/serde.rs @@ -2,37 +2,37 @@ use identity_vc::prelude::*; use serde_json::from_str; fn try_credential(data: &(impl AsRef + ?Sized)) { - from_str::(data.as_ref()) - .unwrap() - .validate() - .unwrap() + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() } fn try_presentation(data: &(impl AsRef + ?Sized)) { - from_str::(data.as_ref()) - .unwrap() - .validate() - .unwrap() + from_str::(data.as_ref()) + .unwrap() + .validate() + .unwrap() } #[test] fn test_parse_credential_examples() { - try_credential(include_str!("input/example-01.json")); - try_credential(include_str!("input/example-02.json")); - try_credential(include_str!("input/example-03.json")); - try_credential(include_str!("input/example-04.json")); - try_credential(include_str!("input/example-05.json")); - try_credential(include_str!("input/example-06.json")); - try_credential(include_str!("input/example-07.json")); + try_credential(include_str!("input/example-01.json")); + try_credential(include_str!("input/example-02.json")); + try_credential(include_str!("input/example-03.json")); + try_credential(include_str!("input/example-04.json")); + try_credential(include_str!("input/example-05.json")); + try_credential(include_str!("input/example-06.json")); + try_credential(include_str!("input/example-07.json")); - try_credential(include_str!("input/example-09.json")); - try_credential(include_str!("input/example-10.json")); - try_credential(include_str!("input/example-11.json")); - try_credential(include_str!("input/example-12.json")); - try_credential(include_str!("input/example-13.json")); + try_credential(include_str!("input/example-09.json")); + try_credential(include_str!("input/example-10.json")); + try_credential(include_str!("input/example-11.json")); + try_credential(include_str!("input/example-12.json")); + try_credential(include_str!("input/example-13.json")); } #[test] fn test_parse_presentation_examples() { - try_presentation(include_str!("input/example-08.json")); + try_presentation(include_str!("input/example-08.json")); } From 1fa0b7a79ef24820f84856a44f34b3f17caa1eed Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 1 Sep 2020 11:05:15 -0700 Subject: [PATCH 30/44] Use identity_core from dev branch --- identity_vc/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/identity_vc/Cargo.toml b/identity_vc/Cargo.toml index fc997aeea2..1902432d41 100644 --- a/identity_vc/Cargo.toml +++ b/identity_vc/Cargo.toml @@ -27,4 +27,5 @@ serde_cbor = "0.11" # timestamps chrono = { version = "0.4", features = ["serde"] } -identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "feat/common-core-types" } +# identity +identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "dev" } From c3f954b094ab47ba1996312f0d18f9144f03eb39 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 1 Sep 2020 11:06:25 -0700 Subject: [PATCH 31/44] Schemas only have a single type --- .../common/credential/credential_schema.rs | 6 +++--- .../common/credential/credential_status.rs | 4 ++-- identity_vc/src/common/credential/evidence.rs | 4 ++-- .../src/common/credential/refresh_service.rs | 4 ++-- .../src/common/credential/terms_of_use.rs | 4 ++-- identity_vc/src/common/credential/utils.rs | 20 ++++++++++++++----- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index f4ae397b36..797d9f7460 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_type, URI}, error::Error, }; @@ -13,7 +13,7 @@ use crate::{ pub struct CredentialSchema { pub id: URI, #[serde(rename = "type")] - pub types: OneOrMany, + pub type_: String, #[serde(flatten)] pub properties: Object, } @@ -25,7 +25,7 @@ impl TryFrom for CredentialSchema { let mut this: Self = Default::default(); this.id = try_take_object_id("CredentialSchema", &mut other)?.into(); - this.types = try_take_object_type("CredentialSchema", &mut other)?; + this.type_ = try_take_object_type("CredentialSchema", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index c928a7b5be..fd6ce7adba 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_types, OneOrMany, URI}, error::Error, }; @@ -25,7 +25,7 @@ impl TryFrom for CredentialStatus { let mut this: Self = Default::default(); this.id = try_take_object_id("CredentialStatus", &mut other)?.into(); - this.types = try_take_object_type("CredentialStatus", &mut other)?; + this.types = try_take_object_types("CredentialStatus", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index e03afe0b9f..d82d852887 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_type, OneOrMany}, + common::{take_object_id, try_take_object_types, OneOrMany}, error::Error, }; @@ -26,7 +26,7 @@ impl TryFrom for Evidence { let mut this: Self = Default::default(); this.id = take_object_id(&mut other); - this.types = try_take_object_type("Evidence", &mut other)?; + this.types = try_take_object_types("Evidence", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index df0ee06b17..ecf8a7ca4a 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_types, OneOrMany, URI}, error::Error, }; @@ -25,7 +25,7 @@ impl TryFrom for RefreshService { let mut this: Self = Default::default(); this.id = try_take_object_id("RefreshService", &mut other)?.into(); - this.types = try_take_object_type("RefreshService", &mut other)?; + this.types = try_take_object_types("RefreshService", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index a71fb116b4..db7165741e 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -2,7 +2,7 @@ use identity_core::common::Object; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_type, OneOrMany, URI}, + common::{take_object_id, try_take_object_types, OneOrMany, URI}, error::Error, }; @@ -27,7 +27,7 @@ impl TryFrom for TermsOfUse { let mut this: Self = Default::default(); this.id = take_object_id(&mut other).map(Into::into); - this.types = try_take_object_type("TermsOfUse", &mut other)?; + this.types = try_take_object_types("TermsOfUse", &mut other)?; this.properties = other; Ok(this) diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs index bfae56f2b1..e083e68618 100644 --- a/identity_vc/src/common/credential/utils.rs +++ b/identity_vc/src/common/credential/utils.rs @@ -8,8 +8,7 @@ use crate::{ pub fn take_object_id(object: &mut Object) -> Option { match object.remove("id") { Some(Value::String(id)) => Some(id), - Some(_) => None, - None => None, + Some(_) | None => None, } } @@ -17,18 +16,29 @@ pub fn try_take_object_id(name: &'static str, object: &mut Object) -> Result Option> { +pub fn take_object_type(object: &mut Object) -> Option { match object.remove("type") { Some(Value::String(value)) => Some(value.into()), - Some(Value::Array(values)) => Some(collect_types(values)), Some(_) | None => None, } } -pub fn try_take_object_type(name: &'static str, object: &mut Object) -> Result> { +pub fn try_take_object_type(name: &'static str, object: &mut Object) -> Result { take_object_type(object).ok_or_else(|| Error::BadObjectConversion(name)) } +pub fn take_object_types(object: &mut Object) -> Option> { + match object.remove("type") { + Some(Value::String(value)) => Some(value.into()), + Some(Value::Array(values)) => Some(collect_types(values)), + Some(_) | None => None, + } +} + +pub fn try_take_object_types(name: &'static str, object: &mut Object) -> Result> { + take_object_types(object).ok_or_else(|| Error::BadObjectConversion(name)) +} + fn collect_types(values: Vec) -> OneOrMany { let mut types: Vec = Vec::with_capacity(values.len()); From 8deee0eb0c26bc757d3e473ba1e684cae67ad239 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Tue, 1 Sep 2020 11:08:57 -0700 Subject: [PATCH 32/44] clippy --- identity_vc/src/common/credential/utils.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs index e083e68618..65cc6247e6 100644 --- a/identity_vc/src/common/credential/utils.rs +++ b/identity_vc/src/common/credential/utils.rs @@ -18,7 +18,7 @@ pub fn try_take_object_id(name: &'static str, object: &mut Object) -> Result Option { match object.remove("type") { - Some(Value::String(value)) => Some(value.into()), + Some(Value::String(value)) => Some(value), Some(_) | None => None, } } From 9cf3f42322dc61a6c03bdd0e682613fab5d9fbdb Mon Sep 17 00:00:00 2001 From: l1h3r Date: Thu, 3 Sep 2020 09:19:09 -0700 Subject: [PATCH 33/44] Move OneOrMany to identity_core::common --- identity_core/src/common.rs | 2 ++ .../oom.rs => identity_core/src/common/one_or_many.rs | 1 + identity_vc/src/common/credential/credential_status.rs | 4 ++-- identity_vc/src/common/credential/evidence.rs | 4 ++-- identity_vc/src/common/credential/refresh_service.rs | 4 ++-- identity_vc/src/common/credential/terms_of_use.rs | 4 ++-- identity_vc/src/common/credential/utils.rs | 7 ++----- identity_vc/src/common/mod.rs | 3 +-- identity_vc/src/credential.rs | 6 +++--- identity_vc/src/lib.rs | 4 ++-- identity_vc/src/presentation.rs | 4 ++-- identity_vc/src/utils/validation.rs | 4 +++- identity_vc/src/verifiable/credential.rs | 4 ++-- identity_vc/src/verifiable/presentation.rs | 4 ++-- 14 files changed, 28 insertions(+), 27 deletions(-) rename identity_vc/src/common/oom.rs => identity_core/src/common/one_or_many.rs (98%) diff --git a/identity_core/src/common.rs b/identity_core/src/common.rs index 7ff820c74b..812a38206e 100644 --- a/identity_core/src/common.rs +++ b/identity_core/src/common.rs @@ -1,8 +1,10 @@ mod macros; mod object; +mod one_or_many; mod timestamp; mod value; pub use object::Object; +pub use one_or_many::OneOrMany; pub use timestamp::Timestamp; pub use value::Value; diff --git a/identity_vc/src/common/oom.rs b/identity_core/src/common/one_or_many.rs similarity index 98% rename from identity_vc/src/common/oom.rs rename to identity_core/src/common/one_or_many.rs index 5d67ae41db..3fc57b6154 100644 --- a/identity_vc/src/common/oom.rs +++ b/identity_core/src/common/one_or_many.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::fmt; /// A generic container that stores one or many values of a given type. diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index fd6ce7adba..a72879c9bb 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -1,8 +1,8 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_types, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_types, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index d82d852887..5028b93e0a 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -1,8 +1,8 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_types, OneOrMany}, + common::{take_object_id, try_take_object_types}, error::Error, }; diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index ecf8a7ca4a..9dfdfe8d16 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -1,8 +1,8 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_types, OneOrMany, URI}, + common::{try_take_object_id, try_take_object_types, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index db7165741e..dcbae5961b 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -1,8 +1,8 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_types, OneOrMany, URI}, + common::{take_object_id, try_take_object_types, URI}, error::Error, }; diff --git a/identity_vc/src/common/credential/utils.rs b/identity_vc/src/common/credential/utils.rs index 65cc6247e6..09e9b5e702 100644 --- a/identity_vc/src/common/credential/utils.rs +++ b/identity_vc/src/common/credential/utils.rs @@ -1,9 +1,6 @@ -use identity_core::common::{Object, Value}; +use identity_core::common::{Object, OneOrMany, Value}; -use crate::{ - common::OneOrMany, - error::{Error, Result}, -}; +use crate::error::{Error, Result}; pub fn take_object_id(object: &mut Object) -> Option { match object.remove("id") { diff --git a/identity_vc/src/common/mod.rs b/identity_vc/src/common/mod.rs index cd4c9edf99..ef9b9d92bb 100644 --- a/identity_vc/src/common/mod.rs +++ b/identity_vc/src/common/mod.rs @@ -1,7 +1,6 @@ mod context; mod credential; mod issuer; -mod oom; mod uri; -pub use self::{context::*, credential::*, issuer::*, oom::*, uri::*}; +pub use self::{context::*, credential::*, issuer::*, uri::*}; diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index bade70faee..627d07a4dc 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,10 +1,10 @@ -use identity_core::common::{Object, Timestamp, Value}; +use identity_core::common::{Object, OneOrMany, Timestamp, Value}; use serde_json::{from_str, to_string}; use crate::{ common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, RefreshService, - TermsOfUse, URI, + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, TermsOfUse, + URI, }, error::{Error, Result}, utils::validate_credential_structure, diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 360bbb2962..2bc043ae3e 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -16,8 +16,8 @@ pub const RESERVED_PROPERTIES: &[&str] = &["issued", "validFrom", "validUntil"]; pub mod prelude { pub use crate::{ common::{ - Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, OneOrMany, - RefreshService, TermsOfUse, URI, + Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, + TermsOfUse, URI, }, credential::{Credential, CredentialBuilder}, error::{Error, Result}, diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index ad6953c02c..97d3e20142 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,8 +1,8 @@ -use identity_core::common::{Object, Value}; +use identity_core::common::{Object, OneOrMany, Value}; use serde_json::{from_str, to_string}; use crate::{ - common::{Context, OneOrMany, RefreshService, TermsOfUse, URI}, + common::{Context, RefreshService, TermsOfUse, URI}, credential::Credential, error::{Error, Result}, utils::validate_presentation_structure, diff --git a/identity_vc/src/utils/validation.rs b/identity_vc/src/utils/validation.rs index a86c21fec3..a1bf06948a 100644 --- a/identity_vc/src/utils/validation.rs +++ b/identity_vc/src/utils/validation.rs @@ -1,5 +1,7 @@ +use identity_core::common::OneOrMany; + use crate::{ - common::{Context, OneOrMany, URI}, + common::{Context, URI}, credential::Credential, error::{Error, Result}, presentation::Presentation, diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs index df45e69ffb..36e6c7f5b0 100644 --- a/identity_vc/src/verifiable/credential.rs +++ b/identity_vc/src/verifiable/credential.rs @@ -1,7 +1,7 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::ops::Deref; -use crate::{common::OneOrMany, credential::Credential}; +use crate::credential::Credential; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiableCredential { diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs index 577a42787d..51b5855fdc 100644 --- a/identity_vc/src/verifiable/presentation.rs +++ b/identity_vc/src/verifiable/presentation.rs @@ -1,7 +1,7 @@ -use identity_core::common::Object; +use identity_core::common::{Object, OneOrMany}; use std::ops::Deref; -use crate::{common::OneOrMany, presentation::Presentation}; +use crate::presentation::Presentation; #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct VerifiablePresentation { From db07c37e13213ababdabc8956eded8507b13e91c Mon Sep 17 00:00:00 2001 From: l1h3r Date: Thu, 3 Sep 2020 09:42:46 -0700 Subject: [PATCH 34/44] Move Uri to identity_core --- identity_core/src/common.rs | 2 ++ .../src/common/uri.rs | 26 ++++++++------ identity_vc/src/common/context.rs | 35 +++++++++++-------- .../common/credential/credential_schema.rs | 6 ++-- .../common/credential/credential_status.rs | 6 ++-- .../common/credential/credential_subject.rs | 9 ++--- .../src/common/credential/refresh_service.rs | 6 ++-- .../src/common/credential/terms_of_use.rs | 6 ++-- identity_vc/src/common/issuer.rs | 22 ++++++------ identity_vc/src/common/mod.rs | 3 +- identity_vc/src/credential.rs | 11 +++--- identity_vc/src/lib.rs | 2 +- identity_vc/src/presentation.rs | 18 +++++----- identity_vc/src/utils/validation.rs | 10 +++--- identity_vc/tests/credential.rs | 4 +-- identity_vc/tests/presentation.rs | 4 +-- 16 files changed, 88 insertions(+), 82 deletions(-) rename {identity_vc => identity_core}/src/common/uri.rs (61%) diff --git a/identity_core/src/common.rs b/identity_core/src/common.rs index 812a38206e..ec287a3c4b 100644 --- a/identity_core/src/common.rs +++ b/identity_core/src/common.rs @@ -2,9 +2,11 @@ mod macros; mod object; mod one_or_many; mod timestamp; +mod uri; mod value; pub use object::Object; pub use one_or_many::OneOrMany; pub use timestamp::Timestamp; +pub use uri::Uri; pub use value::Value; diff --git a/identity_vc/src/common/uri.rs b/identity_core/src/common/uri.rs similarity index 61% rename from identity_vc/src/common/uri.rs rename to identity_core/src/common/uri.rs index de534a6679..1e16f09652 100644 --- a/identity_vc/src/common/uri.rs +++ b/identity_core/src/common/uri.rs @@ -1,27 +1,28 @@ +use serde::{Deserialize, Serialize}; use std::{fmt, ops::Deref}; /// A simple wrapper for URIs adhering to RFC 3986 /// /// TODO: Parse/Validate according to RFC 3986 -/// TODO: impl From for URI +/// TODO: impl From for Uri #[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[repr(transparent)] #[serde(transparent)] -pub struct URI(pub(crate) String); +pub struct Uri(pub(crate) String); -impl URI { +impl Uri { pub fn into_inner(self) -> String { self.0 } } -impl fmt::Debug for URI { +impl fmt::Debug for Uri { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "URI({:?})", self.0) + write!(f, "Uri({:?})", self.0) } } -impl Deref for URI { +impl Deref for Uri { type Target = String; fn deref(&self) -> &Self::Target { @@ -29,20 +30,23 @@ impl Deref for URI { } } -impl From<&'_ str> for URI { +impl From<&'_ str> for Uri { fn from(other: &'_ str) -> Self { Self(other.into()) } } -impl From for URI { +impl From for Uri { fn from(other: String) -> Self { Self(other) } } -impl PartialEq for URI { - fn eq(&self, other: &str) -> bool { - self.0.eq(other) +impl PartialEq for Uri +where + T: AsRef + ?Sized, +{ + fn eq(&self, other: &T) -> bool { + self.0.eq(other.as_ref()) } } diff --git a/identity_vc/src/common/context.rs b/identity_vc/src/common/context.rs index 126843bc82..ad918b93f6 100644 --- a/identity_vc/src/common/context.rs +++ b/identity_vc/src/common/context.rs @@ -1,36 +1,43 @@ -use identity_core::common::Object; +use identity_core::common::{Object, Uri}; use std::fmt; -use crate::common::URI; - /// A reference to a JSON-LD context #[derive(Clone, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Context { - URI(URI), - OBJ(Object), + Uri(Uri), + Obj(Object), } impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::URI(inner) => fmt::Debug::fmt(inner, f), - Self::OBJ(inner) => fmt::Debug::fmt(inner, f), + Self::Uri(inner) => fmt::Debug::fmt(inner, f), + Self::Obj(inner) => fmt::Debug::fmt(inner, f), } } } -impl From for Context -where - T: Into, -{ - fn from(other: T) -> Self { - Self::URI(other.into()) +impl From for Context { + fn from(other: Uri) -> Self { + Self::Uri(other) + } +} + +impl From<&'_ str> for Context { + fn from(other: &'_ str) -> Self { + Self::Uri(other.into()) + } +} + +impl From for Context { + fn from(other: String) -> Self { + Self::Uri(other.into()) } } impl From for Context { fn from(other: Object) -> Self { - Self::OBJ(other) + Self::Obj(other) } } diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index 797d9f7460..01be77c359 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -1,8 +1,8 @@ -use identity_core::common::Object; +use identity_core::common::{Object, Uri}; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_type, URI}, + common::{try_take_object_id, try_take_object_type}, error::Error, }; @@ -11,7 +11,7 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#data-schemas #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialSchema { - pub id: URI, + pub id: Uri, #[serde(rename = "type")] pub type_: String, #[serde(flatten)] diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index a72879c9bb..699b71f398 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -1,8 +1,8 @@ -use identity_core::common::{Object, OneOrMany}; +use identity_core::common::{Object, OneOrMany, Uri}; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_types, URI}, + common::{try_take_object_id, try_take_object_types}, error::Error, }; @@ -11,7 +11,7 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#status #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialStatus { - pub id: URI, + pub id: Uri, #[serde(rename = "type")] pub types: OneOrMany, #[serde(flatten)] diff --git a/identity_vc/src/common/credential/credential_subject.rs b/identity_vc/src/common/credential/credential_subject.rs index bcbd0f7666..7170d09acf 100644 --- a/identity_vc/src/common/credential/credential_subject.rs +++ b/identity_vc/src/common/credential/credential_subject.rs @@ -1,10 +1,7 @@ -use identity_core::common::Object; +use identity_core::common::{Object, Uri}; use std::convert::TryFrom; -use crate::{ - common::{take_object_id, URI}, - error::Error, -}; +use crate::{common::take_object_id, error::Error}; /// An entity who is the target of a set of claims. /// @@ -12,7 +9,7 @@ use crate::{ #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct CredentialSubject { #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, #[serde(flatten)] pub properties: Object, } diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index 9dfdfe8d16..703de90f9f 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -1,8 +1,8 @@ -use identity_core::common::{Object, OneOrMany}; +use identity_core::common::{Object, OneOrMany, Uri}; use std::convert::TryFrom; use crate::{ - common::{try_take_object_id, try_take_object_types, URI}, + common::{try_take_object_id, try_take_object_types}, error::Error, }; @@ -11,7 +11,7 @@ use crate::{ /// Ref: https://www.w3.org/TR/vc-data-model/#refreshing #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct RefreshService { - pub id: URI, + pub id: Uri, #[serde(rename = "type")] pub types: OneOrMany, #[serde(flatten)] diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index dcbae5961b..19ea62c13e 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -1,8 +1,8 @@ -use identity_core::common::{Object, OneOrMany}; +use identity_core::common::{Object, OneOrMany, Uri}; use std::convert::TryFrom; use crate::{ - common::{take_object_id, try_take_object_types, URI}, + common::{take_object_id, try_take_object_types}, error::Error, }; @@ -13,7 +13,7 @@ use crate::{ #[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)] pub struct TermsOfUse { #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, #[serde(rename = "type")] pub types: OneOrMany, #[serde(flatten)] diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs index a723e41c19..5ca13ffbb8 100644 --- a/identity_vc/src/common/issuer.rs +++ b/identity_vc/src/common/issuer.rs @@ -1,35 +1,33 @@ -use identity_core::common::Object; - -use crate::common::URI; +use identity_core::common::{Object, Uri}; /// TODO: -/// - Deserialize single URI into object-style layout +/// - Deserialize single Uri into object-style layout /// - Replace Enum with plain struct #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum Issuer { - URI(URI), - OBJ { - id: URI, + Uri(Uri), + Obj { + id: Uri, #[serde(flatten)] object: Object, }, } impl Issuer { - pub fn uri(&self) -> &URI { + pub fn uri(&self) -> &Uri { match self { - Self::URI(uri) => uri, - Self::OBJ { id, .. } => id, + Self::Uri(uri) => uri, + Self::Obj { id, .. } => id, } } } impl From for Issuer where - T: Into, + T: Into, { fn from(other: T) -> Self { - Self::URI(other.into()) + Self::Uri(other.into()) } } diff --git a/identity_vc/src/common/mod.rs b/identity_vc/src/common/mod.rs index ef9b9d92bb..b3fdb1c6b9 100644 --- a/identity_vc/src/common/mod.rs +++ b/identity_vc/src/common/mod.rs @@ -1,6 +1,5 @@ mod context; mod credential; mod issuer; -mod uri; -pub use self::{context::*, credential::*, issuer::*, uri::*}; +pub use self::{context::*, credential::*, issuer::*}; diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 627d07a4dc..660bac557d 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,10 +1,9 @@ -use identity_core::common::{Object, OneOrMany, Timestamp, Value}; +use identity_core::common::{Object, OneOrMany, Timestamp, Uri, Value}; use serde_json::{from_str, to_string}; use crate::{ common::{ Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, TermsOfUse, - URI, }, error::{Error, Result}, utils::validate_credential_structure, @@ -23,7 +22,7 @@ pub struct Credential { pub context: OneOrMany, /// A unique `URI` referencing the subject of the credential. #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, /// One or more URIs defining the type of credential. /// /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly @@ -96,7 +95,7 @@ impl Credential { #[derive(Debug)] pub struct CredentialBuilder { context: Vec, - id: Option, + id: Option, types: Vec, credential_subject: Vec, issuer: Option, @@ -134,7 +133,7 @@ impl CredentialBuilder { pub fn context(mut self, value: impl Into) -> Self { let value: Context = value.into(); - if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + if !matches!(value, Context::Uri(ref uri) if uri == Credential::BASE_CONTEXT) { self.context.push(value); } @@ -156,7 +155,7 @@ impl CredentialBuilder { self } - impl_builder_setter!(id, id, Option); + impl_builder_setter!(id, id, Option); impl_builder_setter!(subject, credential_subject, Vec); impl_builder_setter!(issuer, issuer, Option); impl_builder_setter!(issuance_date, issuance_date, Timestamp); diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 2bc043ae3e..bfd7e4af41 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -17,7 +17,7 @@ pub mod prelude { pub use crate::{ common::{ Context, CredentialSchema, CredentialStatus, CredentialSubject, Evidence, Issuer, RefreshService, - TermsOfUse, URI, + TermsOfUse, }, credential::{Credential, CredentialBuilder}, error::{Error, Result}, diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 97d3e20142..9997ba3a20 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,8 +1,8 @@ -use identity_core::common::{Object, OneOrMany, Value}; +use identity_core::common::{Object, OneOrMany, Uri, Value}; use serde_json::{from_str, to_string}; use crate::{ - common::{Context, RefreshService, TermsOfUse, URI}, + common::{Context, RefreshService, TermsOfUse}, credential::Credential, error::{Error, Result}, utils::validate_presentation_structure, @@ -21,7 +21,7 @@ pub struct Presentation { pub context: OneOrMany, /// A unique `URI` referencing the subject of the presentation. #[serde(skip_serializing_if = "Option::is_none")] - pub id: Option, + pub id: Option, /// One or more URIs defining the type of presentation. /// /// NOTE: The VC spec defines this as a set of URIs BUT they are commonly @@ -35,7 +35,7 @@ pub struct Presentation { pub verifiable_credential: OneOrMany, /// The entity that generated the presentation. #[serde(skip_serializing_if = "Option::is_none")] - pub holder: Option, + pub holder: Option, /// TODO #[serde(rename = "refreshService", skip_serializing_if = "Option::is_none")] pub refresh_service: Option>, @@ -74,10 +74,10 @@ impl Presentation { #[derive(Debug)] pub struct PresentationBuilder { context: Vec, - id: Option, + id: Option, types: Vec, verifiable_credential: Vec, - holder: Option, + holder: Option, refresh_service: Vec, terms_of_use: Vec, properties: Object, @@ -100,7 +100,7 @@ impl PresentationBuilder { pub fn context(mut self, value: impl Into) -> Self { let value: Context = value.into(); - if !matches!(value, Context::URI(ref uri) if uri == Credential::BASE_CONTEXT) { + if !matches!(value, Context::Uri(ref uri) if uri == Credential::BASE_CONTEXT) { self.context.push(value); } @@ -122,9 +122,9 @@ impl PresentationBuilder { self } - impl_builder_setter!(id, id, Option); + impl_builder_setter!(id, id, Option); impl_builder_setter!(credential, verifiable_credential, Vec); - impl_builder_setter!(holder, holder, Option); + impl_builder_setter!(holder, holder, Option); impl_builder_setter!(refresh, refresh_service, Vec); impl_builder_setter!(terms_of_use, terms_of_use, Vec); impl_builder_setter!(properties, properties, Object); diff --git a/identity_vc/src/utils/validation.rs b/identity_vc/src/utils/validation.rs index a1bf06948a..c5cff9b79f 100644 --- a/identity_vc/src/utils/validation.rs +++ b/identity_vc/src/utils/validation.rs @@ -1,7 +1,7 @@ -use identity_core::common::OneOrMany; +use identity_core::common::{OneOrMany, Uri}; use crate::{ - common::{Context, URI}, + common::Context, credential::Credential, error::{Error, Result}, presentation::Presentation, @@ -67,13 +67,13 @@ pub fn validate_types(name: &'static str, base: &str, types: &OneOrMany) pub fn validate_context(name: &'static str, context: &OneOrMany) -> Result<()> { // The first Credential/Presentation context MUST be a URI representing the base context match context.get(0) { - Some(Context::URI(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), + Some(Context::Uri(uri)) if uri == Credential::BASE_CONTEXT => Ok(()), Some(_) => Err(Error::InvalidBaseContext(name)), None => Err(Error::MissingBaseContext(name)), } } -pub fn validate_uri(name: &'static str, uri: &URI) -> Result<()> { +pub fn validate_uri(name: &'static str, uri: &Uri) -> Result<()> { const KNOWN: [&str; 4] = ["did:", "urn:", "http:", "https:"]; // TODO: Proper URI validation @@ -84,7 +84,7 @@ pub fn validate_uri(name: &'static str, uri: &URI) -> Result<()> { Ok(()) } -pub fn validate_opt_uri(name: &'static str, uri: Option<&URI>) -> Result<()> { +pub fn validate_opt_uri(name: &'static str, uri: Option<&Uri>) -> Result<()> { match uri { Some(uri) => validate_uri(name, uri), None => Ok(()), diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs index 29076f9708..16dc809f5a 100644 --- a/identity_vc/tests/credential.rs +++ b/identity_vc/tests/credential.rs @@ -25,8 +25,8 @@ fn test_builder_valid() { .unwrap(); assert_eq!(credential.context.len(), 3); - assert_matches!(credential.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); - assert_matches!(credential.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + assert_matches!(credential.context.get(0).unwrap(), Context::Uri(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(credential.context.get(1).unwrap(), Context::Uri(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); assert_eq!(credential.id, Some("did:example:123".into())); diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs index d1debc17fb..83531ac9ff 100644 --- a/identity_vc/tests/presentation.rs +++ b/identity_vc/tests/presentation.rs @@ -37,8 +37,8 @@ fn test_builder_valid() { .unwrap(); assert_eq!(presentation.context.len(), 2); - assert_matches!(presentation.context.get(0).unwrap(), Context::URI(ref uri) if uri == Credential::BASE_CONTEXT); - assert_matches!(presentation.context.get(1).unwrap(), Context::URI(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); + assert_matches!(presentation.context.get(0).unwrap(), Context::Uri(ref uri) if uri == Credential::BASE_CONTEXT); + assert_matches!(presentation.context.get(1).unwrap(), Context::Uri(ref uri) if uri == "https://www.w3.org/2018/credentials/examples/v1"); assert_eq!(presentation.id, Some("did:example:id:123".into())); From df469d68766e2ce18d7fad2171a208b25d2601d2 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Thu, 3 Sep 2020 09:44:29 -0700 Subject: [PATCH 35/44] DID -> Uri --- identity_core/src/common/uri.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/identity_core/src/common/uri.rs b/identity_core/src/common/uri.rs index 1e16f09652..3f6335d7cc 100644 --- a/identity_core/src/common/uri.rs +++ b/identity_core/src/common/uri.rs @@ -1,6 +1,8 @@ use serde::{Deserialize, Serialize}; use std::{fmt, ops::Deref}; +use crate::did::DID; + /// A simple wrapper for URIs adhering to RFC 3986 /// /// TODO: Parse/Validate according to RFC 3986 @@ -42,6 +44,12 @@ impl From for Uri { } } +impl From for Uri { + fn from(other: DID) -> Uri { + Self(other.to_string()) + } +} + impl PartialEq for Uri where T: AsRef + ?Sized, From 4028545052faef38f519e51f2b0644bc99163855 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Thu, 3 Sep 2020 11:02:12 -0700 Subject: [PATCH 36/44] Use local identity_core crate for now --- identity_vc/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/identity_vc/Cargo.toml b/identity_vc/Cargo.toml index 1902432d41..25d8e8776e 100644 --- a/identity_vc/Cargo.toml +++ b/identity_vc/Cargo.toml @@ -28,4 +28,5 @@ serde_cbor = "0.11" chrono = { version = "0.4", features = ["serde"] } # identity -identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "dev" } +identity_core = { path = "../identity_core" } +# identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "dev" } From 2317462e9a978eee23df9d899a260837f7187c18 Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 3 Sep 2020 22:13:19 +0200 Subject: [PATCH 37/44] add meeting notes --- docs/meeting-notes/2020-09-02.md | 54 ++++++++++++++++++++++++++++---- docs/meeting-notes/README.md | 5 +-- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/docs/meeting-notes/2020-09-02.md b/docs/meeting-notes/2020-09-02.md index c3c6bcecc7..354fe4a331 100644 --- a/docs/meeting-notes/2020-09-02.md +++ b/docs/meeting-notes/2020-09-02.md @@ -7,13 +7,55 @@ - @tensor-programming - @JelleMillenaar - @huhn511 -- @vidalattias ## 💬 Discussion topics -- -- -- +- Standup +- Diffing Format/Logic +- DID Fragments +- Hash function +- DID Messages +- DIDComm presentation (next week) + +## Standup -## ⏭️ Next Meeting -Wednesday, 2020-09-09 - 17:00 to 18:00 (CEST) \ No newline at end of file +### What was last week's progress on your project? +- prepared DIDComm presentation +- Resolver (Metadata, DID Dereferncing) +- Adds structures for representing Verifiable Credentials and Presentations + +### What will be the project's focus this week? +- Start to implement DIDComm +- DID explainer presentation +- Refactor resolver code and add better error handling +- Finish DID Dereferncing +- Sign credentials + +## Diffing Format/Logic + - Should we use serde-diff or our own lib? @JelleMillenaar expressed concerns about the size of the diff JSON; a home built lib would give us more control over such things. + +## DID Fragments + +Question: How to check the format? Is there a standard for [fragments](https://www.w3.org/TR/did-core/#fragment)? + +There are different format of fragments, to get a public key. +- keys-1 (https://w3c-ccg.github.io/did-resolution/#example-5) +- public-key-1 (https://www.w3.org/TR/did-core/#example-11) + + + Answer: The fragment `keys-1` sould be a name (or key) for the connected value. + - The fragment names should be unique. + - Each fragment need to have an name/key + +Question: Do we check for name uniqueness and throw an error? + +Answer: It's complicated. Let's discuss this in the next meeting or add an whitboard meeting. ([issue here](https://github.com/iotaledger/identity.rs/issues/29)) + +## Hash function +Which hash function do we use for the DID itself? +- Blake2b +- What are the advantages and disadvantages to use Blake2b? + - -> Ask Thibault + +## DID Messages +- more information and comments about DID Messagees are documented in the GoodleDocs document. \ No newline at end of file diff --git a/docs/meeting-notes/README.md b/docs/meeting-notes/README.md index de480437af..1bcb509b57 100644 --- a/docs/meeting-notes/README.md +++ b/docs/meeting-notes/README.md @@ -1,10 +1,11 @@ # Meeting Notes ## Next Meeting -Wednesday, 2020-08-17 - 17:00 to 18:00 (CEST) -- [Preview](https://github.com/iotaledger/identity.rs/pull/13) +Wednesday, 2020-09-09 - 17:00 to 18:00 (CEST) ## Past Meetings +- [2020-09-02](./2020-09-02.md) +- [2020-08-19](./2020-08-19.md) - [2020-08-12](./2020-08-12.md) - [2020-08-07](./2020-08-07.md) - [2020-08-05](./2020-08-05.md) From b54a4d982d28a8c095b01797a913f14859acc03c Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 3 Sep 2020 22:17:38 +0200 Subject: [PATCH 38/44] prepare meeting notes and refactor template --- docs/meeting-notes/2020-09-09.md | 34 ++++++++++++++++++++++++++++++++ docs/meeting-notes/_template.md | 22 +++++++++++++++++---- 2 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 docs/meeting-notes/2020-09-09.md diff --git a/docs/meeting-notes/2020-09-09.md b/docs/meeting-notes/2020-09-09.md new file mode 100644 index 0000000000..7e296d6e36 --- /dev/null +++ b/docs/meeting-notes/2020-09-09.md @@ -0,0 +1,34 @@ +# 🗓️ Team Identity Meeting Notes - 2020-09-09 + +## 👥 Participants +- @Thoralf-M +- @nothingismagick +- @vidalattias +- @tensor-programming +- @JelleMillenaar +- @huhn511 +- @l1h3r + +## 💬 Discussion topics +- Standup +- Questions +- + + + +## Standup + +### What was last week's progress on your project? +- +- +- + +### What will be the project's focus this week? +- +- +- + +## Questions + +Q: +A: \ No newline at end of file diff --git a/docs/meeting-notes/_template.md b/docs/meeting-notes/_template.md index cb33d3215d..f79db77d0b 100644 --- a/docs/meeting-notes/_template.md +++ b/docs/meeting-notes/_template.md @@ -1,4 +1,4 @@ -# 🗓️ Team Identity Meeting Notes - 2020-xx-xx +# 🗓️ Team Identity Meeting Notes - 2020-XX-XX ## 👥 Participants - @Thoralf-M @@ -7,14 +7,28 @@ - @tensor-programming - @JelleMillenaar - @huhn511 - +- @l1h3r ## 💬 Discussion topics +- Standup +- Questions - + + + +## Standup + +### What was last week's progress on your project? - - - -## 🎯 Goals - + +### What will be the project's focus this week? +- - - + +## Questions + +Q: +A: \ No newline at end of file From 66a60f9264a36501846f2ce757b413714348ddc3 Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 3 Sep 2020 22:19:49 +0200 Subject: [PATCH 39/44] fixed headlines --- docs/meeting-notes/2020-09-09.md | 8 ++++---- docs/meeting-notes/_template.md | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/meeting-notes/2020-09-09.md b/docs/meeting-notes/2020-09-09.md index 7e296d6e36..5cacd8a246 100644 --- a/docs/meeting-notes/2020-09-09.md +++ b/docs/meeting-notes/2020-09-09.md @@ -16,19 +16,19 @@ -## Standup +### Standup -### What was last week's progress on your project? +#### What was last week's progress on your project? - - - -### What will be the project's focus this week? +#### What will be the project's focus this week? - - - -## Questions +### Questions Q: A: \ No newline at end of file diff --git a/docs/meeting-notes/_template.md b/docs/meeting-notes/_template.md index f79db77d0b..5cacd8a246 100644 --- a/docs/meeting-notes/_template.md +++ b/docs/meeting-notes/_template.md @@ -1,4 +1,4 @@ -# 🗓️ Team Identity Meeting Notes - 2020-XX-XX +# 🗓️ Team Identity Meeting Notes - 2020-09-09 ## 👥 Participants - @Thoralf-M @@ -16,19 +16,19 @@ -## Standup +### Standup -### What was last week's progress on your project? +#### What was last week's progress on your project? - - - -### What will be the project's focus this week? +#### What will be the project's focus this week? - - - -## Questions +### Questions Q: A: \ No newline at end of file From 59b76ea5aea9461d2d0f53ce33089d8aa33927e0 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Fri, 4 Sep 2020 10:58:30 -0700 Subject: [PATCH 40/44] Move macros to identity_core --- identity_core/src/common/macros.rs | 65 ++++++++++++++++++++++++++++ identity_vc/src/lib.rs | 4 +- identity_vc/src/macros.rs | 68 ------------------------------ 3 files changed, 67 insertions(+), 70 deletions(-) delete mode 100644 identity_vc/src/macros.rs diff --git a/identity_core/src/common/macros.rs b/identity_core/src/common/macros.rs index 1a0ab6e5e6..c675ea5ff6 100644 --- a/identity_core/src/common/macros.rs +++ b/identity_core/src/common/macros.rs @@ -29,3 +29,68 @@ macro_rules! line_error { concat!($string, " @", file!(), ":", line!()) }; } + +#[macro_export] +macro_rules! impl_builder_setter { + ($fn:ident, $field:ident, Option<$ty:ty>) => { + impl_builder_setter!(@impl $fn, $field, $ty, Option); + }; + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + impl_builder_setter!(@impl $fn, $field, $ty, Vec); + }; + ($fn:ident, $field:ident, $ty:ty) => { + impl_builder_setter!(@impl $fn, $field, $ty, None); + }; + (@impl $fn:ident, $field:ident, $inner:ty, $outer:ident) => { + pub fn $fn(mut self, value: impl Into<$inner>) -> Self { + impl_builder_setter!(@expr self, $field, value, $outer); + self + } + }; + (@expr $self:ident, $field:ident, $value:expr, Option) => { + $self.$field = Some($value.into()); + }; + (@expr $self:ident, $field:ident, $value:expr, Vec) => { + $self.$field.push($value.into()); + }; + (@expr $self:ident, $field:ident, $value:expr, None) => { + $self.$field = $value.into(); + }; +} + +#[macro_export] +macro_rules! impl_builder_try_setter { + ($fn:ident, $field:ident, Option<$ty:ty>) => { + impl_builder_try_setter!(@impl $fn, $field, $ty, Option); + }; + + ($fn:ident, $field:ident, Vec<$ty:ty>) => { + impl_builder_try_setter!(@impl $fn, $field, $ty, Vec); + }; + + ($fn:ident, $field:ident, $ty:ty) => { + impl_builder_try_setter!(@impl $fn, $field, $ty, None); + }; + (@impl $fn:ident, $field:ident, $inner:ty, $outer:ident) => { + pub fn $fn(mut self, value: T) -> ::std::result::Result + where + T: ::std::convert::TryInto<$inner> + { + value.try_into() + .map(|value| { + impl_builder_try_setter!(@expr self, $field, value, $outer); + self + }) + .map_err(Into::into) + } + }; + (@expr $self:ident, $field:ident, $value:expr, Option) => { + $self.$field = Some($value); + }; + (@expr $self:ident, $field:ident, $value:expr, Vec) => { + $self.$field.push($value); + }; + (@expr $self:ident, $field:ident, $value:expr, None) => { + $self.$field = $value; + }; +} diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index bfd7e4af41..7a9001fce7 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -1,8 +1,8 @@ #[macro_use] -extern crate serde; +extern crate identity_core; #[macro_use] -mod macros; +extern crate serde; pub mod common; pub mod credential; diff --git a/identity_vc/src/macros.rs b/identity_vc/src/macros.rs deleted file mode 100644 index dd017a1acd..0000000000 --- a/identity_vc/src/macros.rs +++ /dev/null @@ -1,68 +0,0 @@ -macro_rules! impl_builder_setter { - ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field = Some(value.into()); - self - } - }; - ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field.push(value.into()); - self - } - }; - ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: impl Into<$ty>) -> Self { - self.$field = value.into(); - self - } - }; -} - -macro_rules! impl_builder_try_setter { - ($fn:ident, $field:ident, Option<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field = Some(value); - self - }) - .map_err(Into::into) - } - }; - ($fn:ident, $field:ident, Vec<$ty:ty>) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field.push(value); - self - }) - .map_err(Into::into) - } - }; - ($fn:ident, $field:ident, $ty:ty) => { - pub fn $fn(mut self, value: T) -> $crate::error::Result - where - $ty: ::std::convert::TryFrom, - U: Into<$crate::error::Error>, - { - use ::std::convert::TryFrom; - <$ty>::try_from(value) - .map(|value| { - self.$field = value; - self - }) - .map_err(Into::into) - } - }; -} From 2549b00933228beab55db3b3774a11c1c61bf00d Mon Sep 17 00:00:00 2001 From: Rajiv Shah Date: Fri, 4 Sep 2020 15:34:19 -0400 Subject: [PATCH 41/44] fix(ci): Avoid running clippy and rustfmt when docs are updated --- .github/workflows/clippy.yml | 2 ++ .github/workflows/format.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index ad3a366545..4138103f7e 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -8,6 +8,8 @@ on: branches: - main - dev + paths-ignore: + - 'docs/**' jobs: clippy: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2af4595a5b..eac27fe430 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -8,6 +8,8 @@ on: branches: - main - dev + paths-ignore: + - 'docs/**' jobs: format: From 8430b5c6bb8b88e0c69441595d76dfc584cbd991 Mon Sep 17 00:00:00 2001 From: l1h3r Date: Mon, 7 Sep 2020 09:42:53 -0700 Subject: [PATCH 42/44] Explicit macros --- identity_vc/src/common/context.rs | 1 + identity_vc/src/common/credential/credential_schema.rs | 1 + identity_vc/src/common/credential/credential_status.rs | 1 + identity_vc/src/common/credential/credential_subject.rs | 1 + identity_vc/src/common/credential/evidence.rs | 1 + identity_vc/src/common/credential/refresh_service.rs | 1 + identity_vc/src/common/credential/terms_of_use.rs | 1 + identity_vc/src/common/issuer.rs | 1 + identity_vc/src/credential.rs | 6 +++++- identity_vc/src/lib.rs | 6 ------ identity_vc/src/presentation.rs | 6 +++++- identity_vc/src/verifiable/credential.rs | 1 + identity_vc/src/verifiable/presentation.rs | 1 + identity_vc/tests/credential.rs | 4 +--- identity_vc/tests/presentation.rs | 4 +--- 15 files changed, 22 insertions(+), 14 deletions(-) diff --git a/identity_vc/src/common/context.rs b/identity_vc/src/common/context.rs index ad918b93f6..ec3aa774e4 100644 --- a/identity_vc/src/common/context.rs +++ b/identity_vc/src/common/context.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, Uri}; +use serde::{Deserialize, Serialize}; use std::fmt; /// A reference to a JSON-LD context diff --git a/identity_vc/src/common/credential/credential_schema.rs b/identity_vc/src/common/credential/credential_schema.rs index 01be77c359..e13edf8f23 100644 --- a/identity_vc/src/common/credential/credential_schema.rs +++ b/identity_vc/src/common/credential/credential_schema.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, Uri}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ diff --git a/identity_vc/src/common/credential/credential_status.rs b/identity_vc/src/common/credential/credential_status.rs index 699b71f398..2d3f19f468 100644 --- a/identity_vc/src/common/credential/credential_status.rs +++ b/identity_vc/src/common/credential/credential_status.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany, Uri}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ diff --git a/identity_vc/src/common/credential/credential_subject.rs b/identity_vc/src/common/credential/credential_subject.rs index 7170d09acf..7b686c5dba 100644 --- a/identity_vc/src/common/credential/credential_subject.rs +++ b/identity_vc/src/common/credential/credential_subject.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, Uri}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{common::take_object_id, error::Error}; diff --git a/identity_vc/src/common/credential/evidence.rs b/identity_vc/src/common/credential/evidence.rs index 5028b93e0a..f653153a33 100644 --- a/identity_vc/src/common/credential/evidence.rs +++ b/identity_vc/src/common/credential/evidence.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ diff --git a/identity_vc/src/common/credential/refresh_service.rs b/identity_vc/src/common/credential/refresh_service.rs index 703de90f9f..8f59b5c509 100644 --- a/identity_vc/src/common/credential/refresh_service.rs +++ b/identity_vc/src/common/credential/refresh_service.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany, Uri}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ diff --git a/identity_vc/src/common/credential/terms_of_use.rs b/identity_vc/src/common/credential/terms_of_use.rs index 19ea62c13e..4f79848d4b 100644 --- a/identity_vc/src/common/credential/terms_of_use.rs +++ b/identity_vc/src/common/credential/terms_of_use.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany, Uri}; +use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use crate::{ diff --git a/identity_vc/src/common/issuer.rs b/identity_vc/src/common/issuer.rs index 5ca13ffbb8..a4361abd30 100644 --- a/identity_vc/src/common/issuer.rs +++ b/identity_vc/src/common/issuer.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, Uri}; +use serde::{Deserialize, Serialize}; /// TODO: /// - Deserialize single Uri into object-style layout diff --git a/identity_vc/src/credential.rs b/identity_vc/src/credential.rs index 660bac557d..9535e7e883 100644 --- a/identity_vc/src/credential.rs +++ b/identity_vc/src/credential.rs @@ -1,4 +1,8 @@ -use identity_core::common::{Object, OneOrMany, Timestamp, Uri, Value}; +use identity_core::{ + common::{Object, OneOrMany, Timestamp, Uri, Value}, + impl_builder_setter, impl_builder_try_setter, +}; +use serde::{Deserialize, Serialize}; use serde_json::{from_str, to_string}; use crate::{ diff --git a/identity_vc/src/lib.rs b/identity_vc/src/lib.rs index 7a9001fce7..42bfb3d477 100644 --- a/identity_vc/src/lib.rs +++ b/identity_vc/src/lib.rs @@ -1,9 +1,3 @@ -#[macro_use] -extern crate identity_core; - -#[macro_use] -extern crate serde; - pub mod common; pub mod credential; pub mod error; diff --git a/identity_vc/src/presentation.rs b/identity_vc/src/presentation.rs index 9997ba3a20..b98dbcd770 100644 --- a/identity_vc/src/presentation.rs +++ b/identity_vc/src/presentation.rs @@ -1,4 +1,8 @@ -use identity_core::common::{Object, OneOrMany, Uri, Value}; +use identity_core::{ + common::{Object, OneOrMany, Uri, Value}, + impl_builder_setter, impl_builder_try_setter, +}; +use serde::{Deserialize, Serialize}; use serde_json::{from_str, to_string}; use crate::{ diff --git a/identity_vc/src/verifiable/credential.rs b/identity_vc/src/verifiable/credential.rs index 36e6c7f5b0..ab5948746c 100644 --- a/identity_vc/src/verifiable/credential.rs +++ b/identity_vc/src/verifiable/credential.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany}; +use serde::{Deserialize, Serialize}; use std::ops::Deref; use crate::credential::Credential; diff --git a/identity_vc/src/verifiable/presentation.rs b/identity_vc/src/verifiable/presentation.rs index 51b5855fdc..c8e77f2867 100644 --- a/identity_vc/src/verifiable/presentation.rs +++ b/identity_vc/src/verifiable/presentation.rs @@ -1,4 +1,5 @@ use identity_core::common::{Object, OneOrMany}; +use serde::{Deserialize, Serialize}; use std::ops::Deref; use crate::presentation::Presentation; diff --git a/identity_vc/tests/credential.rs b/identity_vc/tests/credential.rs index 16dc809f5a..1cbd9880d7 100644 --- a/identity_vc/tests/credential.rs +++ b/identity_vc/tests/credential.rs @@ -1,9 +1,7 @@ -#[macro_use] -extern crate identity_core; - #[macro_use] mod macros; +use identity_core::object; use identity_vc::prelude::*; #[test] diff --git a/identity_vc/tests/presentation.rs b/identity_vc/tests/presentation.rs index 83531ac9ff..ff0ef2ee0a 100644 --- a/identity_vc/tests/presentation.rs +++ b/identity_vc/tests/presentation.rs @@ -1,9 +1,7 @@ -#[macro_use] -extern crate identity_core; - #[macro_use] mod macros; +use identity_core::object; use identity_vc::prelude::*; #[test] From 6c4f7f95630701bd4318a90204c87b9e88f45b00 Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 10 Sep 2020 15:46:46 +0200 Subject: [PATCH 43/44] add meeting notes --- docs/meeting-notes/2020-09-09.md | 40 +++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/docs/meeting-notes/2020-09-09.md b/docs/meeting-notes/2020-09-09.md index 5cacd8a246..a7884b63fd 100644 --- a/docs/meeting-notes/2020-09-09.md +++ b/docs/meeting-notes/2020-09-09.md @@ -3,32 +3,46 @@ ## 👥 Participants - @Thoralf-M - @nothingismagick -- @vidalattias - @tensor-programming - @JelleMillenaar -- @huhn511 - @l1h3r ## 💬 Discussion topics - Standup +- Fragment Name Uniqueness +- Working Group participation +- LD Proof Scope: Merkle Tree - Questions -- - - +- Account required data and module implementation ### Standup #### What was last week's progress on your project? -- -- -- +- Worked on a library for handling LD Proofs and credential verfication (PR open). Looked at DIDcomm +- Worked on the Proc macro for the Diff library - almost finished. +- Dereferencing part for the resolver + resolver itself +- Started writing the MethodSpec for W3C. Researched DIDcomm enviroment. Looking at DID Auth + NOISE. +- Discovered existing DIDComm Spec and add facts to presentation. + #### What will be the project's focus this week? -- -- -- +- Continue with the resolver- +- finish debugging the proc macro and move on to the account module. +- Continue MethodSpec. Kickstart P2P Comms Layer. +- Add some `jsonwebtoken`-based signature suites and look at SIOP DID- +- Start to write DIDComm as Spec and in Code for experimental purposes- + +### Fragment Name Uniqueness +Question: Do we check for name uniqueness and throw an error? +([issue here](https://github.com/iotaledger/identity.rs/issues/29)) +- Figure out when a fragment reference is legal +- Add a function to check if all fragments are unique +- Execute function when adding a new object to a DID Document +- Leave additional checks up to the implementer (Call the function manually) ### Questions +Q: Can our current Proof implementation handle extra logic such as putting the data through a merkle tree? +A: Yes -Q: -A: \ No newline at end of file +Q: Can we run our signature logic within Stronghold? +A: Most likely without too much hassle. From 59d4c6a7b4ada293e0603069d74b6d79ef94e14a Mon Sep 17 00:00:00 2001 From: huhn511 Date: Thu, 10 Sep 2020 15:47:11 +0200 Subject: [PATCH 44/44] add new meeting notes to index --- docs/meeting-notes/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/meeting-notes/README.md b/docs/meeting-notes/README.md index de480437af..5e96541897 100644 --- a/docs/meeting-notes/README.md +++ b/docs/meeting-notes/README.md @@ -1,10 +1,10 @@ # Meeting Notes ## Next Meeting -Wednesday, 2020-08-17 - 17:00 to 18:00 (CEST) -- [Preview](https://github.com/iotaledger/identity.rs/pull/13) +Wednesday, 2020-09-16 - 17:00 to 18:00 (CEST) ## Past Meetings +- [2020-09-09](./2020-09-09.md) - [2020-08-12](./2020-08-12.md) - [2020-08-07](./2020-08-07.md) - [2020-08-05](./2020-08-05.md)