1use serde::{Deserialize, Serialize};
4use std::{fmt, num::ParseIntError, str::FromStr};
5
6pub mod consts;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[non_exhaustive]
13pub enum WikiId {
14 EntityId(Qid),
16 PropertyId(Pid),
18 LexemeId(Lid),
20}
21
22impl FromStr for WikiId {
23 type Err = IdParseError;
24
25 fn from_str(x: &str) -> Result<Self, Self::Err> {
27 match x.chars().next() {
28 Some('Q') => Qid::from_str(x).map(WikiId::EntityId),
29 Some('P') => Pid::from_str(x).map(WikiId::PropertyId),
30 Some('L') => Lid::from_str(x).map(WikiId::LexemeId),
31 _ => Err(IdParseError::InvalidPrefix),
32 }
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum IdParseError {
39 UnparseableNumber(ParseIntError),
41 InvalidPrefix,
43 TooManyParts,
52 TooFewParts,
61}
62
63macro_rules! id_def {
64 ($name:ident, $full_name:expr, $letter:expr, $khar:expr) => {
65 #[derive(
66 Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
67 )]
68 #[doc = "A Wikidata"]
69 #[doc = $full_name]
70 pub struct $name(pub u64);
71
72 impl $name {
73 #[must_use]
75 pub fn json_url(&self) -> String {
76 format!(
77 concat!(
78 "https://www.wikidata.org/wiki/Special:EntityData/",
79 $letter,
80 "{}.json"
81 ),
82 self.0
83 )
84 }
85 }
86 impl FromStr for $name {
87 type Err = IdParseError;
88
89 fn from_str(x: &str) -> Result<Self, Self::Err> {
91 if x.chars().next() != Some($khar) {
92 return Err(IdParseError::InvalidPrefix);
93 }
94 let num_str = &x[1..];
95 match num_str.parse() {
96 Ok(num) => Ok(Self(num)),
97 Err(e) => Err(IdParseError::UnparseableNumber(e)),
98 }
99 }
100 }
101 impl fmt::Display for $name {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 write!(f, concat!($letter, "{}"), self.0)
105 }
106 }
107 };
108}
109
110id_def!(Qid, "entity ID", "Q", 'Q');
111id_def!(Pid, "property ID", "P", 'P');
112id_def!(Lid, "lexeme ID", "L", 'L');
113
114macro_rules! lexeme_subid_def {
115 ($name:ident, $full_name:expr, $letter:expr, $khar:expr) => {
116 #[doc = $full_name]
118 #[derive(
119 Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
120 )]
121 pub struct $name(pub Lid, pub u16);
122
123 impl fmt::Display for $name {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}-{}{}", self.0, $khar, self.1)
127 }
128 }
129
130 impl FromStr for $name {
131 type Err = IdParseError;
132
133 fn from_str(x: &str) -> Result<Self, Self::Err> {
135 if x.chars().next() != Some('L') {
136 return Err(IdParseError::InvalidPrefix);
137 }
138 let mut parts = x[1..].split('-');
139 let lid = parts
140 .next()
141 .ok_or(IdParseError::TooFewParts)?
142 .parse()
143 .map_err(IdParseError::UnparseableNumber)?;
144 let part2 = parts.next().ok_or(IdParseError::TooFewParts)?;
145 if part2.chars().next() != Some($khar) {
146 return Err(IdParseError::InvalidPrefix);
147 }
148 let id = part2[1..]
149 .parse()
150 .map_err(IdParseError::UnparseableNumber)?;
151 if parts.next().is_some() {
152 Err(IdParseError::TooManyParts)
153 } else {
154 Ok(Self(Lid(lid), id))
155 }
156 }
157 }
158 };
159}
160
161lexeme_subid_def!(Fid, "form ID", "F", 'F');
162lexeme_subid_def!(Sid, "sense ID", "S", 'S');
163
164#[cfg(test)]
165pub mod test {
166 use super::*;
167
168 #[test]
169 fn json_url() {
170 assert_eq!(
171 Qid(42).json_url(),
172 "https://www.wikidata.org/wiki/Special:EntityData/Q42.json"
173 );
174 assert_eq!(
175 Pid(31).json_url(),
176 "https://www.wikidata.org/wiki/Special:EntityData/P31.json"
177 );
178 assert_eq!(
179 Lid(1).json_url(),
180 "https://www.wikidata.org/wiki/Special:EntityData/L1.json"
181 )
182 }
183
184 #[test]
185 fn to_string() {
186 let entity = Qid(42);
187 assert_eq!(format!("{}", entity), "Q42");
188
189 let prop = Pid(6);
190 assert_eq!(format!("{}", prop), "P6");
191
192 let lexeme = Lid(2);
193 assert_eq!(format!("{}", lexeme), "L2");
194
195 let sense = Sid(Lid(5), 9);
196 assert_eq!(format!("{}", sense), "L5-S9");
197
198 let form = Fid(Lid(3), 11);
199 assert_eq!(format!("{}", form), "L3-F11");
200 }
201
202 #[test]
203 fn from_str() {
204 assert_eq!(Qid::from_str("Q42").unwrap(), Qid(42));
205 assert_eq!(Lid::from_str("L944114").unwrap(), Lid(944114));
206 assert_eq!(Pid::from_str("P1341").unwrap(), Pid(1341));
207 assert_eq!(Pid::from_str("Q1341"), Err(IdParseError::InvalidPrefix));
208 assert_eq!(Pid::from_str("1341"), Err(IdParseError::InvalidPrefix));
209 assert!(Qid::from_str("Q").is_err());
210 assert_eq!(Sid::from_str("S1341"), Err(IdParseError::InvalidPrefix));
211 assert_eq!(Sid::from_str("L1341"), Err(IdParseError::TooFewParts));
212 assert_eq!(
213 Sid::from_str("L1341-A123"),
214 Err(IdParseError::InvalidPrefix)
215 );
216 assert_eq!(Sid::from_str("L1341-S123").unwrap(), Sid(Lid(1341), 123));
217 assert!(Lid::from_str("L1341-S123").is_err());
218 assert!(Lid::from_str("L1341-F123").is_err());
219 assert!(WikiId::from_str("L1341-F123").is_err());
220 assert!(WikiId::from_str("L1341-S123").is_err());
221 assert_eq!(WikiId::from_str("A123"), Err(IdParseError::InvalidPrefix));
222 assert_eq!(
223 WikiId::from_str("L1341").unwrap(),
224 WikiId::LexemeId(Lid(1341))
225 );
226 assert_eq!(
227 WikiId::from_str("Q1341").unwrap(),
228 WikiId::EntityId(Qid(1341))
229 );
230 assert_eq!(
231 WikiId::from_str("P1341").unwrap(),
232 WikiId::PropertyId(Pid(1341))
233 );
234 }
235
236 #[test]
237 fn unit_suffix() {
238 assert_eq!(consts::unit_suffix(consts::METRE).unwrap(), " m");
239 assert_eq!(consts::unit_suffix(consts::DEGREE).unwrap(), "°");
240 }
241}