Skip to main content

ferrin_spec/shared/
file_data.rs

1//! File payloads shared by prompts, results and service interfaces.
2
3use bytes::Bytes;
4use serde::Deserialize;
5use serde::Serialize;
6use url::Url;
7
8use super::ProviderReference;
9use super::base64_bytes;
10
11/// The payload of a file: inline bytes, a URL, a provider reference or an
12/// inline text document.
13///
14/// Wire format is tagged by `type`: `{"type": "data", "data": "<base64>"}`,
15/// `{"type": "url", "url": "..."}`, `{"type": "reference", "reference": {..}}`
16/// or `{"type": "text", "text": "..."}`. Generated files (results, stream
17/// parts) only use the `data` and `url` forms.
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "kebab-case")]
20#[non_exhaustive]
21pub enum FileData {
22    /// Inline bytes, serialized as standard base64 with padding.
23    #[serde(rename = "data")]
24    Bytes {
25        /// The bytes.
26        #[serde(with = "base64_bytes")]
27        data: Bytes,
28    },
29    /// A URL the provider fetches itself.
30    Url {
31        /// The URL.
32        url: Url,
33    },
34    /// A file previously uploaded to one or more providers.
35    Reference {
36        /// Provider key → provider-specific file id.
37        reference: ProviderReference,
38    },
39    /// An inline text document.
40    Text {
41        /// The text.
42        text: String,
43    },
44}
45
46impl FileData {
47    /// Inline bytes.
48    #[must_use]
49    pub fn bytes(data: impl Into<Bytes>) -> Self {
50        Self::Bytes { data: data.into() }
51    }
52
53    /// Inline bytes from a base64 string (standard alphabet, padding optional).
54    ///
55    /// # Errors
56    ///
57    /// Returns the decoding error when `text` is not valid base64.
58    pub fn from_base64(text: &str) -> Result<Self, base64::DecodeError> {
59        base64_bytes::decode(text).map(|data| Self::Bytes { data })
60    }
61
62    /// A URL.
63    #[must_use]
64    pub fn url(url: Url) -> Self {
65        Self::Url { url }
66    }
67
68    /// A reference to a file uploaded to a single provider.
69    #[must_use]
70    pub fn reference(provider: impl Into<String>, id: impl Into<String>) -> Self {
71        let mut map = ProviderReference::new();
72        map.insert(provider.into(), id.into());
73        Self::Reference { reference: map }
74    }
75
76    /// An inline text document.
77    #[must_use]
78    pub fn text(text: impl Into<String>) -> Self {
79        Self::Text { text: text.into() }
80    }
81
82    /// Returns the bytes when inline.
83    #[must_use]
84    pub fn as_bytes(&self) -> Option<&Bytes> {
85        match self {
86            Self::Bytes { data } => Some(data),
87            Self::Url { .. } | Self::Reference { .. } | Self::Text { .. } => None,
88        }
89    }
90
91    /// Returns the URL when this is a URL payload.
92    #[must_use]
93    pub fn as_url(&self) -> Option<&Url> {
94        match self {
95            Self::Url { url } => Some(url),
96            Self::Bytes { .. } | Self::Reference { .. } | Self::Text { .. } => None,
97        }
98    }
99
100    /// Returns the provider reference when this is a reference payload.
101    #[must_use]
102    pub fn as_reference(&self) -> Option<&ProviderReference> {
103        match self {
104            Self::Reference { reference } => Some(reference),
105            Self::Bytes { .. } | Self::Url { .. } | Self::Text { .. } => None,
106        }
107    }
108
109    /// Returns the text when this is an inline text document.
110    #[must_use]
111    pub fn as_text(&self) -> Option<&str> {
112        match self {
113            Self::Text { text } => Some(text),
114            Self::Bytes { .. } | Self::Url { .. } | Self::Reference { .. } => None,
115        }
116    }
117
118    /// Returns the inline bytes encoded as standard base64.
119    #[must_use]
120    pub fn to_base64(&self) -> Option<String> {
121        self.as_bytes().map(|bytes| base64_bytes::encode(bytes))
122    }
123}
124
125impl From<Bytes> for FileData {
126    fn from(data: Bytes) -> Self {
127        Self::Bytes { data }
128    }
129}
130
131impl From<Vec<u8>> for FileData {
132    fn from(bytes: Vec<u8>) -> Self {
133        Self::Bytes {
134            data: Bytes::from(bytes),
135        }
136    }
137}
138
139impl From<Url> for FileData {
140    fn from(url: Url) -> Self {
141        Self::Url { url }
142    }
143}
144
145impl From<ProviderReference> for FileData {
146    fn from(reference: ProviderReference) -> Self {
147        Self::Reference { reference }
148    }
149}