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}