1macro_rules! linear_id {
5 ($name:ident, $doc:literal) => {
6 #[doc = $doc]
7 #[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
8 pub struct $name(String);
9
10 impl $name {
11 #[doc = concat!("Creates a new `", stringify!($name), "` from a string-like value.")]
12 pub fn new(id: impl Into<String>) -> Self {
13 Self(id.into())
14 }
15
16 pub fn as_str(&self) -> &str {
18 &self.0
19 }
20
21 pub fn into_inner(self) -> String {
23 self.0
24 }
25 }
26
27 impl std::fmt::Display for $name {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.write_str(&self.0)
30 }
31 }
32
33 impl From<String> for $name {
34 fn from(id: String) -> Self {
35 Self(id)
36 }
37 }
38
39 impl From<&str> for $name {
40 fn from(id: &str) -> Self {
41 Self(id.to_owned())
42 }
43 }
44
45 impl AsRef<str> for $name {
46 fn as_ref(&self) -> &str {
47 &self.0
48 }
49 }
50 };
51}
52
53linear_id!(IssueId, "UUID of a Linear issue.");
54linear_id!(TeamId, "UUID of a Linear team.");
55linear_id!(ProjectId, "UUID of a Linear project.");
56linear_id!(UserId, "UUID of a Linear user.");
57linear_id!(LabelId, "UUID of a Linear issue label.");
58linear_id!(WorkflowStateId, "UUID of a Linear workflow state.");
59linear_id!(ProjectMilestoneId, "UUID of a Linear project milestone.");
60linear_id!(CommentId, "UUID of a Linear comment.");
61linear_id!(IssueRelationId, "UUID of a Linear issue relation.");
62linear_id!(CycleId, "UUID of a Linear cycle.");
63linear_id!(OrganizationId, "UUID of a Linear organization (workspace).");
64linear_id!(ProjectStatusId, "UUID of a Linear project status.");
65linear_id!(TemplateId, "UUID of a Linear template.");
66
67#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum IssueRef {
76 Id(IssueId),
78 Identifier(String),
80}
81
82impl IssueRef {
83 pub fn identifier(identifier: impl Into<String>) -> Self {
85 Self::Identifier(identifier.into())
86 }
87
88 pub fn api_string(&self) -> &str {
90 match self {
91 Self::Id(id) => id.as_str(),
92 Self::Identifier(identifier) => identifier,
93 }
94 }
95}
96
97impl std::fmt::Display for IssueRef {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.write_str(self.api_string())
100 }
101}
102
103impl From<IssueId> for IssueRef {
104 fn from(id: IssueId) -> Self {
105 Self::Id(id)
106 }
107}
108
109impl From<&IssueId> for IssueRef {
110 fn from(id: &IssueId) -> Self {
111 Self::Id(id.clone())
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn id_roundtrips_through_serde_as_a_plain_string() {
121 let id = IssueId::new("abc-123");
122 let json = serde_json::to_string(&id).unwrap();
123 assert_eq!(json, "\"abc-123\"");
124 let back: IssueId = serde_json::from_str(&json).unwrap();
125 assert_eq!(back, id);
126 }
127
128 #[test]
129 fn issue_ref_api_string() {
130 let by_id = IssueRef::from(IssueId::new("uuid-1"));
131 assert_eq!(by_id.api_string(), "uuid-1");
132 let by_identifier = IssueRef::identifier("ENG-123");
133 assert_eq!(by_identifier.api_string(), "ENG-123");
134 assert_eq!(by_identifier.to_string(), "ENG-123");
135 }
136}