1use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
4use std::fmt;
5
6macro_rules! define_id {
7 ($name:ident, $description:literal) => {
8 #[doc = $description]
9 #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
10 #[serde(transparent)]
11 pub struct $name(String);
12
13 impl<'de> Deserialize<'de> for $name {
14 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
15 where
16 D: Deserializer<'de>,
17 {
18 let value = String::deserialize(deserializer)?;
19 Self::new(value).map_err(D::Error::custom)
20 }
21 }
22
23 impl $name {
24 pub fn new(value: impl Into<String>) -> Result<Self, IdError> {
33 let value = value.into();
34 if value.is_empty() {
35 Err(IdError::Empty)
36 } else {
37 Ok(Self(value))
38 }
39 }
40
41 #[must_use]
43 pub fn as_str(&self) -> &str {
44 &self.0
45 }
46 }
47
48 #[cfg(feature = "schema")]
49 impl schemars::JsonSchema for $name {
50 fn schema_name() -> std::borrow::Cow<'static, str> {
51 stringify!($name).into()
52 }
53
54 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
55 schemars::json_schema!({
56 "description": $description,
57 "type": "string",
58 "minLength": 1,
59 })
60 }
61 }
62
63 impl fmt::Display for $name {
64 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
65 formatter.write_str(&self.0)
66 }
67 }
68 };
69}
70
71define_id!(SourceId, "A stable media-source identifier.");
72
73#[derive(Copy, Clone, Debug, Eq, PartialEq)]
75#[non_exhaustive]
76pub enum IdError {
77 Empty,
79}
80
81impl std::error::Error for IdError {}
82
83impl fmt::Display for IdError {
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 formatter.write_str("identifier must not be empty")
86 }
87}