ftd/p2/
record.rs

1#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
2pub struct Record {
3    pub name: String,
4    pub fields: ftd::Map<ftd::p2::Kind>,
5    pub instances: ftd::Map<Vec<Invocation>>,
6    pub order: Vec<String>,
7}
8
9pub(crate) type Invocation = ftd::Map<ftd::PropertyValue>;
10
11impl Record {
12    pub fn variant_name(&self) -> Option<&str> {
13        self.name.split_once('.').map(|(_, r)| r)
14    }
15
16    pub fn fields(
17        &self,
18        p1: &ftd::p1::Section,
19        doc: &ftd::p2::TDoc,
20    ) -> ftd::p1::Result<ftd::Map<ftd::PropertyValue>> {
21        let mut fields: ftd::Map<ftd::PropertyValue> = Default::default();
22        self.assert_no_extra_fields(doc.name, &p1.header, &p1.caption, &p1.body)?;
23        for (name, kind) in self.fields.iter() {
24            let subsections = p1.sub_sections_by_name(name);
25            let value = match (
26                p1.sub_section_by_name(name, doc.name.to_string()),
27                kind.inner(),
28            ) {
29                (Ok(v), ftd::p2::Kind::String { .. }) => ftd::PropertyValue::Value {
30                    value: ftd::Value::String {
31                        text: v.body(doc.name)?,
32                        source: ftd::TextSource::Body,
33                    },
34                },
35                (Ok(v), ftd::p2::Kind::Record { name, .. }) => {
36                    let record = doc.get_record(p1.line_number, name.as_str())?;
37                    ftd::PropertyValue::Value {
38                        value: ftd::Value::Record {
39                            name: doc.resolve_name(p1.line_number, record.name.as_str())?,
40                            fields: record.fields_from_sub_section(v, doc)?,
41                        },
42                    }
43                }
44                (
45                    Err(ftd::p1::Error::NotFound { .. }),
46                    ftd::p2::Kind::List {
47                        kind: list_kind, ..
48                    },
49                ) => match list_kind.as_ref() {
50                    ftd::p2::Kind::OrType {
51                        name: or_type_name, ..
52                    }
53                    | ftd::p2::Kind::OrTypeWithVariant {
54                        name: or_type_name, ..
55                    } => {
56                        let e = doc.get_or_type(p1.line_number, or_type_name)?;
57                        let mut values: Vec<ftd::PropertyValue> = vec![];
58                        for s in p1.sub_sections.0.iter() {
59                            if s.is_commented {
60                                continue;
61                            }
62                            for v in e.variants.iter() {
63                                let variant = v.variant_name().expect("record.fields").to_string();
64                                if s.name == format!("{}.{}", name, variant.as_str()) {
65                                    values.push(ftd::PropertyValue::Value {
66                                        value: ftd::Value::OrType {
67                                            variant,
68                                            name: e.name.to_string(),
69                                            fields: v.fields_from_sub_section(s, doc)?,
70                                        },
71                                    })
72                                }
73                            }
74                        }
75                        ftd::PropertyValue::Value {
76                            value: ftd::Value::List {
77                                kind: list_kind.inner().to_owned(),
78                                data: values,
79                            },
80                        }
81                    }
82                    ftd::p2::Kind::Record { .. } => {
83                        let mut list = ftd::Value::List {
84                            kind: list_kind.inner().to_owned(),
85                            data: vec![],
86                        };
87                        for (i, k, v) in p1.header.0.iter() {
88                            if *k != *name {
89                                continue;
90                            }
91                            list = doc.get_value(i.to_owned(), v)?;
92                        }
93                        ftd::PropertyValue::Value { value: list }
94                    }
95                    ftd::p2::Kind::String { .. } => {
96                        let mut values: Vec<ftd::PropertyValue> = vec![];
97                        for (_, k, v) in p1.header.0.iter() {
98                            if *k != *name {
99                                continue;
100                            }
101                            values.push(ftd::PropertyValue::Value {
102                                value: ftd::Value::String {
103                                    text: v.to_string(),
104                                    source: ftd::TextSource::Header,
105                                },
106                            });
107                        }
108                        ftd::PropertyValue::Value {
109                            value: ftd::Value::List {
110                                kind: list_kind.inner().to_owned(),
111                                data: values,
112                            },
113                        }
114                    }
115                    ftd::p2::Kind::Integer { .. } => {
116                        return ftd::p2::utils::e2("unexpected integer", doc.name, p1.line_number);
117                    }
118                    t => {
119                        return ftd::p2::utils::e2(
120                            format!("not yet implemented: {:?}", t),
121                            doc.name,
122                            p1.line_number,
123                        )
124                    }
125                },
126                (
127                    _,
128                    ftd::p2::Kind::List {
129                        kind: list_kind, ..
130                    },
131                ) if !subsections.is_empty() => match list_kind.as_ref() {
132                    ftd::p2::Kind::OrType {
133                        name: or_type_name, ..
134                    }
135                    | ftd::p2::Kind::OrTypeWithVariant {
136                        name: or_type_name, ..
137                    } => {
138                        let e = doc.get_or_type(p1.line_number, or_type_name)?;
139                        let mut values: Vec<ftd::PropertyValue> = vec![];
140                        for s in p1.sub_sections.0.iter() {
141                            for v in e.variants.iter() {
142                                let variant = v.variant_name().expect("record.fields").to_string();
143                                if s.name == format!("{}.{}", name, variant.as_str()) {
144                                    values.push(ftd::PropertyValue::Value {
145                                        value: ftd::Value::OrType {
146                                            variant,
147                                            name: e.name.to_string(),
148                                            fields: v.fields_from_sub_section(s, doc)?,
149                                        },
150                                    })
151                                }
152                            }
153                        }
154                        ftd::PropertyValue::Value {
155                            value: ftd::Value::List {
156                                kind: list_kind.inner().to_owned(),
157                                data: values,
158                            },
159                        }
160                    }
161                    ftd::p2::Kind::Record { name, .. } => {
162                        let mut list = vec![];
163                        for v in subsections {
164                            let record = doc.get_record(p1.line_number, name.as_str())?;
165                            list.push(ftd::PropertyValue::Value {
166                                value: ftd::Value::Record {
167                                    name: doc.resolve_name(p1.line_number, record.name.as_str())?,
168                                    fields: record.fields_from_sub_section(v, doc)?,
169                                },
170                            });
171                        }
172                        ftd::PropertyValue::Value {
173                            value: ftd::Value::List {
174                                kind: list_kind.inner().to_owned(),
175                                data: list,
176                            },
177                        }
178                    }
179                    ftd::p2::Kind::String { .. } => {
180                        let mut list = vec![];
181                        for v in subsections {
182                            let (text, from_caption) = v.body_or_caption(doc.name)?;
183                            list.push(ftd::PropertyValue::Value {
184                                value: ftd::Value::String {
185                                    text,
186                                    source: match from_caption {
187                                        true => ftd::TextSource::Caption,
188                                        false => ftd::TextSource::Body,
189                                    },
190                                },
191                            });
192                        }
193                        ftd::PropertyValue::Value {
194                            value: ftd::Value::List {
195                                kind: list_kind.inner().to_owned(),
196                                data: list,
197                            },
198                        }
199                    }
200                    ftd::p2::Kind::Integer { .. } => {
201                        return ftd::p2::utils::e2("unexpected integer", doc.name, p1.line_number);
202                    }
203                    t => {
204                        return ftd::p2::utils::e2(
205                            format!("not yet implemented: {:?}", t),
206                            doc.name,
207                            p1.line_number,
208                        )
209                    }
210                },
211                (Ok(_), _) => {
212                    return ftd::p2::utils::e2(
213                        format!("'{:?}' ('{}') can not be a sub-section", kind, name),
214                        doc.name,
215                        p1.line_number,
216                    );
217                }
218                (Err(ftd::p1::Error::NotFound { .. }), _) => {
219                    kind.read_section(p1.line_number, &p1.header, &p1.caption, &p1.body, name, doc)?
220                }
221                (
222                    Err(ftd::p1::Error::MoreThanOneSubSections { .. }),
223                    ftd::p2::Kind::List {
224                        kind: list_kind, ..
225                    },
226                ) => {
227                    let mut values: Vec<ftd::PropertyValue> = vec![];
228                    for s in p1.sub_sections.0.iter() {
229                        if s.name != *name || s.is_commented {
230                            continue;
231                        }
232                        let v = match list_kind.inner().string_any() {
233                            ftd::p2::Kind::Record { name, .. } => {
234                                let record = doc.get_record(p1.line_number, name.as_str())?;
235                                ftd::PropertyValue::Value {
236                                    value: ftd::Value::Record {
237                                        name: doc
238                                            .resolve_name(s.line_number, record.name.as_str())?,
239                                        fields: record.fields_from_sub_section(s, doc)?,
240                                    },
241                                }
242                            }
243                            k => k.read_section(
244                                s.line_number,
245                                &s.header,
246                                &s.caption,
247                                &s.body,
248                                s.name.as_str(),
249                                doc,
250                            )?,
251                        };
252                        values.push(v);
253                    }
254                    ftd::PropertyValue::Value {
255                        value: ftd::Value::List {
256                            kind: list_kind.inner().to_owned(),
257                            data: values,
258                        },
259                    }
260                }
261                (Err(e), _) => return Err(e),
262            };
263            fields.insert(name.to_string(), value);
264        }
265        Ok(fields)
266    }
267
268    pub fn add_instance(
269        &mut self,
270        p1: &ftd::p1::Section,
271        doc: &ftd::p2::TDoc,
272    ) -> ftd::p1::Result<()> {
273        let fields = self.fields(p1, doc)?;
274        self.instances
275            .entry(doc.name.to_string())
276            .or_default()
277            .push(fields);
278        Ok(())
279    }
280
281    pub fn create(
282        &self,
283        p1: &ftd::p1::Section,
284        doc: &ftd::p2::TDoc,
285    ) -> ftd::p1::Result<ftd::PropertyValue> {
286        // todo: check if the its reference to other variable
287        Ok(ftd::PropertyValue::Value {
288            value: ftd::Value::Record {
289                name: doc.resolve_name(p1.line_number, self.name.as_str())?,
290                fields: self.fields(p1, doc)?,
291            },
292        })
293    }
294
295    pub fn fields_from_sub_section(
296        &self,
297        p1: &ftd::p1::SubSection,
298        doc: &ftd::p2::TDoc,
299    ) -> ftd::p1::Result<ftd::Map<ftd::PropertyValue>> {
300        let mut fields: ftd::Map<ftd::PropertyValue> = Default::default();
301        self.assert_no_extra_fields(doc.name, &p1.header, &p1.caption, &p1.body)?;
302        for (name, kind) in self.fields.iter() {
303            fields.insert(
304                name.to_string(),
305                kind.read_section(p1.line_number, &p1.header, &p1.caption, &p1.body, name, doc)?,
306            );
307        }
308        Ok(fields)
309    }
310
311    fn assert_no_extra_fields(
312        &self,
313        doc_id: &str,
314        p1: &ftd::p1::Header,
315        _caption: &Option<String>,
316        _body: &Option<(usize, String)>,
317    ) -> ftd::p1::Result<()> {
318        // TODO: handle caption
319        // TODO: handle body
320        for (i, k, _) in p1.0.iter() {
321            if !self.fields.contains_key(k) && k != "type" && k != "$processor$" {
322                return ftd::p2::utils::e2(
323                    format!(
324                        "unknown key passed: '{}' to '{}', allowed: {:?}",
325                        k,
326                        self.name,
327                        self.fields.keys()
328                    ),
329                    doc_id,
330                    i.to_owned(),
331                );
332            }
333        }
334        Ok(())
335    }
336
337    pub fn from_p1(
338        p1_name: &str,
339        p1_header: &ftd::p1::Header,
340        doc: &ftd::p2::TDoc,
341        line_number: usize,
342    ) -> ftd::p1::Result<Self> {
343        let name = ftd::p2::utils::get_name("record", p1_name, doc.name)?;
344        let full_name = doc.format_name(name);
345        let mut fields = ftd::Map::new();
346        let mut order = vec![];
347        let object_kind = (
348            name,
349            ftd::p2::Kind::Record {
350                name: full_name.clone(),
351                default: None,
352                is_reference: false,
353            },
354        );
355        for (i, k, v) in p1_header.0.iter() {
356            let var_data = match ftd::variable::VariableData::get_name_kind(
357                k,
358                doc,
359                i.to_owned(),
360                vec![].as_slice(),
361            ) {
362                Ok(v) => v,
363                _ => continue,
364            };
365            let v = normalise_value(v)?;
366            validate_key(k)?;
367            let v = if v.is_empty() {
368                None
369            } else {
370                Some(v.to_string())
371            };
372            fields.insert(
373                var_data.name.to_string(),
374                ftd::p2::Kind::for_variable(
375                    i.to_owned(),
376                    k,
377                    v,
378                    doc,
379                    Some(object_kind.clone()),
380                    &Default::default(),
381                )?,
382            );
383            order.push(var_data.name.to_string());
384        }
385        assert_fields_valid(line_number, &fields, doc.name)?;
386        return Ok(Record {
387            name: full_name,
388            fields,
389            instances: Default::default(),
390            order,
391        });
392
393        fn normalise_value(s: &str) -> ftd::p1::Result<String> {
394            // TODO: normalise spaces in v
395            Ok(s.to_string())
396        }
397
398        fn validate_key(_k: &str) -> ftd::p1::Result<()> {
399            // TODO: ensure k in valid (only alphanumeric, _, and -)
400            Ok(())
401        }
402    }
403}
404
405fn assert_fields_valid(
406    line_number: usize,
407    fields: &ftd::Map<ftd::p2::Kind>,
408    doc_id: &str,
409) -> ftd::p1::Result<()> {
410    let mut caption_field: Option<String> = None;
411    let mut body_field: Option<String> = None;
412    for (name, kind) in fields.iter() {
413        if let ftd::p2::Kind::String { caption, body, .. } = kind {
414            if *caption {
415                match &caption_field {
416                    Some(c) => {
417                        return ftd::p2::utils::e2(
418                            format!("both {} and {} are caption fields", name, c),
419                            doc_id,
420                            line_number,
421                        );
422                    }
423                    None => caption_field = Some(name.to_string()),
424                }
425            }
426            if *body {
427                match &body_field {
428                    Some(c) => {
429                        return ftd::p2::utils::e2(
430                            format!("both {} and {} are body fields", name, c),
431                            doc_id,
432                            line_number,
433                        );
434                    }
435                    None => body_field = Some(name.to_string()),
436                }
437            }
438        }
439    }
440    Ok(())
441}