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#[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 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 host: Option<String>,
78 #[serde(rename = "revCount")]
80 rev_count: Option<String>,
81 #[serde(rename = "lastModified")]
83 last_modified: Option<String>,
84 arbitrary: Vec<(String, String)>,
87}
88
89impl 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 }
229 }
230}
231
232#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq)]
233#[non_exhaustive]
234pub enum FlakeRefType {
235 File {
237 url: String,
238 },
239 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 Mercurial {
264 url: String,
265 r#type: UrlType,
266 },
267 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 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 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 let path = Path::new(input);
466 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 if input.starts_with('/') || input == "." {
504 let flake_ref_type = FlakeRefType::Path { path: input.into() };
505 let path = Path::new(input);
506 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 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 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 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 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 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 #[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 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 #[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 #[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 }