Skip to main content

flake_edit/app/commands/
uri.rs

1use nix_uri::FlakeRef;
2
3use crate::change::{Change, ChangeId};
4
5use super::{Error, Result};
6
7/// URI rewriting options that apply to both `add` and `change`.
8///
9/// `no_flake` is *not* part of this shape: it sets the
10/// `inputs.<id>.flake` attribute on the resulting input and is only
11/// meaningful for `add`. It travels separately so a caller cannot
12/// accidentally request it for `change`, where it would be silently
13/// dropped.
14#[derive(Default)]
15pub struct UriOptions<'a> {
16    pub ref_or_rev: Option<&'a str>,
17    pub shallow: bool,
18}
19
20/// Selects which [`Change`] variant [`build_uri_change`] constructs.
21pub(super) enum BuildKind {
22    Add { no_flake: bool },
23    Change,
24}
25
26/// Builds the [`Change::Add`] or [`Change::Change`] requested by the
27/// scripted `id + uri` paths in [`super::add`] and [`super::change`].
28pub(super) fn build_uri_change(
29    kind: BuildKind,
30    id: String,
31    uri: String,
32    opts: &UriOptions<'_>,
33) -> Result<Change> {
34    let final_uri = transform_uri(uri, opts.ref_or_rev, opts.shallow)?;
35    let id = ChangeId::parse(&id).map_err(|source| Error::InvalidInputId { id, source })?;
36    Ok(match kind {
37        BuildKind::Add { no_flake } => Change::Add {
38            id: Some(id),
39            uri: Some(final_uri),
40            flake: !no_flake,
41        },
42        BuildKind::Change => Change::Change {
43            id: Some(id),
44            uri: Some(final_uri),
45        },
46    })
47}
48
49/// Applies `ref_or_rev` and `shallow` to `flake_ref`, leaving every
50/// other field untouched. Kinds that have no ref slot (`Path`) ignore
51/// the `ref_or_rev` value silently.
52pub(super) fn apply_uri_options(
53    flake_ref: FlakeRef,
54    ref_or_rev: Option<&str>,
55    shallow: bool,
56) -> FlakeRef {
57    let mut flake_ref = if let Some(ror) = ref_or_rev {
58        flake_ref.with_ref(Some(ror.to_string()))
59    } else {
60        flake_ref
61    };
62    if shallow {
63        flake_ref.set_shallow(true);
64    }
65    flake_ref
66}
67
68/// Applies `ref_or_rev` and `shallow` to a URI string, returning the
69/// rewritten form.
70///
71/// The URI is always parsed through `nix-uri` so callers get an
72/// early [`Error::InvalidUri`] on malformed input. When neither option
73/// is set the original `uri` is returned verbatim to avoid re-rendering
74/// query parameters the user typed deliberately.
75pub(super) fn transform_uri(
76    uri: String,
77    ref_or_rev: Option<&str>,
78    shallow: bool,
79) -> Result<String> {
80    let flake_ref: FlakeRef = uri.parse().map_err(|source| Error::InvalidUri {
81        uri: uri.clone(),
82        source,
83    })?;
84
85    if ref_or_rev.is_none() && !shallow {
86        return Ok(uri);
87    }
88
89    Ok(apply_uri_options(flake_ref, ref_or_rev, shallow).into_uri())
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn malformed_id_surfaces_as_invalid_input_id() {
98        let opts = UriOptions::default();
99        let err = build_uri_change(
100            BuildKind::Change,
101            "a..b".to_string(),
102            "github:owner/repo".to_string(),
103            &opts,
104        )
105        .expect_err("a malformed id must not build a Change");
106        assert!(
107            matches!(&err, Error::InvalidInputId { id, .. } if id == "a..b"),
108            "expected InvalidInputId for 'a..b', got: {err:?}"
109        );
110    }
111}