Skip to main content

nix_uri/flakeref/
resource_url.rs

1use std::fmt::Display;
2
3use serde::{Deserialize, Serialize};
4use winnow::{
5    ModalResult, Parser,
6    combinator::{alt, opt},
7    error::StrContext,
8    token::take_till,
9};
10
11use crate::{error::tag, parser::parse_sep};
12
13use super::{RefLocation, TransportLayer};
14
15/// A resource-style flake reference (`git+https://...`, `hg+ssh://...`,
16/// `file+https://...`, `tarball+https://...`).
17///
18/// Like [`super::GitForge`] and [`super::FlakeRefType::Indirect`], `ref_` and
19/// `rev` are typed slots fed from `?ref=` / `?rev=` query parameters at parse
20/// time. `ref_location` records where a present value would be rendered on
21/// `Display` so round-trips preserve the form. Resource only supports the
22/// query-parameter form (not the path-component form `GitForge`/`Indirect`
23/// have); `RefLocation::PathComponent` is the slot's default and the parser
24/// flips to `QueryParameter` whenever it routes a query-string value here.
25///
26/// `#[non_exhaustive]` reserves room for future fields without breaking
27/// downstream match arms; in-crate construction with struct-literal syntax
28/// stays allowed.
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
30#[non_exhaustive]
31pub struct ResourceUrl {
32    pub res_type: ResourceType,
33    pub location: String,
34    pub transport_type: Option<TransportLayer>,
35    pub ref_: Option<String>,
36    pub rev: Option<String>,
37    pub ref_location: RefLocation,
38}
39
40impl ResourceUrl {
41    /// Construct a `ResourceUrl` with no ref or rev set; the ref/rev slots
42    /// land via the parser's query-string side-channel
43    /// (`crate::parser::apply_param_ref_rev`) once parsing completes.
44    pub fn new(
45        res_type: ResourceType,
46        location: String,
47        transport_type: Option<TransportLayer>,
48    ) -> Self {
49        Self {
50            res_type,
51            location,
52            transport_type,
53            ref_: None,
54            rev: None,
55            ref_location: RefLocation::PathComponent,
56        }
57    }
58
59    #[allow(dead_code)]
60    pub(crate) fn parse(input: &mut &str) -> ModalResult<Self> {
61        let res_type = ResourceType::parse(input)?;
62        let transport_type = opt(TransportLayer::plus_parse).parse_next(input)?;
63        let _ = parse_sep(input)?;
64        let location = take_till(0.., |c| c == '#' || c == '?')
65            .context(StrContext::Label("url location"))
66            .parse_next(input)?;
67        Ok(Self::new(res_type, location.to_string(), transport_type))
68    }
69}
70
71/// The resource flavour of a [`ResourceUrl`]: which canonical Nix scheme
72/// (`git+`, `hg+`, `file+`, `tarball+`) the URL belongs to. Used to pick
73/// the leading scheme token on `Display`.
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[non_exhaustive]
76pub enum ResourceType {
77    Git,
78    Mercurial,
79    File,
80    Tarball,
81}
82
83impl ResourceType {
84    #[allow(dead_code)]
85    pub(crate) fn parse(input: &mut &str) -> ModalResult<Self> {
86        alt((
87            tag("git").value(Self::Git),
88            tag("hg").value(Self::Mercurial),
89            tag("file").value(Self::File),
90            tag("tarball").value(Self::Tarball),
91        ))
92        .context(StrContext::Label("resource selection"))
93        .parse_next(input)
94    }
95}
96
97impl Display for ResourceType {
98    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99        let out_str = match self {
100            Self::Git => "git",
101            Self::Mercurial => "hg",
102            Self::File => "file",
103            Self::Tarball => "tarball",
104        };
105        write!(f, "{}", out_str)
106    }
107}
108
109#[cfg(test)]
110mod res_url {
111    use cool_asserts::assert_matches;
112
113    use super::*;
114
115    #[test]
116    fn git() {
117        let url = "gitfoobar";
118        let (rest, parsed) = ResourceType::parse.parse_peek(url).unwrap();
119        let expected = ResourceType::Git;
120        assert_eq!(expected, parsed);
121        assert_eq!("foobar", rest);
122    }
123
124    #[test]
125    fn unknown_resource_scheme_routes_to_uri_type_unsupported() {
126        use crate::{NixUriError, error::UnsupportedReason, parser::parse_nix_uri};
127
128        assert_matches!(
129            parse_nix_uri("gat://x"),
130            Err(NixUriError::Unsupported(UnsupportedReason::UriType { ty }))
131                => assert_eq!(ty, "gat")
132        );
133    }
134}