Skip to main content

artifact_api/
media.rs

1//! Media values refer only to committed, scope-owned artifacts.
2use 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: String,
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/// Explicit import of a finite HTTP resource. Scope comes from authenticated context.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase", deny_unknown_fields)]
40pub struct ImportArtifactRequest {
41    pub kind: ArtifactKind,
42    pub source_url: String,
43    #[serde(default, skip_serializing_if = "Option::is_none")]
44    pub transform: Option<crate::ImageTransform>,
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub mime_type: Option<String>,
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    #[test]
53    fn media_rejects_legacy_fields_and_wrong_scope_or_kind() {
54        for value in [
55            serde_json::json!({"url":"https://example.com/a"}),
56            serde_json::json!({"uri":"x","base64":"eA=="}),
57            serde_json::json!({"uri":"x","mimeType":"image/png"}),
58            serde_json::json!({"uri":"x","createdAt":1}),
59        ] {
60            assert!(serde_json::from_value::<MediaReference>(value).is_err());
61        }
62        let reference = ArtifactReference::new(
63            "tenant",
64            "scope",
65            "a".repeat(64),
66            crate::ArtifactMetadata::audio("audio/mpeg", 10, None).unwrap(),
67        )
68        .unwrap();
69        let media = MediaReference {
70            uri: reference.uri().unwrap(),
71        };
72        assert!(media
73            .validate("tenant", "scope", ArtifactKind::Audio)
74            .is_ok());
75        assert!(media
76            .validate("tenant", "other", ArtifactKind::Audio)
77            .is_err());
78        assert!(media
79            .validate("tenant", "scope", ArtifactKind::Image)
80            .is_err());
81        assert!(MediaReference {
82            uri: "https://example.com/a".into()
83        }
84        .reference(ArtifactKind::Audio)
85        .is_err());
86    }
87}