1use crate::prelude::*;
4use crate::util::constants::{RE_PATENT, RE_PATENT_TEXT};
5use crate::util::regex_capture_lookup;
6use core::fmt;
7use derive_more::Display;
8use schemars::JsonSchema;
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Debug, Default, Display, Deserialize, Serialize, JsonSchema)]
13pub enum CountryCode {
14 #[default]
16 #[display("US")]
17 US,
18 #[display("CN")]
20 CN,
21 #[display("DE")]
23 DE,
24 #[display("EP")]
26 EP,
27 #[display("JP")]
29 JP,
30 #[display("KR")]
32 KR,
33}
34#[derive(Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize, JsonSchema)]
43pub enum KindCode {
44 #[display("A1")]
46 #[serde(rename = "A1")]
47 A1,
48 #[display("A2")]
50 #[serde(rename = "A2")]
51 A2,
52 #[display("A9")]
54 #[serde(rename = "A9")]
55 A9,
56 #[display("B1")]
58 #[serde(rename = "B1")]
59 B1,
60 #[display("B2")]
62 #[serde(rename = "B2")]
63 B2,
64 #[display("C1")]
66 #[serde(rename = "C1")]
67 C1,
68 #[display("C2")]
70 #[serde(rename = "C2")]
71 C2,
72 #[display("C3")]
74 #[serde(rename = "C3")]
75 C3,
76 #[display("E1")]
78 #[serde(rename = "E1", alias = "E")]
79 E1,
80 #[display("S1")]
82 #[serde(rename = "S1", alias = "S")]
83 S1,
84 #[display("P1")]
86 #[serde(rename = "P1")]
87 P1,
88 #[display("P2")]
90 #[serde(rename = "P2")]
91 P2,
92 #[display("P3")]
94 #[serde(rename = "P3")]
95 P3,
96 #[display("P4")]
98 #[serde(rename = "P4")]
99 P4,
100 #[display("P9")]
102 #[serde(rename = "P9")]
103 P9,
104 #[display("H1")]
108 #[serde(rename = "H1", alias = "H")]
109 H1,
110 #[display("Unknown")]
112 #[serde(rename = "Unknown")]
113 Unknown,
114}
115#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
117pub enum Patent {
118 Granted {
120 country_code: Option<CountryCode>,
122 kind_code: KindCode,
124 serial_number: String,
126 },
127 Application {
129 serial_number: String,
131 year: Option<String>,
133 },
134 Publication {
136 country_code: Option<CountryCode>,
138 kind_code: KindCode,
140 serial_number: String,
142 year: Option<String>,
144 },
145}
146impl From<&str> for CountryCode {
147 fn from(s: &str) -> Self {
148 match s.to_uppercase().as_str() {
149 | "US" => CountryCode::US,
150 | "CN" => CountryCode::CN,
151 | "DE" => CountryCode::DE,
152 | "EP" => CountryCode::EP,
153 | "JP" => CountryCode::JP,
154 | "KR" => CountryCode::KR,
155 | _ => CountryCode::US,
156 }
157 }
158}
159impl From<String> for CountryCode {
160 fn from(s: String) -> Self {
161 CountryCode::from(s.as_str())
162 }
163}
164impl From<&str> for KindCode {
165 fn from(s: &str) -> Self {
166 match s {
167 | "A1" => KindCode::A1,
168 | "A2" => KindCode::A2,
169 | "A9" => KindCode::A9,
170 | "B1" => KindCode::B1,
171 | "B2" => KindCode::B2,
172 | "C1" => KindCode::C1,
173 | "C2" => KindCode::C2,
174 | "C3" => KindCode::C3,
175 | "E" | "E1" => KindCode::E1,
176 | "S" | "S1" => KindCode::S1,
177 | "P1" => KindCode::P1,
178 | "P2" => KindCode::P2,
179 | "P3" => KindCode::P3,
180 | "P4" => KindCode::P4,
181 | "P9" => KindCode::P9,
182 | "H" | "H1" => KindCode::H1,
183 | _ => KindCode::Unknown,
184 }
185 }
186}
187impl From<String> for KindCode {
188 fn from(s: String) -> Self {
189 KindCode::from(s.as_str())
190 }
191}
192impl KindCode {
193 pub fn is_granted(&self) -> bool {
195 match self {
196 | KindCode::B1
197 | KindCode::B2
198 | KindCode::C1
199 | KindCode::C2
200 | KindCode::C3
201 | KindCode::E1
202 | KindCode::P2
203 | KindCode::P3
204 | KindCode::S1 => true,
205 | _ => false,
206 }
207 }
208}
209impl Default for Patent {
210 fn default() -> Self {
211 Patent::Granted {
212 country_code: Some(CountryCode::US),
213 kind_code: KindCode::Unknown,
214 serial_number: String::new(),
215 }
216 }
217}
218impl fmt::Display for Patent {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 match self {
221 | Patent::Granted {
222 country_code,
223 serial_number,
224 kind_code,
225 } => {
226 write!(f, "{} {serial_number} {kind_code}", country_code.clone().unwrap_or_default())
227 }
228 | Patent::Application { serial_number, year } => {
229 if let Some(year) = year {
230 write!(f, "{year}/{serial_number}")
231 } else {
232 write!(f, "{serial_number}")
233 }
234 }
235 | Patent::Publication {
236 country_code,
237 serial_number,
238 kind_code,
239 year,
240 } => {
241 let country = country_code.clone().unwrap_or_default();
242 if let Some(year) = year {
243 write!(f, "{country} {year}/{serial_number} {kind_code}")
244 } else {
245 write!(f, "{country} {serial_number} {kind_code}")
246 }
247 }
248 }
249 }
250}
251impl From<&str> for Patent {
252 fn from(s: &str) -> Self {
253 Patent::parse(s).unwrap_or_default()
254 }
255}
256impl From<String> for Patent {
257 fn from(s: String) -> Self {
258 Patent::from(s.as_str())
259 }
260}
261impl Patent {
262 pub fn find_all(value: &str) -> Vec<Self> {
264 let re = &RE_PATENT;
265 re.find_iter(value)
266 .filter_map(Result::ok)
267 .filter_map(|m| Patent::parse(m.as_str()))
268 .collect::<Vec<_>>()
269 }
270 pub fn is_valid<S>(value: S) -> bool
272 where
273 S: AsRef<str>,
274 {
275 match Patent::parse(value.as_ref()) {
276 | Some(result) => match result {
277 | Patent::Granted {
278 serial_number, kind_code, ..
279 } => !serial_number.is_empty() && kind_code != KindCode::Unknown,
280 | Patent::Application { serial_number, .. } => !serial_number.is_empty(),
281 | Patent::Publication {
282 serial_number, kind_code, ..
283 } => !serial_number.is_empty() && kind_code != KindCode::Unknown,
284 },
285 | None => false,
286 }
287 }
288 pub fn parse<S>(value: S) -> Option<Self>
292 where
293 S: Into<String> + Clone,
294 {
295 let s: String = value.clone().into().chars().take(2).collect::<String>();
296 match CountryCode::from(s) {
297 | CountryCode::US => {
298 fn preprocess<S>(value: S) -> String
299 where
300 S: Into<String>,
301 {
302 value.into().replace(" ", "").replace(",", "").replace("-", "").to_uppercase()
303 }
304 let pattern = format!("^{RE_PATENT_TEXT}$");
305 let s: String = preprocess(value);
306 let groups = ["country_code", "year", "serial_number", "kind_code"]
307 .into_iter()
308 .map(String::from)
309 .collect::<Vec<_>>();
310 let lookup = regex_capture_lookup(pattern, s, groups);
311 let country_code = lookup.get("country_code").cloned().map(CountryCode::from);
312 let serial_number = lookup.get("serial_number").cloned().unwrap_or_default();
313 let kind_code = match lookup.get("kind_code").cloned() {
314 | Some(value) => KindCode::from(value),
315 | None => KindCode::Unknown,
316 };
317 match kind_code {
318 | KindCode::B1
319 | KindCode::B2
320 | KindCode::C1
321 | KindCode::C2
322 | KindCode::C3
323 | KindCode::E1
324 | KindCode::P2
325 | KindCode::P3
326 | KindCode::S1 => Some(Patent::Granted {
327 country_code,
328 serial_number,
329 kind_code,
330 }),
331 | KindCode::A1 | KindCode::A2 | KindCode::A9 | KindCode::P1 | KindCode::P4 | KindCode::P9 => {
332 let year = lookup.get("year").cloned();
333 Some(Patent::Publication {
334 country_code,
335 serial_number,
336 kind_code,
337 year,
338 })
339 }
340 | _ => None,
341 }
342 }
343 | _ => None,
344 }
345 }
346}