Skip to main content

co_primitives/types/
link.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use crate::CoCid;
5use cid::Cid;
6use either::Either;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use std::{
10	any::type_name,
11	fmt::{Debug, Display},
12	hash::Hash,
13	marker::PhantomData,
14};
15
16pub trait Linkable<T> {
17	fn value(&self) -> Either<Cid, T>;
18}
19
20/// A (serializable) typed link.
21#[derive(Serialize, Deserialize)]
22#[serde(into = "Cid", from = "Cid")]
23pub struct Link<T> {
24	#[serde(skip)]
25	_type: PhantomData<T>,
26	cid: Cid,
27}
28impl<T> Ord for Link<T> {
29	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
30		self.cid.cmp(&other.cid)
31	}
32}
33impl<T> Eq for Link<T> {}
34impl<T> PartialOrd for Link<T> {
35	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
36		Some(self.cmp(other))
37	}
38}
39impl<T> PartialEq for Link<T> {
40	fn eq(&self, other: &Self) -> bool {
41		self.cid == other.cid
42	}
43}
44impl<T> Hash for Link<T> {
45	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
46		std::hash::Hash::hash(&self.cid, state);
47	}
48}
49impl<T> Copy for Link<T> {}
50impl<T> Linkable<T> for Link<T> {
51	fn value(&self) -> Either<Cid, T> {
52		Either::Left(self.cid)
53	}
54}
55impl<T> Link<T> {
56	pub fn new(cid: Cid) -> Self {
57		Self { cid, _type: Default::default() }
58	}
59
60	pub fn cid(&self) -> &Cid {
61		&self.cid
62	}
63}
64impl<T> Clone for Link<T> {
65	fn clone(&self) -> Self {
66		*self
67	}
68}
69impl<T> From<Link<T>> for Cid {
70	fn from(val: Link<T>) -> Self {
71		val.cid
72	}
73}
74impl<T> From<Link<T>> for Option<Cid> {
75	fn from(val: Link<T>) -> Self {
76		Some(val.cid)
77	}
78}
79impl<T> From<Cid> for Link<T> {
80	fn from(value: Cid) -> Self {
81		Self::new(value)
82	}
83}
84impl<T> From<&Cid> for Link<T> {
85	fn from(value: &Cid) -> Self {
86		Self::new(*value)
87	}
88}
89impl<T> AsRef<Cid> for Link<T> {
90	fn as_ref(&self) -> &Cid {
91		&self.cid
92	}
93}
94impl<T> Debug for Link<T> {
95	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96		write!(f, "Link({}: {})", type_name::<T>(), self.cid)
97	}
98}
99impl<T> Display for Link<T> {
100	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101		write!(f, "Link({}: {})", type_name::<T>(), self.cid)
102	}
103}
104
105/// A (serializable) typed link.
106#[derive(Serialize, Deserialize, JsonSchema)]
107#[serde(into = "Option<Cid>", from = "Option<Cid>")]
108pub struct OptionLink<T> {
109	#[serde(skip)]
110	_type: PhantomData<T>,
111	#[schemars(with = "CoCid")]
112	cid: Option<Cid>,
113}
114impl<T> Default for OptionLink<T> {
115	fn default() -> Self {
116		Self { _type: Default::default(), cid: Default::default() }
117	}
118}
119impl<T> PartialEq for OptionLink<T> {
120	fn eq(&self, other: &Self) -> bool {
121		self.cid == other.cid
122	}
123}
124impl<T> Hash for OptionLink<T> {
125	fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
126		self.cid.hash(state);
127	}
128}
129impl<T> Ord for OptionLink<T> {
130	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
131		self.cid.cmp(&other.cid)
132	}
133}
134impl<T> Eq for OptionLink<T> {}
135impl<T> PartialOrd for OptionLink<T> {
136	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
137		Some(self.cmp(other))
138	}
139}
140impl<T: Default> Linkable<T> for OptionLink<T> {
141	fn value(&self) -> Either<Cid, T> {
142		match self.cid {
143			Some(cid) => Either::Left(cid),
144			None => Either::Right(T::default()),
145		}
146	}
147}
148impl<T> Copy for OptionLink<T> {}
149impl<T> OptionLink<T> {
150	pub fn new(cid: Option<Cid>) -> Self {
151		Self { cid, _type: Default::default() }
152	}
153
154	pub fn none() -> Self {
155		Self { cid: None, _type: Default::default() }
156	}
157
158	pub fn is_none(&self) -> bool {
159		self.cid.is_none()
160	}
161
162	pub fn cid(&self) -> &Option<Cid> {
163		&self.cid
164	}
165
166	pub fn set(&mut self, cid: Option<Cid>) {
167		self.cid = cid;
168	}
169
170	pub fn link(&self) -> Option<Link<T>> {
171		self.cid.map(Link::new)
172	}
173
174	pub fn unwrap(&self) -> Link<T> {
175		#[allow(clippy::unwrap_used)]
176		Link::new(self.cid.unwrap())
177	}
178
179	pub fn expect(&self, message: &str) -> Link<T> {
180		Link::new(self.cid.expect(message))
181	}
182}
183impl<T> Clone for OptionLink<T> {
184	fn clone(&self) -> Self {
185		*self
186	}
187}
188impl<T> From<OptionLink<T>> for Option<Cid> {
189	fn from(val: OptionLink<T>) -> Self {
190		val.cid
191	}
192}
193impl<T> From<Option<Cid>> for OptionLink<T> {
194	fn from(value: Option<Cid>) -> Self {
195		Self::new(value)
196	}
197}
198impl<T> From<Link<T>> for OptionLink<T> {
199	fn from(value: Link<T>) -> Self {
200		Self::new(Some(value.into()))
201	}
202}
203impl<T> From<&Option<Cid>> for OptionLink<T> {
204	fn from(value: &Option<Cid>) -> Self {
205		Self::new(*value)
206	}
207}
208impl<T> From<Cid> for OptionLink<T> {
209	fn from(value: Cid) -> Self {
210		Self::new(Some(value))
211	}
212}
213impl<T> From<&Cid> for OptionLink<T> {
214	fn from(value: &Cid) -> Self {
215		Self::new(Some(*value))
216	}
217}
218impl<T> AsRef<Option<Cid>> for OptionLink<T> {
219	fn as_ref(&self) -> &Option<Cid> {
220		&self.cid
221	}
222}
223impl<T> Debug for OptionLink<T> {
224	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225		write!(f, "Link({}: {:?})", type_name::<T>(), self.cid)
226	}
227}
228impl<T> Display for OptionLink<T> {
229	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230		match self.cid {
231			Some(cid) => write!(f, "{} ({})", cid, type_name::<T>()),
232			None => write!(f, "None ({})", type_name::<T>()),
233		}
234	}
235}