Skip to main content

teaql_core/
web.rs

1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4use crate::{BaseEntity, BaseEntityData, Entity, Record, SmartList, Value, record_to_json_value};
5
6pub const STYLE_KEY: &str = "style";
7pub const ACTION_LIST_KEY: &str = "actionList";
8pub const WEB_RESPONSE_VERSION: &str = "1.001";
9
10#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct WebStyle {
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub background_color: Option<String>,
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub color: Option<String>,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub class_names: Option<String>,
19}
20
21impl WebStyle {
22    pub fn new() -> Self {
23        Self::default()
24    }
25
26    pub fn with_background_color(color: impl Into<String>) -> Self {
27        Self::new().background_color(color)
28    }
29
30    pub fn with_font_color(color: impl Into<String>) -> Self {
31        Self::new().font_color(color)
32    }
33
34    pub fn with_class_names(class_names: impl Into<String>) -> Self {
35        Self::new().class_names(class_names)
36    }
37
38    pub fn background_color(mut self, color: impl Into<String>) -> Self {
39        self.background_color = Some(color.into());
40        self
41    }
42
43    pub fn font_color(mut self, color: impl Into<String>) -> Self {
44        self.color = Some(color.into());
45        self
46    }
47
48    pub fn class_names(mut self, class_names: impl Into<String>) -> Self {
49        self.class_names = Some(class_names.into());
50        self
51    }
52
53    pub fn to_json_value(&self) -> serde_json::Value {
54        serde_json::to_value(self).expect("WebStyle serialization cannot fail")
55    }
56
57    pub fn bind_base(&self, entity: &mut BaseEntityData) {
58        entity.put_dynamic(STYLE_KEY, self.to_json_value());
59    }
60
61    pub fn bind_entity<E>(&self, entity: &mut E)
62    where
63        E: BaseEntity,
64    {
65        entity.put_dynamic(STYLE_KEY, self.to_json_value());
66    }
67
68    pub fn bind_record(&self, record: &mut Record) {
69        record.insert(STYLE_KEY.to_owned(), Value::Json(self.to_json_value()));
70    }
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct WebAction {
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub key: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub name: Option<String>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub level: Option<String>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub execute: Option<String>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub target: Option<String>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub component: Option<String>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub warning_message: Option<String>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub role_for_list: Option<String>,
92    #[serde(rename = "requestURL", skip_serializing_if = "Option::is_none")]
93    pub request_url: Option<String>,
94}
95
96impl WebAction {
97    pub fn new() -> Self {
98        Self::default()
99    }
100
101    pub fn view_web_action() -> Self {
102        Self::new()
103            .name("VIEW DETAIL")
104            .level("view")
105            .execute("switchview")
106            .target("detail")
107    }
108
109    pub fn view_sub_list_action(
110        name: impl Into<String>,
111        list_view_name: impl Into<String>,
112        role_for_list: impl Into<String>,
113    ) -> Self {
114        Self::new()
115            .name(name)
116            .level("view")
117            .execute("gotoList")
118            .role_for_list(role_for_list)
119            .target(list_view_name)
120    }
121
122    pub fn simple_component_action(
123        name: impl Into<String>,
124        component_name: impl Into<String>,
125    ) -> Self {
126        Self::new().name(name).component(component_name)
127    }
128
129    pub fn modify_web_action(name: impl Into<String>, url: impl Into<String>) -> Self {
130        Self::modify_web_action_with_warning(name, url, None::<String>)
131    }
132
133    pub fn modify_web_action_with_warning(
134        name: impl Into<String>,
135        url: impl Into<String>,
136        warning_message: Option<impl Into<String>>,
137    ) -> Self {
138        let name = name.into();
139        Self::new()
140            .name(name.clone())
141            .key(name)
142            .level("modify")
143            .execute("switchview")
144            .target("modify")
145            .request_url(url)
146            .optional_warning_message(warning_message)
147    }
148
149    pub fn default_modify_web_action() -> Self {
150        Self::new()
151            .name("UPDATE")
152            .level("modify")
153            .execute("switchview")
154            .target("modify")
155    }
156
157    pub fn delete_web_action() -> Self {
158        Self::new()
159            .name("DELETE")
160            .level("delete")
161            .execute("switchview")
162            .target("deleteview")
163    }
164
165    pub fn delete_web_action_with_warning(
166        url: impl Into<String>,
167        warning_message: impl Into<String>,
168    ) -> Self {
169        Self::modify_web_action_with_warning("web.action.delete", url, Some(warning_message.into()))
170    }
171
172    pub fn audit_web_action(url: impl Into<String>, warning_message: impl Into<String>) -> Self {
173        Self::modify_web_action_with_warning("AUDIT", url, Some(warning_message.into()))
174    }
175
176    pub fn discard_web_action(url: impl Into<String>, warning_message: impl Into<String>) -> Self {
177        Self::modify_web_action_with_warning("DISCARD", url, Some(warning_message.into()))
178    }
179
180    pub fn goto_action(
181        name: impl Into<String>,
182        target: impl Into<String>,
183        url: impl Into<String>,
184    ) -> Self {
185        Self::new()
186            .name(name)
187            .level("modify")
188            .execute("gotoview")
189            .target(target)
190            .request_url(url)
191    }
192
193    pub fn switch_view_action(view_name: impl Into<String>, target: impl Into<String>) -> Self {
194        Self::new()
195            .name(view_name)
196            .level("modify")
197            .execute("switchview")
198            .target(target)
199    }
200
201    pub fn add_new_web_action(object_display_name: impl Into<String>) -> Self {
202        Self::new()
203            .name(format!("NEW {}", object_display_name.into()))
204            .level("modify")
205            .execute("switchview")
206            .target("addnew")
207    }
208
209    pub fn batch_upload_web_action() -> Self {
210        Self::new()
211            .name("BATCH UPLOAD")
212            .level("modify")
213            .execute("switchview")
214            .target("batchupload")
215    }
216
217    pub fn common_web_actions() -> Vec<Self> {
218        vec![Self::view_web_action(), Self::default_modify_web_action()]
219    }
220
221    pub fn key(mut self, key: impl Into<String>) -> Self {
222        self.key = Some(key.into());
223        self
224    }
225
226    pub fn name(mut self, name: impl Into<String>) -> Self {
227        self.name = Some(name.into());
228        self
229    }
230
231    pub fn level(mut self, level: impl Into<String>) -> Self {
232        self.level = Some(level.into());
233        self
234    }
235
236    pub fn execute(mut self, execute: impl Into<String>) -> Self {
237        self.execute = Some(execute.into());
238        self
239    }
240
241    pub fn target(mut self, target: impl Into<String>) -> Self {
242        self.target = Some(target.into());
243        self
244    }
245
246    pub fn component(mut self, component: impl Into<String>) -> Self {
247        self.component = Some(component.into());
248        self
249    }
250
251    pub fn warning_message(mut self, warning_message: impl Into<String>) -> Self {
252        self.warning_message = Some(warning_message.into());
253        self
254    }
255
256    pub fn optional_warning_message(mut self, warning_message: Option<impl Into<String>>) -> Self {
257        self.warning_message = warning_message.map(Into::into);
258        self
259    }
260
261    pub fn role_for_list(mut self, role_for_list: impl Into<String>) -> Self {
262        self.role_for_list = Some(role_for_list.into());
263        self
264    }
265
266    pub fn request_url(mut self, request_url: impl Into<String>) -> Self {
267        self.request_url = Some(request_url.into());
268        self
269    }
270
271    pub fn to_json_value(&self) -> serde_json::Value {
272        serde_json::to_value(self).expect("WebAction serialization cannot fail")
273    }
274
275    pub fn bind_base(&self, entity: &mut BaseEntityData) {
276        append_action(&mut entity.dynamic, self.to_json_value());
277    }
278
279    pub fn bind_entity<E>(&self, entity: &mut E)
280    where
281        E: BaseEntity,
282    {
283        append_action(&mut entity.base_mut().dynamic, self.to_json_value());
284    }
285
286    pub fn bind_record(&self, record: &mut Record) {
287        append_record_action(record, self.to_json_value());
288    }
289}
290
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292#[serde(rename_all = "camelCase")]
293pub struct WebResponse {
294    pub data: Vec<serde_json::Value>,
295    pub result_code: i32,
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub status: Option<String>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub message: Option<String>,
300    pub record_count: u64,
301    pub version: String,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub facets: Option<BTreeMap<String, serde_json::Value>>,
304}
305
306impl WebResponse {
307    pub fn success() -> Self {
308        Self {
309            data: Vec::new(),
310            result_code: 0,
311            status: Some("YES".to_owned()),
312            message: None,
313            record_count: 0,
314            version: WEB_RESPONSE_VERSION.to_owned(),
315            facets: None,
316        }
317    }
318
319    pub fn fail(message: impl Into<String>) -> Self {
320        Self {
321            data: Vec::new(),
322            result_code: 1,
323            status: Some("NO".to_owned()),
324            message: Some(message.into()),
325            record_count: 0,
326            version: WEB_RESPONSE_VERSION.to_owned(),
327            facets: None,
328        }
329    }
330
331    pub fn empty_list(message: impl Into<String>) -> Self {
332        Self {
333            data: Vec::new(),
334            result_code: 0,
335            status: None,
336            message: Some(message.into()),
337            record_count: 0,
338            version: WEB_RESPONSE_VERSION.to_owned(),
339            facets: None,
340        }
341    }
342
343    pub fn from_records(records: impl IntoIterator<Item = Record>) -> Self {
344        let data: Vec<_> = records
345            .into_iter()
346            .map(|record| record_to_json_value(&record))
347            .collect();
348        Self::success().with_data(data)
349    }
350
351    pub fn from_entity<E>(entity: &E) -> Self
352    where
353        E: Entity + Clone,
354    {
355        Self::from_records([entity.clone().into_record()])
356    }
357
358    pub fn from_entities<E>(entities: impl IntoIterator<Item = E>) -> Self
359    where
360        E: Entity,
361    {
362        Self::from_records(entities.into_iter().map(Entity::into_record))
363    }
364
365    pub fn from_smart_list<E>(mut smart_list: SmartList<E>) -> Self
366    where
367        E: Entity,
368    {
369        let total_count = smart_list.total_count_or_len();
370        let facets = if !smart_list.facets.is_empty() {
371            let mut mapped = BTreeMap::new();
372            for (key, facet_list) in smart_list.take_facets() {
373                let data: Vec<_> = facet_list
374                    .data
375                    .iter()
376                    .map(|record| record_to_json_value(record))
377                    .collect();
378                mapped.insert(key, serde_json::Value::Array(data));
379            }
380            Some(mapped)
381        } else {
382            None
383        };
384        Self::from_entities(smart_list)
385            .with_record_count(total_count)
386            .with_facets_option(facets)
387    }
388
389    pub fn with_data(mut self, data: Vec<serde_json::Value>) -> Self {
390        self.record_count = data.len() as u64;
391        self.data = data;
392        self
393    }
394
395    pub fn with_record_count(mut self, record_count: u64) -> Self {
396        self.record_count = record_count;
397        self
398    }
399
400    pub fn with_facets(mut self, facets: BTreeMap<String, serde_json::Value>) -> Self {
401        self.facets = Some(facets);
402        self
403    }
404
405    pub fn with_facets_option(mut self, facets: Option<BTreeMap<String, serde_json::Value>>) -> Self {
406        self.facets = facets;
407        self
408    }
409
410    pub fn push_json(mut self, value: impl Into<serde_json::Value>) -> Self {
411        self.data.push(value.into());
412        self.record_count = self.data.len() as u64;
413        self
414    }
415
416    pub fn to_json_value(&self) -> serde_json::Value {
417        serde_json::to_value(self).expect("WebResponse serialization cannot fail")
418    }
419}
420
421fn append_action(
422    dynamic: &mut std::collections::BTreeMap<String, Value>,
423    action: serde_json::Value,
424) {
425    match dynamic.get_mut(ACTION_LIST_KEY) {
426        Some(Value::Json(serde_json::Value::Array(actions))) => actions.push(action),
427        Some(existing) => {
428            let previous = std::mem::replace(existing, Value::Null);
429            *existing = Value::Json(serde_json::Value::Array(vec![
430                previous.to_json_value(),
431                action,
432            ]));
433        }
434        None => {
435            dynamic.insert(
436                ACTION_LIST_KEY.to_owned(),
437                Value::Json(serde_json::Value::Array(vec![action])),
438            );
439        }
440    }
441}
442
443fn append_record_action(record: &mut Record, action: serde_json::Value) {
444    match record.get_mut(ACTION_LIST_KEY) {
445        Some(Value::Json(serde_json::Value::Array(actions))) => actions.push(action),
446        Some(existing) => {
447            let previous = std::mem::replace(existing, Value::Null);
448            *existing = Value::Json(serde_json::Value::Array(vec![
449                previous.to_json_value(),
450                action,
451            ]));
452        }
453        None => {
454            record.insert(
455                ACTION_LIST_KEY.to_owned(),
456                Value::Json(serde_json::Value::Array(vec![action])),
457            );
458        }
459    }
460}