greentic_types/
component_source.rs1use alloc::string::{String, ToString};
4
5#[cfg(feature = "schemars")]
6use schemars::JsonSchema;
7#[cfg(feature = "serde")]
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
13#[cfg_attr(feature = "schemars", derive(JsonSchema))]
14#[cfg_attr(feature = "serde", serde(try_from = "String", into = "String"))]
15pub enum ComponentSourceRef {
16 Oci(String),
18 Repo(String),
20 Store(String),
22 File(String),
24}
25
26impl ComponentSourceRef {
27 pub fn scheme(&self) -> &'static str {
29 match self {
30 ComponentSourceRef::Oci(_) => "oci",
31 ComponentSourceRef::Repo(_) => "repo",
32 ComponentSourceRef::Store(_) => "store",
33 ComponentSourceRef::File(_) => "file",
34 }
35 }
36
37 pub fn reference(&self) -> &str {
39 match self {
40 ComponentSourceRef::Oci(value) => value,
41 ComponentSourceRef::Repo(value) => value,
42 ComponentSourceRef::Store(value) => value,
43 ComponentSourceRef::File(value) => value,
44 }
45 }
46}
47
48impl core::fmt::Display for ComponentSourceRef {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 write!(f, "{}://{}", self.scheme(), self.reference())
51 }
52}
53
54impl core::str::FromStr for ComponentSourceRef {
55 type Err = ComponentSourceRefError;
56
57 fn from_str(value: &str) -> Result<Self, Self::Err> {
58 if value.is_empty() {
59 return Err(ComponentSourceRefError::EmptyReference);
60 }
61 if value.chars().any(char::is_whitespace) {
62 return Err(ComponentSourceRefError::ContainsWhitespace);
63 }
64 if value.starts_with("oci://") {
65 return parse_with_scheme(value, "oci://").map(ComponentSourceRef::Oci);
66 }
67 if value.starts_with("repo://") {
68 return parse_with_scheme(value, "repo://").map(ComponentSourceRef::Repo);
69 }
70 if value.starts_with("store://") {
71 return parse_with_scheme(value, "store://").map(ComponentSourceRef::Store);
72 }
73 if value.starts_with("file://") {
74 return parse_with_scheme(value, "file://").map(ComponentSourceRef::File);
75 }
76 Err(ComponentSourceRefError::InvalidScheme)
77 }
78}
79
80impl From<ComponentSourceRef> for String {
81 fn from(value: ComponentSourceRef) -> Self {
82 value.to_string()
83 }
84}
85
86impl TryFrom<String> for ComponentSourceRef {
87 type Error = ComponentSourceRefError;
88
89 fn try_from(value: String) -> Result<Self, Self::Error> {
90 value.parse()
91 }
92}
93
94#[derive(Debug, thiserror::Error, PartialEq, Eq)]
96pub enum ComponentSourceRefError {
97 #[error("component source reference cannot be empty")]
99 EmptyReference,
100 #[error("component source reference must not contain whitespace")]
102 ContainsWhitespace,
103 #[error("component source reference must use oci://, repo://, store://, or file://")]
105 InvalidScheme,
106 #[error("component source reference is missing a locator")]
108 MissingLocator,
109}
110
111fn parse_with_scheme(value: &str, scheme: &str) -> Result<String, ComponentSourceRefError> {
112 if let Some(rest) = value.strip_prefix(scheme) {
113 if rest.is_empty() {
114 return Err(ComponentSourceRefError::MissingLocator);
115 }
116 return Ok(rest.to_string());
117 }
118 Err(ComponentSourceRefError::InvalidScheme)
119}