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 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 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 Ok(s.to_string())
396 }
397
398 fn validate_key(_k: &str) -> ftd::p1::Result<()> {
399 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}