use crate::{
config::FileContents,
error::{Error, ErrorKind, with_io_context},
};
use jsonc_parser::{
ParseOptions,
cst::{CstInputValue, CstObject, CstRootNode},
errors::ParseError,
};
use std::io::{Read, Write};
pub(super) struct Root {
config_name: &'static str,
value: CstRootNode,
contents: FileContents,
schema_error_fn: fn(String) -> ErrorKind,
}
impl Root {
pub(super) fn read_from(
config_name: &'static str,
mut contents: FileContents,
syntax_error_fn: fn(ParseError) -> ErrorKind,
schema_error_fn: fn(String) -> ErrorKind,
) -> Result<Self, Error> {
let mut config_contents = String::new();
contents
.original
.read_to_string(&mut config_contents)
.map_err(|e| with_io_context(config_name, "reading config", e))?;
let root =
CstRootNode::parse(&config_contents, &ParseOptions::default()).map_err(|e| Error {
context: format!("parsing {config_name} config"),
kind: syntax_error_fn(e),
})?;
Ok(Self {
config_name,
value: root,
contents,
schema_error_fn,
})
}
fn schema_error(&self, reason: String) -> Error {
Error {
kind: (self.schema_error_fn)(reason),
context: format!("parsing {} config", self.config_name),
}
}
pub(super) fn get_object(&self) -> Result<Object, Error> {
let object = self.value.object_value().ok_or_else(|| {
self.schema_error(format!("{} config must be an object", self.config_name))
})?;
Ok(Object {
config_name: self.config_name,
value: object,
schema_error_fn: self.schema_error_fn,
})
}
pub(super) fn write(&mut self) -> Result<(), Error> {
self.contents
.output
.write_all(self.value.to_string().as_bytes())
.map_err(|e| with_io_context(self.config_name, "writing config", e))
}
}
#[derive(Debug)]
pub struct Object {
config_name: &'static str,
value: CstObject,
schema_error_fn: fn(String) -> ErrorKind,
}
impl Object {
fn schema_error(&self, reason: String) -> Error {
Error {
kind: (self.schema_error_fn)(reason),
context: format!("parsing {} config", self.config_name),
}
}
pub(super) fn get_object_value(&self, name: &str) -> Result<Self, Error> {
let object = self
.value
.object_value_or_create(name)
.ok_or_else(|| self.schema_error(format!("{name} field must be an object")))?;
Ok(Self {
config_name: self.config_name,
value: object,
schema_error_fn: self.schema_error_fn,
})
}
pub(super) fn insert_value(&self, name: &str, value: impl Into<CstInputValue>) {
if let Some(existing) = self.value.get(name) {
existing.set_value(value.into());
} else {
self.value.append(name, value.into());
}
}
}