framework/inner/
table.rs

1use parse::{
2    LowerName,
3    Map,
4    UpperName,
5};
6use parse::xml::*;
7
8use crate::pass0::{
9    Classification0,
10    ClassificationRestrict0,
11    Field0,
12    FieldKind0,
13    Framework0,
14    ReferRestrict0,
15    ReferRestrictTo0,
16    Summary0,
17    Table0,
18    Index0,
19};
20
21use crate::{
22    ChoiceId,
23    ClassificationId,
24    CustomSave,
25    Error,
26    FieldId,
27    IndexId,
28    TableId,
29};
30
31use super::{
32    DottedField,
33    Suggest,
34};
35
36pub struct Table {
37    pub abbreviation: String,
38    pub archived: bool,
39    pub custom_save: CustomSave,
40    pub memory: bool,
41    pub modified: bool,
42    pub name: UpperName,
43    pub singleton: bool,
44    pub title: String,
45
46    pub (crate) fields: Map<LowerName, Field, FieldId>,
47    pub (crate) indexes: Map<LowerName, Index, IndexId>,
48    pub (crate) refer_fields: Option<Summary>,
49    pub (crate) refer_summary: Option<Summary>,
50    pub (crate) suggest: Option<Suggest>,
51    pub (crate) summary: Option<Summary>,
52}
53
54pub struct Field {
55    pub abbreviation: String,
56    pub (crate) kind: FieldKind,
57    pub immutable: bool,
58    pub memory: bool,
59    pub name: LowerName,
60    pub null: bool,
61    pub title: String,
62}
63
64pub (crate) enum FieldKind {
65    Address(AddressField),
66    Alias(DottedField),
67    Binary,
68    Boolean(BooleanField),
69    Choice(ChoiceField),
70    Classification(ClassificationField),
71    Content,
72    Date,
73    Identifier(IdentifierField),
74    Formula,
75    EFTSL,
76    Memo,
77    Money,
78    Names,
79    Number(NumberField),
80    Percent,
81    Refer(ReferField),
82    Sequence,
83    String(StringField),
84    Time,
85}
86
87pub (crate) struct AddressField {
88    pub (crate) address1: FieldId,
89    pub (crate) address2: FieldId,
90    pub (crate) country: FieldId,
91    pub (crate) postcode: FieldId,
92    pub (crate) state: FieldId,
93    pub (crate) suburb: FieldId,
94}
95
96pub struct BooleanField {
97    pub display_as: Option<BooleanDisplayAs>,
98}
99
100#[derive(Clone)]
101pub struct BooleanDisplayAs {
102    pub no: String,
103    pub null: String,
104    pub yes: String,
105}
106
107pub struct ChoiceField {
108    pub display_null_as: Option<String>,
109    pub (crate) choice: ChoiceId,
110}
111
112pub (crate) struct ClassificationField {
113    pub (crate) classification: ClassificationId,
114    pub (crate) restrict: Option<ClassificationRestrict>,
115}
116
117pub (crate) enum ClassificationRestrict {
118    State(DottedField),
119    National(DottedField),
120}
121
122pub struct IdentifierField {
123    pub automate: bool,
124    pub length: usize,
125}
126
127pub struct NumberField {
128    pub range: Option<NumberRange>,
129}
130
131#[derive(Clone)]
132pub struct NumberRange {
133    pub minimum: usize,
134    pub maximum: usize,
135}
136
137pub struct ReferField {
138    pub display_null_as: Option<String>,
139    pub kind: ReferKind,
140    pub (crate) refer: TableId,
141    pub (crate) restrict: Option<ReferRestrict>,
142}
143
144#[derive(Clone)]
145pub enum ReferKind {
146    Container,
147    DropContainer,
148    DropExtend,
149    Extend,
150    Normal,
151    Super,
152}
153
154pub (crate) struct ReferRestrict {
155    pub (crate) field: DottedField,
156    pub (crate) value: ReferRestrictTo,
157}
158
159pub (crate) enum ReferRestrictTo {
160    Field(DottedField),
161    Table,
162}
163
164pub struct StringField {
165    pub kind: StringKind,
166    pub length: usize,
167}
168
169#[derive(Clone)]
170#[derive(Debug)]
171#[derive(PartialEq)]
172pub enum StringKind {
173    ABN,
174    CHESSN,
175    Email,
176    Normal,
177    LUI,
178    Phone,
179    Postcode,
180    Street,
181    Suburb,
182    TFN,
183    USI,
184    VSN,
185    Year,
186}
187
188pub (crate) struct Summary {
189    pub fields: Vec<FieldId>,
190}
191
192pub struct Index {
193    pub (crate) fields: Vec<FieldId>,
194    pub kind: IndexKind,
195    pub name: LowerName,
196}
197
198#[derive(Clone)]
199#[derive(Debug)]
200pub enum IndexKind {
201    Index,
202    Unique {
203        allow_nulls: bool,
204    },
205}
206
207impl Table {
208    pub (crate) fn map(
209        from: &ParsedEntry<UpperName, Table0, TableId>,
210        cx: &Framework0,
211        errors: &mut Vec<Error>,
212    ) -> Option<Self> {
213        let table0 = &from.value;
214
215        let Some(fields) = table0.fields.map_cx2(
216            Field::map,
217            from,
218            cx,
219            errors,
220        ) else {
221            return None;
222        };
223
224        let Some(indexes) = table0.indexes.map_cx(
225            Index::map,
226            table0,
227            errors,
228        ) else {
229            return None;
230        };
231
232        let marker = errors.marker();
233
234        let refer_fields = table0.refer_fields.as_ref().and_then(|summary0|
235            Summary::map(summary0, table0, errors)
236        );
237
238        let refer_summary = table0.refer_summary.as_ref().and_then(|summary0|
239            Summary::map(summary0, table0, errors)
240        );
241
242        let suggest = Suggest::map(&table0.suggest, from, cx, errors);
243
244        let summary = table0.summary.as_ref().and_then(|summary0|
245            Summary::map(summary0, table0, errors)
246        );
247
248        if errors.since(marker) {
249            return None;
250        }
251
252        Some(Table {
253            abbreviation: table0.abbreviation.clone(),
254            archived: table0.archived,
255            custom_save: table0.custom_save,
256            memory: table0.memory,
257            modified: table0.modified,
258            name: table0.name.clone(),
259            singleton: table0.singleton,
260            title: table0.title.clone(),
261
262            fields,
263            indexes,
264            refer_fields,
265            refer_summary,
266            suggest,
267            summary,
268        })
269    }
270
271    pub fn field_by_id(&self, id: &FieldId) -> &Field {
272        self.fields.vec.get(id.index())
273            .expect("Exist by construction")
274    }
275
276    pub fn field_by_name(&self, name: &LowerName) -> Option<(FieldId, &Field)> {
277        if let Some(id) = self.fields.names.get(name) {
278            let field = self.field_by_id(id);
279
280            Some((id.clone(), field))
281        } else {
282            None
283        }
284    }
285}
286
287impl ClassificationRestrict {
288    pub (crate) fn map(
289        table0: &ParsedEntry<UpperName, Table0, TableId>,
290        field0: &Field0,
291        classification0: &Classification0,
292        restrict0: &Option<ClassificationRestrict0>,
293        cx: &Framework0,
294        errors: &mut Vec<Error>,
295    ) -> Option<Self> {
296        if classification0.has_state {
297            if let Some(ClassificationRestrict0::State { state }) = restrict0 {
298                let Some(state) = DottedField::map_field(state, table0, cx, errors) else {
299                    return None;
300                };
301                if !state.field.kind.is_avetmiss_state() {
302                    errors.push(Error::FieldKindNot {
303                        expecting: "choice AvetmissState",
304                        found: state.field.kind.kind_string(),
305                        value: state.range.clone().into(),
306                    });
307                }
308                return Some(ClassificationRestrict::State(state.result));
309            } else {
310                errors.push(Error::RestrictExpectState {
311                    field: field0.range.clone().into(),
312                });
313            }
314        } else if let Some(limit) = &classification0.has_national_limit {
315            if let Some(ClassificationRestrict0::National { national }) = restrict0 {
316                let Some(national) = DottedField::map_field(national, table0, cx, errors) else {
317                    return None;
318                };
319                if !national.field.kind.is_classification(limit) {
320                    errors.push(Error::FieldKindNot {
321                        expecting: "national classification",
322                        found: national.field.kind.kind_string(),
323                        value: national.range.clone().into(),
324                    });
325                }
326                return Some(ClassificationRestrict::National(national.result));
327            } else {
328                errors.push(Error::RestrictExpectNational {
329                    field: field0.range.clone().into(),
330                });
331            }
332        } else {
333            if restrict0.is_some() {
334                errors.push(Error::RestrictExpectNone {
335                    field: field0.range.clone().into(),
336                });
337            }
338        }
339
340        None
341    }
342}
343
344impl Field {
345    pub (crate) fn map(
346        from: &ParsedEntry<LowerName, Field0, FieldId>,
347        table0: &ParsedEntry<UpperName, Table0, TableId>,
348        cx: &Framework0,
349        errors: &mut Vec<Error>,
350    ) -> Option<Self> {
351        let field0 = &from.value;
352
353        let Some(kind) = FieldKind::map(table0, field0, &field0.kind, cx, errors) else {
354            return None;
355        };
356
357        Some(Field {
358            abbreviation: field0.abbreviation.clone(),
359            kind,
360            immutable: field0.immutable,
361            memory: field0.memory,
362            name: from.name.clone(),
363            null: field0.null,
364            title: field0.title.clone(),
365        })
366    }
367}
368
369impl FieldKind {
370    pub (crate) fn map(
371        table0: &ParsedEntry<UpperName, Table0, TableId>,
372        field0: &Field0,
373        kind0: &FieldKind0,
374        cx: &Framework0,
375        errors: &mut Vec<Error>,
376    ) -> Option<Self> {
377        match kind0 {
378            FieldKind0::Address { address1_name, address2_name, country_name, postcode_name, state_name, suburb_name } =>
379                Some(FieldKind::Address(AddressField {
380                    address1: table0.value.fields.get_id(address1_name)
381                        .expect("Exist by construction"),
382                    address2: table0.value.fields.get_id(address2_name)
383                        .expect("Exist by construction"),
384                    country: table0.value.fields.get_id(country_name)
385                        .expect("Exist by construction"),
386                    postcode: table0.value.fields.get_id(postcode_name)
387                        .expect("Exist by construction"),
388                    state: table0.value.fields.get_id(state_name)
389                        .expect("Exist by construction"),
390                    suburb: table0.value.fields.get_id(suburb_name)
391                        .expect("Exist by construction"),
392                })),
393
394            FieldKind0::Alias { alias } => {
395                let Some(alias) = DottedField::map_alias(alias, table0, cx, errors) else {
396                    return None;
397                };
398
399                Some(FieldKind::Alias(alias))
400            }
401
402            FieldKind0::AvetmissRelease =>
403                Self::map_to_choice("AvetmissRelease", cx),
404
405            FieldKind0::AvetmissState =>
406                Self::map_to_choice("AvetmissState", cx),
407
408            FieldKind0::AvetmissSystem =>
409                Self::map_to_choice("AvetmissSystem", cx),
410
411            FieldKind0::Binary =>
412                Some(FieldKind::Binary),
413
414            FieldKind0::Boolean { display_as } =>
415                Some(FieldKind::Boolean(BooleanField {
416                    display_as: display_as.clone(),
417                })),
418
419            FieldKind0::Choice { display_null_as, name } =>
420                if let Some(id) = cx.choices.get_id(&name.value) {
421                    Some(FieldKind::Choice(ChoiceField {
422                        display_null_as: display_null_as.clone(),
423                        choice: id,
424                    }))
425                } else {
426                    errors.push(Error::ChoiceUnknown {
427                        value: name.range.clone().into(),
428                    });
429
430                    None
431                }
432
433            FieldKind0::Classification { name, restrict } =>
434                if let Some((id, classification0)) = cx.classifications.get(&name.value) {
435                    let restrict = ClassificationRestrict::map(
436                        table0,
437                        field0,
438                        classification0,
439                        restrict,
440                        cx,
441                        errors,
442                    );
443
444                    Some(FieldKind::Classification(ClassificationField {
445                        classification: id,
446                        restrict,
447                    }))
448                } else {
449                    errors.push(Error::ClassificationUnknown {
450                        value: name.range.clone().into(),
451                    });
452
453                    None
454                }
455
456            FieldKind0::Country { range } => {
457                if let Some(id) = cx.classifications.get_id("Country") {
458                    Some(FieldKind::Classification(ClassificationField {
459                        classification: id,
460                        restrict: None,
461                    }))
462                } else {
463                    errors.push(Error::ClassificationUnknown {
464                        value: range.clone().into(),
465                    });
466
467                    None
468                }
469            }
470
471            FieldKind0::Content =>
472                Some(FieldKind::Content),
473
474            FieldKind0::Date =>
475                Some(FieldKind::Date),
476
477            FieldKind0::EFTSL =>
478                Some(FieldKind::EFTSL),
479
480            FieldKind0::Formula =>
481                Some(FieldKind::Formula),
482
483            FieldKind0::Identifier { automate, length } =>
484                Some(FieldKind::Identifier(IdentifierField {
485                    automate: automate.clone(),
486                    length: length.clone(),
487                })),
488
489            FieldKind0::Memo =>
490                Some(FieldKind::Memo),
491
492            FieldKind0::Money =>
493                Some(FieldKind::Money),
494
495            FieldKind0::Names =>
496                Some(FieldKind::Names),
497
498            FieldKind0::Number { range } =>
499                Some(FieldKind::Number(NumberField {
500                    range: range.clone(),
501                })),
502
503            FieldKind0::Percent =>
504                Some(FieldKind::Percent),
505
506            FieldKind0::Refer { display_null_as, kind, refer, restrict } =>
507                if let Some((id, refer0)) = cx.tables.get_parsed(&refer.value) {
508                    let restrict = if let Some(restrict0) = restrict {
509                        ReferRestrict::map(
510                            table0,
511                            refer0,
512                            restrict0,
513                            cx,
514                            errors,
515                        )
516                    } else {
517                        None
518                    };
519
520                    Some(FieldKind::Refer(ReferField {
521                        display_null_as: display_null_as.clone(),
522                        kind: kind.clone(),
523                        refer: id,
524                        restrict,
525                    }))
526                } else {
527                    errors.push(Error::TableUnknown {
528                        value: refer.range.clone().into(),
529                    });
530
531                    None
532                }
533
534            FieldKind0::Sequence =>
535                Some(FieldKind::Sequence),
536
537            FieldKind0::State =>
538                Self::map_to_choice("State", cx),
539
540            FieldKind0::String { kind, length } =>
541                Some(FieldKind::String(StringField {
542                    kind: kind.clone(),
543                    length: length.clone(),
544                })),
545
546            FieldKind0::Time =>
547                Some(FieldKind::Time),
548        }
549    }
550
551    fn map_to_choice(name: &'static str, cx: &Framework0) -> Option<Self> {
552        let choice = cx.choices.get_id(name)
553            .expect("Exist as a builtin");
554        Some(FieldKind::Choice(ChoiceField {
555            display_null_as: None,
556            choice,
557        }))
558    }
559}
560
561impl Index {
562    pub (crate) fn map(
563        from: &ParsedEntry<LowerName, Index0, IndexId>,
564        table0: &Table0,
565        errors: &mut Vec<Error>,
566    ) -> Option<Self> {
567        let index0 = &from.value;
568
569        let marker = errors.marker();
570
571        let mut fields = Vec::new();
572        for field0 in &index0.fields {
573            if let Some(id) = table0.fields.get_id(&field0.value) {
574                fields.push(id);
575            } else {
576                errors.push(Error::FieldUnknown {
577                    table: table0.name.to_string(),
578                    value: field0.range.clone().into(),
579                });
580            }
581        }
582
583        if errors.since(marker) {
584            return None;
585        }
586
587        Some(Index {
588            fields,
589            kind: index0.kind.clone(),
590            name: from.name.clone(),
591        })
592    }
593}
594
595impl ReferRestrict {
596    pub (crate) fn map(
597        table0: &ParsedEntry<UpperName, Table0, TableId>,
598        refer0: &ParsedEntry<UpperName, Table0, TableId>,
599        restrict0: &ReferRestrict0,
600        cx: &Framework0,
601        errors: &mut Vec<Error>,
602    ) -> Option<Self> {
603        let Some(field) = DottedField::map_field(&restrict0.field, refer0, cx, errors) else {
604            return None;
605        };
606
607        match &field.field.kind {
608            FieldKind0::Choice { .. } =>
609                match &restrict0.value {
610                    ReferRestrictTo0::Field { field: value } => {
611                        let Some(value) = DottedField::map_field(value, table0, cx, errors) else {
612                            return None;
613                        };
614                        if value.field.kind.is_same_as(&field.field.kind) {
615                            return Some(ReferRestrict {
616                                field: field.result,
617                                value: ReferRestrictTo::Field(value.result),
618                            });
619                        } else {
620                            errors.push(Error::RestrictKinds {
621                                field: field.range.clone().into(),
622                                field_kind: field.field.kind.kind_string(),
623                                value: value.range.clone().into(),
624                                value_kind: value.field.kind.kind_string(),
625                            });
626                        }
627                    }
628
629                    ReferRestrictTo0::Table { range } =>
630                        errors.push(Error::RestrictKinds {
631                            field: field.range.clone().into(),
632                            field_kind: field.field.kind.kind_string(),
633                            value: range.clone().into(),
634                            value_kind: format!("{} refer", refer0.name),
635                        }),
636                }
637
638            FieldKind0::Refer { refer: field_refer, .. } =>
639                match &restrict0.value {
640                    ReferRestrictTo0::Field { field: value } => {
641                        let Some(value) = DottedField::map_field(value, table0, cx, errors) else {
642                            return None;
643                        };
644                        if value.field.kind.is_same_as(&field.field.kind) {
645                            return Some(ReferRestrict {
646                                field: field.result,
647                                value: ReferRestrictTo::Field(value.result),
648                            });
649                        } else {
650                            errors.push(Error::RestrictKinds {
651                                field: field.range.clone().into(),
652                                field_kind: field.field.kind.kind_string(),
653                                value: value.range.clone().into(),
654                                value_kind: value.field.kind.kind_string(),
655                            });
656                        }
657                    }
658
659                    ReferRestrictTo0::Table { range } =>
660                        if field_refer.value == table0.name {
661                            return Some(ReferRestrict {
662                                field: field.result,
663                                value: ReferRestrictTo::Table,
664                            });
665                        } else {
666                            errors.push(Error::RestrictKinds {
667                                field: field.range.clone().into(),
668                                field_kind: field.field.kind.kind_string(),
669                                value: range.clone().into(),
670                                value_kind: format!("{} refer", table0.name),
671                            });
672                        }
673                }
674
675            _ =>
676                errors.push(Error::FieldKindNot {
677                    expecting: "choice or refer",
678                    found: field.field.kind.kind_string(),
679                    value: field.range.clone().into(),
680                }),
681        }
682
683        None
684    }
685}
686
687impl Summary {
688    pub fn map<'input>(
689        summary0: &Summary0,
690        table0: &Table0,
691        errors: &mut Vec<Error>,
692    ) -> Option<Self> {
693        let marker = errors.marker();
694
695        let mut fields = Vec::new();
696        for field0 in &summary0.fields {
697            if let Some(id) = table0.fields.get_id(&field0.value) {
698                fields.push(id);
699            } else {
700                errors.push(Error::FieldUnknown {
701                    table: table0.name.to_string(),
702                    value: field0.range.clone().into(),
703                });
704            }
705        }
706
707        if errors.since(marker) {
708            return None;
709        }
710
711        Some(Summary {
712            fields,
713        })
714    }
715}