nix_uri/flakeref/
location_params.rs

1use std::fmt::Display;
2
3use nom::{
4    IResult, Parser,
5    bytes::complete::{take_till, take_until},
6    character::complete::char,
7    error::context,
8    multi::many_m_n,
9    sequence::separated_pair,
10};
11use serde::{Deserialize, Serialize};
12
13use crate::{IErr, error::NixUriError};
14
15#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[cfg_attr(test, serde(deny_unknown_fields))]
17pub struct LocationParameters {
18    /// The subdirectory of the flake in which flake.nix is located. This parameter
19    /// enables having multiple flakes in a repository or tarball. The default is the
20    /// root directory of the flake.
21    dir: Option<String>,
22    /// The hash of the NAR serialisation (in SRI format) of the contents of the flake.
23    /// This is useful for flake types such as tarballs that lack a unique content
24    /// identifier such as a Git commit hash.
25    #[serde(rename = "narHash")]
26    nar_hash: Option<String>,
27    /// A Git or Mercurial commit hash.
28    rev: Option<String>,
29    ///  A Git or Mercurial branch or tag name.
30    r#ref: Option<String>,
31    branch: Option<String>,
32    submodules: Option<String>,
33    shallow: Option<String>,
34    // Only available to certain types
35    host: Option<String>,
36    // Not available to user
37    #[serde(rename = "revCount")]
38    rev_count: Option<String>,
39    // Not available to user
40    #[serde(rename = "lastModified")]
41    last_modified: Option<String>,
42    /// Arbitrary uri parameters will be allowed during initial parsing
43    /// in case they should be checked for known types run `self.check()`
44    arbitrary: Vec<(String, String)>,
45}
46
47// TODO: convert into macro!
48// or have params in a vec of tuples? with param and option<string>
49impl Display for LocationParameters {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        let mut first = true;
52        let mut write_param = |key: &str, value: &str| {
53            if !first {
54                write!(f, "&")?;
55            }
56            first = false;
57            write!(f, "{key}={value}")
58        };
59
60        if let Some(dir) = &self.dir {
61            write_param("dir", dir)?;
62        }
63        if let Some(branch) = &self.branch {
64            write_param("branch", branch)?;
65        }
66        if let Some(host) = &self.host {
67            write_param("host", host)?;
68        }
69        if let Some(r#ref) = &self.r#ref {
70            write_param("ref", r#ref)?;
71        }
72        if let Some(rev) = &self.rev {
73            write_param("rev", rev)?;
74        }
75        if let Some(nar_hash) = &self.nar_hash {
76            write_param("nar_hash", nar_hash)?;
77        }
78        if let Some(submodules) = &self.submodules {
79            write_param("submodules", submodules)?;
80        }
81        if let Some(shallow) = &self.shallow {
82            write_param("shallow", shallow)?;
83        }
84        for (key, value) in &self.arbitrary {
85            write_param(key, value)?;
86        }
87
88        Ok(())
89    }
90}
91
92impl LocationParameters {
93    pub fn parse(input: &str) -> IResult<&str, Self, IErr<&str>> {
94        let (rest, param_values) = context(
95            "location parameters",
96            many_m_n(
97                0,
98                11,
99                separated_pair(
100                    take_until("="),
101                    char('='),
102                    take_till(|c| c == '&' || c == '#'),
103                ),
104            ),
105        )
106        .parse(input)?;
107
108        let mut params = Self::default();
109        for (param, value) in param_values {
110            // param can start with "&"
111            // TODO: actual error handling instead of unwrapping
112            // TODO: allow check of the parameters
113            if let Ok(param) = param.parse() {
114                match param {
115                    LocationParamKeys::Dir => params.set_dir(Some(value.into())),
116                    LocationParamKeys::NarHash => params.set_nar_hash(Some(value.into())),
117                    LocationParamKeys::Host => params.set_host(Some(value.into())),
118                    LocationParamKeys::Ref => params.set_ref(Some(value.into())),
119                    LocationParamKeys::Rev => params.set_rev(Some(value.into())),
120                    LocationParamKeys::Branch => params.set_branch(Some(value.into())),
121                    LocationParamKeys::Submodules => params.set_submodules(Some(value.into())),
122                    LocationParamKeys::Shallow => params.set_shallow(Some(value.into())),
123                    LocationParamKeys::Arbitrary(param) => {
124                        params.add_arbitrary((param, value.into()));
125                    }
126                }
127            }
128        }
129        Ok((rest, params))
130    }
131
132    pub fn dir(&mut self, dir: Option<String>) -> &mut Self {
133        self.dir = dir;
134        self
135    }
136
137    pub fn nar_hash(&mut self, nar_hash: Option<String>) -> &mut Self {
138        self.nar_hash = nar_hash;
139        self
140    }
141
142    pub fn host(&mut self, host: Option<String>) -> &mut Self {
143        self.host = host;
144        self
145    }
146    pub fn rev(&mut self, rev: Option<String>) -> &mut Self {
147        self.rev = rev;
148        self
149    }
150    pub fn r#ref(&mut self, r#ref: Option<String>) -> &mut Self {
151        self.r#ref = r#ref;
152        self
153    }
154
155    pub fn set_dir(&mut self, dir: Option<String>) {
156        self.dir = dir;
157    }
158
159    pub fn set_nar_hash(&mut self, nar_hash: Option<String>) {
160        self.nar_hash = nar_hash;
161    }
162
163    pub fn set_rev(&mut self, rev: Option<String>) {
164        self.rev = rev;
165    }
166
167    pub fn set_ref(&mut self, r#ref: Option<String>) {
168        self.r#ref = r#ref;
169    }
170
171    pub fn set_host(&mut self, host: Option<String>) {
172        self.host = host;
173    }
174
175    pub fn rev_count_mut(&mut self) -> &mut Option<String> {
176        &mut self.rev_count
177    }
178
179    pub fn set_branch(&mut self, branch: Option<String>) {
180        self.branch = branch;
181    }
182
183    pub fn set_submodules(&mut self, submodules: Option<String>) {
184        self.submodules = submodules;
185    }
186
187    pub fn set_shallow(&mut self, shallow: Option<String>) {
188        self.shallow = shallow;
189    }
190    pub fn add_arbitrary(&mut self, arbitrary: (String, String)) {
191        self.arbitrary.push(arbitrary);
192    }
193    pub const fn get_rev(&self) -> Option<&String> {
194        self.rev.as_ref()
195    }
196    pub const fn get_ref(&self) -> Option<&String> {
197        self.r#ref.as_ref()
198    }
199}
200
201pub enum LocationParamKeys {
202    Dir,
203    NarHash,
204    Host,
205    Ref,
206    Rev,
207    Branch,
208    Submodules,
209    Shallow,
210    Arbitrary(String),
211}
212
213impl std::str::FromStr for LocationParamKeys {
214    type Err = NixUriError;
215
216    fn from_str(s: &str) -> Result<Self, Self::Err> {
217        match s {
218            "dir" | "&dir" => Ok(Self::Dir),
219            "nar_hash" | "&nar_hash" => Ok(Self::NarHash),
220            "host" | "&host" => Ok(Self::Host),
221            "rev" | "&rev" => Ok(Self::Rev),
222            "ref" | "&ref" => Ok(Self::Ref),
223            "branch" | "&branch" => Ok(Self::Branch),
224            "submodules" | "&submodules" => Ok(Self::Submodules),
225            "shallow" | "&shallow" => Ok(Self::Shallow),
226            arbitrary => Ok(Self::Arbitrary(arbitrary.into())),
227            // unknown => Err(NixUriError::UnknownUriParameter(unknown.into())),
228        }
229    }
230}
231
232#[cfg(test)]
233mod inc_parse {
234    use super::*;
235    #[test]
236    fn no_str() {
237        let expected = LocationParameters::default();
238        let in_str = "";
239        let (outstr, parsed_param) = LocationParameters::parse(in_str).unwrap();
240        assert_eq!("", outstr);
241        assert_eq!(expected, parsed_param);
242    }
243    #[test]
244    fn empty() {
245        let expected = LocationParameters::default();
246        let in_str = "";
247        let (rest, output) = LocationParameters::parse(in_str).unwrap();
248        assert_eq!("", rest);
249        assert_eq!(output, expected);
250    }
251    #[test]
252    fn empty_hash_terminated() {
253        let expected = LocationParameters::default();
254        let in_str = "#";
255        let (rest, output) = LocationParameters::parse(in_str).unwrap();
256        assert_eq!("#", rest);
257        assert_eq!(output, expected);
258    }
259    #[test]
260    fn dir() {
261        let mut expected = LocationParameters::default();
262        expected.dir(Some("foo".to_string()));
263
264        let in_str = "dir=foo";
265        let (rest, output) = LocationParameters::parse(in_str).unwrap();
266        assert_eq!("", rest);
267        assert_eq!(output, expected);
268
269        let in_str = "&dir=foo";
270        let (rest, output) = LocationParameters::parse(in_str).unwrap();
271        assert_eq!("", rest);
272        assert_eq!(output, expected);
273        let in_str = "dir=&dir=foo";
274        let (rest, output) = LocationParameters::parse(in_str).unwrap();
275        assert_eq!("", rest);
276        assert_eq!(output, expected);
277
278        expected.dir(Some(String::new()));
279        let in_str = "dir=";
280        let (rest, output) = LocationParameters::parse(in_str).unwrap();
281        assert_eq!("", rest);
282        assert_eq!(output, expected);
283    }
284    #[test]
285    fn dir_hash_term() {
286        let mut expected = LocationParameters::default();
287        expected.dir(Some("foo".to_string()));
288
289        let in_str = "dir=foo#fizz";
290        let (rest, output) = LocationParameters::parse(in_str).unwrap();
291        assert_eq!("#fizz", rest);
292        assert_eq!(output, expected);
293
294        let in_str = "&dir=foo#fizz";
295        let (rest, output) = LocationParameters::parse(in_str).unwrap();
296        assert_eq!("#fizz", rest);
297        assert_eq!(output, expected);
298        let in_str = "dir=&dir=foo#fizz";
299        let (rest, output) = LocationParameters::parse(in_str).unwrap();
300        assert_eq!("#fizz", rest);
301        assert_eq!(output, expected);
302
303        expected.dir(Some(String::new()));
304        let in_str = "dir=#fizz";
305        let (rest, output) = LocationParameters::parse(in_str).unwrap();
306        assert_eq!("#fizz", rest);
307        assert_eq!(output, expected);
308    }
309}