1use std::path::{Path, PathBuf};
2
3use anyhow::{bail, Context, Result};
4use serde::de::DeserializeOwned;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum DocumentFormat {
8 Pkl,
9 TomlCompat,
10}
11
12#[derive(Debug, Clone)]
13pub struct LoadedDocument<T> {
14 pub value: T,
15 pub format: DocumentFormat,
16 pub path: PathBuf,
17}
18
19impl<T> LoadedDocument<T> {
20 pub fn is_canonical(&self) -> bool {
21 self.format == DocumentFormat::Pkl
22 }
23}
24
25pub fn load_document<T>(path: &Path, description: &str) -> Result<LoadedDocument<T>>
26where
27 T: DeserializeOwned,
28{
29 match path.extension().and_then(|ext| ext.to_str()) {
30 Some("pkl") => {
31 let value = crate::pkl::evaluate_json(path)
32 .and_then(|json| serde_json::from_value(json).context("decoding evaluated Pkl JSON"))
33 .with_context(|| format!("loading canonical Pkl {description} {}", path.display()))?;
34 Ok(LoadedDocument {
35 value,
36 format: DocumentFormat::Pkl,
37 path: path.to_path_buf(),
38 })
39 }
40 Some("toml") => {
41 let content = std::fs::read_to_string(path)
42 .with_context(|| format!("reading compatibility TOML {description} {}", path.display()))?;
43 let value = toml::from_str(&content)
44 .with_context(|| format!("parsing compatibility TOML {description} {}", path.display()))?;
45 Ok(LoadedDocument {
46 value,
47 format: DocumentFormat::TomlCompat,
48 path: path.to_path_buf(),
49 })
50 }
51 Some(ext) => bail!(
52 "unsupported {description} extension .{ext}; canonical Nex documents use .pkl (.toml is compatibility/interchange)"
53 ),
54 None => bail!(
55 "{description} path must have an extension; canonical Nex documents use .pkl (.toml is compatibility/interchange)"
56 ),
57 }
58}