use crate::prelude::*;
use crate::util::constants::{RE_PATENT, RE_PATENT_TEXT};
use crate::util::regex_capture_lookup;
use core::fmt;
use derive_more::Display;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Display, Deserialize, Serialize, JsonSchema)]
pub enum CountryCode {
#[default]
#[display("US")]
US,
#[display("CN")]
CN,
#[display("DE")]
DE,
#[display("EP")]
EP,
#[display("JP")]
JP,
#[display("KR")]
KR,
}
#[derive(Clone, Copy, Debug, Deserialize, Display, Eq, Hash, PartialEq, Serialize, JsonSchema)]
pub enum KindCode {
#[display("A1")]
#[serde(rename = "A1")]
A1,
#[display("A2")]
#[serde(rename = "A2")]
A2,
#[display("A9")]
#[serde(rename = "A9")]
A9,
#[display("B1")]
#[serde(rename = "B1")]
B1,
#[display("B2")]
#[serde(rename = "B2")]
B2,
#[display("C1")]
#[serde(rename = "C1")]
C1,
#[display("C2")]
#[serde(rename = "C2")]
C2,
#[display("C3")]
#[serde(rename = "C3")]
C3,
#[display("E1")]
#[serde(rename = "E1", alias = "E")]
E1,
#[display("S1")]
#[serde(rename = "S1", alias = "S")]
S1,
#[display("P1")]
#[serde(rename = "P1")]
P1,
#[display("P2")]
#[serde(rename = "P2")]
P2,
#[display("P3")]
#[serde(rename = "P3")]
P3,
#[display("P4")]
#[serde(rename = "P4")]
P4,
#[display("P9")]
#[serde(rename = "P9")]
P9,
#[display("H1")]
#[serde(rename = "H1", alias = "H")]
H1,
#[display("Unknown")]
#[serde(rename = "Unknown")]
Unknown,
}
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub enum Patent {
Granted {
country_code: Option<CountryCode>,
kind_code: KindCode,
serial_number: String,
},
Application {
serial_number: String,
year: Option<String>,
},
Publication {
country_code: Option<CountryCode>,
kind_code: KindCode,
serial_number: String,
year: Option<String>,
},
}
impl From<&str> for CountryCode {
fn from(s: &str) -> Self {
match s.to_uppercase().as_str() {
| "US" => CountryCode::US,
| "CN" => CountryCode::CN,
| "DE" => CountryCode::DE,
| "EP" => CountryCode::EP,
| "JP" => CountryCode::JP,
| "KR" => CountryCode::KR,
| _ => CountryCode::US,
}
}
}
impl From<String> for CountryCode {
fn from(s: String) -> Self {
CountryCode::from(s.as_str())
}
}
impl From<&str> for KindCode {
fn from(s: &str) -> Self {
match s {
| "A1" => KindCode::A1,
| "A2" => KindCode::A2,
| "A9" => KindCode::A9,
| "B1" => KindCode::B1,
| "B2" => KindCode::B2,
| "C1" => KindCode::C1,
| "C2" => KindCode::C2,
| "C3" => KindCode::C3,
| "E" | "E1" => KindCode::E1,
| "S" | "S1" => KindCode::S1,
| "P1" => KindCode::P1,
| "P2" => KindCode::P2,
| "P3" => KindCode::P3,
| "P4" => KindCode::P4,
| "P9" => KindCode::P9,
| "H" | "H1" => KindCode::H1,
| _ => KindCode::Unknown,
}
}
}
impl From<String> for KindCode {
fn from(s: String) -> Self {
KindCode::from(s.as_str())
}
}
impl KindCode {
pub fn is_granted(&self) -> bool {
match self {
| KindCode::B1
| KindCode::B2
| KindCode::C1
| KindCode::C2
| KindCode::C3
| KindCode::E1
| KindCode::P2
| KindCode::P3
| KindCode::S1 => true,
| _ => false,
}
}
}
impl Default for Patent {
fn default() -> Self {
Patent::Granted {
country_code: Some(CountryCode::US),
kind_code: KindCode::Unknown,
serial_number: String::new(),
}
}
}
impl fmt::Display for Patent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
| Patent::Granted {
country_code,
serial_number,
kind_code,
} => {
write!(f, "{} {serial_number} {kind_code}", country_code.clone().unwrap_or_default())
}
| Patent::Application { serial_number, year } => {
if let Some(year) = year {
write!(f, "{year}/{serial_number}")
} else {
write!(f, "{serial_number}")
}
}
| Patent::Publication {
country_code,
serial_number,
kind_code,
year,
} => {
let country = country_code.clone().unwrap_or_default();
if let Some(year) = year {
write!(f, "{country} {year}/{serial_number} {kind_code}")
} else {
write!(f, "{country} {serial_number} {kind_code}")
}
}
}
}
}
impl From<&str> for Patent {
fn from(s: &str) -> Self {
Patent::parse(s).unwrap_or_default()
}
}
impl From<String> for Patent {
fn from(s: String) -> Self {
Patent::from(s.as_str())
}
}
impl Patent {
pub fn find_all(value: &str) -> Vec<Self> {
let re = &RE_PATENT;
re.find_iter(value)
.filter_map(Result::ok)
.filter_map(|m| Patent::parse(m.as_str()))
.collect::<Vec<_>>()
}
pub fn is_valid<S>(value: S) -> bool
where
S: AsRef<str>,
{
match Patent::parse(value.as_ref()) {
| Some(result) => match result {
| Patent::Granted {
serial_number, kind_code, ..
} => !serial_number.is_empty() && kind_code != KindCode::Unknown,
| Patent::Application { serial_number, .. } => !serial_number.is_empty(),
| Patent::Publication {
serial_number, kind_code, ..
} => !serial_number.is_empty() && kind_code != KindCode::Unknown,
},
| None => false,
}
}
pub fn parse<S>(value: S) -> Option<Self>
where
S: Into<String> + Clone,
{
let s: String = value.clone().into().chars().take(2).collect::<String>();
match CountryCode::from(s) {
| CountryCode::US => {
fn preprocess<S>(value: S) -> String
where
S: Into<String>,
{
value.into().replace(" ", "").replace(",", "").replace("-", "").to_uppercase()
}
let pattern = format!("^{RE_PATENT_TEXT}$");
let s: String = preprocess(value);
let groups = ["country_code", "year", "serial_number", "kind_code"]
.into_iter()
.map(String::from)
.collect::<Vec<_>>();
let lookup = regex_capture_lookup(pattern, s, groups);
let country_code = lookup.get("country_code").cloned().map(CountryCode::from);
let serial_number = lookup.get("serial_number").cloned().unwrap_or_default();
let kind_code = match lookup.get("kind_code").cloned() {
| Some(value) => KindCode::from(value),
| None => KindCode::Unknown,
};
match kind_code {
| KindCode::B1
| KindCode::B2
| KindCode::C1
| KindCode::C2
| KindCode::C3
| KindCode::E1
| KindCode::P2
| KindCode::P3
| KindCode::S1 => Some(Patent::Granted {
country_code,
serial_number,
kind_code,
}),
| KindCode::A1 | KindCode::A2 | KindCode::A9 | KindCode::P1 | KindCode::P4 | KindCode::P9 => {
let year = lookup.get("year").cloned();
Some(Patent::Publication {
country_code,
serial_number,
kind_code,
year,
})
}
| _ => None,
}
}
| _ => None,
}
}
}