Skip to main content

fiberplane_models/providers/schema/fields/
array_field.rs

1use crate::providers::QuerySchema;
2#[cfg(feature = "fp-bindgen")]
3use fp_bindgen::prelude::Serializable;
4use serde::{Deserialize, Serialize};
5
6/// Defines an array of composite fields.
7///
8/// This is commonly used for arbitrarily long list of (key, value) pairs,
9/// or lists of (key, operator, value) filters.
10#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
11#[cfg_attr(
12    feature = "fp-bindgen",
13    derive(Serializable),
14    fp(rust_module = "fiberplane_models::providers")
15)]
16#[non_exhaustive]
17#[serde(rename_all = "camelCase")]
18pub struct ArrayField {
19    /// Suggested label to display along the form field.
20    pub label: String,
21
22    /// Name of the field as it will be included in the encoded query or config
23    /// object.
24    pub name: String,
25
26    /// The minimum number of entries the array must have to be valid.
27    ///
28    /// Leaving the minimum_length to 0 makes the whole field optional.
29    pub minimum_length: u32,
30
31    /// The maximum number of entries the array can have and still be valid.
32    ///
33    /// It is None when there is no maximum number
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub maximum_length: Option<u32>,
36
37    /// The schema of the elements inside a row of the array.
38    ///
39    /// ### Accessing row fields
40    ///
41    /// The name of each QueryField inside the element_schema can be used as
42    /// an indexing key for a field. That means that if `element_schema` contains
43    /// a [TextField](crate::providers::TextField) with the name `parameter_name`,
44    /// then you will be able to access the value of that field using
45    /// `ArrayField::get(i)::get("parameter_name")` for the i-th element.
46    ///
47    /// ### Serialization
48    ///
49    /// For example if an array field has this `element_schema`:
50    /// ```rust,no_run
51    /// # use fiberplane_models::providers::{ArrayField, TextField, SelectField, IntegerField};
52    /// ArrayField::new()
53    ///   .with_name("table")
54    ///   .with_label("example".to_string())
55    ///   .with_element_schema(vec![
56    ///     TextField::new().with_name("key").into(),
57    ///     SelectField::new().with_name("operator").with_options([
58    ///       "<".into(),
59    ///       ">".into(),
60    ///       "<=".into(),
61    ///       ">=".into(),
62    ///       "==".into()
63    ///     ]).into(),
64    ///     IntegerField::new().with_name("value").into(),
65    ///   ]);
66    /// ```
67    ///
68    /// Then the URL-encoded serialization for the fields is expected to use
69    /// the bracketed-notation. This means you _can_ encode all the
70    /// keys in the array in any order you want. It can look like this
71    /// (line breaks are only kept for legibility):
72    /// ```txt
73    ///  "table[0][key]=less+than&
74    ///  table[2][operator]=%3E&
75    ///  table[0][operator]=%3C&
76    ///  table[2][key]=greater+than&
77    ///  table[2][value]=10&
78    ///  table[0][value]=12"
79    /// ```
80    ///
81    /// or you can do the "logic" ordering too:
82    /// ```txt
83    ///  "table[0][key]=less+than&
84    ///  table[0][operator]=%3C&
85    ///  table[0][value]=12&
86    ///  table[1][key]=greater+than&
87    ///  table[1][operator]=%3E&
88    ///  table[1][value]=10"
89    /// ```
90    ///
91    /// Note that we are allowed to skip indices.
92    /// Any of those 2 examples above will
93    /// be read as:
94    /// ```rust,no_run
95    /// # #[derive(Debug, PartialEq)]
96    /// # struct Row { key: String, operator: String, value: u32 }
97    /// # let table: Vec<Row> = vec![];
98    /// assert_eq!(table, vec![
99    ///   Row {
100    ///     key: "less than".to_string(),
101    ///     operator: "<".to_string(),
102    ///     value: 12,
103    ///   },
104    ///   Row {
105    ///     key: "greater than".to_string(),
106    ///     operator: ">".to_string(),
107    ///     value: 10,
108    ///   },
109    /// ]);
110    /// ```
111    ///
112    /// ### Required row fields
113    ///
114    /// Any field that is marked as `required` inside `element_schema` makes it
115    /// mandatory to create a valid row to the Array Field.
116    pub element_schema: QuerySchema,
117}
118
119impl ArrayField {
120    /// Creates a new array field with all default values.
121    pub fn new() -> Self {
122        Default::default()
123    }
124
125    pub fn with_label(self, label: impl Into<String>) -> Self {
126        Self {
127            label: label.into(),
128            ..self
129        }
130    }
131
132    pub fn with_element_schema(self, schema: QuerySchema) -> Self {
133        Self {
134            element_schema: schema,
135            ..self
136        }
137    }
138
139    pub fn with_name(self, name: impl Into<String>) -> Self {
140        Self {
141            name: name.into(),
142            ..self
143        }
144    }
145
146    pub fn with_minimum_length(self, minimum_length: u32) -> Self {
147        Self {
148            minimum_length,
149            ..self
150        }
151    }
152
153    pub fn with_maximum_length(self, maximum_length: u32) -> Self {
154        Self {
155            maximum_length: Some(maximum_length),
156            ..self
157        }
158    }
159}