Skip to main content

testing_conventions/
config.rs

1//! The testing-conventions config schema and loader.
2//!
3//! One config file is read into the in-memory [`Config`] below. The loader
4//! parses *and* validates the config itself (the "self-guard" from issue #12):
5//! a malformed or unknown-key config is an error, never a silently-accepted
6//! default.
7//!
8//! Nothing consumes [`Config`] yet — this module only turns the file on disk
9//! into the in-memory structure.
10
11use std::path::Path;
12
13use anyhow::{Context, Result};
14use serde::Deserialize;
15
16/// A fully-parsed testing-conventions config file.
17///
18/// Holds the per-language coverage thresholds — the `[python]` / `[typescript]`
19/// / `[rust]` tables from the README's "Configuration" section. Each table is
20/// optional so a repo can configure only the languages it ships. Test locations
21/// follow convention, not config, so there are no location keys here.
22#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
23#[serde(deny_unknown_fields)]
24pub struct Config {
25    pub python: Option<PythonConfig>,
26    pub typescript: Option<TypeScriptConfig>,
27    pub rust: Option<RustConfig>,
28}
29
30/// The `[python]` table.
31#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
32#[serde(deny_unknown_fields)]
33pub struct PythonConfig {
34    pub coverage: PythonCoverage,
35}
36
37/// The `[typescript]` table.
38#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct TypeScriptConfig {
41    pub coverage: TypeScriptCoverage,
42}
43
44/// The `[rust]` table.
45#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct RustConfig {
48    pub coverage: RustCoverage,
49}
50
51/// `[python].coverage`.
52#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
53#[serde(deny_unknown_fields)]
54pub struct PythonCoverage {
55    pub branch: bool,
56    pub fail_under: u8,
57}
58
59/// `[typescript].coverage`.
60#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
61#[serde(deny_unknown_fields)]
62pub struct TypeScriptCoverage {
63    pub lines: u8,
64    pub branches: u8,
65    pub functions: u8,
66    pub statements: u8,
67}
68
69/// `[rust].coverage`. Branch coverage is still experimental, so only
70/// regions/lines are configurable.
71#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct RustCoverage {
74    pub regions: u8,
75    pub lines: u8,
76}
77
78/// Read one config file at `path` into a [`Config`], validating it on the way.
79///
80/// The validation is the config's self-guard: `serde`'s `deny_unknown_fields`
81/// rejects keys that aren't part of the schema, missing required keys and
82/// wrong-typed values are type errors, and malformed TOML fails to parse. Any
83/// of these surfaces as an `Err` rather than a silently-accepted default.
84pub fn load_config(path: impl AsRef<Path>) -> Result<Config> {
85    let path = path.as_ref();
86    let contents = std::fs::read_to_string(path)
87        .with_context(|| format!("reading config file `{}`", path.display()))?;
88    toml::from_str(&contents).with_context(|| format!("parsing config file `{}`", path.display()))
89}