Skip to content

Commit

Permalink
Merge pull request #20 from iotaledger/feat/verifiable-credentials
Browse files Browse the repository at this point in the history
feat(vc): Create verifiable credentials
  • Loading branch information
Jelle Femmo Millenaar authored Sep 9, 2020
2 parents f8b3d0a + 8430b5c commit 3f6f228
Show file tree
Hide file tree
Showing 43 changed files with 1,875 additions and 8 deletions.
4 changes: 4 additions & 0 deletions identity_core/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
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;
65 changes: 65 additions & 0 deletions identity_core/src/common/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(mut self, value: T) -> ::std::result::Result<Self, T::Error>
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;
};
}
120 changes: 120 additions & 0 deletions identity_core/src/common/one_or_many.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use serde::{Deserialize, Serialize};
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<T> {
One(T),
Many(Vec<T>),
}

impl<T> OneOrMany<T> {
/// 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<T>,
{
match self {
Self::One(inner) => inner == value,
Self::Many(inner) => inner.contains(value),
}
}

pub fn iter(&self) -> impl Iterator<Item = &T> + '_ {
OneOrManyIter::new(self)
}

/// Consumes the `OneOrMany`, returning the contents as a `Vec`.
pub fn into_vec(self) -> Vec<T> {
match self {
Self::One(inner) => vec![inner],
Self::Many(inner) => inner,
}
}
}

impl<T> fmt::Debug for OneOrMany<T>
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<T> Default for OneOrMany<T> {
fn default() -> Self {
Self::Many(Vec::new())
}
}

impl<T> From<T> for OneOrMany<T> {
fn from(other: T) -> Self {
Self::One(other)
}
}

impl<T> From<Vec<T>> for OneOrMany<T> {
fn from(mut other: Vec<T>) -> Self {
if other.len() == 1 {
Self::One(other.pop().expect("infallible"))
} else {
Self::Many(other)
}
}
}

impl<T> From<OneOrMany<T>> for Vec<T> {
fn from(other: OneOrMany<T>) -> Self {
other.into_vec()
}
}

struct OneOrManyIter<'a, T> {
inner: &'a OneOrMany<T>,
index: usize,
}

impl<'a, T> OneOrManyIter<'a, T> {
pub fn new(inner: &'a OneOrMany<T>) -> Self {
Self { inner, index: 0 }
}
}

impl<'a, T> Iterator for OneOrManyIter<'a, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
self.index += 1;
self.inner.get(self.index - 1)
}
}
60 changes: 60 additions & 0 deletions identity_core/src/common/uri.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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
/// TODO: impl From<DID> for Uri
#[derive(Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
#[repr(transparent)]
#[serde(transparent)]
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 {
write!(f, "Uri({:?})", self.0)
}
}

impl Deref for Uri {
type Target = String;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl From<&'_ str> for Uri {
fn from(other: &'_ str) -> Self {
Self(other.into())
}
}

impl From<String> for Uri {
fn from(other: String) -> Self {
Self(other)
}
}

impl From<DID> for Uri {
fn from(other: DID) -> Uri {
Self(other.to_string())
}
}

impl<T> PartialEq<T> for Uri
where
T: AsRef<str> + ?Sized,
{
fn eq(&self, other: &T) -> bool {
self.0.eq(other.as_ref())
}
}
21 changes: 19 additions & 2 deletions identity_vc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@ 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"] }

# identity
identity_core = { path = "../identity_core" }
# identity_core = { git = "https://github.com/iotaledger/identity.rs", branch = "dev" }
34 changes: 34 additions & 0 deletions identity_vc/bin/vc_test_suite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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<String> = 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)?;

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)?;

data.validate()?;

println!("{}", to_string(&data)?);
}
test => {
panic!("Unknown Test: {:?}", test);
}
}

Ok(())
}
44 changes: 44 additions & 0 deletions identity_vc/src/common/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use identity_core::common::{Object, Uri};
use serde::{Deserialize, Serialize};
use std::fmt;

/// 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<Uri> 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<String> for Context {
fn from(other: String) -> Self {
Self::Uri(other.into())
}
}

impl From<Object> for Context {
fn from(other: Object) -> Self {
Self::Obj(other)
}
}
Loading

0 comments on commit 3f6f228

Please sign in to comment.