1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use crate::providers::QuerySchema;
#[cfg(feature = "fp-bindgen")]
use fp_bindgen::prelude::Serializable;
use serde::{Deserialize, Serialize};

/// Defines an array of composite fields.
///
/// This is commonly used for arbitrarily long list of (key, value) pairs,
/// or lists of (key, operator, value) filters.
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
#[cfg_attr(
    feature = "fp-bindgen",
    derive(Serializable),
    fp(rust_module = "fiberplane_models::providers")
)]
#[non_exhaustive]
#[serde(rename_all = "camelCase")]
pub struct ArrayField {
    /// Suggested label to display along the form field.
    pub label: String,

    /// Name of the field as it will be included in the encoded query or config
    /// object.
    pub name: String,

    /// The minimum number of entries the array must have to be valid.
    ///
    /// Leaving the minimum_length to 0 makes the whole field optional.
    pub minimum_length: u32,

    /// The maximum number of entries the array can have and still be valid.
    ///
    /// It is None when there is no maximum number
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub maximum_length: Option<u32>,

    /// The schema of the elements inside a row of the array.
    ///
    /// ### Accessing row fields
    ///
    /// The name of each QueryField inside the element_schema can be used as
    /// an indexing key for a field. That means that if `element_schema` contains
    /// a [TextField](crate::providers::TextField) with the name `parameter_name`,
    /// then you will be able to access the value of that field using
    /// `ArrayField::get(i)::get("parameter_name")` for the i-th element.
    ///
    /// ### Serialization
    ///
    /// For example if an array field has this `element_schema`:
    /// ```rust,no_run
    /// # use fiberplane_models::providers::{ArrayField, TextField, SelectField, IntegerField};
    /// ArrayField::new()
    ///   .with_name("table")
    ///   .with_label("example".to_string())
    ///   .with_element_schema(vec![
    ///     TextField::new().with_name("key").into(),
    ///     SelectField::new().with_name("operator").with_options([
    ///       "<".into(),
    ///       ">".into(),
    ///       "<=".into(),
    ///       ">=".into(),
    ///       "==".into()
    ///     ]).into(),
    ///     IntegerField::new().with_name("value").into(),
    ///   ]);
    /// ```
    ///
    /// Then the URL-encoded serialization for the fields is expected to use
    /// the bracketed-notation. This means you _can_ encode all the
    /// keys in the array in any order you want. It can look like this
    /// (line breaks are only kept for legibility):
    /// ```txt
    ///  "table[0][key]=less+than&
    ///  table[2][operator]=%3E&
    ///  table[0][operator]=%3C&
    ///  table[2][key]=greater+than&
    ///  table[2][value]=10&
    ///  table[0][value]=12"
    /// ```
    ///
    /// or you can do the "logic" ordering too:
    /// ```txt
    ///  "table[0][key]=less+than&
    ///  table[0][operator]=%3C&
    ///  table[0][value]=12&
    ///  table[1][key]=greater+than&
    ///  table[1][operator]=%3E&
    ///  table[1][value]=10"
    /// ```
    ///
    /// Note that we are allowed to skip indices.
    /// Any of those 2 examples above will
    /// be read as:
    /// ```rust,no_run
    /// # #[derive(Debug, PartialEq)]
    /// # struct Row { key: String, operator: String, value: u32 }
    /// # let table: Vec<Row> = vec![];
    /// assert_eq!(table, vec![
    ///   Row {
    ///     key: "less than".to_string(),
    ///     operator: "<".to_string(),
    ///     value: 12,
    ///   },
    ///   Row {
    ///     key: "greater than".to_string(),
    ///     operator: ">".to_string(),
    ///     value: 10,
    ///   },
    /// ]);
    /// ```
    ///
    /// ### Required row fields
    ///
    /// Any field that is marked as `required` inside `element_schema` makes it
    /// mandatory to create a valid row to the Array Field.
    pub element_schema: QuerySchema,
}

impl ArrayField {
    /// Creates a new array field with all default values.
    pub fn new() -> Self {
        Default::default()
    }

    pub fn with_label(self, label: impl Into<String>) -> Self {
        Self {
            label: label.into(),
            ..self
        }
    }

    pub fn with_element_schema(self, schema: QuerySchema) -> Self {
        Self {
            element_schema: schema,
            ..self
        }
    }

    pub fn with_name(self, name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            ..self
        }
    }

    pub fn with_minimum_length(self, minimum_length: u32) -> Self {
        Self {
            minimum_length,
            ..self
        }
    }

    pub fn with_maximum_length(self, maximum_length: u32) -> Self {
        Self {
            maximum_length: Some(maximum_length),
            ..self
        }
    }
}