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(&["<", ">", "<=", ">=", "=="]).into(),
58 /// IntegerField::new().with_name("value").into(),
59 /// ]);
60 /// ```
61 ///
62 /// Then the URL-encoded serialization for the fields is expected to use
63 /// the bracketed-notation. This means you _can_ encode all the
64 /// keys in the array in any order you want. It can look like this
65 /// (line breaks are only kept for legibility):
66 /// ```txt
67 /// "table[0][key]=less+than&
68 /// table[2][operator]=%3E&
69 /// table[0][operator]=%3C&
70 /// table[2][key]=greater+than&
71 /// table[2][value]=10&
72 /// table[0][value]=12"
73 /// ```
74 ///
75 /// or you can do the "logic" ordering too:
76 /// ```txt
77 /// "table[0][key]=less+than&
78 /// table[0][operator]=%3C&
79 /// table[0][value]=12&
80 /// table[1][key]=greater+than&
81 /// table[1][operator]=%3E&
82 /// table[1][value]=10"
83 /// ```
84 ///
85 /// Note that we are allowed to skip indices.
86 /// Any of those 2 examples above will
87 /// be read as:
88 /// ```rust,no_run
89 /// # #[derive(Debug, PartialEq)]
90 /// # struct Row { key: String, operator: String, value: u32 }
91 /// # let table: Vec<Row> = vec![];
92 /// assert_eq!(table, vec![
93 /// Row {
94 /// key: "less than".to_string(),
95 /// operator: "<".to_string(),
96 /// value: 12,
97 /// },
98 /// Row {
99 /// key: "greater than".to_string(),
100 /// operator: ">".to_string(),
101 /// value: 10,
102 /// },
103 /// ]);
104 /// ```
105 ///
106 /// ### Required row fields
107 ///
108 /// Any field that is marked as `required` inside `element_schema` makes it
109 /// mandatory to create a valid row to the Array Field.
110 pub element_schema: QuerySchema,
111}
112
113impl ArrayField {
114 /// Creates a new array field with all default values.
115 pub fn new() -> Self {
116 Default::default()
117 }
118
119 pub fn with_label(self, label: impl Into<String>) -> Self {
120 Self {
121 label: label.into(),
122 ..self
123 }
124 }
125
126 pub fn with_element_schema(self, schema: QuerySchema) -> Self {
127 Self {
128 element_schema: schema,
129 ..self
130 }
131 }
132
133 pub fn with_name(self, name: impl Into<String>) -> Self {
134 Self {
135 name: name.into(),
136 ..self
137 }
138 }
139
140 pub fn with_minimum_length(self, minimum_length: u32) -> Self {
141 Self {
142 minimum_length,
143 ..self
144 }
145 }
146
147 pub fn with_maximum_length(self, maximum_length: u32) -> Self {
148 Self {
149 maximum_length: Some(maximum_length),
150 ..self
151 }
152 }
153}