#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![deny(missing_docs)]
#![cfg_attr(
feature = "validation",
doc = r#"
# CRD Validation Pipeline (feature = `validation`)
Compile and evaluate `x-kubernetes-validations` CEL rules client-side,
without an API server.
```toml
kube-cel = { version = "0.6", features = ["validation"] }
```
```rust
use kube_cel::Validator;
use serde_json::json;
let schema = json!({
"type": "object",
"x-kubernetes-validations": [
{"rule": "self.replicas >= 0", "message": "must be non-negative"}
],
"properties": { "replicas": {"type": "integer"} }
});
let object = json!({"replicas": -1});
let errors = Validator::new().validate(&schema, &object, None);
assert_eq!(errors.len(), 1);
```
For repeated validation against the same schema, pre-compile with
[`compile_schema`] and use [`Validator::validate_compiled`].
"#
)]
pub use cel;
mod ext;
mod functions;
#[cfg(feature = "validation")] mod validation;
pub use ext::KubeCelExt;
#[cfg(feature = "validation")]
pub use crate::validation::{
ErrorKind, RootContext, ValidationError, Validator,
analysis::{
AnalysisWarning, ScopeContext, WarningKind, analyze_rule, check_rule_scope, estimate_rule_cost,
},
compilation::{CompilationError, CompilationResult, CompiledSchema, Rule, compile_schema},
defaults::apply_defaults,
validate, validate_compiled,
values::SchemaFormat,
vap::{
AdmissionRequest, CompiledVapExpression, GroupVersionKind, GroupVersionResource, VapError,
VapEvaluator, VapEvaluatorBuilder, VapExpression, VapResult,
},
};
pub(crate) use functions::register_all;
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused_imports)] use std::sync::Arc;
use cel::{Context, Program, Value};
#[allow(dead_code)]
fn eval(expr: &str) -> Value {
let ctx = Context::default().with_all();
Program::compile(expr).unwrap().execute(&ctx).unwrap()
}
#[test]
#[cfg(feature = "strings")]
fn test_integration_strings() {
assert_eq!(eval("'hello'.charAt(1)"), Value::String(Arc::new("e".into())));
assert_eq!(
eval("'HELLO'.lowerAscii()"),
Value::String(Arc::new("hello".into()))
);
assert_eq!(
eval("' hello '.trim()"),
Value::String(Arc::new("hello".into()))
);
}
#[test]
#[cfg(feature = "lists")]
fn test_integration_lists() {
assert_eq!(eval("[1, 2, 3].isSorted()"), Value::Bool(true));
assert_eq!(eval("[3, 1, 2].isSorted()"), Value::Bool(false));
assert_eq!(eval("[1, 2, 3].sum()"), Value::Int(6));
}
#[test]
#[cfg(feature = "sets")]
fn test_integration_sets() {
assert_eq!(eval("sets.contains([1, 2, 3], [1, 2])"), Value::Bool(true));
assert_eq!(eval("sets.intersects([1, 2], [2, 3])"), Value::Bool(true));
}
#[test]
#[cfg(feature = "regex_funcs")]
fn test_integration_regex() {
assert_eq!(
eval("'hello world'.find('[a-z]+')"),
Value::String(Arc::new("hello".into()))
);
}
#[test]
#[cfg(feature = "strings")]
fn test_dispatch_index_of_string() {
assert_eq!(eval("'hello world'.indexOf('world')"), Value::Int(6));
assert_eq!(eval("'hello'.indexOf('x')"), Value::Int(-1));
}
#[test]
#[cfg(feature = "lists")]
fn test_dispatch_index_of_list() {
assert_eq!(eval("[1, 2, 3].indexOf(2)"), Value::Int(1));
assert_eq!(eval("[1, 2, 3].indexOf(4)"), Value::Int(-1));
}
#[test]
#[cfg(feature = "strings")]
fn test_dispatch_last_index_of_string() {
assert_eq!(eval("'abcabc'.lastIndexOf('abc')"), Value::Int(3));
}
#[test]
#[cfg(feature = "lists")]
fn test_dispatch_last_index_of_list() {
assert_eq!(eval("[1, 2, 3, 2].lastIndexOf(2)"), Value::Int(3));
}
#[test]
#[cfg(feature = "format")]
fn test_integration_format() {
assert_eq!(
eval("'hello %s'.format(['world'])"),
Value::String(Arc::new("hello world".into()))
);
assert_eq!(
eval("'%d items'.format([5])"),
Value::String(Arc::new("5 items".into()))
);
}
#[test]
#[cfg(feature = "semver_funcs")]
fn test_integration_semver() {
assert_eq!(eval("isSemver('1.2.3')"), Value::Bool(true));
assert_eq!(eval("semver('1.2.3').major()"), Value::Int(1));
assert_eq!(
eval("semver('2.0.0').isGreaterThan(semver('1.0.0'))"),
Value::Bool(true)
);
}
}