1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4
5#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub enum SortType {
8 String,
9 Numeric,
10 Date,
11 None,
12}
13
14impl Default for SortType {
15 fn default() -> Self {
16 Self::String
17 }
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub enum Primitive {
23 String,
24 Number,
25 Boolean,
26 File,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct Reference {
32 pub entity: String,
33 #[serde(default = "default_display_field")]
34 pub display_field: String,
35}
36
37fn default_display_field() -> String {
38 "name".to_owned()
39}
40
41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase", tag = "kind")]
43pub enum FieldType {
44 Primitive {
45 primitive: Primitive,
46 },
47 Reference {
48 reference: Reference,
49 },
50 Array {
51 item: Box<FieldType>,
52 },
53 Shape {
54 fields: IndexMap<String, ShapeField>,
55 },
56}
57
58#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct ShapeField {
61 pub label: String,
62 #[serde(rename = "type")]
63 pub field_type: FieldType,
64 #[serde(default)]
65 pub options: FieldOptions,
66}
67
68#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct InputHint {
71 pub kind: String,
72 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
73 pub props: IndexMap<String, JsonValue>,
74}
75
76impl InputHint {
77 pub fn new(kind: impl Into<String>) -> Self {
78 Self {
79 kind: kind.into(),
80 props: IndexMap::new(),
81 }
82 }
83}
84
85#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct FieldOptions {
88 #[serde(default)]
89 pub sort: SortType,
90 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub input: Option<InputHint>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
93 pub fixed_value: Option<JsonValue>,
94 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub rule: Option<String>,
96 #[serde(default, skip_serializing_if = "IndexMap::is_empty")]
97 pub metadata: IndexMap<String, JsonValue>,
98}
99
100#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct Field {
103 pub label: String,
104 #[serde(rename = "type")]
105 pub field_type: FieldType,
106 #[serde(default)]
107 pub options: FieldOptions,
108}
109
110impl Field {
111 pub fn new(label: impl Into<String>, field_type: FieldType) -> Self {
112 Self {
113 label: label.into(),
114 field_type,
115 options: FieldOptions::default(),
116 }
117 }
118
119 pub fn string(label: impl Into<String>) -> Self {
120 Self::new(
121 label,
122 FieldType::Primitive {
123 primitive: Primitive::String,
124 },
125 )
126 .with_input(InputHint::new("text"))
127 }
128
129 pub fn number(label: impl Into<String>) -> Self {
130 Self::new(
131 label,
132 FieldType::Primitive {
133 primitive: Primitive::Number,
134 },
135 )
136 .with_sort(SortType::Numeric)
137 .with_input(InputHint::new("number"))
138 }
139
140 pub fn boolean(label: impl Into<String>) -> Self {
141 Self::new(
142 label,
143 FieldType::Primitive {
144 primitive: Primitive::Boolean,
145 },
146 )
147 .with_sort(SortType::None)
148 .with_input(InputHint::new("checkbox"))
149 }
150
151 pub fn file(label: impl Into<String>) -> Self {
152 Self::new(
153 label,
154 FieldType::Primitive {
155 primitive: Primitive::File,
156 },
157 )
158 .with_sort(SortType::None)
159 .with_input(InputHint::new("file"))
160 }
161
162 pub fn reference(label: impl Into<String>, entity: impl Into<String>) -> Self {
163 Self::new(
164 label,
165 FieldType::Reference {
166 reference: Reference {
167 entity: entity.into(),
168 display_field: default_display_field(),
169 },
170 },
171 )
172 .with_input(InputHint::new("select"))
173 }
174
175 pub fn array(label: impl Into<String>, item: FieldType) -> Self {
176 Self::new(
177 label,
178 FieldType::Array {
179 item: Box::new(item),
180 },
181 )
182 .with_sort(SortType::None)
183 }
184
185 pub fn shape(label: impl Into<String>, fields: IndexMap<String, ShapeField>) -> Self {
186 Self::new(label, FieldType::Shape { fields }).with_sort(SortType::None)
187 }
188
189 pub fn with_sort(mut self, sort: SortType) -> Self {
190 self.options.sort = sort;
191 self
192 }
193
194 pub fn with_input(mut self, input: InputHint) -> Self {
195 self.options.input = Some(input);
196 self
197 }
198
199 pub fn with_metadata(mut self, key: impl Into<String>, value: JsonValue) -> Self {
200 self.options.metadata.insert(key.into(), value);
201 self
202 }
203}
204
205impl ShapeField {
206 pub fn new(label: impl Into<String>, field_type: FieldType) -> Self {
207 Self {
208 label: label.into(),
209 field_type,
210 options: FieldOptions::default(),
211 }
212 }
213}
214
215pub fn make_field(kind: &str, label: &str, entity: Option<&str>) -> Field {
221 match kind {
222 "number" => Field::number(label),
223 "boolean" => Field::boolean(label),
224 "file" => Field::file(label),
225 "ref" => Field::reference(label, entity.unwrap_or("")),
226 _ => Field::string(label),
227 }
228}