use core::fmt;
use core::str::FromStr;
use indexmap::IndexMap;
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use super::{Did, DidDocument, DidError};
pub const APPLICATION_DID_JSON: &str = "application/did+json";
pub const APPLICATION_DID_LD_JSON: &str = "application/did+ld+json";
#[derive(Debug, Clone, PartialEq, Eq, Hash, thiserror::Error)]
#[non_exhaustive]
pub enum ResolutionError {
#[error("invalid DID")]
InvalidDid,
#[error("DID not found")]
NotFound,
#[error("DID method not supported")]
MethodNotSupported,
#[error("representation not supported")]
RepresentationNotSupported,
#[error("invalid DID document")]
InvalidDidDocument,
#[error("internal resolver error")]
InternalError,
#[error("resolution error `{0}`")]
Other(String),
}
impl ResolutionError {
pub fn as_str(&self) -> &str {
match self {
Self::InvalidDid => "invalidDid",
Self::NotFound => "notFound",
Self::MethodNotSupported => "methodNotSupported",
Self::RepresentationNotSupported => "representationNotSupported",
Self::InvalidDidDocument => "invalidDidDocument",
Self::InternalError => "internalError",
Self::Other(keyword) => keyword,
}
}
}
impl FromStr for ResolutionError {
type Err = core::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"invalidDid" => Self::InvalidDid,
"notFound" => Self::NotFound,
"methodNotSupported" => Self::MethodNotSupported,
"representationNotSupported" => Self::RepresentationNotSupported,
"invalidDidDocument" => Self::InvalidDidDocument,
"internalError" => Self::InternalError,
other => Self::Other(other.to_owned()),
})
}
}
impl From<DidError> for ResolutionError {
fn from(_: DidError) -> Self {
Self::InvalidDid
}
}
impl Serialize for ResolutionError {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for ResolutionError {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let keyword = String::deserialize(deserializer)?;
if keyword.is_empty() || !keyword.is_ascii() {
return Err(D::Error::custom("error must be an ASCII keyword"));
}
keyword.parse().map_err(|never| match never {})
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ResolutionOptions {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub accept: Option<String>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl ResolutionOptions {
pub fn accept(media_type: impl Into<String>) -> Self {
Self {
accept: Some(media_type.into()),
properties: IndexMap::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ResolutionMetadata {
#[serde(
rename = "contentType",
default,
skip_serializing_if = "Option::is_none"
)]
pub content_type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<ResolutionError>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl ResolutionMetadata {
pub fn error(error: ResolutionError) -> Self {
Self {
content_type: None,
error: Some(error),
properties: IndexMap::new(),
}
}
pub fn content_type(media_type: impl Into<String>) -> Self {
Self {
content_type: Some(media_type.into()),
error: None,
properties: IndexMap::new(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct DocumentMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub created: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub updated: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deactivated: Option<bool>,
#[serde(
rename = "nextUpdate",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_update: Option<String>,
#[serde(rename = "versionId", default, skip_serializing_if = "Option::is_none")]
pub version_id: Option<String>,
#[serde(
rename = "nextVersionId",
default,
skip_serializing_if = "Option::is_none"
)]
pub next_version_id: Option<String>,
#[serde(
rename = "equivalentId",
default,
skip_serializing_if = "Vec::is_empty"
)]
pub equivalent_id: Vec<Did>,
#[serde(
rename = "canonicalId",
default,
skip_serializing_if = "Option::is_none"
)]
pub canonical_id: Option<Did>,
#[serde(flatten)]
pub properties: IndexMap<String, Value>,
}
impl DocumentMetadata {
pub fn is_deactivated(&self) -> bool {
self.deactivated == Some(true)
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ResolutionResult {
#[serde(
rename = "didDocument",
default,
skip_serializing_if = "Option::is_none"
)]
pub document: Option<DidDocument>,
#[serde(rename = "didResolutionMetadata", default)]
pub resolution_metadata: ResolutionMetadata,
#[serde(rename = "didDocumentMetadata", default)]
pub document_metadata: DocumentMetadata,
}
impl ResolutionResult {
pub fn new(document: DidDocument) -> Self {
Self {
document: Some(document),
resolution_metadata: ResolutionMetadata::default(),
document_metadata: DocumentMetadata::default(),
}
}
pub fn error(error: ResolutionError) -> Self {
Self {
document: None,
resolution_metadata: ResolutionMetadata::error(error),
document_metadata: DocumentMetadata::default(),
}
}
#[must_use]
pub fn with_document_metadata(mut self, metadata: DocumentMetadata) -> Self {
self.document_metadata = metadata;
self
}
#[must_use]
pub fn with_resolution_metadata(mut self, metadata: ResolutionMetadata) -> Self {
self.resolution_metadata = metadata;
self
}
pub fn error_kind(&self) -> Option<&ResolutionError> {
self.resolution_metadata.error.as_ref()
}
pub fn into_document(self) -> Result<DidDocument, ResolutionError> {
match (self.document, self.resolution_metadata.error) {
(_, Some(error)) => Err(error),
(Some(document), None) => Ok(document),
(None, None) => Err(ResolutionError::NotFound),
}
}
}
impl fmt::Display for ResolutionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match serde_json::to_string(self) {
Ok(json) => f.write_str(&json),
Err(e) => write!(f, "<unserializable resolution result: {e}>"),
}
}
}