fiberplane_models/
blobs.rs

1use crate::debug_print_bytes;
2use base64::DecodeError;
3use bytes::Bytes;
4#[cfg(feature = "fp-bindgen")]
5use fp_bindgen::prelude::Serializable;
6use serde::{Deserialize, Serialize};
7use std::convert::TryFrom;
8use std::fmt::{self, Debug, Formatter};
9use typed_builder::TypedBuilder;
10
11/// Binary blob for passing data in arbitrary encodings.
12///
13/// Binary blobs are both consumed and produced by providers. Note that for many
14/// use-cases, we use agreed on MIME types as defined in
15/// [RFC 47](https://www.notion.so/fiberplane/RFC-47-Data-Model-for-Providers-2-0-0b5b1716dbc8450f882d33effb388c5b).
16/// Providers are able to use custom MIME types if they desire.
17///
18/// We can also store blobs in cells, but for this we use [EncodedBlob] to allow
19/// JSON serialization.
20#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize, TypedBuilder)]
21#[cfg_attr(
22    feature = "fp-bindgen",
23    derive(Serializable),
24    fp(rust_module = "fiberplane_models::blobs")
25)]
26#[non_exhaustive]
27#[serde(rename_all = "camelCase")]
28pub struct Blob {
29    /// Raw data.
30    #[builder(setter(into))]
31    pub data: Bytes,
32
33    /// MIME type to use for interpreting the raw data.
34    ///
35    /// We keep track of this, so that we can elide unnecessary calls to
36    /// `extract_data()`, and are able to perform migrations on data specified
37    /// in any of the `application/vnd.fiberplane.*` types. For other types of
38    /// data, providers are responsible for migrations, and they are able to
39    /// include version numbers in their MIME type strings, if desired.
40    #[builder(setter(into))]
41    pub mime_type: String,
42}
43
44impl TryFrom<EncodedBlob> for Blob {
45    type Error = DecodeError;
46
47    fn try_from(blob: EncodedBlob) -> Result<Self, Self::Error> {
48        Ok(Self {
49            data: base64::decode(&blob.data)?.into(),
50            mime_type: blob.mime_type,
51        })
52    }
53}
54
55impl TryFrom<&EncodedBlob> for Blob {
56    type Error = DecodeError;
57
58    fn try_from(blob: &EncodedBlob) -> Result<Self, Self::Error> {
59        Ok(Self {
60            data: base64::decode(&blob.data)?.into(),
61            mime_type: blob.mime_type.clone(),
62        })
63    }
64}
65
66impl Debug for Blob {
67    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
68        f.debug_struct("Blob")
69            .field("mime_type", &self.mime_type)
70            .field("data_length", &self.data.len())
71            .field("data", &debug_print_bytes(&self.data))
72            .finish()
73    }
74}
75
76/// base64-encoded version of [Blob].
77#[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize, TypedBuilder)]
78#[cfg_attr(
79    feature = "fp-bindgen",
80    derive(Serializable),
81    fp(rust_module = "fiberplane_models::blobs")
82)]
83#[non_exhaustive]
84#[serde(rename_all = "camelCase")]
85pub struct EncodedBlob {
86    /// Raw data, encoded using base64 so it can be serialized using JSON.
87    #[builder(setter(into))]
88    pub data: String,
89
90    /// MIME type to use for interpreting the raw data.
91    ///
92    /// See [Blob::mime_type].
93    #[builder(setter(into))]
94    pub mime_type: String,
95}
96
97impl From<Blob> for EncodedBlob {
98    fn from(blob: Blob) -> Self {
99        Self {
100            data: base64::encode(blob.data.as_ref()),
101            mime_type: blob.mime_type,
102        }
103    }
104}
105
106impl From<&Blob> for EncodedBlob {
107    fn from(blob: &Blob) -> Self {
108        Self {
109            data: base64::encode(blob.data.as_ref()),
110            mime_type: blob.mime_type.clone(),
111        }
112    }
113}
114
115impl Debug for EncodedBlob {
116    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
117        f.debug_struct("EncodedBlob")
118            .field("mime_type", &self.mime_type)
119            .field("data_length", &self.data.len())
120            .field("data", &debug_print_bytes(self.data.as_bytes()))
121            .finish()
122    }
123}