use crate::{CowStr, IntoStatic, cowstr::ToCowStr};
use alloc::string::{String, ToString};
pub use cid::Cid as IpldCid;
use core::{convert::Infallible, fmt, ops::Deref, str::FromStr};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Visitor};
use smol_str::ToSmolStr;
pub const ATP_CID_CODEC: u64 = 0x55;
pub const ATP_CID_HASH: u64 = 0x12;
pub const ATP_CID_BASE: multibase::Base = multibase::Base::Base32Lower;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Cid<'c> {
Ipld {
cid: IpldCid,
s: CowStr<'c>,
},
Str(CowStr<'c>),
}
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[non_exhaustive]
pub enum Error {
#[error("Invalid IPLD CID {:?}", 0)]
Ipld(#[from] cid::Error),
#[error("{:?}", 0)]
Utf8(#[from] core::str::Utf8Error),
}
impl<'c> Cid<'c> {
pub fn new(cid: &'c [u8]) -> Result<Self, Error> {
if let Ok(cid) = IpldCid::try_from(cid.as_ref()) {
Ok(Self::ipld(cid))
} else {
let cid_str = CowStr::from_utf8(cid)?;
Ok(Self::Str(cid_str))
}
}
pub fn new_owned(cid: &[u8]) -> Result<Cid<'static>, Error> {
if let Ok(cid) = IpldCid::try_from(cid.as_ref()) {
Ok(Self::ipld(cid))
} else {
let cid_str = CowStr::from_utf8(cid)?;
Ok(Cid::Str(cid_str.into_static()))
}
}
pub fn ipld(cid: IpldCid) -> Cid<'static> {
let s = CowStr::Owned(
cid.to_string_of_base(ATP_CID_BASE)
.unwrap_or_default()
.to_smolstr(),
);
Cid::Ipld { cid, s }
}
pub fn str(cid: &'c str) -> Self {
Self::Str(CowStr::Borrowed(cid))
}
pub fn cow_str(cid: CowStr<'c>) -> Self {
Self::Str(cid)
}
pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> {
match self {
Cid::Ipld { cid, s: _ } => Ok(cid.clone()),
Cid::Str(cow_str) => IpldCid::try_from(cow_str.as_ref()),
}
}
pub fn as_str(&self) -> &str {
match self {
Cid::Ipld { cid: _, s } => s.as_ref(),
Cid::Str(cow_str) => cow_str.as_ref(),
}
}
pub fn is_valid(&self) -> bool {
match self {
Cid::Ipld { .. } => true,
Cid::Str(s) => IpldCid::try_from(s.as_ref()).is_ok(),
}
}
}
impl core::fmt::Display for Cid<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Cid::Ipld { cid: _, s } => f.write_str(&s),
Cid::Str(cow_str) => f.write_str(&cow_str),
}
}
}
impl FromStr for Cid<'_> {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Cid::Str(CowStr::Owned(s.to_smolstr())))
}
}
impl IntoStatic for Cid<'_> {
type Output = Cid<'static>;
fn into_static(self) -> Self::Output {
match self {
Cid::Ipld { cid, s } => Cid::Ipld {
cid,
s: s.into_static(),
},
Cid::Str(cow_str) => Cid::Str(cow_str.into_static()),
}
}
}
impl Serialize for Cid<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Cid::Ipld { cid, s: _ } => cid.serialize(serializer),
Cid::Str(cow_str) => cow_str.serialize(serializer),
}
}
}
impl<'de, 'a> Deserialize<'de> for Cid<'a>
where
'de: 'a,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
struct StrVisitor;
impl<'de> Visitor<'de> for StrVisitor {
type Value = Cid<'de>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a CID string")
}
fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(Cid::str(v))
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(FromStr::from_str(v).unwrap())
}
}
deserializer.deserialize_str(StrVisitor)
} else {
let cid = IpldCid::deserialize(deserializer)?;
Ok(Cid::ipld(cid))
}
}
}
impl From<Cid<'_>> for String {
fn from(value: Cid) -> Self {
let cow_str = match value {
Cid::Ipld { cid: _, s } => s,
Cid::Str(cow_str) => cow_str,
};
cow_str.to_string()
}
}
impl<'d> From<Cid<'d>> for CowStr<'d> {
fn from(value: Cid<'d>) -> Self {
match value {
Cid::Ipld { cid: _, s } => s,
Cid::Str(cow_str) => cow_str,
}
}
}
impl From<String> for Cid<'_> {
fn from(value: String) -> Self {
Cid::Str(CowStr::Owned(value.to_smolstr()))
}
}
impl<'d> From<CowStr<'d>> for Cid<'d> {
fn from(value: CowStr<'d>) -> Self {
Cid::Str(value)
}
}
impl From<IpldCid> for Cid<'_> {
fn from(value: IpldCid) -> Self {
Cid::ipld(value)
}
}
impl AsRef<str> for Cid<'_> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Deref for Cid<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct CidLink<'c>(pub Cid<'c>);
impl<'c> CidLink<'c> {
pub fn new(cid: &'c [u8]) -> Result<Self, Error> {
Ok(Self(Cid::new(cid)?))
}
pub fn new_owned(cid: &[u8]) -> Result<CidLink<'static>, Error> {
Ok(CidLink(Cid::new_owned(cid)?))
}
pub fn new_static(cid: &'static str) -> Self {
Self(Cid::str(cid))
}
pub fn ipld(cid: IpldCid) -> CidLink<'static> {
CidLink(Cid::ipld(cid))
}
pub fn str(cid: &'c str) -> Self {
Self(Cid::str(cid))
}
pub fn cow_str(cid: CowStr<'c>) -> Self {
Self(Cid::cow_str(cid))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> {
self.0.to_ipld()
}
pub fn into_inner(self) -> Cid<'c> {
self.0
}
}
impl fmt::Display for CidLink<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl FromStr for CidLink<'_> {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CidLink(Cid::from_str(s)?))
}
}
impl IntoStatic for CidLink<'_> {
type Output = CidLink<'static>;
fn into_static(self) -> Self::Output {
CidLink(self.0.into_static())
}
}
impl Serialize for CidLink<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("$link", self.0.as_str())?;
map.end()
} else {
self.0.serialize(serializer)
}
}
}
impl<'de, 'a> Deserialize<'de> for CidLink<'a>
where
'de: 'a,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
struct LinkVisitor;
impl<'de> Visitor<'de> for LinkVisitor {
type Value = CidLink<'static>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a CID link object with $link field")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(CidLink::cow_str(v.to_cowstr()).into_static())
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
use serde::de::Error;
let mut link: Option<String> = None;
while let Some(key) = map.next_key::<String>()? {
if key == "$link" {
link = Some(map.next_value()?);
} else {
let _: serde::de::IgnoredAny = map.next_value()?;
}
}
if let Some(cid_str) = link {
Ok(CidLink(Cid::from(cid_str)))
} else {
Err(A::Error::missing_field("$link"))
}
}
}
deserializer.deserialize_any(LinkVisitor)
} else {
Ok(CidLink(Cid::deserialize(deserializer)?))
}
}
}
impl From<CidLink<'_>> for String {
fn from(value: CidLink) -> Self {
value.0.into()
}
}
impl<'c> From<CidLink<'c>> for CowStr<'c> {
fn from(value: CidLink<'c>) -> Self {
value.0.into()
}
}
impl From<String> for CidLink<'_> {
fn from(value: String) -> Self {
CidLink(Cid::from(value))
}
}
impl<'c> From<CowStr<'c>> for CidLink<'c> {
fn from(value: CowStr<'c>) -> Self {
CidLink(Cid::from(value))
}
}
impl From<IpldCid> for CidLink<'_> {
fn from(value: IpldCid) -> Self {
CidLink(Cid::from(value))
}
}
impl<'c> From<Cid<'c>> for CidLink<'c> {
fn from(value: Cid<'c>) -> Self {
CidLink(value)
}
}
impl<'c> From<CidLink<'c>> for Cid<'c> {
fn from(value: CidLink<'c>) -> Self {
value.0
}
}
impl AsRef<str> for CidLink<'_> {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl Deref for CidLink<'_> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.0.deref()
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_CID: &str = "bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha";
#[test]
fn cidlink_serialize_json() {
let link = CidLink::str(TEST_CID);
let json = serde_json::to_string(&link).unwrap();
assert_eq!(
json,
r#"{"$link":"bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha"}"#
);
}
#[test]
fn cidlink_deserialize_json() {
let json = r#"{"$link":"bafyreih4g7bvo6hdq2juolev5bfzpbo4ewkxh5mzxwgvkjp3kitc6hqkha"}"#;
let link: CidLink = serde_json::from_str(json).unwrap();
assert_eq!(link.as_str(), TEST_CID);
}
#[test]
fn cidlink_roundtrip_json() {
let link = CidLink::str(TEST_CID);
let json = serde_json::to_string(&link).unwrap();
let parsed: CidLink = serde_json::from_str(&json).unwrap();
assert_eq!(link, parsed);
assert_eq!(link.as_str(), TEST_CID);
}
#[test]
fn cidlink_constructors() {
let link1 = CidLink::str(TEST_CID);
let link2 = CidLink::cow_str(CowStr::Borrowed(TEST_CID));
let link3 = CidLink::from(TEST_CID.to_string());
let link4 = CidLink::new_static(TEST_CID);
assert_eq!(link1.as_str(), TEST_CID);
assert_eq!(link2.as_str(), TEST_CID);
assert_eq!(link3.as_str(), TEST_CID);
assert_eq!(link4.as_str(), TEST_CID);
}
#[test]
fn cidlink_conversions() {
let link = CidLink::str(TEST_CID);
let cid: Cid = link.clone().into();
assert_eq!(cid.as_str(), TEST_CID);
let link2: CidLink = cid.into();
assert_eq!(link2.as_str(), TEST_CID);
let s: String = link.clone().into();
assert_eq!(s, TEST_CID);
let cow: CowStr = link.into();
assert_eq!(cow.as_ref(), TEST_CID);
}
#[test]
fn cidlink_display() {
let link = CidLink::str(TEST_CID);
assert_eq!(format!("{}", link), TEST_CID);
}
#[test]
fn cidlink_deref() {
let link = CidLink::str(TEST_CID);
assert_eq!(&*link, TEST_CID);
assert_eq!(link.as_ref(), TEST_CID);
}
}