hlsl_snapshots/
lib.rs

1use std::{error::Error, fmt::Display, fs, io, path::Path};
2
3use anyhow::{anyhow, ensure};
4use nanoserde::{self, DeRon, DeRonErr, SerRon};
5
6#[derive(Debug)]
7struct BadRonParse(BadRonParseKind);
8
9impl Display for BadRonParse {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        write!(f, "failed to read RON configuration of HLSL snapshot test")
12    }
13}
14
15impl Error for BadRonParse {
16    fn source(&self) -> Option<&(dyn Error + 'static)> {
17        Some(&self.0)
18    }
19}
20
21#[derive(Debug)]
22enum BadRonParseKind {
23    Read { source: io::Error },
24    Parse { source: DeRonErr },
25    Empty,
26}
27
28impl Display for BadRonParseKind {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            BadRonParseKind::Read { source } => Display::fmt(source, f),
32            BadRonParseKind::Parse { source } => Display::fmt(source, f),
33            BadRonParseKind::Empty => write!(f, "no configuration was specified"),
34        }
35    }
36}
37
38impl Error for BadRonParseKind {
39    fn source(&self) -> Option<&(dyn Error + 'static)> {
40        match self {
41            BadRonParseKind::Read { source } => source.source(),
42            BadRonParseKind::Parse { source } => source.source(),
43            BadRonParseKind::Empty => None,
44        }
45    }
46}
47
48#[derive(Debug, DeRon, SerRon)]
49pub struct Config {
50    pub vertex: Vec<ConfigItem>,
51    pub fragment: Vec<ConfigItem>,
52    pub compute: Vec<ConfigItem>,
53}
54
55impl Config {
56    #[must_use]
57    pub fn empty() -> Self {
58        Self {
59            vertex: Default::default(),
60            fragment: Default::default(),
61            compute: Default::default(),
62        }
63    }
64
65    pub fn from_path(path: impl AsRef<Path>) -> anyhow::Result<Config> {
66        let path = path.as_ref();
67        let raw_config = fs::read_to_string(path)
68            .map_err(|source| BadRonParse(BadRonParseKind::Read { source }))?;
69        let config = Config::deserialize_ron(&raw_config)
70            .map_err(|source| BadRonParse(BadRonParseKind::Parse { source }))?;
71        ensure!(!config.is_empty(), BadRonParse(BadRonParseKind::Empty));
72        Ok(config)
73    }
74
75    pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
76        let path = path.as_ref();
77        let mut s = self.serialize_ron();
78        s.push('\n');
79        fs::write(path, &s).map_err(|e| anyhow!("failed to write to {}: {e}", path.display()))
80    }
81
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        let Self {
85            vertex,
86            fragment,
87            compute,
88        } = self;
89        vertex.is_empty() && fragment.is_empty() && compute.is_empty()
90    }
91}
92
93#[derive(Debug, DeRon, SerRon)]
94pub struct ConfigItem {
95    pub entry_point: String,
96    /// See also
97    /// <https://learn.microsoft.com/en-us/windows/win32/direct3dtools/dx-graphics-tools-fxc-using>.
98    pub target_profile: String,
99}