use std::{
collections::BTreeMap,
path::{Path, PathBuf},
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use star_toml::{Validate, Validator};
use crate::error::{AppError, Result};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GgenConfig {
pub project: Project,
pub ontology: Ontology,
#[serde(default)]
pub packs: BTreeMap<String, PackRef>,
pub templates: Templates,
#[serde(default)]
pub law: Law,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Law {
#[serde(default)]
pub rules: Vec<PathBuf>,
#[serde(default)]
pub shapes: Vec<PathBuf>,
#[serde(default)]
pub gates: Vec<PathBuf>,
#[serde(default)]
pub reflexive: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Project {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Ontology {
pub source: PathBuf,
#[serde(default)]
pub imports: Vec<PathBuf>,
#[serde(default)]
pub prefixes: BTreeMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(untagged)]
pub enum PackRef {
Path {
path: PathBuf,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
extra_ontologies: Vec<PathBuf>,
#[serde(default = "default_true")]
lock: bool,
},
Git {
git: String,
version: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
subdir: Option<PathBuf>,
},
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct Templates {
pub dir: PathBuf,
#[serde(default)]
pub aggregate_modules: bool,
#[serde(default)]
pub consumer_mode: bool,
}
impl GgenConfig {
pub fn load(path: &Path) -> Result<Self> {
let parsed = star_toml::load_file::<Self>(path).map_err(|e| match e {
star_toml::Error::FileNotFound(p) => AppError::fm_config(
1,
format!(
"ggen.toml not found at `{}`. Remediation: create the manifest or fix the path.",
p.display()
),
),
star_toml::Error::Io { .. } => AppError::fm_config(
1,
format!("cannot read `{}`: {e}. Remediation: check file permissions.", path.display()),
),
other => AppError::fm_config(
2,
format!(
"invalid ggen.toml at `{}`: {other}. Remediation: fix the TOML syntax or remove unknown keys.",
path.display()
),
),
})?;
parsed.validated().map_err(|errs| {
AppError::fm_config(
3,
format!(
"ggen.toml at `{}` failed semantic validation: {errs}. \
Remediation: fix the reported field(s).",
path.display()
),
)
})
}
pub fn from_toml_str(toml: &str) -> Result<Self> {
let parsed = star_toml::from_str::<Self>(toml).map_err(|e| {
AppError::fm_config(
2,
format!("invalid ggen.toml document: {e}. Remediation: fix the TOML syntax or remove unknown keys."),
)
})?;
parsed.validated().map_err(|errs| {
AppError::fm_config(
3,
format!(
"ggen.toml failed semantic validation: {errs}. \
Remediation: fix the reported field(s)."
),
)
})
}
}
impl Validate for Project {
fn validate(&self, v: &mut Validator) {
v.check_non_empty("name", &self.name);
}
}
impl Validate for Ontology {
fn validate(&self, v: &mut Validator) {
v.check_path("source", &self.source.to_string_lossy(), Some(false));
for (i, import) in self.imports.iter().enumerate() {
v.check_path(
&format!("imports[{i}]"),
&import.to_string_lossy(),
Some(false),
);
}
}
}
impl Validate for Templates {
fn validate(&self, v: &mut Validator) {
v.check_path("dir", &self.dir.to_string_lossy(), Some(false));
}
}
impl Validate for PackRef {
fn validate(&self, v: &mut Validator) {
match self {
Self::Path {
path,
extra_ontologies,
lock: _,
} => {
v.check_non_empty("path", &path.to_string_lossy());
for (i, extra) in extra_ontologies.iter().enumerate() {
v.check_path(
&format!("extra_ontologies[{i}]"),
&extra.to_string_lossy(),
Some(false),
);
}
}
Self::Git {
git,
version,
subdir,
} => {
v.check_non_empty("git", git);
v.check_non_empty("version", version);
if let Some(sub) = subdir {
v.check_path("subdir", &sub.to_string_lossy(), Some(false));
}
}
}
}
}
impl Validate for Law {
fn validate(&self, v: &mut Validator) {
for (i, rule) in self.rules.iter().enumerate() {
v.check_path(&format!("rules[{i}]"), &rule.to_string_lossy(), Some(false));
}
for (i, shape) in self.shapes.iter().enumerate() {
v.check_path(
&format!("shapes[{i}]"),
&shape.to_string_lossy(),
Some(false),
);
}
for (i, gate) in self.gates.iter().enumerate() {
v.check_path(&format!("gates[{i}]"), &gate.to_string_lossy(), Some(false));
}
}
}
impl Validate for GgenConfig {
fn validate(&self, v: &mut Validator) {
v.field("project", |v| self.project.validate(v));
v.field("ontology", |v| self.ontology.validate(v));
v.field("templates", |v| self.templates.validate(v));
v.field("law", |v| self.law.validate(v));
for (name, pack_ref) in &self.packs {
v.field(&format!("packs.{name}"), |v| pack_ref.validate(v));
}
}
}