Skip to main content

Validator

Struct Validator 

Source
pub struct Validator { /* private fields */ }
Available on crate feature validation only.
Expand description

Validates Kubernetes objects against CRD schema CEL validation rules.

Walks the OpenAPI schema tree, compiles x-kubernetes-validations rules at each node, and evaluates them against the corresponding object values.

For repeated validation against the same schema, use compile_schema + validate_compiled to avoid re-compilation.

§Thread Safety

Validator is Send and can be moved across threads.

Implementations§

Source§

impl Validator

Source

pub fn new() -> Self

Create a new Validator with all K8s CEL functions pre-registered.

Examples found in repository?
examples/message_expression.rs (line 35)
14fn main() {
15    let schema = json!({
16        "type": "object",
17        "properties": {
18            "spec": {
19                "type": "object",
20                "properties": {
21                    "replicas": {"type": "integer"}
22                },
23                "x-kubernetes-validations": [{
24                    "rule": "self.replicas <= 5",
25                    // Static fallback, used if messageExpression is absent/non-string.
26                    "message": "too many replicas",
27                    // Dynamic message: embeds the actual value and the limit.
28                    "messageExpression":
29                        "'requested ' + string(self.replicas) + ' replicas, max allowed is 5'"
30                }]
31            }
32        }
33    });
34
35    let validator = Validator::new();
36
37    // Two invalid objects: the dynamic message differs per object.
38    for replicas in [8, 20] {
39        let object = json!({"spec": {"replicas": replicas}});
40        if let Err(errors) = validator.validate(&schema, &object, None) {
41            for err in &errors {
42                // Note the message reflects this object's value, not a fixed string.
43                println!("replicas={replicas}: {}", err.message);
44            }
45        }
46    }
47
48    // A valid object produces no errors (and so no message at all).
49    let ok = json!({"spec": {"replicas": 3}});
50    match validator.validate(&schema, &ok, None) {
51        Ok(()) => println!("replicas=3: 0 error(s)"),
52        Err(errors) => println!("replicas=3: {} error(s)", errors.len()),
53    }
54}
More examples
Hide additional examples
examples/transition_rules.rs (line 38)
17fn main() {
18    let schema = json!({
19        "type": "object",
20        "properties": {
21            "storageClass": {"type": "string"},
22            "replicas": {"type": "integer"}
23        },
24        "x-kubernetes-validations": [
25            // Immutable field: may never change once set.
26            {
27                "rule": "self.storageClass == oldSelf.storageClass",
28                "message": "storageClass is immutable"
29            },
30            // Monotonic: replicas may only grow (no scale-down).
31            {
32                "rule": "self.replicas >= oldSelf.replicas",
33                "message": "replicas cannot be decreased"
34            }
35        ]
36    });
37
38    let validator = Validator::new();
39    let stored = json!({"storageClass": "fast-ssd", "replicas": 3});
40
41    // CREATE: no previous state, so transition rules are skipped entirely.
42    let created = json!({"storageClass": "fast-ssd", "replicas": 3});
43    report("CREATE (old = None)", validator.validate(&schema, &created, None));
44
45    // UPDATE that respects both rules: same storageClass, scaling up.
46    let scale_up = json!({"storageClass": "fast-ssd", "replicas": 5});
47    report(
48        "UPDATE scale up 3->5",
49        validator.validate(&schema, &scale_up, Some(&stored)),
50    );
51
52    // UPDATE that violates the monotonic rule: scaling down.
53    let scale_down = json!({"storageClass": "fast-ssd", "replicas": 1});
54    report(
55        "UPDATE scale down 3->1",
56        validator.validate(&schema, &scale_down, Some(&stored)),
57    );
58
59    // UPDATE that mutates an immutable field.
60    let restorage = json!({"storageClass": "cheap-hdd", "replicas": 3});
61    report(
62        "UPDATE change storageClass",
63        validator.validate(&schema, &restorage, Some(&stored)),
64    );
65}
examples/kube_workflow.rs (line 29)
19fn main() {
20    // In a real controller this comes from the cluster, e.g.:
21    //     use kube::{Api, Client};
22    //     use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition;
23    //     let crds: Api<CustomResourceDefinition> = Api::all(client);
24    //     let crd = crds.get("widgets.example.com").await?;
25    //     let schema = crd.spec.versions[0].schema.unwrap().open_api_v3_schema.unwrap();
26    // Here we mock that fetched OpenAPI v3 schema directly.
27    let schema = fetch_crd_schema();
28
29    let validator = Validator::new();
30
31    // A controller reconcile loop typically builds a *desired* object and applies
32    // it. We show two desired states: one valid, one that violates the CRD rules.
33    let desired_ok = json!({
34        "spec": { "replicas": 3, "image": "registry.example.com/widget:1.4.2" }
35    });
36    let desired_bad = json!({
37        "spec": { "replicas": 0, "image": "widget:latest" }
38    });
39
40    for (label, desired) in [("valid desired", &desired_ok), ("invalid desired", &desired_bad)] {
41        println!("=== reconcile: {label} ===");
42
43        // Client-side gate, run BEFORE touching the apiserver.
44        match validator.validate(&schema, desired, None) {
45            Ok(()) => {
46                println!("  validation passed -> apply to cluster");
47                // The real apply happens only on the happy path:
48                //     let widgets: Api<Widget> = Api::namespaced(client, "default");
49                //     widgets.patch("my-widget", &PatchParams::apply("my-controller"),
50                //                   &Patch::Apply(desired)).await?;
51            }
52            Err(errors) => {
53                println!("  validation failed -> skip apply, record on status:");
54                for err in &errors {
55                    println!("    [{}] {}", err.field_path, err.message);
56                }
57                // Instead of a doomed apply, the controller would surface the reason:
58                //     widgets.patch_status("my-widget", &pp,
59                //         &Patch::Merge(json!({"status": {"validationError": err.message}}))).await?;
60            }
61        }
62        println!();
63    }
64}
examples/validate_crd.rs (line 29)
8fn main() {
9    let schema = json!({
10        "type": "object",
11        "properties": {
12            "spec": {
13                "type": "object",
14                "properties": {
15                    "replicas": {
16                        "type": "integer",
17                        "x-kubernetes-validations": [
18                            {"rule": "self >= 0", "message": "replicas must be non-negative"}
19                        ]
20                    }
21                },
22                "x-kubernetes-validations": [
23                    {"rule": "self.replicas >= 1", "message": "at least one replica required"}
24                ]
25            }
26        }
27    });
28
29    let validator = Validator::new();
30
31    // Valid object
32    let valid = json!({"spec": {"replicas": 3}});
33    match validator.validate(&schema, &valid, None) {
34        Ok(()) => println!("Valid object: OK"),
35        Err(errors) => println!("Valid object: {} errors", errors.len()),
36    }
37
38    // Invalid object
39    let invalid = json!({"spec": {"replicas": -1}});
40    if let Err(errors) = validator.validate(&schema, &invalid, None) {
41        println!("\nInvalid object: {} errors", errors.len());
42        for err in &errors {
43            println!("  [{path}] {msg}", path = err.field_path, msg = err.message);
44        }
45    }
46
47    // Transition rule (update check)
48    let transition_schema = json!({
49        "type": "object",
50        "x-kubernetes-validations": [{
51            "rule": "self.replicas >= oldSelf.replicas",
52            "message": "cannot scale down"
53        }],
54        "properties": {
55            "replicas": {"type": "integer"}
56        }
57    });
58
59    let new_obj = json!({"replicas": 2});
60    let old_obj = json!({"replicas": 5});
61
62    // Create (no old object): transition rule skipped
63    match validator.validate(&transition_schema, &new_obj, None) {
64        Ok(()) => println!("\nCreate (no old): OK"),
65        Err(errors) => println!("\nCreate (no old): {} errors", errors.len()),
66    }
67
68    // Update (scale down): transition rule fires
69    if let Err(errors) = validator.validate(&transition_schema, &new_obj, Some(&old_obj)) {
70        println!("Update (scale down): {} errors", errors.len());
71        for err in &errors {
72            println!("  {}", err);
73        }
74    }
75}
examples/error_chain.rs (line 39)
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}
examples/defaults_and_context.rs (line 46)
8fn main() {
9    let schema = json!({
10        "type": "object",
11        "properties": {
12            "replicas": {
13                "type": "integer",
14                "default": 1,
15                "x-kubernetes-validations": [
16                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
17                ]
18            },
19            "strategy": {
20                "type": "string",
21                "default": "RollingUpdate"
22            }
23        },
24        "x-kubernetes-validations": [
25            {"rule": "apiGroup == 'apps'", "message": "only apps group allowed"},
26            {"rule": "kind == 'Deployment'", "message": "must be a Deployment"}
27        ]
28    });
29
30    // Object with missing fields (would have defaults in K8s)
31    let object = json!({"replicas": 3});
32
33    // 1. Apply defaults
34    let defaulted = apply_defaults(&schema, &object);
35    println!("=== Default Injection ===");
36    println!("Before: {object}");
37    println!("After:  {defaulted}\n");
38
39    // 2. Validate with root context
40    let root_ctx = RootContext {
41        api_version: "apps/v1".into(),
42        api_group: "apps".into(),
43        kind: "Deployment".into(),
44    };
45
46    let validator = Validator::new();
47    let result = validator.validate_with_context(&schema, &defaulted, None, Some(&root_ctx));
48
49    println!("=== Validation Results ===");
50    match result {
51        Ok(()) => println!("All rules passed!"),
52        Err(errors) => {
53            for e in &errors {
54                println!("[FAIL] {}: {}", e.field_path, e.message);
55            }
56        }
57    }
58
59    // 3. Demonstrate validate_with_defaults convenience method
60    // Uses a simpler schema without root-context variables (apiGroup/kind),
61    // since validate_with_defaults does not accept a RootContext.
62    println!("\n=== validate_with_defaults ===");
63    let simple_schema = json!({
64        "type": "object",
65        "properties": {
66            "replicas": {
67                "type": "integer",
68                "default": 1,
69                "x-kubernetes-validations": [
70                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
71                ]
72            },
73            "strategy": {
74                "type": "string",
75                "default": "RollingUpdate"
76            }
77        }
78    });
79    let sparse_object = json!({}); // no fields at all
80    let result = validator.validate_with_defaults(&simple_schema, &sparse_object, None);
81    println!("Validating empty object with defaults applied:");
82    match result {
83        Ok(()) => println!("All rules passed (replicas defaulted to 1, strategy to RollingUpdate)"),
84        Err(errors) => {
85            for e in &errors {
86                println!("[FAIL] {}", e.message);
87            }
88        }
89    }
90}
Source

