nix_uri/
flakeref.rs

1use std::{fmt::Display, path::Path};
2
3use nom::{
4    bytes::complete::{tag, take_until},
5    combinator::{opt, rest},
6    IResult,
7};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11    error::{NixUriError, NixUriResult},
12    parser::{parse_owner_repo_ref, parse_url_type},
13};
14
15/// The General Flake Ref Schema
16#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[cfg_attr(test, serde(deny_unknown_fields))]
18pub struct FlakeRef {
19    pub r#type: FlakeRefType,
20    flake: Option<bool>,
21    pub params: FlakeRefParameters,
22}
23
24impl FlakeRef {
25    pub fn new(r#type: FlakeRefType) -> Self {
26        Self {
27            r#type,
28            ..Self::default()
29        }
30    }
31
32    pub fn from<S>(input: S) -> Result<Self, NixUriError>
33    where
34        S: AsRef<str>,
35    {
36        TryInto::<Self>::try_into(input.as_ref())
37    }
38
39    pub fn r#type(&mut self, r#type: FlakeRefType) -> &mut Self {
40        self.r#type = r#type;
41        self
42    }
43    pub fn id(&self) -> Option<String> {
44        self.r#type.get_id()
45    }
46
47    pub fn params(&mut self, params: FlakeRefParameters) -> &mut Self {
48        self.params = params;
49        self
50    }
51}
52
53impl Display for FlakeRef {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        // TODO: convert into Option
56        let params = self.params.to_string();
57        if params.is_empty() {
58            write!(f, "{}", self.r#type)
59        } else {
60            write!(f, "{}?{params}", self.r#type)
61        }
62    }
63}
64
65#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
66#[cfg_attr(test, serde(deny_unknown_fields))]
67pub struct FlakeRefParameters {
68    dir: Option<String>,
69    #[serde(rename = "narHash")]
70    nar_hash: Option<String>,
71    rev: Option<String>,
72    r#ref: Option<String>,
73    branch: Option<String>,
74    submodules: Option<String>,
75    shallow: Option<String>,
76    // Only available to certain types
77    host: Option<String>,
78    // Not available to user
79    #[serde(rename = "revCount")]
80    rev_count: Option<String>,
81    // Not available to user
82    #[serde(rename = "lastModified")]
83    last_modified: Option<String>,
84    /// Arbitrary uri parameters will be allowed during initial parsing
85    /// in case they should be checked for known types run `self.check()`
86    arbitrary: Vec<(String, String)>,
87}
88
89// TODO: convert into macro!
90// or have params in a vec of tuples? with param and option<string>
91impl Display for FlakeRefParameters {
92    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93        let mut res = String::new();
94        if let Some(dir) = &self.dir {
95            res.push_str("dir=");
96            res.push_str(dir);
97        }
98        if let Some(branch) = &self.branch {
99            if !res.is_empty() {
100                res.push('?');
101            }
102            res.push_str("branch=");
103            res.push_str(branch);
104        }
105        if let Some(host) = &self.host {
106            if !res.is_empty() {
107                res.push('?');
108            }
109            res.push_str("host=");
110            res.push_str(host);
111        }
112        if let Some(r#ref) = &self.r#ref {
113            if !res.is_empty() {
114                res.push('?');
115            }
116            res.push_str("ref=");
117            res.push_str(r#ref);
118        }
119        if let Some(rev) = &self.rev {
120            if !res.is_empty() {
121                res.push('?');
122            }
123            res.push_str("rev=");
124            res.push_str(rev);
125        }
126        write!(f, "{res}")
127    }
128}
129
130impl FlakeRefParameters {
131    pub fn dir(&mut self, dir: Option<String>) -> &mut Self {
132        self.dir = dir;
133        self
134    }
135
136    pub fn nar_hash(&mut self, nar_hash: Option<String>) -> &mut Self {
137        self.nar_hash = nar_hash;
138        self
139    }
140
141    pub fn host(&mut self, host: Option<String>) -> &mut Self {
142        self.host = host;
143        self
144    }
145    pub fn rev(&mut self, rev: Option<String>) -> &mut Self {
146        self.rev = rev;
147        self
148    }
149    pub fn r#ref(&mut self, r#ref: Option<String>) -> &mut Self {
150        self.r#ref = r#ref;
151        self
152    }
153
154    pub fn set_dir(&mut self, dir: Option<String>) {
155        self.dir = dir;
156    }
157
158    pub fn set_nar_hash(&mut self, nar_hash: Option<String>) {
159        self.nar_hash = nar_hash;
160    }
161
162    pub fn set_rev(&mut self, rev: Option<String>) {
163        self.rev = rev;
164    }
165
166    pub fn set_ref(&mut self, r#ref: Option<String>) {
167        self.r#ref = r#ref;
168    }
169
170    pub fn set_host(&mut self, host: Option<String>) {
171        self.host = host;
172    }
173
174    pub fn rev_count_mut(&mut self) -> &mut Option<String> {
175        &mut self.rev_count
176    }
177
178    pub fn set_branch(&mut self, branch: Option<String>) {
179        self.branch = branch;
180    }
181
182    pub fn set_submodules(&mut self, submodules: Option<String>) {
183        self.submodules = submodules;
184    }
185
186    pub fn set_shallow(&mut self, shallow: Option<String>) {
187        self.shallow = shallow;
188    }
189    pub fn add_arbitrary(&mut self, arbitrary: (String, String)) {
190        self.arbitrary.push(arbitrary);
191    }
192    pub fn get_rev(&self) -> Option<&String> {
193        self.rev.as_ref()
194    }
195    pub fn get_ref(&self) -> Option<&String> {
196        self.r#ref.as_ref()
197    }
198}
199
200pub enum FlakeRefParam {
201    Dir,
202    NarHash,
203    Host,
204    Ref,
205    Rev,
206    Branch,
207    Submodules,
208    Shallow,
209    Arbitrary(String),
210}
211
212impl std::str::FromStr for FlakeRefParam {
213    type Err = NixUriError;
214
215    fn from_str(s: &str) -> Result<Self, Self::Err> {
216        use FlakeRefParam::*;
217        match s {
218            "dir" | "&dir" => Ok(Dir),
219            "nar_hash" | "&nar_hash" => Ok(NarHash),
220            "host" | "&host" => Ok(Host),
221            "rev" | "&rev" => Ok(Rev),
222            "ref" | "&ref" => Ok(Ref),
223            "branch" | "&branch" => Ok(Branch),
224            "submodules" | "&submodules" => Ok(Submodules),
225            "shallow" | "&shallow" => Ok(Shallow),
226            arbitrary => Ok(Arbitrary(arbitrary.into())),
227            // unknown => Err(NixUriError::UnknownUriParameter(unknown.into())),
228        }
229    }
230}
231
232#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
233#[non_exhaustive]
234pub enum FlakeRefType {
235    // In URL form, the schema must be file+http://, file+https:// or file+file://. If the extension doesn’t correspond to a known archive format (as defined by the tarball fetcher), then the file+ prefix can be dropped.
236    File {
237        url: String,
238    },
239    /// Git repositories. The location of the repository is specified by the attribute
240    /// `url`. The `ref` arrribute defaults to resolving the `HEAD` reference.
241    /// The `rev` attribute must exist in the branch or tag specified by `ref`, defaults
242    /// to `ref`.
243    Git {
244        url: String,
245        r#type: UrlType,
246    },
247    GitHub {
248        owner: String,
249        repo: String,
250        ref_or_rev: Option<String>,
251    },
252    GitLab {
253        owner: String,
254        repo: String,
255        ref_or_rev: Option<String>,
256    },
257    Indirect {
258        id: String,
259        ref_or_rev: Option<String>,
260    },
261    // Matches `git` type, but schema is one of the following:
262    // `hg+http`, `hg+https`, `hg+ssh` or `hg+file`.
263    Mercurial {
264        url: String,
265        r#type: UrlType,
266    },
267    /// Path must be a directory in the filesystem containing a `flake.nix`.
268    /// Path must be an absolute path.
269    Path {
270        path: String,
271    },
272    Sourcehut {
273        owner: String,
274        repo: String,
275        ref_or_rev: Option<String>,
276    },
277    Tarball {
278        url: String,
279        r#type: UrlType,
280    },
281    #[default]
282    None,
283}
284
285impl Display for FlakeRefType {
286    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287        match self {
288            FlakeRefType::File { url } => write!(f, "file:{url}"),
289            FlakeRefType::Git { url, r#type } => {
290                if let UrlType::None = r#type {
291                    return write!(f, "git:{url}");
292                }
293                let uri = format!("git+{}:{url}", r#type);
294                write!(f, "{uri}")
295            }
296            FlakeRefType::GitHub {
297                owner,
298                repo,
299                ref_or_rev,
300            } => {
301                if let Some(ref_or_rev) = ref_or_rev {
302                    write!(f, "github:{owner}/{repo}/{ref_or_rev}")
303                } else {
304                    write!(f, "github:{owner}/{repo}")
305                }
306            }
307            FlakeRefType::GitLab {
308                owner,
309                repo,
310                ref_or_rev,
311            } => {
312                if let Some(ref_or_rev) = ref_or_rev {
313                    write!(f, "gitlab:{owner}/{repo}/{ref_or_rev}")
314                } else {
315                    write!(f, "gitlab:{owner}/{repo}")
316                }
317            }
318            FlakeRefType::Indirect { id, ref_or_rev } => {
319                if let Some(ref_or_rev) = ref_or_rev {
320                    write!(f, "{id}/{ref_or_rev}")
321                } else {
322                    write!(f, "{id}")
323                }
324            }
325            FlakeRefType::Mercurial { url, r#type } => todo!(),
326            FlakeRefType::Path { path } => todo!(),
327            FlakeRefType::Sourcehut {
328                owner,
329                repo,
330                ref_or_rev,
331            } => {
332                if let Some(ref_or_rev) = ref_or_rev {
333                    write!(f, "sourcehut:{owner}/{repo}/{ref_or_rev}")
334                } else {
335                    write!(f, "sourcehut:{owner}/{repo}")
336                }
337            }
338            // TODO: alternate tarball representation
339            FlakeRefType::Tarball { url, r#type } => {
340                write!(f, "file:{url}")
341            }
342            FlakeRefType::None => todo!(),
343        }
344    }
345}
346
347#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
348pub enum UrlType {
349    #[default]
350    None,
351    Https,
352    Ssh,
353    File,
354}
355
356impl TryFrom<&str> for UrlType {
357    type Error = NixUriError;
358
359    fn try_from(value: &str) -> Result<Self, Self::Error> {
360        use UrlType::*;
361        match value {
362            "" => Ok(None),
363            "https" => Ok(Https),
364            "ssh" => Ok(Ssh),
365            "file" => Ok(File),
366            err => Err(NixUriError::UnknownUrlType(err.into())),
367        }
368    }
369}
370
371impl Display for UrlType {
372    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
373        match self {
374            UrlType::None => write!(f, "No Url Type Specified"),
375            UrlType::Https => write!(f, "https"),
376            UrlType::Ssh => write!(f, "ssh"),
377            UrlType::File => write!(f, "file"),
378        }
379    }
380}
381
382impl FlakeRefType {
383    /// Parse type specific information, returns the [`FlakeRefType`]
384    /// and the unparsed input
385    pub fn parse_type(input: &str) -> NixUriResult<FlakeRefType> {
386        use nom::sequence::separated_pair;
387        let (_, maybe_explicit_type) = opt(separated_pair(
388            take_until::<&str, &str, (&str, nom::error::ErrorKind)>(":"),
389            tag(":"),
390            rest,
391        ))(input)?;
392        if let Some((flake_ref_type, input)) = maybe_explicit_type {
393            match flake_ref_type {
394                "github" => {
395                    let (input, owner_and_repo_or_ref) = parse_owner_repo_ref(input)?;
396                    let owner =
397                        owner_and_repo_or_ref
398                            .first()
399                            .ok_or(NixUriError::MissingTypeParameter(
400                                flake_ref_type.into(),
401                                "owner".into(),
402                            ))?;
403                    let repo =
404                        owner_and_repo_or_ref
405                            .get(1)
406                            .ok_or(NixUriError::MissingTypeParameter(
407                                flake_ref_type.into(),
408                                "repo".into(),
409                            ))?;
410                    let flake_ref_type = FlakeRefType::GitHub {
411                        owner: owner.to_string(),
412                        repo: repo.to_string(),
413                        ref_or_rev: owner_and_repo_or_ref.get(2).map(|s| s.to_string()),
414                    };
415                    Ok(flake_ref_type)
416                }
417                "gitlab" => {
418                    let (input, owner_and_repo_or_ref) = parse_owner_repo_ref(input)?;
419                    let owner =
420                        owner_and_repo_or_ref
421                            .first()
422                            .ok_or(NixUriError::MissingTypeParameter(
423                                flake_ref_type.into(),
424                                "owner".into(),
425                            ))?;
426                    let repo =
427                        owner_and_repo_or_ref
428                            .get(1)
429                            .ok_or(NixUriError::MissingTypeParameter(
430                                flake_ref_type.into(),
431                                "repo".into(),
432                            ))?;
433                    let flake_ref_type = FlakeRefType::GitLab {
434                        owner: owner.to_string(),
435                        repo: repo.to_string(),
436                        ref_or_rev: owner_and_repo_or_ref.get(2).map(|s| s.to_string()),
437                    };
438                    Ok(flake_ref_type)
439                }
440                "sourcehut" => {
441                    let (input, owner_and_repo_or_ref) = parse_owner_repo_ref(input)?;
442                    let owner =
443                        owner_and_repo_or_ref
444                            .first()
445                            .ok_or(NixUriError::MissingTypeParameter(
446                                flake_ref_type.into(),
447                                "owner".into(),
448                            ))?;
449                    let repo =
450                        owner_and_repo_or_ref
451                            .get(1)
452                            .ok_or(NixUriError::MissingTypeParameter(
453                                flake_ref_type.into(),
454                                "repo".into(),
455                            ))?;
456                    let flake_ref_type = FlakeRefType::Sourcehut {
457                        owner: owner.to_string(),
458                        repo: repo.to_string(),
459                        ref_or_rev: owner_and_repo_or_ref.get(2).map(|s| s.to_string()),
460                    };
461                    Ok(flake_ref_type)
462                }
463                "path" => {
464                    // TODO: check if path is an absolute path, if not error
465                    let path = Path::new(input);
466                    // TODO: make this check configurable for cli usage
467                    if !path.is_absolute() || input.contains(']') || input.contains('[') {
468                        return Err(NixUriError::NotAbsolute(input.into()));
469                    }
470                    if input.contains('#') || input.contains('?') {
471                        return Err(NixUriError::PathCharacter(input.into()));
472                    }
473                    let flake_ref_type = FlakeRefType::Path { path: input.into() };
474                    Ok(flake_ref_type)
475                }
476
477                _ => {
478                    if flake_ref_type.starts_with("git+") {
479                        let url_type = parse_url_type(flake_ref_type)?;
480                        let (input, _tag) =
481                            opt(tag::<&str, &str, (&str, nom::error::ErrorKind)>("//"))(input)?;
482                        let flake_ref_type = FlakeRefType::Git {
483                            url: input.into(),
484                            r#type: url_type,
485                        };
486                        Ok(flake_ref_type)
487                    } else if flake_ref_type.starts_with("hg+") {
488                        let url_type = parse_url_type(flake_ref_type)?;
489                        let (input, _tag) =
490                            tag::<&str, &str, (&str, nom::error::ErrorKind)>("//")(input)?;
491                        let flake_ref_type = FlakeRefType::Mercurial {
492                            url: input.into(),
493                            r#type: url_type,
494                        };
495                        Ok(flake_ref_type)
496                    } else {
497                        Err(NixUriError::UnknownUriType(flake_ref_type.into()))
498                    }
499                }
500            }
501        } else {
502            // Implicit types can be paths, indirect flake_refs, or uri's.
503            if input.starts_with('/') || input == "." {
504                let flake_ref_type = FlakeRefType::Path { path: input.into() };
505                let path = Path::new(input);
506                // TODO: make this check configurable for cli usage
507                if !path.is_absolute()
508                    || input.contains(']')
509                    || input.contains('[')
510                    || !input.chars().all(|c| c.is_ascii())
511                {
512                    return Err(NixUriError::NotAbsolute(input.into()));
513                }
514                if input.contains('#') || input.contains('?') {
515                    return Err(NixUriError::PathCharacter(input.into()));
516                }
517                return Ok(flake_ref_type);
518            }
519            //TODO: parse uri
520            let (input, owner_and_repo_or_ref) = parse_owner_repo_ref(input)?;
521            if !owner_and_repo_or_ref.is_empty() {
522                let id = if let Some(id) = owner_and_repo_or_ref.first() {
523                    id
524                } else {
525                    input
526                };
527                if !id
528                    .chars()
529                    .all(|c| c.is_ascii_alphabetic() || c.is_control())
530                    || id.is_empty()
531                {
532                    return Err(NixUriError::InvalidUrl(input.into()));
533                }
534                let flake_ref_type = FlakeRefType::Indirect {
535                    id: id.to_string(),
536                    ref_or_rev: owner_and_repo_or_ref.get(1).map(|s| s.to_string()),
537                };
538                Ok(flake_ref_type)
539            } else {
540                let (_input, owner_and_repo_or_ref) = parse_owner_repo_ref(input)?;
541                let id = if let Some(id) = owner_and_repo_or_ref.first() {
542                    id
543                } else {
544                    input
545                };
546                if !id.chars().all(|c| c.is_ascii_alphabetic()) || id.is_empty() {
547                    return Err(NixUriError::InvalidUrl(input.into()));
548                }
549                Ok(FlakeRefType::Indirect {
550                    id: id.to_string(),
551                    ref_or_rev: owner_and_repo_or_ref.get(1).map(|s| s.to_string()),
552                })
553            }
554        }
555    }
556    /// Extract a common identifier from it's [`FlakeRefType`] variant.
557    pub(crate) fn get_id(&self) -> Option<String> {
558        match self {
559            FlakeRefType::File { url } => None,
560            FlakeRefType::Git { url, r#type } => None,
561            FlakeRefType::GitHub {
562                owner,
563                repo,
564                ref_or_rev,
565            } => Some(repo.to_string()),
566            FlakeRefType::GitLab {
567                owner,
568                repo,
569                ref_or_rev,
570            } => Some(repo.to_string()),
571            FlakeRefType::Indirect { id, ref_or_rev } => None,
572            FlakeRefType::Mercurial { url, r#type } => None,
573            FlakeRefType::Path { path } => None,
574            FlakeRefType::Sourcehut {
575                owner,
576                repo,
577                ref_or_rev,
578            } => Some(repo.to_string()),
579            FlakeRefType::Tarball { url, r#type } => None,
580            FlakeRefType::None => None,
581        }
582    }
583    pub fn get_repo(&self) -> Option<String> {
584        match self {
585            FlakeRefType::GitHub { repo, .. }
586            | FlakeRefType::GitLab { repo, .. }
587            | FlakeRefType::Sourcehut { repo, .. } => Some(repo.into()),
588            // TODO: return a proper error, if ref_or_rev is tried to be specified
589            FlakeRefType::Mercurial { .. }
590            | FlakeRefType::Path { .. }
591            | FlakeRefType::Indirect { .. }
592            | FlakeRefType::Tarball { .. }
593            | FlakeRefType::File { .. }
594            | FlakeRefType::Git { .. }
595            | FlakeRefType::None => None,
596        }
597    }
598    pub fn get_owner(&self) -> Option<String> {
599        match self {
600            FlakeRefType::GitHub { owner, .. }
601            | FlakeRefType::GitLab { owner, .. }
602            | FlakeRefType::Sourcehut { owner, .. } => Some(owner.into()),
603            // TODO: return a proper error, if ref_or_rev is tried to be specified
604            FlakeRefType::Mercurial { .. }
605            | FlakeRefType::Path { .. }
606            | FlakeRefType::Indirect { .. }
607            | FlakeRefType::Tarball { .. }
608            | FlakeRefType::File { .. }
609            | FlakeRefType::Git { .. }
610            | FlakeRefType::None => None,
611        }
612    }
613    pub fn ref_or_rev(&mut self, ref_or_rev_alt: Option<String>) -> Result<(), NixUriError> {
614        match self {
615            FlakeRefType::GitHub { ref_or_rev, .. }
616            | FlakeRefType::GitLab { ref_or_rev, .. }
617            | FlakeRefType::Indirect { ref_or_rev, .. }
618            | FlakeRefType::Sourcehut { ref_or_rev, .. } => {
619                *ref_or_rev = ref_or_rev_alt;
620            }
621            // TODO: return a proper error, if ref_or_rev is tried to be specified
622            FlakeRefType::Mercurial { .. }
623            | FlakeRefType::Path { .. }
624            | FlakeRefType::Tarball { .. }
625            | FlakeRefType::File { .. }
626            | FlakeRefType::Git { .. }
627            | FlakeRefType::None => todo!(),
628        }
629        Ok(())
630    }
631}
632
633impl TryFrom<&str> for FlakeRef {
634    type Error = NixUriError;
635
636    fn try_from(value: &str) -> Result<Self, Self::Error> {
637        use crate::parser::parse_nix_uri;
638        parse_nix_uri(value)
639    }
640}
641
642impl std::str::FromStr for FlakeRef {
643    type Err = NixUriError;
644
645    fn from_str(s: &str) -> Result<Self, Self::Err> {
646        use crate::parser::parse_nix_uri;
647        parse_nix_uri(s)
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use crate::parser::{parse_nix_uri, parse_params};
655
656    #[test]
657    fn parse_simple_uri() {
658        let uri = "github:nixos/nixpkgs";
659        let expected = FlakeRef::default()
660            .r#type(FlakeRefType::GitHub {
661                owner: "nixos".into(),
662                repo: "nixpkgs".into(),
663                ref_or_rev: None,
664            })
665            .clone();
666        let parsed: FlakeRef = uri.try_into().unwrap();
667        assert_eq!(expected, parsed);
668    }
669
670    #[test]
671    fn parse_simple_uri_parsed() {
672        let uri = "github:zellij-org/zellij";
673        let expected = FlakeRef::default()
674            .r#type(FlakeRefType::GitHub {
675                owner: "zellij-org".into(),
676                repo: "zellij".into(),
677                ref_or_rev: None,
678            })
679            .clone();
680        let parsed: FlakeRef = uri.parse().unwrap();
681        assert_eq!(expected, parsed);
682    }
683
684    #[test]
685    fn parse_simple_uri_nom() {
686        let uri = "github:zellij-org/zellij";
687        let flake_ref = FlakeRef::default()
688            .r#type(FlakeRefType::GitHub {
689                owner: "zellij-org".into(),
690                repo: "zellij".into(),
691                ref_or_rev: None,
692            })
693            .clone();
694        let parsed = parse_nix_uri(uri).unwrap();
695        assert_eq!(flake_ref, parsed);
696    }
697    #[test]
698    fn parse_simple_uri_nom_params() {
699        let uri = "github:zellij-org/zellij";
700        let flake_attrs = None;
701        let parsed = parse_params(uri).unwrap();
702        assert_eq!(("github:zellij-org/zellij", flake_attrs), parsed);
703    }
704    #[test]
705    fn parse_simple_uri_attr_nom_params() {
706        let uri = "github:zellij-org/zellij?dir=assets";
707        let mut flake_attrs = FlakeRefParameters::default();
708        flake_attrs.dir(Some("assets".into()));
709        let parsed = parse_params(uri).unwrap();
710        assert_eq!(("github:zellij-org/zellij", Some(flake_attrs)), parsed);
711    }
712    #[test]
713    fn parse_simple_uri_ref() {
714        let uri = "github:zellij-org/zellij?ref=main";
715        let mut flake_attrs = FlakeRefParameters::default();
716        flake_attrs.r#ref(Some("main".into()));
717        let flake_ref = FlakeRef::default()
718            .r#type(FlakeRefType::GitHub {
719                owner: "zellij-org".into(),
720                repo: "zellij".into(),
721                ref_or_rev: None,
722            })
723            .params(flake_attrs)
724            .clone();
725        let parsed = parse_nix_uri(uri).unwrap();
726        assert_eq!(flake_ref, parsed);
727    }
728    #[test]
729    fn parse_simple_uri_rev() {
730        let uri = "github:zellij-org/zellij?rev=b2df4e4e80e04cbb33a350f87717f4bd6140d298";
731        let mut flake_attrs = FlakeRefParameters::default();
732        flake_attrs.rev(Some("b2df4e4e80e04cbb33a350f87717f4bd6140d298".into()));
733        let flake_ref = FlakeRef::default()
734            .r#type(FlakeRefType::GitHub {
735                owner: "zellij-org".into(),
736                repo: "zellij".into(),
737                ref_or_rev: None,
738            })
739            .params(flake_attrs)
740            .clone();
741        let parsed = parse_nix_uri(uri).unwrap();
742        assert_eq!(flake_ref, parsed);
743    }
744    #[test]
745    fn parse_simple_uri_ref_or_rev_nom() {
746        let uri = "github:zellij-org/zellij/main";
747        let flake_ref = FlakeRef::default()
748            .r#type(FlakeRefType::GitHub {
749                owner: "zellij-org".into(),
750                repo: "zellij".into(),
751                ref_or_rev: Some("main".into()),
752            })
753            .clone();
754        let parsed = parse_nix_uri(uri).unwrap();
755        assert_eq!(flake_ref, parsed);
756    }
757    #[test]
758    fn parse_simple_uri_ref_or_rev_attr_nom() {
759        let uri = "github:zellij-org/zellij/main?dir=assets";
760        let mut params = FlakeRefParameters::default();
761        params.dir(Some("assets".into()));
762        let flake_ref = FlakeRef::default()
763            .r#type(FlakeRefType::GitHub {
764                owner: "zellij-org".into(),
765                repo: "zellij".into(),
766                ref_or_rev: Some("main".into()),
767            })
768            .params(params)
769            .clone();
770
771        let parsed = parse_nix_uri(uri).unwrap();
772        assert_eq!(flake_ref, parsed);
773    }
774    #[test]
775    fn parse_simple_uri_attr_nom() {
776        let uri = "github:zellij-org/zellij?dir=assets";
777        let mut params = FlakeRefParameters::default();
778        params.dir(Some("assets".into()));
779        let flake_ref = FlakeRef::default()
780            .r#type(FlakeRefType::GitHub {
781                owner: "zellij-org".into(),
782                repo: "zellij".into(),
783                ref_or_rev: None,
784            })
785            .params(params)
786            .clone();
787        let parsed = parse_nix_uri(uri).unwrap();
788        assert_eq!(flake_ref, parsed);
789    }
790    #[test]
791    fn parse_simple_uri_attr_nom_alt() {
792        let uri = "github:zellij-org/zellij/?dir=assets";
793        let mut params = FlakeRefParameters::default();
794        params.dir(Some("assets".into()));
795        let flake_ref = FlakeRef::default()
796            .r#type(FlakeRefType::GitHub {
797                owner: "zellij-org".into(),
798                repo: "zellij".into(),
799                ref_or_rev: None,
800            })
801            .params(params)
802            .clone();
803        let parsed = parse_nix_uri(uri).unwrap();
804        assert_eq!(flake_ref, parsed);
805    }
806    #[test]
807    fn parse_simple_uri_params_nom_alt() {
808        let uri = "github:zellij-org/zellij/?dir=assets&nar_hash=fakeHash256";
809        let mut params = FlakeRefParameters::default();
810        params.dir(Some("assets".into()));
811        params.nar_hash(Some("fakeHash256".into()));
812        let flake_ref = FlakeRef::default()
813            .r#type(FlakeRefType::GitHub {
814                owner: "zellij-org".into(),
815                repo: "zellij".into(),
816                ref_or_rev: None,
817            })
818            .params(params)
819            .clone();
820        let parsed = parse_nix_uri(uri).unwrap();
821        assert_eq!(flake_ref, parsed);
822    }
823    #[test]
824    fn parse_simple_path_nom() {
825        let uri = "path:/home/kenji/.config/dotfiles/";
826        let flake_ref = FlakeRef::default()
827            .r#type(FlakeRefType::Path {
828                path: "/home/kenji/.config/dotfiles/".into(),
829            })
830            .clone();
831        let parsed = parse_nix_uri(uri).unwrap();
832        assert_eq!(flake_ref, parsed);
833    }
834    #[test]
835    fn parse_simple_path_params_nom() {
836        let uri = "path:/home/kenji/.config/dotfiles/?dir=assets";
837        let mut params = FlakeRefParameters::default();
838        params.dir(Some("assets".into()));
839        let flake_ref = FlakeRef::default()
840            .r#type(FlakeRefType::Path {
841                path: "/home/kenji/.config/dotfiles/".into(),
842            })
843            .params(params)
844            .clone();
845        let parsed = parse_nix_uri(uri).unwrap();
846        assert_eq!(flake_ref, parsed);
847    }
848    #[test]
849    fn parse_gitlab_simple() {
850        let uri = "gitlab:veloren/veloren";
851        let flake_ref = FlakeRef::default()
852            .r#type(FlakeRefType::GitLab {
853                owner: "veloren".into(),
854                repo: "veloren".into(),
855                ref_or_rev: None,
856            })
857            .clone();
858        let parsed = parse_nix_uri(uri).unwrap();
859        assert_eq!(flake_ref, parsed);
860    }
861    #[test]
862    fn parse_gitlab_simple_ref_or_rev() {
863        let uri = "gitlab:veloren/veloren/master";
864        let parsed = parse_nix_uri(uri).unwrap();
865        let flake_ref = FlakeRef::default()
866            .r#type(FlakeRefType::GitLab {
867                owner: "veloren".into(),
868                repo: "veloren".into(),
869                ref_or_rev: Some("master".into()),
870            })
871            .clone();
872        assert_eq!(flake_ref, parsed);
873    }
874    #[test]
875    fn parse_gitlab_simple_ref_or_rev_alt() {
876        let uri = "gitlab:veloren/veloren/19742bb9300fb0be9fdc92f30766c95230a8a371";
877        let parsed = crate::parser::parse_nix_uri(uri).unwrap();
878        let flake_ref = FlakeRef::default()
879            .r#type(FlakeRefType::GitLab {
880                owner: "veloren".into(),
881                repo: "veloren".into(),
882                ref_or_rev: Some("19742bb9300fb0be9fdc92f30766c95230a8a371".into()),
883            })
884            .clone();
885        assert_eq!(flake_ref, parsed);
886    }
887    // TODO: replace / with %2F
888    // #[test]
889    // fn parse_gitlab_nested_subgroup() {
890    //     let uri = "gitlab:veloren%2Fdev/rfcs";
891    //     let parsed = parse_nix_uri(uri).unwrap();
892    //     let flake_ref = FlakeRef::default()
893    //         .r#type(FlakeRefType::GitLab {
894    //             owner: "veloren".into(),
895    //             repo: "dev".into(),
896    //             ref_or_rev: Some("rfcs".to_owned()),
897    //         })
898    //         .clone();
899    //     assert_eq!(("", flake_ref), parsed);
900    // }
901    #[test]
902    fn parse_gitlab_simple_host_param() {
903        let uri = "gitlab:openldap/openldap?host=git.openldap.org";
904        let parsed = crate::parser::parse_nix_uri(uri).unwrap();
905        let mut params = FlakeRefParameters::default();
906        params.host(Some("git.openldap.org".into()));
907        let flake_ref = FlakeRef::default()
908            .r#type(FlakeRefType::GitLab {
909                owner: "openldap".into(),
910                repo: "openldap".into(),
911                ref_or_rev: None,
912            })
913            .params(params)
914            .clone();
915        assert_eq!(flake_ref, parsed);
916    }
917    #[test]
918    fn parse_git_and_https_simple() {
919        let uri = "git+https://git.somehost.tld/user/path";
920        let expected = FlakeRef::default()
921            .r#type(FlakeRefType::Git {
922                url: "git.somehost.tld/user/path".into(),
923                r#type: UrlType::Https,
924            })
925            .clone();
926        let parsed: FlakeRef = uri.try_into().unwrap();
927        assert_eq!(expected, parsed);
928    }
929    #[test]
930    fn parse_git_and_https_params() {
931        let uri = "git+https://git.somehost.tld/user/path?ref=branch&rev=fdc8ef970de2b4634e1b3dca296e1ed918459a9e";
932        let mut params = FlakeRefParameters::default();
933        params.r#ref(Some("branch".into()));
934        params.rev(Some("fdc8ef970de2b4634e1b3dca296e1ed918459a9e".into()));
935        let expected = FlakeRef::default()
936            .r#type(FlakeRefType::Git {
937                url: "git.somehost.tld/user/path".into(),
938                r#type: UrlType::Https,
939            })
940            .params(params)
941            .clone();
942        let parsed: FlakeRef = uri.try_into().unwrap();
943        assert_eq!(expected, parsed);
944    }
945    #[test]
946    fn parse_git_and_file_params() {
947        let uri = "git+file:///nix/nixpkgs?ref=upstream/nixpkgs-unstable";
948        let mut params = FlakeRefParameters::default();
949        params.r#ref(Some("upstream/nixpkgs-unstable".into()));
950        let expected = FlakeRef::default()
951            .r#type(FlakeRefType::Git {
952                url: "/nix/nixpkgs".into(),
953                r#type: UrlType::File,
954            })
955            .params(params)
956            .clone();
957        let parsed: FlakeRef = uri.try_into().unwrap();
958        assert_eq!(expected, parsed);
959    }
960    #[test]
961    fn parse_git_and_file_simple() {
962        let uri = "git+file:///nix/nixpkgs";
963        let expected = FlakeRef::default()
964            .r#type(FlakeRefType::Git {
965                url: "/nix/nixpkgs".into(),
966                r#type: UrlType::File,
967            })
968            .clone();
969        let parsed: FlakeRef = uri.try_into().unwrap();
970        assert_eq!(expected, parsed);
971    }
972    #[test]
973    // TODO: is this correct?
974    // git+file:/home/user/forked-flake?branch=feat/myNewFeature
975    fn parse_git_and_file_params_alt() {
976        let uri = "git+file:///home/user/forked-flake?branch=feat/myNewFeature";
977        let mut params = FlakeRefParameters::default();
978        params.set_branch(Some("feat/myNewFeature".into()));
979        let expected = FlakeRef::default()
980            .r#type(FlakeRefType::Git {
981                url: "/home/user/forked-flake".into(),
982                r#type: UrlType::File,
983            })
984            .params(params)
985            .clone();
986        let parsed: FlakeRef = uri.try_into().unwrap();
987        assert_eq!(expected, parsed);
988    }
989    #[test]
990    fn parse_github_simple_tag_non_alphabetic_params() {
991        let uri = "github:smunix/MyST-Parser?ref=fix.hls-docutils";
992        let mut params = FlakeRefParameters::default();
993        params.set_ref(Some("fix.hls-docutils".to_owned()));
994        let expected = FlakeRef::default()
995            .r#type(FlakeRefType::GitHub {
996                owner: "smunix".into(),
997                repo: "MyST-Parser".into(),
998                ref_or_rev: None,
999            })
1000            .params(params)
1001            .clone();
1002        let parsed: FlakeRef = uri.try_into().unwrap();
1003        assert_eq!(expected, parsed);
1004    }
1005    #[test]
1006    fn parse_github_simple_tag() {
1007        let uri = "github:cachix/devenv/v0.5";
1008        let mut params = FlakeRefParameters::default();
1009        let expected = FlakeRef::default()
1010            .r#type(FlakeRefType::GitHub {
1011                owner: "cachix".into(),
1012                repo: "devenv".into(),
1013                ref_or_rev: Some("v0.5".into()),
1014            })
1015            .clone();
1016        let parsed: FlakeRef = uri.try_into().unwrap();
1017        assert_eq!(expected, parsed);
1018    }
1019    #[test]
1020    fn parse_git_and_file_params_alt_branch() {
1021        let uri = "git+file:///home/user/forked-flake?branch=feat/myNewFeature";
1022        let mut params = FlakeRefParameters::default();
1023        params.set_branch(Some("feat/myNewFeature".into()));
1024        let expected = FlakeRef::default()
1025            .r#type(FlakeRefType::Git {
1026                url: "/home/user/forked-flake".into(),
1027                r#type: UrlType::File,
1028            })
1029            .params(params)
1030            .clone();
1031        let parsed: FlakeRef = uri.try_into().unwrap();
1032        assert_eq!(expected, parsed);
1033    }
1034    #[test]
1035    fn parse_gitlab_with_host_params_alt() {
1036        let uri = "gitlab:fpottier/menhir/20201216?host=gitlab.inria.fr";
1037        let mut params = FlakeRefParameters::default();
1038        params.set_host(Some("gitlab.inria.fr".into()));
1039        let expected = FlakeRef::default()
1040            .r#type(FlakeRefType::GitLab {
1041                owner: "fpottier".to_owned(),
1042                repo: "menhir".to_owned(),
1043                ref_or_rev: Some("20201216".to_owned()),
1044            })
1045            .params(params)
1046            .clone();
1047        let parsed: FlakeRef = uri.try_into().unwrap();
1048        assert_eq!(expected, parsed);
1049    }
1050    #[test]
1051    fn parse_git_and_https_params_submodules() {
1052        let uri = "git+https://www.github.com/ocaml/ocaml-lsp?submodules=1";
1053        let mut params = FlakeRefParameters::default();
1054        params.set_submodules(Some("1".into()));
1055        let expected = FlakeRef::default()
1056            .r#type(FlakeRefType::Git {
1057                url: "www.github.com/ocaml/ocaml-lsp".to_owned(),
1058                r#type: UrlType::Https,
1059            })
1060            .params(params)
1061            .clone();
1062        let parsed: FlakeRef = uri.try_into().unwrap();
1063        assert_eq!(expected, parsed);
1064    }
1065    #[test]
1066    fn parse_marcurial_and_https_simpe_uri() {
1067        let uri = "hg+https://www.github.com/ocaml/ocaml-lsp";
1068        let mut params = FlakeRefParameters::default();
1069        let expected = FlakeRef::default()
1070            .r#type(FlakeRefType::Mercurial {
1071                url: "www.github.com/ocaml/ocaml-lsp".to_owned(),
1072                r#type: UrlType::Https,
1073            })
1074            .clone();
1075        let parsed: FlakeRef = uri.try_into().unwrap();
1076        assert_eq!(expected, parsed);
1077    }
1078    #[test]
1079    #[should_panic]
1080    fn parse_git_and_https_params_submodules_wrong_type() {
1081        let uri = "gt+https://www.github.com/ocaml/ocaml-lsp?submodules=1";
1082        let mut params = FlakeRefParameters::default();
1083        params.set_submodules(Some("1".into()));
1084        let expected = FlakeRef::default()
1085            .r#type(FlakeRefType::Git {
1086                url: "www.github.com/ocaml/ocaml-lsp".to_owned(),
1087                r#type: UrlType::Https,
1088            })
1089            .params(params)
1090            .clone();
1091        let parsed: FlakeRef = uri.try_into().unwrap();
1092        assert_eq!(expected, parsed);
1093    }
1094    #[test]
1095    fn parse_git_and_file_shallow() {
1096        let uri = "git+file:/path/to/repo?shallow=1";
1097        let mut params = FlakeRefParameters::default();
1098        params.set_shallow(Some("1".into()));
1099        let expected = FlakeRef::default()
1100            .r#type(FlakeRefType::Git {
1101                url: "/path/to/repo".to_owned(),
1102                r#type: UrlType::File,
1103            })
1104            .params(params)
1105            .clone();
1106        let parsed: FlakeRef = uri.try_into().unwrap();
1107        assert_eq!(expected, parsed);
1108    }
1109    // TODO: allow them with an optional cli parser
1110    // #[test]
1111    // fn parse_simple_path_uri_indirect() {
1112    //     let uri = "path:../.";
1113    //     let expected = FlakeRef::default()
1114    //         .r#type(FlakeRefType::Path {
1115    //             path: "../.".to_owned(),
1116    //         })
1117    //         .clone();
1118    //     let parsed: FlakeRef = uri.try_into().unwrap();
1119    //     assert_eq!(expected, parsed);
1120    // }
1121    // TODO: allow them with an optional cli parser
1122    // #[test]
1123    // fn parse_simple_path_uri_indirect_local() {
1124    //     let uri = "path:.";
1125    //     let expected = FlakeRef::default()
1126    //         .r#type(FlakeRefType::Path {
1127    //             path: ".".to_owned(),
1128    //         })
1129    //         .clone();
1130    //     let parsed: FlakeRef = uri.try_into().unwrap();
1131    //     assert_eq!(expected, parsed);
1132    // }
1133    #[test]
1134    fn parse_simple_uri_sourcehut() {
1135        let uri = "sourcehut:~misterio/nix-colors";
1136        let expected = FlakeRef::default()
1137            .r#type(FlakeRefType::Sourcehut {
1138                owner: "~misterio".to_owned(),
1139                repo: "nix-colors".to_owned(),
1140                ref_or_rev: None,
1141            })
1142            .clone();
1143        let parsed: FlakeRef = uri.try_into().unwrap();
1144        assert_eq!(expected, parsed);
1145    }
1146    #[test]
1147    fn parse_simple_uri_sourcehut_rev() {
1148        let uri = "sourcehut:~misterio/nix-colors/main";
1149        let expected = FlakeRef::default()
1150            .r#type(FlakeRefType::Sourcehut {
1151                owner: "~misterio".to_owned(),
1152                repo: "nix-colors".to_owned(),
1153                ref_or_rev: Some("main".to_owned()),
1154            })
1155            .clone();
1156        let parsed: FlakeRef = uri.try_into().unwrap();
1157        assert_eq!(expected, parsed);
1158    }
1159    #[test]
1160    fn parse_simple_uri_sourcehut_host_param() {
1161        let uri = "sourcehut:~misterio/nix-colors?host=git.example.org";
1162        let mut params = FlakeRefParameters::default();
1163        params.set_host(Some("git.example.org".into()));
1164        let expected = FlakeRef::default()
1165            .r#type(FlakeRefType::Sourcehut {
1166                owner: "~misterio".to_owned(),
1167                repo: "nix-colors".to_owned(),
1168                ref_or_rev: None,
1169            })
1170            .params(params)
1171            .clone();
1172        let parsed: FlakeRef = uri.try_into().unwrap();
1173        assert_eq!(expected, parsed);
1174    }
1175    #[test]
1176    fn parse_simple_uri_sourcehut_ref() {
1177        let uri = "sourcehut:~misterio/nix-colors/182b4b8709b8ffe4e9774a4c5d6877bf6bb9a21c";
1178        let expected = FlakeRef::default()
1179            .r#type(FlakeRefType::Sourcehut {
1180                owner: "~misterio".to_owned(),
1181                repo: "nix-colors".to_owned(),
1182                ref_or_rev: Some("182b4b8709b8ffe4e9774a4c5d6877bf6bb9a21c".to_owned()),
1183            })
1184            .clone();
1185        let parsed: FlakeRef = uri.try_into().unwrap();
1186        assert_eq!(expected, parsed);
1187    }
1188    #[test]
1189    fn parse_simple_uri_sourcehut_ref_params() {
1190        let uri =
1191            "sourcehut:~misterio/nix-colors/21c1a380a6915d890d408e9f22203436a35bb2de?host=hg.sr.ht";
1192        let mut params = FlakeRefParameters::default();
1193        params.set_host(Some("hg.sr.ht".into()));
1194        let expected = FlakeRef::default()
1195            .r#type(FlakeRefType::Sourcehut {
1196                owner: "~misterio".to_owned(),
1197                repo: "nix-colors".to_owned(),
1198                ref_or_rev: Some("21c1a380a6915d890d408e9f22203436a35bb2de".to_owned()),
1199            })
1200            .params(params)
1201            .clone();
1202        let parsed: FlakeRef = uri.try_into().unwrap();
1203        assert_eq!(expected, parsed);
1204    }
1205    #[test]
1206    fn display_simple_sourcehut_uri_ref_or_rev() {
1207        let expected = "sourcehut:~misterio/nix-colors/21c1a380a6915d890d408e9f22203436a35bb2de";
1208        let flake_ref = FlakeRef::default()
1209            .r#type(FlakeRefType::Sourcehut {
1210                owner: "~misterio".to_owned(),
1211                repo: "nix-colors".to_owned(),
1212                ref_or_rev: Some("21c1a380a6915d890d408e9f22203436a35bb2de".to_owned()),
1213            })
1214            .to_string();
1215        assert_eq!(expected, flake_ref);
1216    }
1217    #[test]
1218    fn display_simple_sourcehut_uri_ref_or_rev_host_param() {
1219        let expected =
1220            "sourcehut:~misterio/nix-colors/21c1a380a6915d890d408e9f22203436a35bb2de?host=hg.sr.ht";
1221        let mut params = FlakeRefParameters::default();
1222        params.set_host(Some("hg.sr.ht".into()));
1223        let flake_ref = FlakeRef::default()
1224            .r#type(FlakeRefType::Sourcehut {
1225                owner: "~misterio".to_owned(),
1226                repo: "nix-colors".to_owned(),
1227                ref_or_rev: Some("21c1a380a6915d890d408e9f22203436a35bb2de".to_owned()),
1228            })
1229            .params(params)
1230            .to_string();
1231        assert_eq!(expected, flake_ref);
1232    }
1233    #[test]
1234    fn display_simple_github_uri_ref() {
1235        let expected = "github:zellij-org/zellij?ref=main";
1236        let mut flake_attrs = FlakeRefParameters::default();
1237        flake_attrs.r#ref(Some("main".into()));
1238        let flake_ref = FlakeRef::default()
1239            .r#type(FlakeRefType::GitHub {
1240                owner: "zellij-org".into(),
1241                repo: "zellij".into(),
1242                ref_or_rev: None,
1243            })
1244            .params(flake_attrs)
1245            .to_string();
1246        assert_eq!(flake_ref, expected);
1247    }
1248    #[test]
1249    fn display_simple_github_uri_rev() {
1250        let expected = "github:zellij-org/zellij?rev=b2df4e4e80e04cbb33a350f87717f4bd6140d298";
1251        let mut flake_attrs = FlakeRefParameters::default();
1252        flake_attrs.rev(Some("b2df4e4e80e04cbb33a350f87717f4bd6140d298".into()));
1253        let flake_ref = FlakeRef::default()
1254            .r#type(FlakeRefType::GitHub {
1255                owner: "zellij-org".into(),
1256                repo: "zellij".into(),
1257                ref_or_rev: None,
1258            })
1259            .params(flake_attrs)
1260            .to_string();
1261        assert_eq!(flake_ref, expected);
1262    }
1263    #[test]
1264    fn parse_simple_path_uri_indirect_absolute_without_prefix() {
1265        let uri = "/home/kenji/git";
1266        let expected = FlakeRef::default()
1267            .r#type(FlakeRefType::Path {
1268                path: "/home/kenji/git".to_owned(),
1269            })
1270            .clone();
1271        let parsed: FlakeRef = uri.try_into().unwrap();
1272        assert_eq!(expected, parsed);
1273    }
1274    #[test]
1275    fn parse_simple_path_uri_indirect_absolute_without_prefix_with_params() {
1276        let uri = "/home/kenji/git?dir=dev";
1277        let mut params = FlakeRefParameters::default();
1278        params.set_dir(Some("dev".into()));
1279        let expected = FlakeRef::default()
1280            .r#type(FlakeRefType::Path {
1281                path: "/home/kenji/git".to_owned(),
1282            })
1283            .params(params)
1284            .clone();
1285        let parsed: FlakeRef = uri.try_into().unwrap();
1286        assert_eq!(expected, parsed);
1287    }
1288
1289    // TODO: allow them with an optional cli parser
1290    // #[test]
1291    // fn parse_simple_path_uri_indirect_local_without_prefix() {
1292    //     let uri = ".";
1293    //     let expected = FlakeRef::default()
1294    //         .r#type(FlakeRefType::Path {
1295    //             path: ".".to_owned(),
1296    //         })
1297    //         .clone();
1298    //     let parsed: FlakeRef = uri.try_into().unwrap();
1299    //     assert_eq!(expected, parsed);
1300    // }
1301
1302    #[test]
1303    fn parse_wrong_git_uri_extension_type() {
1304        let uri = "git+(:z";
1305        let expected = NixUriError::UnknownUrlType("(".into());
1306        let parsed: NixUriResult<FlakeRef> = uri.try_into();
1307        assert_eq!(expected, parsed.unwrap_err());
1308    }
1309    #[test]
1310    fn parse_github_missing_parameter() {
1311        let uri = "github:";
1312        let expected = NixUriError::MissingTypeParameter("github".into(), ("owner".into()));
1313        let parsed: NixUriResult<FlakeRef> = uri.try_into();
1314        assert_eq!(expected, parsed.unwrap_err());
1315    }
1316    #[test]
1317    fn parse_github_missing_parameter_repo() {
1318        let uri = "github:nixos/";
1319        assert_eq!(
1320            uri.parse::<FlakeRef>(),
1321            Err(NixUriError::MissingTypeParameter(
1322                "github".into(),
1323                ("repo".into())
1324            ))
1325        );
1326    }
1327    #[test]
1328    fn parse_github_starts_with_whitespace() {
1329        let uri = " github:nixos/nixpkgs";
1330        assert_eq!(
1331            uri.parse::<FlakeRef>(),
1332            Err(NixUriError::InvalidUrl(uri.into()))
1333        );
1334    }
1335    #[test]
1336    fn parse_github_ends_with_whitespace() {
1337        let uri = "github:nixos/nixpkgs ";
1338        assert_eq!(
1339            uri.parse::<FlakeRef>(),
1340            Err(NixUriError::InvalidUrl(uri.into()))
1341        );
1342    }
1343    #[test]
1344    fn parse_empty_invalid_url() {
1345        let uri = "";
1346        assert_eq!(
1347            uri.parse::<FlakeRef>(),
1348            Err(NixUriError::InvalidUrl(uri.into()))
1349        );
1350    }
1351    #[test]
1352    fn parse_empty_trim_invalid_url() {
1353        let uri = "  ";
1354        assert_eq!(
1355            uri.parse::<FlakeRef>(),
1356            Err(NixUriError::InvalidUrl(uri.into()))
1357        );
1358    }
1359    #[test]
1360    fn parse_slash_trim_invalid_url() {
1361        let uri = "   /   ";
1362        assert_eq!(
1363            uri.parse::<FlakeRef>(),
1364            Err(NixUriError::InvalidUrl(uri.into()))
1365        );
1366    }
1367    #[test]
1368    fn parse_double_trim_invalid_url() {
1369        let uri = "   :   ";
1370        assert_eq!(
1371            uri.parse::<FlakeRef>(),
1372            Err(NixUriError::InvalidUrl(uri.into()))
1373        );
1374    }
1375
1376    // #[test]
1377    // fn parse_simple_indirect() {
1378    //     let uri = "nixos/nixpkgs";
1379    //     let expected = FlakeRef::default()
1380    //         .r#type(FlakeRefType::Indirect {
1381    //             id: "nixos/nixpkgs".to_owned(),
1382    //             ref_or_rev: None,
1383    //         })
1384    //         .clone();
1385    //     let parsed: FlakeRef = uri.try_into().unwrap();
1386    //     assert_eq!(expected, parsed);
1387    // }
1388
1389    // TODO: indirect uris
1390    // #[test]
1391    // fn parse_simple_tarball() {
1392    //     let uri = "https://hackage.haskell.org/package/lsp-test-0.14.0.3/lsp-test-0.14.0.3.tar.gz";
1393    //     let mut params = FlakeRefParameters::default();
1394    //     let expected = FlakeRef::default()
1395    //         .r#type(FlakeRefType::Tarball {
1396    //             id: "nixpkgs".to_owned(),
1397    //             ref_or_rev: Some("nixos-23.05".to_owned()),
1398    //         })
1399    //         .params(params)
1400    //         .clone();
1401    //     let parsed: FlakeRef = uri.try_into().unwrap();
1402    //     assert_eq!(expected, parsed);
1403    // }
1404}