Skip to main content

error_chain/
error_chain.rs

1//! Inspecting validation errors: the `ErrorKind` classification and the
2//! `std::error::Error::source()` cause chain.
3//!
4//! Every [`ValidationError`] carries a machine-readable [`ErrorKind`] so callers
5//! can branch on *why* a rule failed — the object was invalid, the rule hit a
6//! runtime error, or the rule used a CEL feature this build cannot evaluate. For
7//! the runtime cases the underlying `cel` execution error is preserved and
8//! reachable via `source()`, so you can walk the cause chain like any other
9//! `std::error::Error`.
10//!
11//! Run with: `cargo run --example error_chain --features validation`
12
13use std::error::Error;
14
15use kube_cel::{ErrorKind, Validator};
16use serde_json::json;
17
18fn main() {
19    // Three rules, each failing a different way against the same object.
20    let schema = json!({
21        "type": "object",
22        "x-kubernetes-validations": [
23            // (1) the object simply violates the rule -> ValidationFailure
24            {"rule": "self.replicas >= 1", "message": "replicas must be at least 1"},
25            // (2) the rule references a field that isn't there -> EvaluationError
26            //     (compiles fine, errors at runtime; the cel error is the cause)
27            {"rule": "self.maxSurge > 0", "message": "maxSurge must be positive"},
28            // (3) the rule uses a CEL macro cel 0.13 can't evaluate -> UnsupportedReference
29            {"rule": "self.zones.sortBy(z, z) == self.zones", "message": "zones must be sorted"}
30        ],
31        "properties": {
32            "replicas": {"type": "integer"},
33            "zones": {"type": "array", "items": {"type": "string"}}
34        }
35    });
36
37    let object = json!({"replicas": 0, "zones": ["b", "a"]});
38
39    let errors = Validator::new()
40        .validate(&schema, &object, None)
41        .expect_err("object violates every rule, so validation must fail");
42
43    println!("{} validation error(s)\n", errors.len());
44    for err in &errors {
45        println!("rule: {}", err.rule);
46        println!("  kind: {:?}", err.kind);
47        println!("  message: {err}");
48
49        // Branch on the classification. `ErrorKind` is `#[non_exhaustive]`, so a
50        // wildcard arm is required.
51        match err.kind {
52            ErrorKind::ValidationFailure => {
53                println!("  -> the object violated the rule; reject it");
54            }
55            ErrorKind::EvaluationError => {
56                println!("  -> the rule failed at runtime; likely a malformed object");
57            }
58            ErrorKind::UnsupportedReference => {
59                println!("  -> coverage gap: this kube-cel build can't evaluate the rule");
60            }
61            _ => println!("  -> other ({:?})", err.kind),
62        }
63
64        // Walk the `source()` cause chain. Runtime failures carry the owned `cel`
65        // execution error as their cause; pure ValidationFailure has none.
66        let mut cause = err.source();
67        if cause.is_none() {
68            println!("  (no underlying cause)");
69        }
70        let mut depth = 1;
71        while let Some(e) = cause {
72            println!("  {:indent$}caused by: {e}", "", indent = depth * 2);
73            cause = e.source();
74            depth += 1;
75        }
76        println!();
77    }
78}