Skip to main content

gam_problem/
block_count_error.rs

1//! Neutral block-count arity mismatch carrier.
2//!
3//! Descended from `gam-models`' `block_layout::block_count` so that lower
4//! tiers (e.g. `gam-terms`) can route their own error enums through
5//! `From<BlockCountMismatch>` without depending on `gam-models`. The
6//! `validate_block_count` helper continues to live in `gam-models` and
7//! re-exports this type unchanged.
8
9/// A block-count arity mismatch: a family that needs exactly `expected`
10/// parameter blocks was handed `got` of them.
11///
12/// This is the neutral carrier produced by `validate_block_count`; each
13/// module converts it into its own error type via `From<BlockCountMismatch>`.
14pub struct BlockCountMismatch {
15    /// Human-readable family / term name used as the message prefix.
16    pub family: String,
17    /// Number of parameter blocks the family requires.
18    pub expected: usize,
19    /// Number of parameter blocks that were actually supplied.
20    pub got: usize,
21}
22
23impl BlockCountMismatch {
24    /// The canonical arity-mismatch message, e.g.
25    /// `"FooFamily expects 2 blocks, got 1"` (plural) or
26    /// `"BarFamily expects 1 block, got 0"` (singular when `expected == 1`).
27    pub fn message(&self) -> String {
28        let unit = if self.expected == 1 {
29            "block"
30        } else {
31            "blocks"
32        };
33        format!(
34            "{} expects {} {unit}, got {}",
35            self.family, self.expected, self.got
36        )
37    }
38}
39
40impl From<BlockCountMismatch> for String {
41    fn from(err: BlockCountMismatch) -> String {
42        err.message()
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn message_uses_plural_blocks_when_expected_greater_than_one() {
52        let m = BlockCountMismatch {
53            family: "FooFamily".to_string(),
54            expected: 2,
55            got: 0,
56        };
57        let msg = m.message();
58        assert!(msg.contains("blocks"), "message: {msg}");
59        assert!(msg.contains("FooFamily"), "message: {msg}");
60        assert!(msg.contains("2") && msg.contains("0"), "message: {msg}");
61    }
62
63    #[test]
64    fn message_uses_singular_block_when_expected_is_one() {
65        let m = BlockCountMismatch {
66            family: "BarFamily".to_string(),
67            expected: 1,
68            got: 3,
69        };
70        let msg = m.message();
71        assert!(
72            msg.contains("block") && !msg.contains("blocks"),
73            "message: {msg}"
74        );
75        assert!(msg.contains("1") && msg.contains("3"), "message: {msg}");
76    }
77
78    #[test]
79    fn from_block_count_mismatch_for_string_matches_message() {
80        let m = BlockCountMismatch {
81            family: "Baz".to_string(),
82            expected: 2,
83            got: 1,
84        };
85        let expected_msg = m.message();
86        let as_string = String::from(BlockCountMismatch {
87            family: "Baz".to_string(),
88            expected: 2,
89            got: 1,
90        });
91        assert_eq!(as_string, expected_msg);
92    }
93}