1use crate::{ArtifactKind, ArtifactReference, ArtifactReferenceError};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(deny_unknown_fields)]
7pub struct MediaReference {
8 pub uri: ArtifactUri,
9}
10
11impl MediaReference {
12 pub fn reference(
13 &self,
14 kind: ArtifactKind,
15 ) -> Result<ArtifactReference, ArtifactReferenceError> {
16 let reference = ArtifactReference::parse(&self.uri)?;
17 if reference.metadata().kind() != kind {
18 return Err(super::invalid(
19 "media artifact kind does not match the field type",
20 ));
21 }
22 Ok(reference)
23 }
24
25 pub fn validate(
26 &self,
27 tenant: &str,
28 scope: &str,
29 kind: ArtifactKind,
30 ) -> Result<ArtifactReference, ArtifactReferenceError> {
31 let reference = self.reference(kind)?;
32 reference.ensure_scope(tenant, scope)?;
33 Ok(reference)
34 }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase", deny_unknown_fields)]
40pub struct ImportArtifactRequest {
41 #[serde(default)]
42 pub lifecycle: crate::ArtifactLifecycle,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub creation_id: Option<String>,
45 pub kind: ArtifactKind,
46 pub source_url: String,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub mime_type: Option<String>,
49}
50
51#[cfg(test)]
52mod tests {
53 use super::*;
54 #[test]
55 fn media_rejects_legacy_fields_and_wrong_scope_or_kind() {
56 for value in [
57 serde_json::json!({"url":"https://example.com/a"}),
58 serde_json::json!({"uri":"x","base64":"eA=="}),
59 serde_json::json!({"uri":"x","mimeType":"image/png"}),
60 serde_json::json!({"uri":"x","createdAt":1}),
61 ] {
62 assert!(serde_json::from_value::<MediaReference>(value).is_err());
63 }
64 let reference = ArtifactReference::new(
65 "tenant",
66 "scope",
67 "a".repeat(64),
68 crate::ArtifactMetadata::audio("audio/mpeg", 10, None).unwrap(),
69 )
70 .unwrap();
71 let media = MediaReference {
72 uri: reference.uri().unwrap().parse().unwrap(),
73 };
74 assert!(media
75 .validate("tenant", "scope", ArtifactKind::Audio)
76 .is_ok());
77 assert!(media
78 .validate("tenant", "other", ArtifactKind::Audio)
79 .is_err());
80 assert!(media
81 .validate("tenant", "scope", ArtifactKind::Image)
82 .is_err());
83 assert!(serde_json::from_value::<MediaReference>(serde_json::json!({
84 "uri": "https://example.com/a"
85 }))
86 .is_err());
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[serde(try_from = "String", into = "String")]
93pub struct ArtifactUri(String);
94
95impl ArtifactUri {
96 pub fn as_str(&self) -> &str {
97 &self.0
98 }
99 pub fn reference(&self) -> ArtifactReference {
100 ArtifactReference::parse(&self.0).expect("ArtifactUri was validated at construction")
101 }
102}
103impl std::str::FromStr for ArtifactUri {
104 type Err = ArtifactReferenceError;
105 fn from_str(value: &str) -> Result<Self, Self::Err> {
106 let reference = ArtifactReference::parse(value)?;
107 Ok(Self(reference.uri()?))
108 }
109}
110impl TryFrom<String> for ArtifactUri {
111 type Error = ArtifactReferenceError;
112 fn try_from(value: String) -> Result<Self, Self::Error> {
113 value.parse()
114 }
115}
116impl From<ArtifactUri> for String {
117 fn from(value: ArtifactUri) -> String {
118 value.0
119 }
120}
121impl std::ops::Deref for ArtifactUri {
122 type Target = str;
123 fn deref(&self) -> &str {
124 self.as_str()
125 }
126}
127impl AsRef<str> for ArtifactUri {
128 fn as_ref(&self) -> &str {
129 self.as_str()
130 }
131}
132impl std::fmt::Display for ArtifactUri {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 self.0.fmt(f)
135 }
136}