1use std::{
2 collections::{HashMap, HashSet},
3 error::Error,
4 fmt::{self, Display},
5 path::PathBuf,
6 str::FromStr,
7};
8
9use anyhow::anyhow;
10use lazy_static::lazy_static;
11use regex::Regex;
12use serde::{
13 de::{self, IntoDeserializer},
14 Deserialize, Deserializer, Serialize,
15};
16use support::constants::{INFAILABLE, SHOULD_COMPILE, THIS_IS_A_BUG};
17use tokio::fs;
18use url::Url;
19
20use super::{errors::ConversionError, resources::Resources};
21
22pub type Duration = u32;
24
25pub type Port = u16;
27
28pub type ParaId = u32;
30
31#[derive(Default, Debug, Clone, PartialEq)]
34pub struct U128(pub(crate) u128);
35
36impl From<u128> for U128 {
37 fn from(value: u128) -> Self {
38 Self(value)
39 }
40}
41
42impl TryFrom<&str> for U128 {
43 type Error = Box<dyn Error>;
44
45 fn try_from(value: &str) -> Result<Self, Self::Error> {
46 Ok(Self(value.to_string().parse::<u128>()?))
47 }
48}
49
50impl Serialize for U128 {
51 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52 where
53 S: serde::Serializer,
54 {
55 serializer.serialize_str(&format!("U128%{}", self.0))
58 }
59}
60
61struct U128Visitor;
62
63impl de::Visitor<'_> for U128Visitor {
64 type Value = U128;
65
66 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
67 formatter.write_str("an integer between 0 and 2^128 − 1.")
68 }
69
70 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
71 where
72 E: de::Error,
73 {
74 v.try_into().map_err(de::Error::custom)
75 }
76}
77
78impl<'de> Deserialize<'de> for U128 {
79 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80 where
81 D: Deserializer<'de>,
82 {
83 deserializer.deserialize_str(U128Visitor)
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103pub struct Chain(String);
104
105impl TryFrom<&str> for Chain {
106 type Error = ConversionError;
107
108 fn try_from(value: &str) -> Result<Self, Self::Error> {
109 if value.contains(char::is_whitespace) {
110 return Err(ConversionError::ContainsWhitespaces(value.to_string()));
111 }
112
113 if value.is_empty() {
114 return Err(ConversionError::CantBeEmpty);
115 }
116
117 Ok(Self(value.to_string()))
118 }
119}
120
121impl Chain {
122 pub fn as_str(&self) -> &str {
123 &self.0
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct Image(String);
146
147impl TryFrom<&str> for Image {
148 type Error = ConversionError;
149
150 fn try_from(value: &str) -> Result<Self, Self::Error> {
151 static IP_PART: &str = "((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))";
152 static HOSTNAME_PART: &str = "((([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]).)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]))";
153 static TAG_NAME_PART: &str = "([a-z0-9](-*[a-z0-9])*)";
154 static TAG_VERSION_PART: &str = "([a-z0-9_]([-._a-z0-9])*)";
155 lazy_static! {
156 static ref RE: Regex = Regex::new(&format!(
157 "^({IP_PART}|{HOSTNAME_PART}/)?{TAG_NAME_PART}(:{TAG_VERSION_PART})?$",
158 ))
159 .expect(&format!("{SHOULD_COMPILE}, {THIS_IS_A_BUG}"));
160 };
161
162 if !RE.is_match(value) {
163 return Err(ConversionError::DoesntMatchRegex {
164 value: value.to_string(),
165 regex: "^([ip]|[hostname]/)?[tag_name]:[tag_version]?$".to_string(),
166 });
167 }
168
169 Ok(Self(value.to_string()))
170 }
171}
172
173impl Image {
174 pub fn as_str(&self) -> &str {
175 &self.0
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193pub struct Command(String);
194
195impl TryFrom<&str> for Command {
196 type Error = ConversionError;
197
198 fn try_from(value: &str) -> Result<Self, Self::Error> {
199 if value.contains(char::is_whitespace) {
200 return Err(ConversionError::ContainsWhitespaces(value.to_string()));
201 }
202
203 Ok(Self(value.to_string()))
204 }
205}
206impl Default for Command {
207 fn default() -> Self {
208 Self(String::from("polkadot"))
209 }
210}
211
212impl Command {
213 pub fn as_str(&self) -> &str {
214 &self.0
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
232pub struct CommandWithCustomArgs(Command, Vec<Arg>);
233
234impl TryFrom<&str> for CommandWithCustomArgs {
235 type Error = ConversionError;
236
237 fn try_from(value: &str) -> Result<Self, Self::Error> {
238 if value.is_empty() {
239 return Err(ConversionError::CantBeEmpty);
240 }
241
242 let mut parts = value.split_whitespace().collect::<Vec<&str>>();
243 let cmd = parts.remove(0).try_into().unwrap();
244 let args = parts
245 .iter()
246 .map(|x| {
247 Arg::deserialize(x.into_deserializer()).map_err(|_: serde_json::Error| {
248 ConversionError::DeserializeError(String::from(*x))
249 })
250 })
251 .collect::<Result<Vec<Arg>, _>>()?;
252
253 Ok(Self(cmd, args))
254 }
255}
256impl Default for CommandWithCustomArgs {
257 fn default() -> Self {
258 Self("polkadot".try_into().unwrap(), vec![])
259 }
260}
261
262impl CommandWithCustomArgs {
263 pub fn cmd(&self) -> &Command {
264 &self.0
265 }
266
267 pub fn args(&self) -> &Vec<Arg> {
268 &self.1
269 }
270}
271
272#[derive(Debug, Clone, PartialEq)]
292pub enum AssetLocation {
293 Url(Url),
294 FilePath(PathBuf),
295}
296
297impl From<Url> for AssetLocation {
298 fn from(value: Url) -> Self {
299 Self::Url(value)
300 }
301}
302
303impl From<PathBuf> for AssetLocation {
304 fn from(value: PathBuf) -> Self {
305 Self::FilePath(value)
306 }
307}
308
309impl From<&str> for AssetLocation {
310 fn from(value: &str) -> Self {
311 if let Ok(parsed_url) = Url::parse(value) {
312 return Self::Url(parsed_url);
313 }
314
315 Self::FilePath(PathBuf::from_str(value).expect(&format!("{INFAILABLE}, {THIS_IS_A_BUG}")))
316 }
317}
318
319impl Display for AssetLocation {
320 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
321 match self {
322 AssetLocation::Url(value) => write!(f, "{}", value.as_str()),
323 AssetLocation::FilePath(value) => write!(f, "{}", value.display()),
324 }
325 }
326}
327
328impl AssetLocation {
329 pub async fn get_asset(&self) -> Result<Vec<u8>, anyhow::Error> {
331 let contents = match self {
332 AssetLocation::Url(location) => {
333 let res = reqwest::get(location.as_ref()).await.map_err(|err| {
334 anyhow!("Error dowinloding asset from url {location} - {err}")
335 })?;
336
337 res.bytes().await.unwrap().into()
338 },
339 AssetLocation::FilePath(filepath) => {
340 tokio::fs::read(filepath).await.map_err(|err| {
341 anyhow!(
342 "Error reading asset from path {} - {}",
343 filepath.to_string_lossy(),
344 err
345 )
346 })?
347 },
348 };
349
350 Ok(contents)
351 }
352
353 pub async fn dump_asset(&self, dst_path: impl Into<PathBuf>) -> Result<(), anyhow::Error> {
355 let contents = self.get_asset().await?;
356 fs::write(dst_path.into(), contents).await?;
357 Ok(())
358 }
359
360 pub fn extract_name(&self) -> String {
362 match self {
363 AssetLocation::Url(url) => {
364 if let Some(mut segment) = url.path_segments() {
365 let last = segment.next_back().unwrap_or(url.as_str());
366 last.to_string()
367 } else {
368 url.as_str().to_string()
369 }
370 },
371 AssetLocation::FilePath(path_buf) => {
372 let name = path_buf.file_name().unwrap_or(path_buf.as_os_str());
373 name.to_str()
374 .expect("file path should be valid")
375 .to_string()
376 },
377 }
378 }
379}
380
381impl Serialize for AssetLocation {
382 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
383 where
384 S: serde::Serializer,
385 {
386 serializer.serialize_str(&self.to_string())
387 }
388}
389
390struct AssetLocationVisitor;
391
392impl de::Visitor<'_> for AssetLocationVisitor {
393 type Value = AssetLocation;
394
395 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
396 formatter.write_str("a string")
397 }
398
399 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
400 where
401 E: de::Error,
402 {
403 Ok(AssetLocation::from(v))
404 }
405}
406
407impl<'de> Deserialize<'de> for AssetLocation {
408 fn deserialize<D>(deserializer: D) -> Result<AssetLocation, D::Error>
409 where
410 D: Deserializer<'de>,
411 {
412 deserializer.deserialize_any(AssetLocationVisitor)
413 }
414}
415
416#[derive(Debug, Clone, PartialEq)]
435pub enum Arg {
436 Flag(String),
437 Option(String, String),
438 Array(String, Vec<String>),
439 Positional(String),
440}
441
442impl From<&str> for Arg {
443 fn from(flag: &str) -> Self {
444 Self::Flag(flag.to_owned())
445 }
446}
447
448impl From<(&str, &str)> for Arg {
449 fn from((option, value): (&str, &str)) -> Self {
450 Self::Option(option.to_owned(), value.to_owned())
451 }
452}
453
454impl<T> From<(&str, &[T])> for Arg
455where
456 T: AsRef<str> + Clone,
457{
458 fn from((option, values): (&str, &[T])) -> Self {
459 Self::Array(
460 option.to_owned(),
461 values.iter().map(|v| v.as_ref().to_string()).collect(),
462 )
463 }
464}
465
466impl<T> From<(&str, Vec<T>)> for Arg
467where
468 T: AsRef<str>,
469{
470 fn from((option, values): (&str, Vec<T>)) -> Self {
471 Self::Array(
472 option.to_owned(),
473 values.into_iter().map(|v| v.as_ref().to_string()).collect(),
474 )
475 }
476}
477
478impl Arg {
479 pub fn to_vec(&self) -> Vec<String> {
481 match self {
482 Arg::Flag(arg) => vec![arg.to_string()],
483 Arg::Option(k, v) => vec![k.to_string(), v.to_string()],
484 Arg::Array(k, items) => [
485 vec![k.to_string()],
486 items.iter().map(|x| x.to_string()).collect::<Vec<String>>(),
487 ]
488 .concat(),
489 Arg::Positional(value) => vec![value.to_string()],
490 }
491 }
492}
493impl Serialize for Arg {
494 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
495 where
496 S: serde::Serializer,
497 {
498 match self {
499 Arg::Flag(value) => serializer.serialize_str(value),
500 Arg::Option(option, value) => serializer.serialize_str(&format!("{option}={value}")),
501 Arg::Array(option, values) => {
502 serializer.serialize_str(&format!("{}=[{}]", option, values.join(",")))
503 },
504 Arg::Positional(value) => serializer.serialize_str(value),
505 }
506 }
507}
508
509struct ArgVisitor;
510
511impl de::Visitor<'_> for ArgVisitor {
512 type Value = Arg;
513
514 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
515 formatter.write_str("a string")
516 }
517
518 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
519 where
520 E: de::Error,
521 {
522 if v.starts_with("-l") || v.starts_with("-log") {
525 return Ok(Arg::Flag(v.to_string()));
526 }
527 if v.starts_with("-:") {
529 return Ok(Arg::Flag(v.to_string()));
530 }
531
532 let re = Regex::new("^(?<name_prefix>(?<prefix>-{1,2})?(?<name>[a-zA-Z]+(-[a-zA-Z]+)*))((?<separator>=| )(?<value>\\[[^\\]]*\\]|[^ ]+))?$").unwrap();
533
534 let captures = re.captures(v);
535 if let Some(captures) = captures {
536 if let Some(value) = captures.name("value") {
537 let name_prefix = captures
538 .name("name_prefix")
539 .expect("BUG: name_prefix capture group missing")
540 .as_str()
541 .to_string();
542
543 let val = value.as_str();
544 if val.starts_with('[') && val.ends_with(']') {
545 let inner = &val[1..val.len() - 1];
547 let items: Vec<String> = inner
548 .split(',')
549 .map(|s| s.trim().to_string())
550 .filter(|s| !s.is_empty())
551 .collect();
552 return Ok(Arg::Array(name_prefix, items));
553 } else {
554 return Ok(Arg::Option(name_prefix, val.to_string()));
555 }
556 }
557 if let Some(name_prefix) = captures.name("name_prefix") {
558 return Ok(Arg::Flag(name_prefix.as_str().to_string()));
559 }
560 }
561
562 Ok(Arg::Positional(v.to_string()))
564 }
565}
566
567impl<'de> Deserialize<'de> for Arg {
568 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
569 where
570 D: Deserializer<'de>,
571 {
572 deserializer.deserialize_any(ArgVisitor)
573 }
574}
575
576#[derive(Debug, Default, Clone)]
577pub struct ValidationContext {
578 pub used_ports: Vec<Port>,
579 pub used_nodes_names: HashSet<String>,
580 pub used_para_ids: HashMap<ParaId, u8>,
582}
583
584#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
585pub struct ChainDefaultContext {
586 pub(crate) default_command: Option<Command>,
587 pub(crate) default_image: Option<Image>,
588 pub(crate) default_resources: Option<Resources>,
589 pub(crate) default_db_snapshot: Option<AssetLocation>,
590 #[serde(default)]
591 pub(crate) default_args: Vec<Arg>,
592}
593
594#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
597pub struct ChainSpecRuntime {
598 pub location: AssetLocation,
599 pub preset: Option<String>,
600}
601
602impl ChainSpecRuntime {
603 pub fn new(location: AssetLocation) -> Self {
604 ChainSpecRuntime {
605 location,
606 preset: None,
607 }
608 }
609
610 pub fn with_preset(location: AssetLocation, preset: impl Into<String>) -> Self {
611 ChainSpecRuntime {
612 location,
613 preset: Some(preset.into()),
614 }
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
623#[serde(untagged)]
624pub enum JsonOverrides {
625 Location(AssetLocation),
627 Json(serde_json::Value),
629}
630
631impl From<AssetLocation> for JsonOverrides {
632 fn from(value: AssetLocation) -> Self {
633 Self::Location(value)
634 }
635}
636
637impl From<serde_json::Value> for JsonOverrides {
638 fn from(value: serde_json::Value) -> Self {
639 Self::Json(value)
640 }
641}
642
643impl From<&str> for JsonOverrides {
644 fn from(value: &str) -> Self {
645 Self::Location(AssetLocation::from(value))
646 }
647}
648
649impl Display for JsonOverrides {
650 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
651 match self {
652 JsonOverrides::Location(location) => write!(f, "{location}"),
653 JsonOverrides::Json(json) => write!(f, "{json}"),
654 }
655 }
656}
657
658impl JsonOverrides {
659 pub async fn get(&self) -> Result<serde_json::Value, anyhow::Error> {
660 let contents = match self {
661 Self::Location(location) => serde_json::from_slice(&location.get_asset().await?)
662 .map_err(|err| anyhow!("Error converting asset to json {location} - {err}")),
663 Self::Json(json) => Ok(json.clone()),
664 };
665
666 contents
667 }
668}
669
670#[cfg(test)]
671mod tests {
672 use super::*;
673
674 #[test]
675 fn test_arg_flag_roundtrip() {
676 let arg = Arg::from("verbose");
677 let serialized = serde_json::to_string(&arg).unwrap();
678 let deserialized: Arg = serde_json::from_str(&serialized).unwrap();
679 assert_eq!(arg, deserialized);
680 }
681
682 #[test]
683 fn test_urls_as_arg() {
684 let arg = Arg::from("ws://127.0.0.1:10000");
685 assert_eq!(Arg::Flag(String::from("ws://127.0.0.1:10000")), arg);
686 }
687 #[test]
688 fn test_script_as_arg() {
689 let arg = Arg::from("scripts/assign-cores.sh");
690 assert_eq!(Arg::Flag(String::from("scripts/assign-cores.sh")), arg);
691 }
692
693 #[test]
694 fn test_arg_option_roundtrip() {
695 let arg = Arg::from(("mode", "fast"));
696 let serialized = serde_json::to_string(&arg).unwrap();
697 let deserialized: Arg = serde_json::from_str(&serialized).unwrap();
698 assert_eq!(arg, deserialized);
699 }
700
701 #[test]
702 fn test_arg_array_roundtrip() {
703 let arg = Arg::from(("items", ["a", "b", "c"].as_slice()));
704
705 let serialized = serde_json::to_string(&arg).unwrap();
706 println!("serialized = {serialized}");
707 let deserialized: Arg = serde_json::from_str(&serialized).unwrap();
708 assert_eq!(arg, deserialized);
709 }
710
711 #[test]
712 fn test_arg_option_valid_input() {
713 let expected = Arg::from(("--foo", "bar"));
714
715 let valid = "\"--foo=bar\"";
717 let result: Result<Arg, _> = serde_json::from_str(valid);
718 assert_eq!(result.unwrap(), expected);
719
720 let valid = "\"--foo bar\"";
722 let result: Result<Arg, _> = serde_json::from_str(valid);
723 assert_eq!(result.unwrap(), expected);
724
725 let expected = Arg::from(("--foo", "bar=baz"));
727 let valid = "\"--foo=bar=baz\"";
728 let result: Result<Arg, _> = serde_json::from_str(valid);
729 assert_eq!(result.unwrap(), expected);
730 }
731
732 #[test]
733 fn test_arg_array_valid_input() {
734 let expected = Arg::from(("--foo", vec!["bar", "baz"]));
735
736 let valid = "\"--foo=[bar,baz]\"";
738 let result: Result<Arg, _> = serde_json::from_str(valid);
739 assert_eq!(result.unwrap(), expected);
740
741 let valid = "\"--foo [bar,baz]\"";
743 let result: Result<Arg, _> = serde_json::from_str(valid);
744 assert_eq!(result.unwrap(), expected);
745
746 let valid = "\"--foo [bar , baz]\"";
748 let result: Result<Arg, _> = serde_json::from_str(valid);
749 assert_eq!(result.unwrap(), expected);
750
751 let expected = Arg::from(("--foo", Vec::<&str>::new()));
753 let valid = "\"--foo []\"";
754 let result: Result<Arg, _> = serde_json::from_str(valid);
755 assert_eq!(result.unwrap(), expected);
756 }
757
758 #[test]
759 fn test_arg_positional_input() {
760 let input = "\"--foo[bar]\"";
764 let result: Result<Arg, _> = serde_json::from_str(input);
765 assert_eq!(result.unwrap(), Arg::Positional("--foo[bar]".to_string()));
766
767 let input = "\"--foo=bar baz\"";
769 let result: Result<Arg, _> = serde_json::from_str(input);
770 assert_eq!(
771 result.unwrap(),
772 Arg::Positional("--foo=bar baz".to_string())
773 );
774 }
775
776 #[test]
777 fn test_arg_positional_valid_input() {
778 let expected = Arg::Positional("scripts/assign-cores.sh".to_string());
780 let valid = "\"scripts/assign-cores.sh\"";
781 let result: Result<Arg, _> = serde_json::from_str(valid);
782 assert_eq!(result.unwrap(), expected);
783
784 let expected = Arg::Positional("ws://127.0.0.1:10000".to_string());
786 let valid = "\"ws://127.0.0.1:10000\"";
787 let result: Result<Arg, _> = serde_json::from_str(valid);
788 assert_eq!(result.unwrap(), expected);
789
790 let expected = Arg::Positional("42".to_string());
792 let valid = "\"42\"";
793 let result: Result<Arg, _> = serde_json::from_str(valid);
794 assert_eq!(result.unwrap(), expected);
795 }
796
797 #[test]
798 fn test_arg_positional_roundtrip() {
799 let arg = Arg::Positional("script.sh".to_string());
801 let serialized = serde_json::to_string(&arg).unwrap();
802 assert_eq!(serialized, "\"script.sh\"");
803 let deserialized: Arg = serde_json::from_str(&serialized).unwrap();
804 assert_eq!(arg, deserialized);
805 }
806
807 #[test]
808 fn test_arg_positional_to_vec() {
809 let arg = Arg::Positional("scripts/test.sh".to_string());
810 assert_eq!(arg.to_vec(), vec!["scripts/test.sh".to_string()]);
811 }
812
813 #[test]
814 fn converting_a_str_without_whitespaces_into_a_chain_should_succeeds() {
815 let got: Result<Chain, ConversionError> = "mychain".try_into();
816
817 assert_eq!(got.unwrap().as_str(), "mychain");
818 }
819
820 #[test]
821 fn converting_a_str_containing_tag_name_into_an_image_should_succeeds() {
822 let got: Result<Image, ConversionError> = "myimage".try_into();
823
824 assert_eq!(got.unwrap().as_str(), "myimage");
825 }
826
827 #[test]
828 fn converting_a_str_containing_tag_name_and_tag_version_into_an_image_should_succeeds() {
829 let got: Result<Image, ConversionError> = "myimage:version".try_into();
830
831 assert_eq!(got.unwrap().as_str(), "myimage:version");
832 }
833
834 #[test]
835 fn converting_a_str_containing_hostname_and_tag_name_into_an_image_should_succeeds() {
836 let got: Result<Image, ConversionError> = "myrepository.com/myimage".try_into();
837
838 assert_eq!(got.unwrap().as_str(), "myrepository.com/myimage");
839 }
840
841 #[test]
842 fn converting_a_str_containing_hostname_tag_name_and_tag_version_into_an_image_should_succeeds()
843 {
844 let got: Result<Image, ConversionError> = "myrepository.com/myimage:version".try_into();
845
846 assert_eq!(got.unwrap().as_str(), "myrepository.com/myimage:version");
847 }
848
849 #[test]
850 fn converting_a_str_containing_ip_and_tag_name_into_an_image_should_succeeds() {
851 let got: Result<Image, ConversionError> = "myrepository.com/myimage".try_into();
852
853 assert_eq!(got.unwrap().as_str(), "myrepository.com/myimage");
854 }
855
856 #[test]
857 fn converting_a_str_containing_ip_tag_name_and_tag_version_into_an_image_should_succeeds() {
858 let got: Result<Image, ConversionError> = "127.0.0.1/myimage:version".try_into();
859
860 assert_eq!(got.unwrap().as_str(), "127.0.0.1/myimage:version");
861 }
862
863 #[test]
864 fn converting_a_str_without_whitespaces_into_a_command_should_succeeds() {
865 let got: Result<Command, ConversionError> = "mycommand".try_into();
866
867 assert_eq!(got.unwrap().as_str(), "mycommand");
868 }
869
870 #[test]
871 fn converting_an_url_into_an_asset_location_should_succeeds() {
872 let url = Url::from_str("https://mycloudstorage.com/path/to/my/file.tgz").unwrap();
873 let got: AssetLocation = url.clone().into();
874
875 assert!(matches!(got, AssetLocation::Url(value) if value == url));
876 }
877
878 #[test]
879 fn converting_a_pathbuf_into_an_asset_location_should_succeeds() {
880 let pathbuf = PathBuf::from_str("/tmp/path/to/my/file").unwrap();
881 let got: AssetLocation = pathbuf.clone().into();
882
883 assert!(matches!(got, AssetLocation::FilePath(value) if value == pathbuf));
884 }
885
886 #[test]
887 fn converting_a_str_into_an_url_asset_location_should_succeeds() {
888 let url = "https://mycloudstorage.com/path/to/my/file.tgz";
889 let got: AssetLocation = url.into();
890
891 assert!(matches!(got, AssetLocation::Url(value) if value == Url::from_str(url).unwrap()));
892 }
893
894 #[test]
895 fn converting_a_str_into_an_filepath_asset_location_should_succeeds() {
896 let filepath = "/tmp/path/to/my/file";
897 let got: AssetLocation = filepath.into();
898
899 assert!(matches!(
900 got,
901 AssetLocation::FilePath(value) if value == PathBuf::from_str(filepath).unwrap()
902 ));
903 }
904
905 #[test]
906 fn converting_a_str_into_an_flag_arg_should_succeeds() {
907 let got: Arg = "myflag".into();
908
909 assert!(matches!(got, Arg::Flag(flag) if flag == "myflag"));
910 }
911
912 #[test]
913 fn converting_a_str_tuple_into_an_option_arg_should_succeeds() {
914 let got: Arg = ("name", "value").into();
915
916 assert!(matches!(got, Arg::Option(name, value) if name == "name" && value == "value"));
917 }
918
919 #[test]
920 fn converting_a_str_with_whitespaces_into_a_chain_should_fails() {
921 let got: Result<Chain, ConversionError> = "my chain".try_into();
922
923 assert!(matches!(
924 got.clone().unwrap_err(),
925 ConversionError::ContainsWhitespaces(_)
926 ));
927 assert_eq!(
928 got.unwrap_err().to_string(),
929 "'my chain' shouldn't contains whitespace"
930 );
931 }
932
933 #[test]
934 fn converting_an_empty_str_into_a_chain_should_fails() {
935 let got: Result<Chain, ConversionError> = "".try_into();
936
937 assert!(matches!(
938 got.clone().unwrap_err(),
939 ConversionError::CantBeEmpty
940 ));
941 assert_eq!(got.unwrap_err().to_string(), "can't be empty");
942 }
943
944 #[test]
945 fn converting_a_str_containing_only_ip_into_an_image_should_fails() {
946 let got: Result<Image, ConversionError> = "127.0.0.1".try_into();
947
948 assert!(matches!(
949 got.clone().unwrap_err(),
950 ConversionError::DoesntMatchRegex { value: _, regex: _ }
951 ));
952 assert_eq!(
953 got.unwrap_err().to_string(),
954 "'127.0.0.1' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'"
955 );
956 }
957
958 #[test]
959 fn converting_a_str_containing_only_ip_and_tag_version_into_an_image_should_fails() {
960 let got: Result<Image, ConversionError> = "127.0.0.1:version".try_into();
961
962 assert!(matches!(
963 got.clone().unwrap_err(),
964 ConversionError::DoesntMatchRegex { value: _, regex: _ }
965 ));
966 assert_eq!(got.unwrap_err().to_string(), "'127.0.0.1:version' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'");
967 }
968
969 #[test]
970 fn converting_a_str_containing_only_hostname_into_an_image_should_fails() {
971 let got: Result<Image, ConversionError> = "myrepository.com".try_into();
972
973 assert!(matches!(
974 got.clone().unwrap_err(),
975 ConversionError::DoesntMatchRegex { value: _, regex: _ }
976 ));
977 assert_eq!(got.unwrap_err().to_string(), "'myrepository.com' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'");
978 }
979
980 #[test]
981 fn converting_a_str_containing_only_hostname_and_tag_version_into_an_image_should_fails() {
982 let got: Result<Image, ConversionError> = "myrepository.com:version".try_into();
983
984 assert!(matches!(
985 got.clone().unwrap_err(),
986 ConversionError::DoesntMatchRegex { value: _, regex: _ }
987 ));
988 assert_eq!(got.unwrap_err().to_string(), "'myrepository.com:version' doesn't match regex '^([ip]|[hostname]/)?[tag_name]:[tag_version]?$'");
989 }
990
991 #[test]
992 fn converting_a_str_with_whitespaces_into_a_command_should_fails() {
993 let got: Result<Command, ConversionError> = "my command".try_into();
994
995 assert!(matches!(
996 got.clone().unwrap_err(),
997 ConversionError::ContainsWhitespaces(_)
998 ));
999 assert_eq!(
1000 got.unwrap_err().to_string(),
1001 "'my command' shouldn't contains whitespace"
1002 );
1003 }
1004
1005 #[test]
1006 fn test_convert_to_json_overrides() {
1007 let url: AssetLocation = "https://example.com/overrides.json".into();
1008 assert!(matches!(
1009 url.into(),
1010 JsonOverrides::Location(AssetLocation::Url(_))
1011 ));
1012
1013 let path: AssetLocation = "/path/to/overrides.json".into();
1014 assert!(matches!(
1015 path.into(),
1016 JsonOverrides::Location(AssetLocation::FilePath(_))
1017 ));
1018
1019 let inline = serde_json::json!({ "para_id": 2000});
1020 assert!(matches!(
1021 inline.into(),
1022 JsonOverrides::Json(serde_json::Value::Object(_))
1023 ));
1024 }
1025}