1use std::fmt;
2
3use serde::de::{self, Visitor};
4use serde::{Deserialize, Deserializer, Serialize};
5
6#[derive(Clone, Debug, Serialize, Deserialize)]
12pub struct HalDoc {
13 #[serde(default, deserialize_with = "string_or_number")]
18 pub docid: String,
19
20 #[serde(default)]
22 pub label_s: Option<String>,
23
24 #[serde(default)]
26 pub uri_s: Option<String>,
27
28 #[serde(default)]
30 pub title_s: Option<Vec<String>>,
31
32 #[serde(default, rename = "authFullName_s")]
34 pub auth_full_name_s: Option<Vec<String>>,
35
36 #[serde(default)]
38 pub produced_date_s: Option<String>,
39
40 #[serde(default)]
42 pub doc_type_s: Option<String>,
43
44 #[serde(flatten)]
46 pub extra: serde_json::Map<String, serde_json::Value>,
47}
48
49impl HalDoc {
50 pub fn title(&self) -> Option<&str> {
52 self.title_s.as_ref()?.first().map(String::as_str)
53 }
54
55 pub fn authors(&self) -> Option<String> {
57 self.auth_full_name_s.as_ref().map(|a| a.join(", "))
58 }
59
60 pub fn heading(&self) -> Option<&str> {
63 self.title().or(self.label_s.as_deref())
64 }
65}
66
67impl fmt::Display for HalDoc {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 match self.heading() {
70 Some(heading) => write!(f, "{} ({})", heading, self.docid),
71 None => write!(f, "{}", self.docid),
72 }
73 }
74}
75
76fn string_or_number<'de, D>(deserializer: D) -> Result<String, D::Error>
78where
79 D: Deserializer<'de>,
80{
81 struct StringOrNumber;
82
83 impl<'de> Visitor<'de> for StringOrNumber {
84 type Value = String;
85
86 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 f.write_str("a string or an integer")
88 }
89
90 fn visit_str<E: de::Error>(self, v: &str) -> Result<String, E> {
91 Ok(v.to_owned())
92 }
93
94 fn visit_string<E: de::Error>(self, v: String) -> Result<String, E> {
95 Ok(v)
96 }
97
98 fn visit_i64<E: de::Error>(self, v: i64) -> Result<String, E> {
99 Ok(v.to_string())
100 }
101
102 fn visit_u64<E: de::Error>(self, v: u64) -> Result<String, E> {
103 Ok(v.to_string())
104 }
105 }
106
107 deserializer.deserialize_any(StringOrNumber)
108}