Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Enforce non-empty subject for VCDM v2 #572

Merged
merged 11 commits into from
Jul 8, 2024
42 changes: 32 additions & 10 deletions crates/claims/crates/vc/src/syntax/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod context;
mod credential;
mod non_empty_object;
mod non_empty_vec;
mod presentation;
mod types;

Expand All @@ -10,6 +11,7 @@ pub use context::*;
pub use credential::*;
use iref::{Uri, UriBuf};
pub use non_empty_object::*;
pub use non_empty_vec::*;
pub use presentation::*;
use serde::{Deserialize, Serialize};
pub use types::*;
Expand Down Expand Up @@ -102,6 +104,7 @@ pub struct TypedObject {

pub(crate) mod value_or_array {
use serde::{Deserialize, Serialize};
use ssi_core::OneOrMany;

pub fn serialize<T: Serialize, S>(value: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
Expand All @@ -113,22 +116,41 @@ pub(crate) mod value_or_array {
}
}

#[derive(Deserialize)]
#[serde(untagged)]
enum SingleOrArray<T> {
Array(Vec<T>),
Single(T),
}

pub fn deserialize<'de, T: Deserialize<'de>, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: serde::Deserializer<'de>,
{
match SingleOrArray::deserialize(deserializer)? {
SingleOrArray::Array(v) => Ok(v),
SingleOrArray::Single(t) => Ok(vec![t]),
Ok(OneOrMany::deserialize(deserializer)?.into_vec())
}
}

pub(crate) mod non_empty_value_or_array {
use serde::{Deserialize, Serialize};
use ssi_core::OneOrMany;

use super::NonEmptyVec;

pub fn serialize<T: Serialize, S>(value: &[T], serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match value.split_first() {
Some((first, [])) => first.serialize(serializer),
_ => value.serialize(serializer),
}
}

pub fn deserialize<'de, T: Deserialize<'de>, D>(
deserializer: D,
) -> Result<NonEmptyVec<T>, D::Error>
where
D: serde::Deserializer<'de>,
{
OneOrMany::deserialize(deserializer)?
.into_vec()
.try_into()
.map_err(serde::de::Error::custom)
}
}

/// Deserialize an `Option::Some`, without accepting `None` (null) as a value.
Expand Down
103 changes: 103 additions & 0 deletions crates/claims/crates/vc/src/syntax/non_empty_vec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use serde::{Deserialize, Serialize};
use std::ops::{Deref, DerefMut};

#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
#[serde(transparent)]
pub struct NonEmptyVec<T>(Vec<T>);

#[derive(Debug, thiserror::Error)]
#[error("empty vec")]
pub struct EmptyVecError;

impl<T> NonEmptyVec<T> {
pub fn new(t: T) -> Self {
Self(vec![t])
}

pub fn try_from_vec(v: Vec<T>) -> Result<Self, EmptyVecError> {
Self::try_from(v)
}

pub fn push(&mut self, t: T) {
self.0.push(t)
}

pub fn into_inner(self) -> Vec<T> {
self.0
}
}

impl<T> TryFrom<Vec<T>> for NonEmptyVec<T> {
type Error = EmptyVecError;

fn try_from(v: Vec<T>) -> Result<NonEmptyVec<T>, Self::Error> {
if v.is_empty() {
return Err(EmptyVecError);
}
Ok(NonEmptyVec(v))
}
}

impl<T> From<NonEmptyVec<T>> for Vec<T> {
fn from(NonEmptyVec(v): NonEmptyVec<T>) -> Vec<T> {
v
}
}

impl<T> AsRef<[T]> for NonEmptyVec<T> {
fn as_ref(&self) -> &[T] {
&self.0
}
}

impl<T> Deref for NonEmptyVec<T> {
type Target = [T];

fn deref(&self) -> &[T] {
&self.0
}
}

impl<T> DerefMut for NonEmptyVec<T> {
fn deref_mut(&mut self) -> &mut [T] {
&mut self.0
}
}

impl<T> IntoIterator for NonEmptyVec<T> {
type Item = T;
type IntoIter = std::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl<'a, T> IntoIterator for &'a NonEmptyVec<T> {
type Item = &'a T;
type IntoIter = std::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter().collect::<Vec<Self::Item>>().into_iter()
}
}

impl<'a, T> IntoIterator for &'a mut NonEmptyVec<T> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;

fn into_iter(self) -> Self::IntoIter {
self.0.iter_mut()
}
}

impl<'de, T: Deserialize<'de>> Deserialize<'de> for NonEmptyVec<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Vec::<T>::deserialize(deserializer)?
.try_into()
.map_err(serde::de::Error::custom)
}
}
15 changes: 6 additions & 9 deletions crates/claims/crates/vc/src/v2/syntax/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ use std::{borrow::Cow, collections::BTreeMap, hash::Hash};

use super::{Context, InternationalString, RelatedResource};
use crate::syntax::{
not_null, value_or_array, IdOr, IdentifiedObject, IdentifiedTypedObject,
MaybeIdentifiedTypedObject, NonEmptyObject, RequiredContextList, RequiredTypeSet, TypedObject,
non_empty_value_or_array, not_null, value_or_array, IdOr, IdentifiedObject,
IdentifiedTypedObject, MaybeIdentifiedTypedObject, NonEmptyObject, NonEmptyVec,
RequiredContextList, RequiredTypeSet, TypedObject,
};
use iref::{Uri, UriBuf};
use rdf_types::VocabularyMut;
Expand Down Expand Up @@ -49,12 +50,8 @@ pub struct SpecializedJsonCredential<S = NonEmptyObject, C = (), T = ()> {

/// Credential subjects.
#[serde(rename = "credentialSubject")]
#[serde(
with = "value_or_array",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub credential_subjects: Vec<S>,
#[serde(with = "non_empty_value_or_array")]
pub credential_subjects: NonEmptyVec<S>,

/// Issuer.
pub issuer: IdOr<IdentifiedObject>,
Expand Down Expand Up @@ -120,7 +117,7 @@ impl<S, C: RequiredContextList, T: RequiredTypeSet> SpecializedJsonCredential<S,
pub fn new(
id: Option<UriBuf>,
issuer: IdOr<IdentifiedObject>,
credential_subjects: Vec<S>,
credential_subjects: NonEmptyVec<S>,
) -> Self {
Self {
context: Context::default(),
Expand Down