pub fn validate( &self, schema: &Value, object: &Value, old_object: Option<&Value>, ) -> Result<(), ValidationErrors>

Validate an object against a CRD schema’s CEL validation rules.

Schema defaults are applied to the object before the rules run, matching the apiserver, which defaults during admission before evaluating CEL. A CR that omits a defaulted field is therefore validated as the apiserver sees it (with the default in place), not as the raw object.

Compiles rules on each call. For repeated validation against the same schema, prefer compile_schema + validate_compiled.

Examples found in repository?
examples/message_expression.rs (line 40)
14fn main() {
15    let schema = json!({
16        "type": "object",
17        "properties": {
18            "spec": {
19                "type": "object",
20                "properties": {
21                    "replicas": {"type": "integer"}
22                },
23                "x-kubernetes-validations": [{
24                    "rule": "self.replicas <= 5",
25                    // Static fallback, used if messageExpression is absent/non-string.
26                    "message": "too many replicas",
27                    // Dynamic message: embeds the actual value and the limit.
28                    "messageExpression":
29                        "'requested ' + string(self.replicas) + ' replicas, max allowed is 5'"
30                }]
31            }
32        }
33    });
34
35    let validator = Validator::new();
36
37    // Two invalid objects: the dynamic message differs per object.
38    for replicas in [8, 20] {
39        let object = json!({"spec": {"replicas": replicas}});
40        if let Err(errors) = validator.validate(&schema, &object, None) {
41            for err in &errors {
42                // Note the message reflects this object's value, not a fixed string.
43                println!("replicas={replicas}: {}", err.message);
44            }
45        }
46    }
47
48    // A valid object produces no errors (and so no message at all).
49    let ok = json!({"spec": {"replicas": 3}});
50    match validator.validate(&schema, &ok, None) {
51        Ok(()) => println!("replicas=3: 0 error(s)"),
52        Err(errors) => println!("replicas=3: {} error(s)", errors.len()),
53    }
54}
More examples
Hide additional examples
examples/transition_rules.rs (line 43)
17fn main() {
18    let schema = json!({
19        "type": "object",
20        "properties": {
21            "storageClass": {"type": "string"},
22            "replicas": {"type": "integer"}
23        },
24        "x-kubernetes-validations": [
25            // Immutable field: may never change once set.
26            {
27                "rule": "self.storageClass == oldSelf.storageClass",
28                "message": "storageClass is immutable"
29            },
30            // Monotonic: replicas may only grow (no scale-down).
31            {
32                "rule": "self.replicas >= oldSelf.replicas",
33                "message": "replicas cannot be decreased"
34            }
35        ]
36    });
37
38    let validator = Validator::new();
39    let stored = json!({"storageClass": "fast-ssd", "replicas": 3});
40
41    // CREATE: no previous state, so transition rules are skipped entirely.
42    let created = json!({"storageClass": "fast-ssd", "replicas": 3});
43    report("CREATE (old = None)", validator.validate(&schema, &created, None));
44
45    // UPDATE that respects both rules: same storageClass, scaling up.
46    let scale_up = json!({"storageClass": "fast-ssd", "replicas": 5});
47    report(
48        "UPDATE scale up 3->5",
49        validator.validate(&schema, &scale_up, Some(&stored)),
50    );
51
52    // UPDATE that violates the monotonic rule: scaling down.
53    let scale_down = json!({"storageClass": "fast-ssd", "replicas": 1});
54    report(
55        "UPDATE scale down 3->1",
56        validator.validate(&schema, &scale_down, Some(&stored)),
57    );
58
59    // UPDATE that mutates an immutable field.
60    let restorage = json!({"storageClass": "cheap-hdd", "replicas": 3});
61    report(
62        "UPDATE change storageClass",
63        validator.validate(&schema, &restorage, Some(&stored)),
64    );
65}
examples/kube_workflow.rs (line 44)
19fn main() {
20    // In a real controller this comes from the cluster, e.g.:
21    //     use kube::{Api, Client};
22    //     use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::CustomResourceDefinition;
23    //     let crds: Api<CustomResourceDefinition> = Api::all(client);
24    //     let crd = crds.get("widgets.example.com").await?;
25    //     let schema = crd.spec.versions[0].schema.unwrap().open_api_v3_schema.unwrap();
26    // Here we mock that fetched OpenAPI v3 schema directly.
27    let schema = fetch_crd_schema();
28
29    let validator = Validator::new();
30
31    // A controller reconcile loop typically builds a *desired* object and applies
32    // it. We show two desired states: one valid, one that violates the CRD rules.
33    let desired_ok = json!({
34        "spec": { "replicas": 3, "image": "registry.example.com/widget:1.4.2" }
35    });
36    let desired_bad = json!({
37        "spec": { "replicas": 0, "image": "widget:latest" }
38    });
39
40    for (label, desired) in [("valid desired", &desired_ok), ("invalid desired", &desired_bad)] {
41        println!("=== reconcile: {label} ===");
42
43        // Client-side gate, run BEFORE touching the apiserver.
44        match validator.validate(&schema, desired, None) {
45            Ok(()) => {
46                println!("  validation passed -> apply to cluster");
47                // The real apply happens only on the happy path:
48                //     let widgets: Api<Widget> = Api::namespaced(client, "default");
49                //     widgets.patch("my-widget", &PatchParams::apply("my-controller"),
50                //                   &Patch::Apply(desired)).await?;
51            }
52            Err(errors) => {
53                println!("  validation failed -> skip apply, record on status:");
54                for err in &errors {
55                    println!("    [{}] {}", err.field_path, err.message);
56                }
57                // Instead of a doomed apply, the controller would surface the reason:
58                //     widgets.patch_status("my-widget", &pp,
59                //         &Patch::Merge(json!({"status": {"validationError": err.message}}))).await?;
60            }
61        }
62        println!();
63    }
64}
examples/validate_crd.rs (line 33)
8fn main() {
9    let schema = json!({
10        "type": "object",
11        "properties": {
12            "spec": {
13                "type": "object",
14                "properties": {
15                    "replicas": {
16                        "type": "integer",
17                        "x-kubernetes-validations": [
18                            {"rule": "self >= 0", "message": "replicas must be non-negative"}
19                        ]
20                    }
21                },
22                "x-kubernetes-validations": [
23                    {"rule": "self.replicas >= 1", "message": "at least one replica required"}
24                ]
25            }
26        }
27    });
28
29    let validator = Validator::new();
30
31    // Valid object
32    let valid = json!({"spec": {"replicas": 3}});
33    match validator.validate(&schema, &valid, None) {
34        Ok(()) => println!("Valid object: OK"),
35        Err(errors) => println!("Valid object: {} errors", errors.len()),
36    }
37
38    // Invalid object
39    let invalid = json!({"spec": {"replicas": -1}});
40    if let Err(errors) = validator.validate(&schema, &invalid, None) {
41        println!("\nInvalid object: {} errors", errors.len());
42        for err in &errors {
43            println!("  [{path}] {msg}", path = err.field_path, msg = err.message);
44        }
45    }
46
47    // Transition rule (update check)
48    let transition_schema = json!({
49        "type": "object",
50        "x-kubernetes-validations": [{
51            "rule": "self.replicas >= oldSelf.replicas",
52            "message": "cannot scale down"
53        }],
54        "properties": {
55            "replicas": {"type": "integer"}
56        }
57    });
58
59    let new_obj = json!({"replicas": 2});
60    let old_obj = json!({"replicas": 5});
61
62    // Create (no old object): transition rule skipped
63    match validator.validate(&transition_schema, &new_obj, None) {
64        Ok(()) => println!("\nCreate (no old): OK"),
65        Err(errors) => println!("\nCreate (no old): {} errors", errors.len()),
66    }
67
68    // Update (scale down): transition rule fires
69    if let Err(errors) = validator.validate(&transition_schema, &new_obj, Some(&old_obj)) {
70        println!("Update (scale down): {} errors", errors.len());
71        for err in &errors {
72            println!("  {}", err);
73        }
74    }
75}
examples/error_chain.rs (line 40)
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}
Source

