Skip to main content

flix_tmdb/model/
id.rs

1//! Typed TMDB IDs
2
3use core::cmp::Ordering;
4use core::fmt;
5use core::hash::{Hash, Hasher};
6use core::marker::PhantomData;
7
8#[cfg(feature = "sea-orm")]
9use sea_orm::sea_query::{ArrayType, Nullable, ValueType, ValueTypeErr};
10#[cfg(feature = "sea-orm")]
11use sea_orm::{ColIdx, ColumnType, DbErr, QueryResult, TryFromU64, TryGetError, TryGetable, Value};
12
13/// The internal representation used by TMDB
14pub type TmdbRepr = u32;
15
16/// An opaque type representing a TMDB ID
17#[derive(serde::Serialize, serde::Deserialize)]
18#[serde(transparent)]
19#[repr(transparent)]
20pub struct Id<T> {
21	id: TmdbRepr,
22	#[serde(skip_serializing, default)]
23	_phantom: PhantomData<T>,
24}
25
26// Manual implementation since `T: Clone` is not required
27impl<T> Clone for Id<T> {
28	fn clone(&self) -> Self {
29		*self
30	}
31}
32
33// Manual implementation since `T: Copy` is not required
34impl<T> Copy for Id<T> {}
35
36// Manual implementation since `T: PartialEq` is not required
37impl<T> PartialEq for Id<T> {
38	fn eq(&self, other: &Self) -> bool {
39		self.id == other.id
40	}
41}
42
43// Manual implementation since `T: Eq` is not required
44impl<T> Eq for Id<T> {}
45
46// Manual implementation since `T: PartialOrd` is not required
47impl<T> PartialOrd for Id<T> {
48	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
49		Some(self.cmp(other))
50	}
51}
52
53// Manual implementation since `T: Ord` is not required
54impl<T> Ord for Id<T> {
55	fn cmp(&self, other: &Self) -> Ordering {
56		self.id.cmp(&other.id)
57	}
58}
59
60// Manual implementation since `T: Hash` is not required
61impl<T> Hash for Id<T> {
62	fn hash<H: Hasher>(&self, state: &mut H) {
63		self.id.hash(state);
64	}
65}
66
67impl<T> Id<T> {
68	/// Allows the conversion from a raw value to [Id], though the use is discouraged.
69	pub fn from_raw(raw: TmdbRepr) -> Self {
70		Self {
71			id: raw,
72			_phantom: PhantomData,
73		}
74	}
75
76	/// Allows extracting the raw value, though the use is discouraged.
77	pub fn into_raw(self) -> TmdbRepr {
78		self.id
79	}
80}
81
82impl<T> fmt::Debug for Id<T> {
83	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84		f.debug_struct("Id")
85			.field("T", &core::any::type_name::<T>())
86			.field("id", &self.id)
87			.finish()
88	}
89}
90
91#[cfg(feature = "sea-orm")]
92impl<T> ValueType for Id<T> {
93	fn try_from(v: Value) -> Result<Self, ValueTypeErr> {
94		<TmdbRepr as ValueType>::try_from(v).map(|id| Self {
95			id,
96			_phantom: PhantomData,
97		})
98	}
99
100	fn type_name() -> String {
101		format!("Id<{}>", &core::any::type_name::<T>())
102	}
103
104	fn array_type() -> ArrayType {
105		TmdbRepr::array_type()
106	}
107
108	fn column_type() -> ColumnType {
109		TmdbRepr::column_type()
110	}
111}
112
113#[cfg(feature = "sea-orm")]
114impl<T> From<Id<T>> for Value {
115	fn from(value: Id<T>) -> Self {
116		value.id.into()
117	}
118}
119
120#[cfg(feature = "sea-orm")]
121impl<T> TryGetable for Id<T> {
122	fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError> {
123		TmdbRepr::try_get_by(res, index).map(|id| Self {
124			id,
125			_phantom: PhantomData,
126		})
127	}
128}
129
130#[cfg(feature = "sea-orm")]
131impl<T> TryFromU64 for Id<T> {
132	fn try_from_u64(n: u64) -> Result<Self, DbErr> {
133		TmdbRepr::try_from_u64(n).map(|id| Self {
134			id,
135			_phantom: PhantomData,
136		})
137	}
138}
139
140#[cfg(feature = "sea-orm")]
141impl<T> Nullable for Id<T> {
142	fn null() -> Value {
143		TmdbRepr::null()
144	}
145}
146
147#[cfg(test)]
148mod tests {
149	#[test]
150	#[cfg(feature = "sea-orm")]
151	fn test_sea_orm() {
152		use sea_orm::{
153			ActiveModelBehavior, DeriveEntityModel, DerivePrimaryKey, DeriveRelation, EntityTrait,
154			EnumIter, PrimaryKeyTrait,
155		};
156
157		use super::Id;
158
159		#[allow(dead_code)]
160		#[derive(Debug, Clone, PartialEq, Eq, DeriveEntityModel)]
161		#[sea_orm(table_name = "ids")]
162		pub struct Model {
163			#[sea_orm(primary_key, auto_increment = false)]
164			id: Id<Model>,
165			nullable: Option<Id<Model>>,
166		}
167
168		impl ActiveModelBehavior for ActiveModel {}
169
170		#[allow(dead_code)]
171		#[derive(Debug, EnumIter, DeriveRelation)]
172		pub enum Relation {}
173	}
174
175	#[test]
176	fn test_serde() {
177		use super::Id;
178
179		let id: Id<()> = Id::from_raw(1234);
180		serde_test::assert_tokens(&id, &[serde_test::Token::U32(1234)]);
181	}
182}
183
184/// Type alias for the raw ID representation
185pub use self::TmdbRepr as RawId;
186
187#[doc(hidden)]
188pub enum Collection {}
189/// Type alias for a collection ID
190pub type CollectionId = Id<Collection>;
191
192impl From<CollectionId> for flix_model::id::CollectionId {
193	fn from(value: CollectionId) -> Self {
194		Self::from_raw(value.into_raw().into())
195	}
196}
197
198impl TryFrom<flix_model::id::CollectionId> for CollectionId {
199	type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
200
201	fn try_from(value: flix_model::id::CollectionId) -> Result<Self, Self::Error> {
202		value.into_raw().try_into().map(Self::from_raw)
203	}
204}
205
206#[doc(hidden)]
207pub enum Movie {}
208/// Type alias for a movie ID
209pub type MovieId = Id<Movie>;
210
211impl From<MovieId> for flix_model::id::MovieId {
212	fn from(value: MovieId) -> Self {
213		Self::from_raw(value.into_raw().into())
214	}
215}
216
217impl TryFrom<flix_model::id::MovieId> for MovieId {
218	type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
219
220	fn try_from(value: flix_model::id::MovieId) -> Result<Self, Self::Error> {
221		value.into_raw().try_into().map(Self::from_raw)
222	}
223}
224
225#[doc(hidden)]
226pub enum Show {}
227/// Type alias for a show ID
228pub type ShowId = Id<Show>;
229
230impl From<ShowId> for flix_model::id::ShowId {
231	fn from(value: ShowId) -> Self {
232		Self::from_raw(value.into_raw().into())
233	}
234}
235
236impl TryFrom<flix_model::id::ShowId> for ShowId {
237	type Error = <RawId as TryFrom<flix_model::id::RawId>>::Error;
238
239	fn try_from(value: flix_model::id::ShowId) -> Result<Self, Self::Error> {
240		value.into_raw().try_into().map(Self::from_raw)
241	}
242}