Skip to main content

scalar_img/
lib.rs

1use scalar_cms::{
2    db::{ContentActions, ValidationContext},
3    editor_field::ToEditorField,
4    validations::{ErroredField, Validate, ValidationError},
5    DatabaseConnection, Document, EditorField,
6};
7use serde::{Deserialize, Serialize};
8
9use url::Url;
10
11#[cfg(feature = "s3")]
12pub mod bucket;
13#[cfg(feature = "s3")]
14pub use bucket::*;
15
16/// Indicates no data (similar to the unit type ())
17/// This exists for the sole purpose of thwarting databases that trim
18/// null field types.
19#[derive(Serialize, Deserialize, Default, Debug)]
20pub struct Null([(); 0]);
21
22impl ToEditorField for Null {
23    fn to_editor_field(
24        _default: Option<impl Into<Self>>,
25        name: &'static str,
26        title: &'static str,
27        placeholder: Option<&'static str>,
28        validator: Option<&'static str>,
29        component_key: Option<&'static str>,
30    ) -> EditorField
31    where
32        Self: std::marker::Sized,
33    {
34        EditorField {
35            name,
36            title,
37            placeholder,
38            required: true,
39            validator,
40            field_type: scalar_cms::EditorType::Null {
41                component_key: component_key.map(Into::into),
42            },
43        }
44    }
45}
46
47#[derive(EditorField, Serialize, Deserialize)]
48#[field(editor_component = "image")]
49#[derive(Debug)]
50pub struct ImageData<D: ToEditorField> {
51    pub url: Url,
52    pub additional_data: D,
53}
54
55pub type Image = ImageData<Null>;
56
57impl<D: ToEditorField + Validate + Sync> Validate for ImageData<D> {
58    async fn validate<DB: DatabaseConnection + ContentActions<DD> + Sync, DD: Document + Sync>(
59        &self,
60        ctx: ValidationContext<'_, DB, DD>,
61    ) -> Result<(), scalar_cms::validations::ValidationError> {
62        self.additional_data.validate(ctx).await
63    }
64}
65
66#[derive(EditorField, Debug, Serialize, Deserialize)]
67#[field(editor_component = "cropped-image")]
68/// A cropped image with additional data.
69/// The VALIDATE flag is a workaround for implementing traits in rust.
70pub struct CroppedImageData<D: ToEditorField, const VALIDATE: bool = true> {
71    pub url: Url,
72    pub gravity_x: f32,
73    pub gravity_y: f32,
74    pub additional_data: D,
75}
76
77pub type CroppedImage = CroppedImageData<Null, false>;
78
79impl<const VALIDATE: bool, D: ToEditorField> CroppedImageData<D, VALIDATE> {
80    #[inline]
81    fn validate_inner(
82        &self,
83        additional_result: Result<(), ValidationError>,
84    ) -> Result<(), ValidationError> {
85        let results = [
86            (0.0..=1.0).contains(&self.gravity_x).then_some(()).ok_or((
87                "gravity_x",
88                ValidationError::Single("gravity_x must be between 0 and 1".into()),
89            )),
90            (0.0..=1.0).contains(&self.gravity_y).then_some(()).ok_or((
91                "gravity_y",
92                ValidationError::Single("gravity_y must be between 0 and 1".into()),
93            )),
94            additional_result.map_err(|e| ("additional_data", e)),
95        ];
96        // if all errors are ok, don't bother even allocating a vec
97        if results.iter().all(Result::is_ok) {
98            Ok(())
99        } else {
100            Err(ValidationError::Composite(
101                results
102                    .into_iter()
103                    .filter_map(|r| {
104                        r.err().map(|(field, error)| ErroredField {
105                            field: field.into(),
106                            error,
107                        })
108                    })
109                    .collect(),
110            ))
111        }
112    }
113}
114
115impl<D: ToEditorField + Validate + Sync> Validate for CroppedImageData<D, true> {
116    async fn validate<DB: DatabaseConnection + ContentActions<DD> + Sync, DD: Document + Sync>(
117        &self,
118        ctx: ValidationContext<'_, DB, DD>,
119    ) -> Result<(), scalar_cms::validations::ValidationError> {
120        self.validate_inner(self.additional_data.validate(ctx).await)
121    }
122}
123
124impl<D: ToEditorField + Send + Sync> Validate for CroppedImageData<D, false> {
125    async fn validate<DB: DatabaseConnection, DD: Document>(
126        &self,
127        _ctx: ValidationContext<'_, DB, DD>,
128    ) -> Result<(), scalar_cms::validations::ValidationError> {
129        self.validate_inner(Ok(()))
130    }
131}
132
133#[derive(EditorField, Debug, Serialize, Deserialize)]
134#[field(editor_component = "file")]
135pub struct FileData<D: ToEditorField> {
136    pub url: Url,
137    pub additional_data: D,
138}
139
140pub type File = FileData<Null>;
141
142impl<D: ToEditorField + Validate + Send + Sync> Validate for FileData<D> {
143    async fn validate<
144        DB: DatabaseConnection + ContentActions<DD> + Sync,
145        DD: Document + Send + Sync,
146    >(
147        &self,
148        ctx: ValidationContext<'_, DB, DD>,
149    ) -> Result<(), scalar_cms::validations::ValidationError> {
150        self.additional_data.validate(ctx).await
151    }
152}