use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use super::error::StateError;
use super::state::{SystemState, TimePoint};
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct FieldSpec {
#[serde(skip)]
index: usize,
name: Box<str>,
#[serde(rename = "type")]
type_tag: Box<str>,
}
impl FieldSpec {
fn new(index: usize, name: &str, type_tag: &str) -> Self {
Self {
index,
name: name.trim().into(),
type_tag: type_tag.trim().into(),
}
}
pub fn index(&self) -> usize {
self.index
}
pub fn name(&self) -> &str {
&self.name
}
pub fn type_tag(&self) -> &str {
&self.type_tag
}
}
#[derive(Clone, Debug)]
pub struct StateSpec {
inner: Arc<StateLayout>,
}
impl StateSpec {
pub fn load(path: impl AsRef<Path>) -> Result<Self, StateError> {
let path = path.as_ref();
let bytes = fs::read(path).map_err(|source| StateError::TemplateRead {
path: path.to_path_buf(),
source,
})?;
let template: StateTemplate =
serde_json::from_slice(&bytes).map_err(|source| StateError::TemplateParse {
path: path.to_path_buf(),
source,
})?;
Self::from_template(path.to_path_buf(), template)
}
pub fn empty(&self, time: TimePoint) -> SystemState {
SystemState::new(self.clone(), time)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&StateTemplateRef {
fields: self.fields(),
})
}
pub fn source(&self) -> &Path {
&self.inner.source
}
pub fn fields(&self) -> &[FieldSpec] {
&self.inner.fields
}
pub fn len(&self) -> usize {
self.inner.fields.len()
}
pub fn is_empty(&self) -> bool {
self.inner.fields.is_empty()
}
pub fn get(&self, name: &str) -> Option<&FieldSpec> {
let index = self.inner.by_name.get(name)?;
self.inner.fields.get(*index)
}
pub fn contains(&self, name: &str) -> bool {
self.inner.by_name.contains_key(name)
}
pub(crate) fn index_of(&self, name: &str) -> Result<usize, StateError> {
self.inner
.by_name
.get(name)
.copied()
.ok_or_else(|| StateError::UnknownField {
field: name.to_owned(),
})
}
fn from_template(source: PathBuf, template: StateTemplate) -> Result<Self, StateError> {
let mut fields = Vec::with_capacity(template.fields.len());
let mut by_name = HashMap::with_capacity(template.fields.len());
for (index, declaration) in template.fields.into_iter().enumerate() {
let name = declaration.name.trim();
if name.is_empty() {
return Err(StateError::EmptyFieldName { index });
}
let type_tag = declaration.type_tag.trim();
if type_tag.is_empty() {
return Err(StateError::EmptyTypeTag {
field: name.to_owned(),
});
}
if by_name.contains_key(name) {
return Err(StateError::DuplicateField {
field: name.to_owned(),
});
}
let field = FieldSpec::new(index, name, type_tag);
by_name.insert(field.name.clone(), index);
fields.push(field);
}
Ok(Self {
inner: Arc::new(StateLayout {
source,
fields,
by_name,
}),
})
}
}
#[derive(Debug)]
struct StateLayout {
source: PathBuf,
fields: Vec<FieldSpec>,
by_name: HashMap<Box<str>, usize>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct StateTemplate {
fields: Vec<FieldDeclaration>,
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct FieldDeclaration {
name: String,
#[serde(rename = "type")]
type_tag: String,
}
#[derive(Serialize)]
struct StateTemplateRef<'a> {
fields: &'a [FieldSpec],
}