馃 The Definitive Gemini Protocol Toolkit
gemini
gemini-protocol
gemtext
parser
zero-dependency
toolkit
ast
converter
html
markdown
cli
networking
1use std::{borrow::Cow, collections::HashMap, fmt::Display};
2
3/// Structure-ize a Gemini response's meta section into it's mime type and it's
4/// parameters.
5#[derive(Debug, Default, Clone, PartialEq, Eq)]
6pub struct Meta {
7 /// The mime type of a Gemini response
8 mime: String,
9 /// The parameters of a Gemini response
10 parameters: HashMap<String, String>,
11}
12
13impl Display for Meta {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "{}", self.mime)?;
16
17 if !self.parameters.is_empty() {
18 write!(f, "; ")?;
19
20 let mut parameters: Vec<_> = self.parameters.iter().collect();
21
22 parameters.sort_by(|a, b| a.0.cmp(b.0));
23
24 for (i, (key, value)) in parameters.iter().enumerate() {
25 if i > 0 {
26 write!(f, "; ")?;
27 }
28
29 write!(f, "{key}={value}")?;
30 }
31 }
32
33 Ok(())
34 }
35}
36
37impl Meta {
38 /// Create a new `Meta`
39 ///
40 /// # Example
41 ///
42 /// ```rust
43 /// let mut meta = germ::meta::Meta::new();
44 /// ```
45 #[must_use]
46 pub fn new() -> Self { Self::default() }
47
48 /// Create a `Meta` from a string
49 ///
50 /// # Example
51 ///
52 /// ```rust
53 /// assert_eq!(
54 /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2").mime(),
55 /// "text/gemini",
56 /// );
57 /// ```
58 #[must_use]
59 pub fn from_string<'a>(meta: impl Into<std::borrow::Cow<'a, str>>) -> Self {
60 let meta = meta.into().to_string();
61 let mut metas = meta.split(';');
62 let mime = metas.next().unwrap_or("").to_string();
63 let mut parameters = HashMap::new();
64
65 for parameter in metas {
66 let trimmed = parameter.trim_start();
67
68 // Only parse parameters containing '=' as those without are malformed
69 // according to RFC 2045
70 if let Some(equal_pos) = trimmed.find('=') {
71 let (key, value) = trimmed.split_at(equal_pos);
72
73 parameters.insert(key.to_string(), value[1..].to_string());
74 }
75 }
76
77 Self { mime, parameters }
78 }
79
80 /// Obtain non-mutable access to the mime of the `Meta`
81 ///
82 /// # Example
83 ///
84 /// ```rust
85 /// assert_eq!(
86 /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2").mime(),
87 /// "text/gemini",
88 /// );
89 /// ```
90 #[allow(clippy::missing_const_for_fn)]
91 #[must_use]
92 pub fn mime(&self) -> Cow<'_, str> { Cow::Borrowed(&self.mime) }
93
94 /// Obtain mutable access to the mime of the `Meta`
95 ///
96 /// # Example
97 ///
98 /// ```rust
99 /// let mut meta = germ::meta::Meta::new();
100 ///
101 /// *meta.mime_mut() = "text/gemini".to_string();
102 /// ```
103 pub const fn mime_mut(&mut self) -> &mut String { &mut self.mime }
104
105 /// Obtain non-mutable access to the parameters of the `Meta`
106 ///
107 /// # Example
108 ///
109 /// ```rust
110 /// assert_eq!(
111 /// germ::meta::Meta::from_string("text/gemini; hi=2; hi2=string=2")
112 /// .parameters()
113 /// .get("hi2"),
114 /// Some(&"string=2".to_string()),
115 /// );
116 /// ```
117 #[must_use]
118 pub const fn parameters(&self) -> &HashMap<String, String> {
119 &self.parameters
120 }
121
122 /// Obtain mutable access to the parameters of the `Meta`
123 ///
124 /// # Example
125 ///
126 /// ```rust
127 /// let mut meta = germ::meta::Meta::new();
128 /// let mut parameters = std::collections::HashMap::new();
129 ///
130 /// parameters.insert("hi".to_string(), "2".to_string());
131 /// parameters.insert("hi2".to_string(), "string=2".to_string());
132 ///
133 /// *meta.parameters_mut() = parameters;
134 /// ```
135 pub const fn parameters_mut(&mut self) -> &mut HashMap<String, String> {
136 &mut self.parameters
137 }
138}