pub fn validate_with_context( &self, schema: &Value, object: &Value, old_object: Option<&Value>, root_ctx: Option<&RootContext>, ) -> Result<(), ValidationErrors>

Validate an object against a CRD schema’s CEL validation rules, with optional root context.

Like validate, but also binds apiVersion, apiGroup, and kind as root-level CEL variables when a RootContext is provided.

Schema defaults are applied to the object before evaluation, matching the apiserver’s admission order (defaults run before CEL rules).

Examples found in repository?
examples/defaults_and_context.rs (line 47)
8fn main() {
9    let schema = json!({
10        "type": "object",
11        "properties": {
12            "replicas": {
13                "type": "integer",
14                "default": 1,
15                "x-kubernetes-validations": [
16                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
17                ]
18            },
19            "strategy": {
20                "type": "string",
21                "default": "RollingUpdate"
22            }
23        },
24        "x-kubernetes-validations": [
25            {"rule": "apiGroup == 'apps'", "message": "only apps group allowed"},
26            {"rule": "kind == 'Deployment'", "message": "must be a Deployment"}
27        ]
28    });
29
30    // Object with missing fields (would have defaults in K8s)
31    let object = json!({"replicas": 3});
32
33    // 1. Apply defaults
34    let defaulted = apply_defaults(&schema, &object);
35    println!("=== Default Injection ===");
36    println!("Before: {object}");
37    println!("After:  {defaulted}\n");
38
39    // 2. Validate with root context
40    let root_ctx = RootContext {
41        api_version: "apps/v1".into(),
42        api_group: "apps".into(),
43        kind: "Deployment".into(),
44    };
45
46    let validator = Validator::new();
47    let result = validator.validate_with_context(&schema, &defaulted, None, Some(&root_ctx));
48
49    println!("=== Validation Results ===");
50    match result {
51        Ok(()) => println!("All rules passed!"),
52        Err(errors) => {
53            for e in &errors {
54                println!("[FAIL] {}: {}", e.field_path, e.message);
55            }
56        }
57    }
58
59    // 3. Demonstrate validate_with_defaults convenience method
60    // Uses a simpler schema without root-context variables (apiGroup/kind),
61    // since validate_with_defaults does not accept a RootContext.
62    println!("\n=== validate_with_defaults ===");
63    let simple_schema = json!({
64        "type": "object",
65        "properties": {
66            "replicas": {
67                "type": "integer",
68                "default": 1,
69                "x-kubernetes-validations": [
70                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
71                ]
72            },
73            "strategy": {
74                "type": "string",
75                "default": "RollingUpdate"
76            }
77        }
78    });
79    let sparse_object = json!({}); // no fields at all
80    let result = validator.validate_with_defaults(&simple_schema, &sparse_object, None);
81    println!("Validating empty object with defaults applied:");
82    match result {
83        Ok(()) => println!("All rules passed (replicas defaulted to 1, strategy to RollingUpdate)"),
84        Err(errors) => {
85            for e in &errors {
86                println!("[FAIL] {}", e.message);
87            }
88        }
89    }
90}
Source

