use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::did::{Context, Did, DidUrl, OneOrMany, Uri};
use super::CredentialError;
pub const CONTEXT_V1: &str = "https://www.w3.org/2018/credentials/v1";
pub const CONTEXT_V2: &str = "https://www.w3.org/ns/credentials/v2";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
#[non_exhaustive]
pub enum Party {
Uri(Uri),
Object {
id: Uri,
#[serde(flatten)]
properties: IndexMap<String, Value>,
},
}
impl Party {
pub fn object(id: impl Into<Uri>, properties: IndexMap<String, Value>) -> Self {
Self::Object {
id: id.into(),
properties,
}
}
pub fn id(&self) -> &Uri {
match self {
Self::Uri(id) | Self::Object { id, .. } => id,
}
}
pub fn properties(&self) -> Option<&IndexMap<String, Value>> {
match self {
Self::Uri(_) => None,
Self::Object { properties, .. } => Some(properties),
}
}
}
impl From<Uri> for Party {
fn from(id: Uri) -> Self {
Self::Uri(id)
}
}
impl From<Did> for Party {
fn from(did: Did) -> Self {
Self::Uri(did.into())
}
}
impl From<&Did> for Party {
fn from(did: &Did) -> Self {
Self::Uri(did.into())
}
}
impl From<DidUrl> for Party {
fn from(url: DidUrl) -> Self {
Self::Uri(url.into())
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct Subject {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Uri>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl Subject {
pub fn new(id: impl Into<Uri>) -> Self {
Self {
id: Some(id.into()),
properties: IndexMap::new(),
}
}
#[must_use]
pub fn with_property(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.properties.insert(name.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TypedObject {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Uri>,
#[serde(rename = "type")]
pub types: OneOrMany<String>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl TypedObject {
pub fn new(object_type: impl Into<String>) -> Self {
Self {
id: None,
types: OneOrMany::One(object_type.into()),
properties: IndexMap::new(),
}
}
#[must_use]
pub fn with_id(mut self, id: impl Into<Uri>) -> Self {
self.id = Some(id.into());
self
}
#[must_use]
pub fn with_property(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
self.properties.insert(name.into(), value.into());
self
}
pub fn has_type(&self, object_type: &str) -> bool {
self.types.iter().any(|t| t == object_type)
}
pub fn object_type(&self) -> &str {
self.types.first().map_or("", String::as_str)
}
}
pub(super) fn first_context(context: &Context) -> Option<&str> {
match context.as_value() {
Value::String(uri) => Some(uri),
Value::Array(items) => items.first().and_then(Value::as_str),
_ => None,
}
}
pub(super) fn check_context_and_type(
context: &Context,
base: &'static str,
types: &OneOrMany<String>,
required: &'static str,
) -> Result<(), CredentialError> {
if first_context(context) != Some(base) {
return Err(CredentialError::InvalidContext { expected: base });
}
if !types.iter().any(|t| t == required) {
return Err(CredentialError::MissingType(required));
}
Ok(())
}
pub(super) fn append_context(context: Context, uri: String) -> Context {
let mut uris = match context.into_value() {
Value::Array(items) => items,
single => vec![single],
};
uris.push(Value::String(uri));
Context::new(Value::Array(uris)).expect("a non-empty array")
}
pub(super) fn push_type(types: &mut OneOrMany<String>, added: String) {
let mut list = core::mem::replace(types, OneOrMany::Many(Vec::new())).into_vec();
list.push(added);
*types = OneOrMany::Many(list);
}