Skip to main content

ssi_dids_core/document/
representation.rs

1//! DID Document representations.
2//!
3//! A "representation" is a concrete serialization of a DID document.
4//! Multiple representations are defined by the DID specification, such as
5//! JSON and JSON-LD. In practice, any representation can be used, such as XML
6//! or YAML, as long as it is capable of expressing the data model of DID
7//! documents.
8//!
9//! A simple serialization (e.g. with `serde`) is not possible for all
10//! representations, as some of them, such as JSON-LD, require serializing
11//! representation-specific properties that are outside the core data model
12//! (for instance the `@context` property of a JSON-LD document).
13//!
14//! See: <https://www.w3.org/TR/did-core/#representations>
15
16use core::fmt;
17use std::{ops::Deref, str::FromStr};
18
19use serde::{Deserialize, Serialize};
20pub mod json;
21pub mod json_ld;
22
23pub use self::json_ld::JsonLd;
24pub use json::Json;
25
26use crate::Document;
27
28/// DID document in a specific representation.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(untagged)]
31pub enum Represented<D = Document> {
32    Json(Json<D>),
33    JsonLd(JsonLd<D>),
34}
35
36impl<D> Represented<D> {
37    pub fn new(document: D, options: Options) -> Self {
38        match options {
39            Options::Json => Self::Json(Json::new(document)),
40            Options::JsonLd(o) => Self::JsonLd(JsonLd::new(document, o)),
41        }
42    }
43
44    pub fn media_type(&self) -> MediaType {
45        match self {
46            Self::Json(_) => MediaType::Json,
47            Self::JsonLd(_) => MediaType::JsonLd,
48        }
49    }
50
51    pub fn document(&self) -> &D {
52        match self {
53            Self::Json(d) => d.document(),
54            Self::JsonLd(d) => d.document(),
55        }
56    }
57
58    pub fn into_document(self) -> D {
59        match self {
60            Self::Json(d) => d.into_document(),
61            Self::JsonLd(d) => d.into_document(),
62        }
63    }
64}
65
66impl<D: Serialize> Represented<D> {
67    pub fn to_bytes(&self) -> Vec<u8> {
68        match self {
69            Self::Json(d) => d.to_bytes(),
70            Self::JsonLd(d) => d.to_bytes(),
71        }
72    }
73}
74
75impl<D> Deref for Represented<D> {
76    type Target = D;
77
78    fn deref(&self) -> &Self::Target {
79        self.document()
80    }
81}
82
83#[derive(Debug, thiserror::Error)]
84#[error("unknown DID document representation `{0}`")]
85pub struct Unknown(pub String);
86
87/// DID document representation media type.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
89pub enum MediaType {
90    /// `application/did+json`.
91    #[serde(rename = "application/did+json")]
92    Json,
93
94    /// `application/did+ld+json`.
95    #[serde(rename = "application/did+ld+json")]
96    JsonLd,
97}
98
99impl MediaType {
100    /// Returns the name of the media type.
101    pub fn name(&self) -> &'static str {
102        match self {
103            Self::Json => "application/did+json",
104            Self::JsonLd => "application/did+ld+json",
105        }
106    }
107
108    pub fn into_name(self) -> &'static str {
109        self.name()
110    }
111
112    pub fn from_bytes(s: &[u8]) -> Result<Self, Unknown> {
113        match s {
114            b"application/did+json" => Ok(Self::Json),
115            b"application/did+ld+json" => Ok(Self::JsonLd),
116            unknown => Err(Unknown(String::from_utf8_lossy(unknown).into_owned())),
117        }
118    }
119}
120
121impl From<MediaType> for String {
122    fn from(value: MediaType) -> Self {
123        value.name().to_owned()
124    }
125}
126
127impl fmt::Display for MediaType {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        self.name().fmt(f)
130    }
131}
132
133impl FromStr for MediaType {
134    type Err = Unknown;
135
136    fn from_str(s: &str) -> Result<Self, Self::Err> {
137        match s {
138            "application/did+json" => Ok(Self::Json),
139            "application/did+ld+json" => Ok(Self::JsonLd),
140            unknown => Err(Unknown(unknown.to_string())),
141        }
142    }
143}
144
145#[derive(Debug, thiserror::Error)]
146pub enum InvalidMediaType {
147    #[error(transparent)]
148    Unknown(Unknown),
149
150    #[error("invalid DID document media type")]
151    NotAString,
152}
153
154impl<'a> TryFrom<&'a [u8]> for MediaType {
155    type Error = InvalidMediaType;
156
157    fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
158        match s {
159            b"application/did+json" => Ok(Self::Json),
160            b"application/did+ld+json" => Ok(Self::JsonLd),
161            unknown => match String::from_utf8(unknown.to_vec()) {
162                Ok(s) => Err(InvalidMediaType::Unknown(Unknown(s))),
163                Err(_) => Err(InvalidMediaType::NotAString),
164            },
165        }
166    }
167}
168
169/// Representation configuration.
170pub enum Options {
171    Json,
172    JsonLd(json_ld::Options),
173}
174
175impl Options {
176    pub fn from_media_type(
177        type_: MediaType,
178        json_ld_options: impl FnOnce() -> json_ld::Options,
179    ) -> Self {
180        match type_ {
181            MediaType::Json => Self::Json,
182            MediaType::JsonLd => Self::JsonLd(json_ld_options()),
183        }
184    }
185}