pub fn validate_compiled( &self, compiled: &CompiledSchema, object: &Value, old_object: Option<&Value>, ) -> Result<(), ValidationErrors>

Validate an object using a pre-compiled schema tree.

Use compile_schema to build the CompiledSchema, then call this method for each object to validate — rules are compiled only once.

Schema defaults (retained on the compiled tree) are applied to the object before evaluation, matching validate and the apiserver.

Source

pub fn validate_compiled_with_context( &self, compiled: &CompiledSchema, object: &Value, old_object: Option<&Value>, root_ctx: Option<&RootContext>, ) -> Result<(), ValidationErrors>

Validate an object using a pre-compiled schema tree, with optional root context.

Like validate_compiled, but also binds apiVersion, apiGroup, and kind as root-level CEL variables when a RootContext is provided.

Source

pub fn validate_with_defaults( &self, schema: &Value, object: &Value, old_object: Option<&Value>, ) -> Result<(), ValidationErrors>

Validate with schema defaults applied to the object first.

Alias for validate. As of 0.8, plain validate applies schema defaults itself, so this method is equivalent and retained only for backward compatibility.

Examples found in repository?
examples/defaults_and_context.rs (line 80)
8fn main() {
9    let schema = json!({
10        "type": "object",
11        "properties": {
12            "replicas": {
13                "type": "integer",
14                "default": 1,
15                "x-kubernetes-validations": [
16                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
17                ]
18            },
19            "strategy": {
20                "type": "string",
21                "default": "RollingUpdate"
22            }
23        },
24        "x-kubernetes-validations": [
25            {"rule": "apiGroup == 'apps'", "message": "only apps group allowed"},
26            {"rule": "kind == 'Deployment'", "message": "must be a Deployment"}
27        ]
28    });
29
30    // Object with missing fields (would have defaults in K8s)
31    let object = json!({"replicas": 3});
32
33    // 1. Apply defaults
34    let defaulted = apply_defaults(&schema, &object);
35    println!("=== Default Injection ===");
36    println!("Before: {object}");
37    println!("After:  {defaulted}\n");
38
39    // 2. Validate with root context
40    let root_ctx = RootContext {
41        api_version: "apps/v1".into(),
42        api_group: "apps".into(),
43        kind: "Deployment".into(),
44    };
45
46    let validator = Validator::new();
47    let result = validator.validate_with_context(&schema, &defaulted, None, Some(&root_ctx));
48
49    println!("=== Validation Results ===");
50    match result {
51        Ok(()) => println!("All rules passed!"),
52        Err(errors) => {
53            for e in &errors {
54                println!("[FAIL] {}: {}", e.field_path, e.message);
55            }
56        }
57    }
58
59    // 3. Demonstrate validate_with_defaults convenience method
60    // Uses a simpler schema without root-context variables (apiGroup/kind),
61    // since validate_with_defaults does not accept a RootContext.
62    println!("\n=== validate_with_defaults ===");
63    let simple_schema = json!({
64        "type": "object",
65        "properties": {
66            "replicas": {
67                "type": "integer",
68                "default": 1,
69                "x-kubernetes-validations": [
70                    {"rule": "self >= 0", "message": "replicas must be non-negative"}
71                ]
72            },
73            "strategy": {
74                "type": "string",
75                "default": "RollingUpdate"
76            }
77        }
78    });
79    let sparse_object = json!({}); // no fields at all
80    let result = validator.validate_with_defaults(&simple_schema, &sparse_object, None);
81    println!("Validating empty object with defaults applied:");
82    match result {
83        Ok(()) => println!("All rules passed (replicas defaulted to 1, strategy to RollingUpdate)"),
84        Err(errors) => {
85            for e in &errors {
86                println!("[FAIL] {}", e.message);
87            }
88        }
89    }
90}
Source

