use quote::quote;
use syn::parse_quote;
use tinc_cel::CelValue;
use super::Function;
use crate::codegen::cel::compiler::{CompileError, CompiledExpr, CompilerCtx, ConstantCompiledExpr, RuntimeCompiledExpr};
use crate::codegen::cel::types::CelType;
use crate::types::{ProtoModifiedValueType, ProtoType, ProtoValueType};
#[derive(Debug, Clone, Default)]
pub(crate) struct Contains;
impl Function for Contains {
fn name(&self) -> &'static str {
"contains"
}
fn syntax(&self) -> &'static str {
"<this>.contains(<arg>)"
}
fn compile(&self, mut ctx: CompilerCtx) -> Result<CompiledExpr, CompileError> {
let Some(this) = ctx.this.take() else {
return Err(CompileError::syntax("missing this", self));
};
if ctx.args.len() != 1 {
return Err(CompileError::syntax("takes exactly one argument", self));
}
let arg = ctx.resolve(&ctx.args[0])?.into_cel()?;
if let CompiledExpr::Runtime(RuntimeCompiledExpr {
expr,
ty:
ty @ CelType::Proto(ProtoType::Modified(
ProtoModifiedValueType::Repeated(item) | ProtoModifiedValueType::Map(item, _),
)),
}) = &this
&& !matches!(item, ProtoValueType::Message { .. } | ProtoValueType::Enum(_))
{
let op = match &ty {
CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(_))) => {
quote! { array_contains }
}
CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(_, _))) => {
quote! { map_contains }
}
_ => unreachable!(),
};
return Ok(CompiledExpr::runtime(
CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
parse_quote! {
::tinc::__private::cel::#op(
#expr,
#arg,
)
},
));
}
let this = this.clone().into_cel()?;
match (this, arg) {
(
CompiledExpr::Constant(ConstantCompiledExpr { value: this }),
CompiledExpr::Constant(ConstantCompiledExpr { value: arg }),
) => Ok(CompiledExpr::constant(CelValue::cel_contains(this, arg)?)),
(this, arg) => Ok(CompiledExpr::runtime(
CelType::Proto(ProtoType::Value(ProtoValueType::Bool)),
parse_quote! {
::tinc::__private::cel::CelValue::cel_contains(
#this,
#arg,
)?
},
)),
}
}
}
#[cfg(test)]
#[cfg(feature = "prost")]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use quote::quote;
use syn::parse_quote;
use tinc_cel::CelValue;
use crate::codegen::cel::compiler::{CompiledExpr, Compiler, CompilerCtx};
use crate::codegen::cel::functions::{Contains, Function};
use crate::codegen::cel::types::CelType;
use crate::extern_paths::ExternPaths;
use crate::path_set::PathSet;
use crate::types::{ProtoModifiedValueType, ProtoType, ProtoTypeRegistry, ProtoValueType};
#[test]
fn test_contains_syntax() {
let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
let compiler = Compiler::new(®istry);
insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), None, &[])), @r#"
Err(
InvalidSyntax {
message: "missing this",
syntax: "<this>.contains(<arg>)",
},
)
"#);
insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::String("hi".into()))), &[])), @r#"
Err(
InvalidSyntax {
message: "takes exactly one argument",
syntax: "<this>.contains(<arg>)",
},
)
"#);
insta::assert_debug_snapshot!(Contains.compile(CompilerCtx::new(compiler.child(), Some(CompiledExpr::constant(CelValue::List(Default::default()))), &[
cel_parser::parse("1 + 1").unwrap(),
])), @r"
Ok(
Constant(
ConstantCompiledExpr {
value: Bool(
false,
),
},
),
)
");
}
#[test]
#[cfg(not(valgrind))]
fn test_contains_runtime_string() {
let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
let compiler = Compiler::new(®istry);
let string_value =
CompiledExpr::runtime(CelType::Proto(ProtoType::Value(ProtoValueType::String)), parse_quote!(input));
let output = Contains
.compile(CompilerCtx::new(
compiler.child(),
Some(string_value),
&[cel_parser::parse("(1 + 1).string()").unwrap()],
))
.unwrap();
insta::assert_snapshot!(postcompile::compile_str!(
postcompile::config! {
test: true,
dependencies: vec![
postcompile::Dependency::path("tinc", "../tinc"),
],
},
quote! {
fn contains(input: &String) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
Ok(#output)
}
#[test]
fn test_contains() {
assert_eq!(contains(&"in2dastring".into()).unwrap(), true);
assert_eq!(contains(&"in3dastring".into()).unwrap(), false);
}
},
));
}
#[test]
#[cfg(not(valgrind))]
fn test_contains_runtime_map() {
let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
let compiler = Compiler::new(®istry);
let string_value = CompiledExpr::runtime(
CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Map(
ProtoValueType::String,
ProtoValueType::Bool,
))),
parse_quote!(input),
);
let output = Contains
.compile(CompilerCtx::new(
compiler.child(),
Some(string_value),
&[cel_parser::parse("'value'").unwrap()],
))
.unwrap();
insta::assert_snapshot!(postcompile::compile_str!(
postcompile::config! {
test: true,
dependencies: vec![
postcompile::Dependency::path("tinc", "../tinc"),
],
},
quote! {
fn contains(input: &std::collections::HashMap<String, bool>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
Ok(#output)
}
#[test]
fn test_contains() {
assert_eq!(contains(&{
let mut map = std::collections::HashMap::new();
map.insert("value".to_string(), true);
map
}).unwrap(), true);
assert_eq!(contains(&{
let mut map = std::collections::HashMap::new();
map.insert("not_value".to_string(), true);
map
}).unwrap(), false);
assert_eq!(contains(&{
let mut map = std::collections::HashMap::new();
map.insert("xd".to_string(), true);
map.insert("value".to_string(), true);
map
}).unwrap(), true);
}
},
));
}
#[test]
#[cfg(not(valgrind))]
fn test_contains_runtime_repeated() {
let registry = ProtoTypeRegistry::new(crate::Mode::Prost, ExternPaths::new(crate::Mode::Prost), PathSet::default());
let compiler = Compiler::new(®istry);
let string_value = CompiledExpr::runtime(
CelType::Proto(ProtoType::Modified(ProtoModifiedValueType::Repeated(ProtoValueType::String))),
parse_quote!(input),
);
let output = Contains
.compile(CompilerCtx::new(
compiler.child(),
Some(string_value),
&[cel_parser::parse("'value'").unwrap()],
))
.unwrap();
insta::assert_snapshot!(postcompile::compile_str!(
postcompile::config! {
test: true,
dependencies: vec![
postcompile::Dependency::path("tinc", "../tinc"),
],
},
quote! {
fn contains(input: &Vec<String>) -> Result<bool, ::tinc::__private::cel::CelError<'_>> {
Ok(#output)
}
#[test]
fn test_contains() {
assert_eq!(contains(&vec!["value".into()]).unwrap(), true);
assert_eq!(contains(&vec!["not_value".into()]).unwrap(), false);
assert_eq!(contains(&vec!["xd".into(), "value".into()]).unwrap(), true);
}
},
));
}
}