#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct Record {
pub name: String,
pub fields: ftd::Map<ftd::p2::Kind>,
pub instances: ftd::Map<Vec<Invocation>>,
pub order: Vec<String>,
}
pub(crate) type Invocation = ftd::Map<ftd::PropertyValue>;
impl Record {
pub fn variant_name(&self) -> Option<&str> {
self.name.split_once('.').map(|(_, r)| r)
}
pub fn fields(
&self,
p1: &ftd::p1::Section,
doc: &ftd::p2::TDoc,
) -> ftd::p1::Result<ftd::Map<ftd::PropertyValue>> {
let mut fields: ftd::Map<ftd::PropertyValue> = Default::default();
self.assert_no_extra_fields(doc.name, &p1.header, &p1.caption, &p1.body)?;
for (name, kind) in self.fields.iter() {
let subsections = p1.sub_sections_by_name(name);
let value = match (
p1.sub_section_by_name(name, doc.name.to_string()),
kind.inner(),
) {
(Ok(v), ftd::p2::Kind::String { .. }) => ftd::PropertyValue::Value {
value: ftd::Value::String {
text: v.body(doc.name)?,
source: ftd::TextSource::Body,
},
},
(Ok(v), ftd::p2::Kind::Record { name, .. }) => {
let record = doc.get_record(p1.line_number, name.as_str())?;
ftd::PropertyValue::Value {
value: ftd::Value::Record {
name: doc.resolve_name(p1.line_number, record.name.as_str())?,
fields: record.fields_from_sub_section(v, doc)?,
},
}
}
(
Err(ftd::p1::Error::NotFound { .. }),
ftd::p2::Kind::List {
kind: list_kind, ..
},
) => match list_kind.as_ref() {
ftd::p2::Kind::OrType {
name: or_type_name, ..
}
| ftd::p2::Kind::OrTypeWithVariant {
name: or_type_name, ..
} => {
let e = doc.get_or_type(p1.line_number, or_type_name)?;
let mut values: Vec<ftd::PropertyValue> = vec![];
for s in p1.sub_sections.0.iter() {
if s.is_commented {
continue;
}
for v in e.variants.iter() {
let variant = v.variant_name().expect("record.fields").to_string();
if s.name == format!("{}.{}", name, variant.as_str()) {
values.push(ftd::PropertyValue::Value {
value: ftd::Value::OrType {
variant,
name: e.name.to_string(),
fields: v.fields_from_sub_section(s, doc)?,
},
})
}
}
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: values,
},
}
}
ftd::p2::Kind::Record { .. } => {
let mut list = ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: vec![],
};
for (i, k, v) in p1.header.0.iter() {
if *k != *name {
continue;
}
list = doc.get_value(i.to_owned(), v)?;
}
ftd::PropertyValue::Value { value: list }
}
ftd::p2::Kind::String { .. } => {
let mut values: Vec<ftd::PropertyValue> = vec![];
for (_, k, v) in p1.header.0.iter() {
if *k != *name {
continue;
}
values.push(ftd::PropertyValue::Value {
value: ftd::Value::String {
text: v.to_string(),
source: ftd::TextSource::Header,
},
});
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: values,
},
}
}
ftd::p2::Kind::Integer { .. } => {
return ftd::p2::utils::e2("unexpected integer", doc.name, p1.line_number);
}
t => {
return ftd::p2::utils::e2(
format!("not yet implemented: {:?}", t),
doc.name,
p1.line_number,
)
}
},
(
_,
ftd::p2::Kind::List {
kind: list_kind, ..
},
) if !subsections.is_empty() => match list_kind.as_ref() {
ftd::p2::Kind::OrType {
name: or_type_name, ..
}
| ftd::p2::Kind::OrTypeWithVariant {
name: or_type_name, ..
} => {
let e = doc.get_or_type(p1.line_number, or_type_name)?;
let mut values: Vec<ftd::PropertyValue> = vec![];
for s in p1.sub_sections.0.iter() {
for v in e.variants.iter() {
let variant = v.variant_name().expect("record.fields").to_string();
if s.name == format!("{}.{}", name, variant.as_str()) {
values.push(ftd::PropertyValue::Value {
value: ftd::Value::OrType {
variant,
name: e.name.to_string(),
fields: v.fields_from_sub_section(s, doc)?,
},
})
}
}
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: values,
},
}
}
ftd::p2::Kind::Record { name, .. } => {
let mut list = vec![];
for v in subsections {
let record = doc.get_record(p1.line_number, name.as_str())?;
list.push(ftd::PropertyValue::Value {
value: ftd::Value::Record {
name: doc.resolve_name(p1.line_number, record.name.as_str())?,
fields: record.fields_from_sub_section(v, doc)?,
},
});
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: list,
},
}
}
ftd::p2::Kind::String { .. } => {
let mut list = vec![];
for v in subsections {
let (text, from_caption) = v.body_or_caption(doc.name)?;
list.push(ftd::PropertyValue::Value {
value: ftd::Value::String {
text,
source: match from_caption {
true => ftd::TextSource::Caption,
false => ftd::TextSource::Body,
},
},
});
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: list,
},
}
}
ftd::p2::Kind::Integer { .. } => {
return ftd::p2::utils::e2("unexpected integer", doc.name, p1.line_number);
}
t => {
return ftd::p2::utils::e2(
format!("not yet implemented: {:?}", t),
doc.name,
p1.line_number,
)
}
},
(Ok(_), _) => {
return ftd::p2::utils::e2(
format!("'{:?}' ('{}') can not be a sub-section", kind, name),
doc.name,
p1.line_number,
);
}
(Err(ftd::p1::Error::NotFound { .. }), _) => {
kind.read_section(p1.line_number, &p1.header, &p1.caption, &p1.body, name, doc)?
}
(
Err(ftd::p1::Error::MoreThanOneSubSections { .. }),
ftd::p2::Kind::List {
kind: list_kind, ..
},
) => {
let mut values: Vec<ftd::PropertyValue> = vec![];
for s in p1.sub_sections.0.iter() {
if s.name != *name || s.is_commented {
continue;
}
let v = match list_kind.inner().string_any() {
ftd::p2::Kind::Record { name, .. } => {
let record = doc.get_record(p1.line_number, name.as_str())?;
ftd::PropertyValue::Value {
value: ftd::Value::Record {
name: doc
.resolve_name(s.line_number, record.name.as_str())?,
fields: record.fields_from_sub_section(s, doc)?,
},
}
}
k => k.read_section(
s.line_number,
&s.header,
&s.caption,
&s.body,
s.name.as_str(),
doc,
)?,
};
values.push(v);
}
ftd::PropertyValue::Value {
value: ftd::Value::List {
kind: list_kind.inner().to_owned(),
data: values,
},
}
}
(Err(e), _) => return Err(e),
};
fields.insert(name.to_string(), value);
}
Ok(fields)
}
pub fn add_instance(
&mut self,
p1: &ftd::p1::Section,
doc: &ftd::p2::TDoc,
) -> ftd::p1::Result<()> {
let fields = self.fields(p1, doc)?;
self.instances
.entry(doc.name.to_string())
.or_default()
.push(fields);
Ok(())
}
pub fn create(
&self,
p1: &ftd::p1::Section,
doc: &ftd::p2::TDoc,
) -> ftd::p1::Result<ftd::PropertyValue> {
Ok(ftd::PropertyValue::Value {
value: ftd::Value::Record {
name: doc.resolve_name(p1.line_number, self.name.as_str())?,
fields: self.fields(p1, doc)?,
},
})
}
pub fn fields_from_sub_section(
&self,
p1: &ftd::p1::SubSection,
doc: &ftd::p2::TDoc,
) -> ftd::p1::Result<ftd::Map<ftd::PropertyValue>> {
let mut fields: ftd::Map<ftd::PropertyValue> = Default::default();
self.assert_no_extra_fields(doc.name, &p1.header, &p1.caption, &p1.body)?;
for (name, kind) in self.fields.iter() {
fields.insert(
name.to_string(),
kind.read_section(p1.line_number, &p1.header, &p1.caption, &p1.body, name, doc)?,
);
}
Ok(fields)
}
fn assert_no_extra_fields(
&self,
doc_id: &str,
p1: &ftd::p1::Header,
_caption: &Option<String>,
_body: &Option<(usize, String)>,
) -> ftd::p1::Result<()> {
for (i, k, _) in p1.0.iter() {
if !self.fields.contains_key(k) && k != "type" && k != "$processor$" {
return ftd::p2::utils::e2(
format!(
"unknown key passed: '{}' to '{}', allowed: {:?}",
k,
self.name,
self.fields.keys()
),
doc_id,
i.to_owned(),
);
}
}
Ok(())
}
pub fn from_p1(
p1_name: &str,
p1_header: &ftd::p1::Header,
doc: &ftd::p2::TDoc,
line_number: usize,
) -> ftd::p1::Result<Self> {
let name = ftd::p2::utils::get_name("record", p1_name, doc.name)?;
let full_name = doc.format_name(name);
let mut fields = ftd::Map::new();
let mut order = vec![];
let object_kind = (
name,
ftd::p2::Kind::Record {
name: full_name.clone(),
default: None,
is_reference: false,
},
);
for (i, k, v) in p1_header.0.iter() {
let var_data = match ftd::variable::VariableData::get_name_kind(
k,
doc,
i.to_owned(),
vec![].as_slice(),
) {
Ok(v) => v,
_ => continue,
};
let v = normalise_value(v)?;
validate_key(k)?;
let v = if v.is_empty() {
None
} else {
Some(v.to_string())
};
fields.insert(
var_data.name.to_string(),
ftd::p2::Kind::for_variable(
i.to_owned(),
k,
v,
doc,
Some(object_kind.clone()),
&Default::default(),
)?,
);
order.push(var_data.name.to_string());
}
assert_fields_valid(line_number, &fields, doc.name)?;
return Ok(Record {
name: full_name,
fields,
instances: Default::default(),
order,
});
fn normalise_value(s: &str) -> ftd::p1::Result<String> {
Ok(s.to_string())
}
fn validate_key(_k: &str) -> ftd::p1::Result<()> {
Ok(())
}
}
}
fn assert_fields_valid(
line_number: usize,
fields: &ftd::Map<ftd::p2::Kind>,
doc_id: &str,
) -> ftd::p1::Result<()> {
let mut caption_field: Option<String> = None;
let mut body_field: Option<String> = None;
for (name, kind) in fields.iter() {
if let ftd::p2::Kind::String { caption, body, .. } = kind {
if *caption {
match &caption_field {
Some(c) => {
return ftd::p2::utils::e2(
format!("both {} and {} are caption fields", name, c),
doc_id,
line_number,
);
}
None => caption_field = Some(name.to_string()),
}
}
if *body {
match &body_field {
Some(c) => {
return ftd::p2::utils::e2(
format!("both {} and {} are body fields", name, c),
doc_id,
line_number,
);
}
None => body_field = Some(name.to_string()),
}
}
}
}
Ok(())
}