pub fn validate_with_defaults_and_context( &self, schema: &Value, object: &Value, old_object: Option<&Value>, root_ctx: Option<&RootContext>, ) -> Result<(), ValidationErrors>

Validate with schema defaults applied and root context variables bound.

Alias for validate_with_context. As of 0.8, that method applies schema defaults itself, so this is equivalent and retained only for backward compatibility.

Trait Implementations§

Source§

impl Debug for Validator

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Validator

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> AnyExt for T
where T: Any + ?Sized,

Source§

fn downcast_ref<T>(this: &Self) -> Option<&T>
where T: Any,

Attempts to downcast this to T behind reference
Source§

fn downcast_mut<T>(this: &mut Self) -> Option<&mut T>
where T: Any,

Attempts to downcast this to T behind mutable reference
Source§

fn downcast_rc<T>(this: Rc<Self>) -> Result<Rc<T>, Rc<Self>>
where T: Any,

Attempts to downcast this to T behind Rc pointer
Source§

fn downcast_arc<T>(this: Arc<Self>) -> Result<Arc<T>, Arc<Self>>
where T: Any,

Attempts to downcast this to T behind Arc pointer
Source§

fn downcast_box<T>(this: Box<Self>) -> Result<Box<T>, Box<Self>>
where T: Any,

Attempts to downcast this to T behind Box pointer
Source§

fn downcast_move<T>(this: Self) -> Option<T>
where T: Any, Self: Sized,

Attempts to downcast owned Self to T, useful only in generic context as a workaround for specialization
Source§

impl<T> AsDebug for T
where T: Debug,

Source§

fn as_debug(&self) -> &dyn Debug

Returns self as a &dyn Debug trait object.
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T, X> CoerceTo<T> for X
where T: CoerceFrom<X> + ?Sized,

Source§

fn coerce_rc_to(self: Rc<X>) -> Rc<T>

Source§

fn coerce_box_to(self: Box<X>) -> Box<T>

Source§

fn coerce_ref_to(&self) -> &T

Source§

fn coerce_mut_to(&mut self) -> &mut T

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.