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::{SimulationTime, SystemState};
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct StateFieldSchema {
#[serde(skip)]
index: usize,
name: Box<str>,
#[serde(skip_serializing_if = "Option::is_none")]
description: Option<Box<str>>,
}
impl StateFieldSchema {
fn new(index: usize, name: &str, description: Option<&str>) -> Self {
Self {
index,
name: name.trim().into(),
description: description
.map(str::trim)
.filter(|description| !description.is_empty())
.map(Into::into),
}
}
pub fn position(&self) -> usize {
self.index
}
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> Option<&str> {
self.description.as_deref()
}
}
#[derive(Clone, Debug)]
pub struct SystemStateSchema {
inner: Arc<StateLayout>,
}
impl SystemStateSchema {
pub fn load_json_template(path: impl AsRef<Path>) -> Result<Self, StateError> {
let source = path.as_ref().to_path_buf();
let bytes = fs::read(&source).map_err(|error| StateError::TemplateRead {
path: source.clone(),
source: error,
})?;
Self::parse(source, &bytes)
}
pub(crate) fn parse(source: PathBuf, bytes: &[u8]) -> Result<Self, StateError> {
let template: StateTemplate =
serde_json::from_slice(bytes).map_err(|error| StateError::TemplateParse {
path: source.clone(),
source: error,
})?;
Self::from_template(source, template)
}
pub fn create_empty_state(&self, time: SimulationTime) -> SystemState {
SystemState::new(self.clone(), time)
}
pub fn to_json_template(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&StateTemplateRef {
fields: self.field_schemas(),
})
}
pub fn template_path(&self) -> &Path {
&self.inner.source
}
pub fn field_schemas(&self) -> &[StateFieldSchema] {
&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 field_schema(&self, name: &str) -> Option<&StateFieldSchema> {
let index = self.inner.by_name.get(name)?;
self.inner.fields.get(*index)
}
pub fn contains_field(&self, name: &str) -> bool {
self.inner.by_name.contains_key(name)
}
pub(crate) fn shares_schema_instance(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.inner, &other.inner)
}
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 });
}
if by_name.contains_key(name) {
return Err(StateError::DuplicateField {
field: name.to_owned(),
});
}
let field = StateFieldSchema::new(index, name, declaration.description.as_deref());
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<StateFieldSchema>,
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(default)]
description: Option<String>,
}
#[derive(Serialize)]
struct StateTemplateRef<'a> {
fields: &'a [StateFieldSchema],
}