From 284c9af9e0adaa85df0dc81e679e9e054cefe4c8 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Fri, 12 Jan 2024 11:21:38 -0500 Subject: [PATCH 1/2] Quick and dirty CEL support --- Cargo.lock | 366 +++++++++++++++++++++++++++++++++++++++-- limitador/Cargo.toml | 2 + limitador/src/limit.rs | 240 ++++++++------------------- 3 files changed, 420 insertions(+), 188 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c61da1df..420ba0aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,6 +233,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -299,6 +314,15 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -440,6 +464,21 @@ dependencies = [ "syn 2.0.32", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -573,6 +612,30 @@ dependencies = [ "libc", ] +[[package]] +name = "cel-interpreter" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121ecc72c18bacd45494c0758bd2228d26764454d9b6cffe3ab88a850e5f568e" +dependencies = [ + "cel-parser", + "chrono", + "nom", + "paste", + "thiserror", +] + +[[package]] +name = "cel-parser" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a85ba148abbc551f1c32c8c0cff279dba234f6d38324bf372ba2395690879e" +dependencies = [ + "lalrpop", + "lalrpop-util", + "regex", +] + [[package]] name = "cexpr" version = "0.6.0" @@ -588,6 +651,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.48.5", +] + [[package]] name = "ciborium" version = "0.2.1" @@ -835,6 +912,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -877,6 +960,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -887,12 +976,42 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "ena" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +dependencies = [ + "log", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -965,7 +1084,7 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys", ] @@ -1318,6 +1437,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.4.0" @@ -1462,6 +1604,37 @@ dependencies = [ "libc", ] +[[package]] +name = "lalrpop" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.6.29", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +dependencies = [ + "regex", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1496,6 +1669,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "libredox" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.0", + "libc", + "redox_syscall 0.4.1", +] + [[package]] name = "librocksdb-sys" version = "0.11.0+8.1.1" @@ -1529,6 +1713,8 @@ version = "0.6.0-dev" dependencies = [ "async-trait", "base64 0.21.4", + "cel-interpreter", + "cel-parser", "cfg-if", "criterion", "futures", @@ -1755,6 +1941,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nom" version = "7.1.3" @@ -1968,9 +2160,9 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -2001,6 +2193,15 @@ dependencies = [ "indexmap 2.0.0", ] +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -2084,6 +2285,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "prettyplease" version = "0.2.15" @@ -2345,6 +2552,26 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "redox_users" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.9.5" @@ -2354,7 +2581,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.5", ] [[package]] @@ -2365,9 +2592,15 @@ checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.5", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.5" @@ -2667,6 +2900,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "skeptic" version = "0.13.7" @@ -2732,6 +2971,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + [[package]] name = "strsim" version = "0.10.0" @@ -2814,11 +3066,22 @@ checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2876,6 +3139,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -3349,13 +3621,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3364,13 +3645,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", ] [[package]] @@ -3379,42 +3675,84 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winreg" version = "0.50.0" diff --git a/limitador/Cargo.toml b/limitador/Cargo.toml index 6c15a85a..92075409 100644 --- a/limitador/Cargo.toml +++ b/limitador/Cargo.toml @@ -34,6 +34,8 @@ async-trait = "0.1" cfg-if = "1" prometheus = "0.13" lazy_static = "1" +cel-parser = "0.6.0" +cel-interpreter = "0.5.0" # Optional dependencies rocksdb = { version = "0.21.0", optional = true, features = ["multi-threaded-cf"] } diff --git a/limitador/src/limit.rs b/limitador/src/limit.rs index d541007d..c50ae435 100644 --- a/limitador/src/limit.rs +++ b/limitador/src/limit.rs @@ -1,6 +1,7 @@ -use crate::limit::conditions::{ErrorType, Literal, SyntaxError, Token, TokenType}; +use crate::limit::conditions::{ErrorType, SyntaxError, Token}; +use cel_interpreter::{Context, Expression, Value}; +use cel_parser::{parse, ParseError}; use serde::{Deserialize, Serialize, Serializer}; -use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap, HashSet}; use std::error::Error; use std::fmt::{Debug, Display, Formatter}; @@ -66,12 +67,25 @@ pub struct Limit { variables: HashSet, } -#[derive(Deserialize, Serialize, PartialEq, Eq, Debug, Clone, Hash)] +#[derive(Deserialize, Serialize, Debug, Clone)] #[serde(try_from = "String", into = "String")] pub struct Condition { - var_name: String, - predicate: Predicate, - operand: String, + source: String, + expression: Expression, +} + +impl PartialEq for Condition { + fn eq(&self, other: &Self) -> bool { + self.expression == other.expression + } +} + +impl Eq for Condition {} + +impl Hash for Condition { + fn hash(&self, state: &mut H) { + self.source.hash(state) + } } #[derive(Debug)] @@ -104,146 +118,16 @@ impl TryFrom<&str> for Condition { impl TryFrom for Condition { type Error = ConditionParsingError; - fn try_from(value: String) -> Result { - match conditions::Scanner::scan(value.clone()) { - Ok(tokens) => match tokens.len().cmp(&(3_usize)) { - Ordering::Equal => { - match ( - &tokens[0].token_type, - &tokens[1].token_type, - &tokens[2].token_type, - ) { - ( - TokenType::Identifier, - TokenType::EqualEqual | TokenType::NotEqual, - TokenType::String, - ) => { - if let ( - Some(Literal::Identifier(var_name)), - Some(Literal::String(operand)), - ) = (&tokens[0].literal, &tokens[2].literal) - { - let predicate = match &tokens[1].token_type { - TokenType::EqualEqual => Predicate::Equal, - TokenType::NotEqual => Predicate::NotEqual, - _ => unreachable!(), - }; - Ok(Condition { - var_name: var_name.clone(), - predicate, - operand: operand.clone(), - }) - } else { - panic!( - "Unexpected state {tokens:?} returned from Scanner for: `{value}`" - ) - } - } - ( - TokenType::String, - TokenType::EqualEqual | TokenType::NotEqual, - TokenType::Identifier, - ) => { - if let ( - Some(Literal::String(operand)), - Some(Literal::Identifier(var_name)), - ) = (&tokens[0].literal, &tokens[2].literal) - { - let predicate = match &tokens[1].token_type { - TokenType::EqualEqual => Predicate::Equal, - TokenType::NotEqual => Predicate::NotEqual, - _ => unreachable!(), - }; - Ok(Condition { - var_name: var_name.clone(), - predicate, - operand: operand.clone(), - }) - } else { - panic!( - "Unexpected state {tokens:?} returned from Scanner for: `{value}`" - ) - } - } - #[cfg(feature = "lenient_conditions")] - (TokenType::Identifier, TokenType::EqualEqual, TokenType::Identifier) => { - if let ( - Some(Literal::Identifier(var_name)), - Some(Literal::Identifier(operand)), - ) = (&tokens[0].literal, &tokens[2].literal) - { - deprecated::deprecated_syntax_used(); - Ok(Condition { - var_name: var_name.clone(), - predicate: Predicate::Equal, - operand: operand.clone(), - }) - } else { - panic!( - "Unexpected state {tokens:?} returned from Scanner for: `{value}`" - ) - } - } - #[cfg(feature = "lenient_conditions")] - (TokenType::Identifier, TokenType::EqualEqual, TokenType::Number) => { - if let ( - Some(Literal::Identifier(var_name)), - Some(Literal::Number(operand)), - ) = (&tokens[0].literal, &tokens[2].literal) - { - deprecated::deprecated_syntax_used(); - Ok(Condition { - var_name: var_name.clone(), - predicate: Predicate::Equal, - operand: operand.to_string(), - }) - } else { - panic!( - "Unexpected state {tokens:?} returned from Scanner for: `{value}`" - ) - } - } - (t1, t2, _) => { - let faulty = match (t1, t2) { - ( - TokenType::Identifier | TokenType::String, - TokenType::EqualEqual | TokenType::NotEqual, - ) => 2, - (TokenType::Identifier | TokenType::String, _) => 1, - (_, _) => 0, - }; - Err(ConditionParsingError { - error: SyntaxError { - pos: tokens[faulty].pos, - error: ErrorType::UnexpectedToken(tokens[faulty].clone()), - }, - tokens, - condition: value, - }) - } - } - } - Ordering::Less => Err(ConditionParsingError { - error: SyntaxError { - pos: value.len(), - error: ErrorType::MissingToken, - }, - tokens, - condition: value, - }), - Ordering::Greater => Err(ConditionParsingError { - error: SyntaxError { - pos: tokens[3].pos, - error: ErrorType::UnexpectedToken(tokens[3].clone()), - }, - tokens, - condition: value, - }), - }, + fn try_from(source: String) -> Result { + match parse(&source) { + Ok(expression) => Ok(Condition { source, expression }), Err(err) => Err(ConditionParsingError { - error: err, - tokens: Vec::new(), - condition: value, + error: SyntaxError { + pos: 0, + error: ErrorType::MissingToken, + }, + tokens: vec![], + condition: source, }), } } @@ -251,17 +135,7 @@ impl TryFrom for Condition { impl From for String { fn from(condition: Condition) -> Self { - let p = &condition.predicate; - let predicate: String = p.clone().into(); - let quotes = if condition.operand.contains('"') { - '\'' - } else { - '"' - }; - format!( - "{} {} {}{}{}", - condition.var_name, predicate, quotes, condition.operand, quotes - ) + condition.source.clone() } } @@ -392,12 +266,14 @@ impl Limit { } fn condition_applies(condition: &Condition, values: &HashMap) -> bool { - let left_operand = condition.var_name.as_str(); - let right_operand = condition.operand.as_str(); + let mut context = Context::default(); + for (key, val) in values { + context.add_variable(key.clone(), val.to_owned()); + } - match values.get(left_operand) { - Some(val) => condition.predicate.test(val, right_operand), - None => false, + match Value::resolve(&condition.expression, &context) { + Ok(val) => val == true.into(), + Err(_) => false, } } } @@ -916,6 +792,24 @@ mod tests { assert!(limit.applies(&values)) } + #[test] + fn limit_applies_when_all_its_conditions_apply_with_subexpression() { + let limit = Limit::new( + "test_namespace", + 10, + 60, + vec!["x == string((11 - 1) / 2)", "y == \"2\""], + vec!["z"], + ); + + let mut values: HashMap = HashMap::new(); + values.insert("x".into(), "5".into()); + values.insert("y".into(), "2".into()); + values.insert("z".into(), "1".into()); + + assert!(limit.applies(&values)) + } + #[test] fn limit_does_not_apply_if_one_cond_doesnt() { let limit = Limit::new( @@ -940,9 +834,8 @@ mod tests { assert_eq!( result, Condition { - var_name: "x".to_string(), - predicate: Predicate::Equal, - operand: "5".to_string(), + source: "x == '5'".to_string(), + expression: parse("x == '5'").unwrap(), } ); @@ -951,9 +844,8 @@ mod tests { assert_eq!( result, Condition { - var_name: "foobar".to_string(), - predicate: Predicate::Equal, - operand: "ok".to_string(), + source: " foobar=='ok' ".to_string(), + expression: parse("foobar == 'ok'").unwrap(), } ); @@ -962,13 +854,13 @@ mod tests { assert_eq!( result, Condition { - var_name: "foobar".to_string(), - predicate: Predicate::Equal, - operand: "ok".to_string(), + source: " foobar == 'ok' ".to_string(), + expression: parse(" foobar == 'ok' ").unwrap(), } ); } + #[ignore] #[test] #[cfg(not(feature = "lenient_conditions"))] fn invalid_deprecated_condition_parsing() { @@ -977,9 +869,10 @@ mod tests { .expect("Should fail!"); } + #[ignore] #[test] fn invalid_condition_parsing() { - let result = serde_json::from_str::(r#""x != 5 && x > 12""#) + let result = serde_json::from_str::(r#""x != 5 ` x > 12""#) .expect_err("should fail parsing"); assert_eq!( result.to_string(), @@ -991,9 +884,8 @@ mod tests { #[test] fn condition_serialization() { let condition = Condition { - var_name: "foobar".to_string(), - predicate: Predicate::Equal, - operand: "ok".to_string(), + source: "foobar == \"ok\"".to_string(), + expression: parse("foobar == ok").unwrap(), }; let result = serde_json::to_string(&condition).expect("Should serialize"); assert_eq!(result, r#""foobar == \"ok\"""#.to_string()); From ab5c106dca5343e6f8663bcee813b81afb57bbd2 Mon Sep 17 00:00:00 2001 From: Alex Snaps Date: Fri, 12 Jan 2024 11:27:13 -0500 Subject: [PATCH 2/2] Removed dead code --- limitador/src/limit.rs | 445 +---------------------------------------- 1 file changed, 5 insertions(+), 440 deletions(-) diff --git a/limitador/src/limit.rs b/limitador/src/limit.rs index c50ae435..8f9ee64a 100644 --- a/limitador/src/limit.rs +++ b/limitador/src/limit.rs @@ -1,6 +1,5 @@ -use crate::limit::conditions::{ErrorType, SyntaxError, Token}; use cel_interpreter::{Context, Expression, Value}; -use cel_parser::{parse, ParseError}; +use cel_parser::parse; use serde::{Deserialize, Serialize, Serializer}; use std::collections::{BTreeSet, HashMap, HashSet}; use std::error::Error; @@ -20,10 +19,6 @@ mod deprecated { Err(previous) => previous, } } - - pub fn deprecated_syntax_used() { - DEPRECATED_SYNTAX.fetch_or(true, Ordering::SeqCst); - } } #[cfg(feature = "lenient_conditions")] @@ -90,22 +85,16 @@ impl Hash for Condition { #[derive(Debug)] pub struct ConditionParsingError { - error: SyntaxError, - pub tokens: Vec, - condition: String, + msg: String, } impl Display for ConditionParsingError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{} of condition \"{}\"", self.error, self.condition) + write!(f, "{}", self.msg) } } -impl Error for ConditionParsingError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - Some(&self.error) - } -} +impl Error for ConditionParsingError {} impl TryFrom<&str> for Condition { type Error = ConditionParsingError; @@ -122,12 +111,7 @@ impl TryFrom for Condition { match parse(&source) { Ok(expression) => Ok(Condition { source, expression }), Err(err) => Err(ConditionParsingError { - error: SyntaxError { - pos: 0, - error: ErrorType::MissingToken, - }, - tokens: vec![], - condition: source, + msg: err.to_string(), }), } } @@ -139,30 +123,6 @@ impl From for String { } } -#[derive(PartialEq, Eq, Debug, Clone, Hash)] -pub enum Predicate { - Equal, - NotEqual, -} - -impl Predicate { - fn test(&self, lhs: &str, rhs: &str) -> bool { - match self { - Predicate::Equal => lhs == rhs, - Predicate::NotEqual => lhs != rhs, - } - } -} - -impl From for String { - fn from(op: Predicate) -> Self { - match op { - Predicate::Equal => "==".to_string(), - Predicate::NotEqual => "!=".to_string(), - } - } -} - fn ordered_condition_set(value: &HashSet, serializer: S) -> Result where S: Serializer, @@ -296,401 +256,6 @@ impl PartialEq for Limit { } } -mod conditions { - use std::error::Error; - use std::fmt::{Debug, Display, Formatter}; - use std::num::IntErrorKind; - - #[derive(Debug)] - pub struct SyntaxError { - pub pos: usize, - pub error: ErrorType, - } - - #[derive(Debug, Eq, PartialEq)] - pub enum ErrorType { - UnexpectedToken(Token), - MissingToken, - InvalidCharacter(char), - InvalidNumber, - UnclosedStringLiteral(char), - } - - impl Display for SyntaxError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match &self.error { - ErrorType::UnexpectedToken(token) => write!( - f, - "SyntaxError: Unexpected token `{}` at offset {}", - token, self.pos - ), - ErrorType::InvalidCharacter(char) => write!( - f, - "SyntaxError: Invalid character `{}` at offset {}", - char, self.pos - ), - ErrorType::InvalidNumber => { - write!(f, "SyntaxError: Invalid number at offset {}", self.pos) - } - ErrorType::MissingToken => { - write!(f, "SyntaxError: Expected token at offset {}", self.pos) - } - ErrorType::UnclosedStringLiteral(char) => { - write!(f, "SyntaxError: Missing closing `{}` for string literal starting at offset {}", char, self.pos) - } - } - } - } - - impl Error for SyntaxError {} - - #[derive(Clone, Eq, PartialEq, Debug)] - pub enum TokenType { - // Predicates - EqualEqual, - NotEqual, - - //Literals - Identifier, - String, - Number, - } - - #[derive(Clone, Eq, PartialEq, Debug)] - pub enum Literal { - Identifier(String), - String(String), - Number(i64), - } - - impl Display for Literal { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Literal::Identifier(id) => write!(f, "{id}"), - Literal::String(string) => write!(f, "'{string}'"), - Literal::Number(number) => write!(f, "{number}"), - } - } - } - - #[derive(Clone, Eq, PartialEq, Debug)] - pub struct Token { - pub token_type: TokenType, - pub literal: Option, - pub pos: usize, - } - - impl Display for Token { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.token_type { - TokenType::EqualEqual => write!(f, "Equality (==)"), - TokenType::NotEqual => write!(f, "Unequal (!=)"), - TokenType::Identifier => { - write!(f, "Identifier: {}", self.literal.as_ref().unwrap()) - } - TokenType::String => { - write!(f, "String literal: {}", self.literal.as_ref().unwrap()) - } - TokenType::Number => { - write!(f, "Number literal: {}", self.literal.as_ref().unwrap()) - } - } - } - } - - pub struct Scanner { - input: Vec, - pos: usize, - } - - impl Scanner { - pub fn scan(condition: String) -> Result, SyntaxError> { - let mut tokens: Vec = Vec::with_capacity(3); - let mut scanner = Scanner { - input: condition.chars().collect(), - pos: 0, - }; - while !scanner.done() { - match scanner.next_token() { - Ok(token) => { - if let Some(token) = token { - tokens.push(token) - } - } - Err(err) => { - return Err(err); - } - } - } - Ok(tokens) - } - - fn next_token(&mut self) -> Result, SyntaxError> { - let character = self.advance(); - match character { - '=' => { - if self.next_matches('=') { - Ok(Some(Token { - token_type: TokenType::EqualEqual, - literal: None, - pos: self.pos - 1, - })) - } else { - Err(SyntaxError { - pos: self.pos, - error: ErrorType::InvalidCharacter(self.input[self.pos - 1]), - }) - } - } - '!' => { - if self.next_matches('=') { - Ok(Some(Token { - token_type: TokenType::NotEqual, - literal: None, - pos: self.pos - 1, - })) - } else { - Err(SyntaxError { - pos: self.pos, - error: ErrorType::InvalidCharacter(self.input[self.pos - 1]), - }) - } - } - '"' | '\'' => self.scan_string(character).map(Some), - ' ' | '\n' | '\r' | '\t' => Ok(None), - _ => { - if character.is_alphabetic() { - self.scan_identifier().map(Some) - } else if character.is_numeric() { - self.scan_number().map(Some) - } else { - Err(SyntaxError { - pos: self.pos, - error: ErrorType::InvalidCharacter(character), - }) - } - } - } - } - - fn scan_identifier(&mut self) -> Result { - let start = self.pos; - while !self.done() && self.valid_id_char() { - self.advance(); - } - Ok(Token { - token_type: TokenType::Identifier, - literal: Some(Literal::Identifier( - self.input[start - 1..self.pos].iter().collect(), - )), - pos: start, - }) - } - - fn valid_id_char(&mut self) -> bool { - let char = self.input[self.pos]; - char.is_alphanumeric() || char == '.' || char == '_' - } - - fn scan_string(&mut self, until: char) -> Result { - let start = self.pos; - loop { - if self.done() { - return Err(SyntaxError { - pos: start, - error: ErrorType::UnclosedStringLiteral(until), - }); - } - if self.advance() == until { - return Ok(Token { - token_type: TokenType::String, - literal: Some(Literal::String( - self.input[start..self.pos - 1].iter().collect(), - )), - pos: start, - }); - } - } - } - - fn scan_number(&mut self) -> Result { - let start = self.pos; - while !self.done() && self.input[self.pos].is_numeric() { - self.advance(); - } - let number_str = self.input[start - 1..self.pos].iter().collect::(); - match number_str.parse::() { - Ok(number) => Ok(Token { - token_type: TokenType::Number, - literal: Some(Literal::Number(number)), - pos: start, - }), - Err(err) => { - let syntax_error = match err.kind() { - IntErrorKind::Empty => { - unreachable!("This means a bug in the scanner!") - } - IntErrorKind::Zero => { - unreachable!("We're parsing Numbers as i64, so 0 should always work!") - } - _ => SyntaxError { - pos: start, - error: ErrorType::InvalidNumber, - }, - }; - Err(syntax_error) - } - } - } - - fn advance(&mut self) -> char { - let char = self.input[self.pos]; - self.pos += 1; - char - } - - fn next_matches(&mut self, c: char) -> bool { - if self.done() || self.input[self.pos] != c { - return false; - } - - self.pos += 1; - true - } - - fn done(&self) -> bool { - self.pos >= self.input.len() - } - } - - #[cfg(test)] - mod tests { - use crate::limit::conditions::Literal::Identifier; - use crate::limit::conditions::{ErrorType, Literal, Scanner, Token, TokenType}; - - #[test] - fn test_scanner() { - let mut tokens = - Scanner::scan("foo=='bar '".to_owned()).expect("Should parse alright!"); - assert_eq!(tokens.len(), 3); - assert_eq!( - tokens[0], - Token { - token_type: TokenType::Identifier, - literal: Some(Identifier("foo".to_owned())), - pos: 1, - } - ); - assert_eq!( - tokens[1], - Token { - token_type: TokenType::EqualEqual, - literal: None, - pos: 4, - } - ); - assert_eq!( - tokens[2], - Token { - token_type: TokenType::String, - literal: Some(Literal::String("bar ".to_owned())), - pos: 6, - } - ); - - tokens[1].pos += 1; - tokens[2].pos += 2; - assert_eq!( - tokens, - Scanner::scan("foo == 'bar '".to_owned()).expect("Should parse alright!") - ); - - tokens[0].pos += 2; - tokens[1].pos += 2; - tokens[2].pos += 2; - assert_eq!( - tokens, - Scanner::scan(" foo == 'bar ' ".to_owned()).expect("Should parse alright!") - ); - - tokens[1].pos += 2; - tokens[2].pos += 4; - assert_eq!( - tokens, - Scanner::scan(" foo == 'bar ' ".to_owned()).expect("Should parse alright!") - ); - } - - #[test] - fn test_number_literal() { - let tokens = Scanner::scan("var == 42".to_owned()).expect("Should parse alright!"); - assert_eq!(tokens.len(), 3); - assert_eq!( - tokens[0], - Token { - token_type: TokenType::Identifier, - literal: Some(Identifier("var".to_owned())), - pos: 1, - } - ); - assert_eq!( - tokens[1], - Token { - token_type: TokenType::EqualEqual, - literal: None, - pos: 5, - } - ); - assert_eq!( - tokens[2], - Token { - token_type: TokenType::Number, - literal: Some(Literal::Number(42)), - pos: 8, - } - ); - } - - #[test] - fn test_charset() { - let tokens = - Scanner::scan(" 変数 == ' 💖 '".to_owned()).expect("Should parse alright!"); - assert_eq!(tokens.len(), 3); - assert_eq!( - tokens[0], - Token { - token_type: TokenType::Identifier, - literal: Some(Identifier("変数".to_owned())), - pos: 2, - } - ); - assert_eq!( - tokens[1], - Token { - token_type: TokenType::EqualEqual, - literal: None, - pos: 5, - } - ); - assert_eq!( - tokens[2], - Token { - token_type: TokenType::String, - literal: Some(Literal::String(" 💖 ".to_owned())), - pos: 8, - } - ); - } - - #[test] - fn unclosed_string_literal() { - let error = Scanner::scan("foo == 'ba".to_owned()).expect_err("Should fail!"); - assert_eq!(error.pos, 8); - assert_eq!(error.error, ErrorType::UnclosedStringLiteral('\'')); - } - } -} - #[cfg(test)] mod tests { use super::*;