use crate::bos::{Bos, DefaultStr};
use crate::{CowStr, IntoStatic};
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::{SmolStr, 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<S: Bos<str> = DefaultStr> {
Ipld {
cid: IpldCid,
s: SmolStr,
},
Str(S),
}
#[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),
#[error("converting from a string slice")]
#[cfg_attr(feature = "std", diagnostic(code(jacquard::cid::str_conversion)))]
Conversion,
}
impl<S: Bos<str> + AsRef<str>> Cid<S> {
pub fn as_str(&self) -> &str {
match self {
Cid::Ipld { cid: _, s } => s.as_ref(),
Cid::Str(s) => s.as_ref(),
}
}
pub fn to_ipld(&self) -> Result<IpldCid, cid::Error> {
match self {
Cid::Ipld { cid, s: _ } => Ok(cid.clone()),
Cid::Str(s) => IpldCid::try_from(s.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<S: Bos<str>> Cid<S> {
pub unsafe fn unchecked_str(s: S) -> Self {
Cid::Str(s)
}
pub fn ipld(cid: IpldCid) -> Self {
let s = cid
.to_string_of_base(ATP_CID_BASE)
.unwrap_or_default()
.to_smolstr();
Cid::Ipld { cid, s }
}
}
impl<'c> Cid<&'c str> {
pub fn str(cid: &'c str) -> Self {
Self::Str(cid)
}
}
impl<S: Bos<str>> Cid<S> {
pub fn new<'c>(cid: &'c [u8]) -> Result<Cid<S>, Error>
where
S: From<&'c str>,
{
if let Ok(cid) = IpldCid::try_from(cid.as_ref()) {
Ok(Cid::ipld(cid))
} else {
let cid_str = core::str::from_utf8(cid)?;
Ok(Cid::Str(cid_str.into()))
}
}
}
impl<S: Bos<str> + FromStr> Cid<S> {
pub fn new_owned(cid: &[u8]) -> Result<Self, Error> {
if let Ok(cid) = IpldCid::try_from(cid.as_ref()) {
Ok(Self::ipld(cid))
} else {
let cid_str = core::str::from_utf8(cid)?;
Ok(Cid::Str(
S::from_str(cid_str).map_err(|_| Error::Conversion)?,
))
}
}
}
impl<'c> Cid<CowStr<'c>> {
pub fn cow_str(cid: CowStr<'c>) -> Self {
Self::Str(cid)
}
}
impl<S: Bos<str> + AsRef<str>> Serialize for Cid<S> {
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: Serializer,
{
match self {
Cid::Ipld { cid, s: _ } => cid.serialize(serializer),
Cid::Str(s) => s.as_ref().serialize(serializer),
}
}
}
impl<'de, S> Deserialize<'de> for Cid<S>
where
S: Bos<str> + AsRef<str> + Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = S::deserialize(deserializer)?;
Ok(Cid::Str(s))
} else {
let cid = IpldCid::deserialize(deserializer)?;
Ok(Cid::ipld(cid))
}
}
}
impl<S: Bos<str> + AsRef<str>> fmt::Display for Cid<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Cid::Ipld { cid: _, s } => f.write_str(s),
Cid::Str(s) => f.write_str(s.as_ref()),
}
}
}
impl FromStr for Cid {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Cid::Str(s.to_smolstr()))
}
}
impl<S: Bos<str> + IntoStatic> IntoStatic for Cid<S>
where
S::Output: Bos<str>,
{
type Output = Cid<S::Output>;
fn into_static(self) -> Self::Output {
match self {
Cid::Ipld { cid, s } => Cid::Ipld { cid, s },
Cid::Str(s) => Cid::Str(s.into_static()),
}
}
}
impl<S: Bos<str>> Cid<S> {
pub fn convert<B: Bos<str> + From<S>>(self) -> Cid<B> {
match self {
Cid::Ipld { cid, s } => Cid::Ipld { cid, s },
Cid::Str(s) => Cid::Str(B::from(s)),
}
}
}
impl<S: Bos<str> + AsRef<str>> From<Cid<S>> for String {
fn from(value: Cid<S>) -> Self {
value.as_str().to_string()
}
}
impl From<String> for Cid {
fn from(value: String) -> Self {
Cid::Str(value.to_smolstr())
}
}
impl<'d> From<CowStr<'d>> for Cid<CowStr<'d>> {
fn from(value: CowStr<'d>) -> Self {
Cid::Str(value)
}
}
impl<S: Bos<str>> From<IpldCid> for Cid<S> {
fn from(value: IpldCid) -> Self {
Cid::ipld(value)
}
}
impl<S: Bos<str> + AsRef<str>> AsRef<str> for Cid<S> {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl<S: Bos<str> + AsRef<str>> Deref for Cid<S> {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct CidLink<S: Bos<str> = DefaultStr>(pub Cid<S>);
impl<S: Bos<str> + AsRef<str>> CidLink<S> {
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<S> {
self.0
}
}
impl<S: Bos<str>> CidLink<S> {
pub fn ipld(cid: IpldCid) -> Self {
CidLink(Cid::ipld(cid))
}
pub fn new<'c>(cid: &'c [u8]) -> Result<CidLink<S>, Error>
where
S: Bos<str> + From<&'c str>,
{
Ok(CidLink(Cid::new(cid)?))
}
}
impl<'c> CidLink<&'c str> {
pub fn str(cid: &'c str) -> Self {
Self(Cid::str(cid))
}
pub fn new_static(cid: &'static str) -> Self {
Self(Cid::str(cid))
}
}
impl<S: Bos<str> + FromStr> CidLink<S> {
pub fn new_owned(cid: &[u8]) -> Result<Self, Error> {
Ok(CidLink(Cid::new_owned(cid)?))
}
}
impl<'c> CidLink<CowStr<'c>> {
pub fn cow_str(cid: CowStr<'c>) -> Self {
Self(Cid::cow_str(cid))
}
}
impl<S: Bos<str> + AsRef<str>> Serialize for CidLink<S> {
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
where
Ser: 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, S> Deserialize<'de> for CidLink<S>
where
S: Bos<str> + AsRef<str> + Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use core::marker::PhantomData;
if deserializer.is_human_readable() {
struct LinkVisitor<S>(PhantomData<fn() -> S>);
impl<'de, S> Visitor<'de> for LinkVisitor<S>
where
S: Bos<str> + AsRef<str> + Deserialize<'de>,
{
type Value = CidLink<S>;
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,
{
let s = S::deserialize(serde::de::value::StrDeserializer::<E>::new(v))?;
Ok(CidLink(Cid::Str(s)))
}
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let cid = IpldCid::try_from(v).map_err(E::custom)?;
Ok(CidLink(Cid::ipld(cid)))
}
fn visit_byte_buf<E>(self, v: alloc::vec::Vec<u8>) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
self.visit_bytes(&v)
}
fn visit_newtype_struct<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
where
D: serde::de::Deserializer<'de>,
{
struct BytesVisitor;
impl<'de> Visitor<'de> for BytesVisitor {
type Value = alloc::vec::Vec<u8>;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("CID bytes")
}
fn visit_bytes<E: serde::de::Error>(
self,
v: &[u8],
) -> Result<Self::Value, E> {
Ok(v.to_vec())
}
fn visit_byte_buf<E: serde::de::Error>(
self,
v: alloc::vec::Vec<u8>,
) -> Result<Self::Value, E> {
Ok(v)
}
}
let bytes = deserializer.deserialize_bytes(BytesVisitor)?;
self.visit_byte_buf(bytes)
}
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<Cid<S>> = None;
while let Some(key) = map.next_key::<&str>()? {
if key == "$link" {
link = Some(map.next_value()?);
} else {
let _: serde::de::IgnoredAny = map.next_value()?;
}
}
if let Some(cid) = link {
Ok(CidLink(cid))
} else {
Err(A::Error::missing_field("$link"))
}
}
}
deserializer.deserialize_any(LinkVisitor(PhantomData))
} else {
Ok(CidLink(Cid::deserialize(deserializer)?))
}
}
}
impl<S: Bos<str> + AsRef<str>> fmt::Display for CidLink<S> {
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<S: Bos<str> + IntoStatic> IntoStatic for CidLink<S>
where
S::Output: Bos<str>,
{
type Output = CidLink<S::Output>;
fn into_static(self) -> Self::Output {
CidLink(self.0.into_static())
}
}
impl<S: Bos<str>> CidLink<S> {
pub fn convert<B: Bos<str> + From<S>>(self) -> CidLink<B> {
CidLink(self.0.convert())
}
}
impl<S: Bos<str> + AsRef<str>> From<CidLink<S>> for String {
fn from(value: CidLink<S>) -> 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<CowStr<'c>> {
fn from(value: CowStr<'c>) -> Self {
CidLink(Cid::from(value))
}
}
impl<S: Bos<str>> From<IpldCid> for CidLink<S> {
fn from(value: IpldCid) -> Self {
CidLink(Cid::from(value))
}
}
impl<S: Bos<str>> From<Cid<S>> for CidLink<S> {
fn from(value: Cid<S>) -> Self {
CidLink(value)
}
}
impl<S: Bos<str>> From<CidLink<S>> for Cid<S> {
fn from(value: CidLink<S>) -> Self {
value.0
}
}
impl<S: Bos<str> + AsRef<str>> AsRef<str> for CidLink<S> {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
impl<S: Bos<str> + AsRef<str>> Deref for CidLink<S> {
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.as_str(), parsed.as_str());
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::<SmolStr>::from(TEST_CID.to_string());
let cid: Cid<SmolStr> = link.clone().into();
assert_eq!(cid.as_str(), TEST_CID);
let link2: CidLink<SmolStr> = cid.into();
assert_eq!(link2.as_str(), TEST_CID);
let s: String = link.clone().into();
assert_eq!(s, 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);
}
}