From b2222919b10b1089678e0dbe1777fd5ef339d0bd Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 23 Jul 2024 12:51:33 -0400 Subject: [PATCH 1/4] Set up CI --- .github/workflows/test.yml | 35 ++++++ ogcapi-features | 2 +- src/lib.rs | 232 ++++++++++++++++++++----------------- 3 files changed, 161 insertions(+), 108 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..f38e8cc --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Rust + +on: + push: + branches: + - main + pull_request: + +jobs: + lint-test: + name: Lint and Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Cargo fmt + run: cargo fmt --all -- --check + + - name: "clippy --all" + run: cargo clippy --all --all-features --tests -- -D warnings + + - name: "cargo check" + run: cargo check --all --all-features + + - name: "cargo test" + run: | + cargo test --all + cargo test --all --all-features diff --git a/ogcapi-features b/ogcapi-features index d7aa4e4..0c508be 160000 --- a/ogcapi-features +++ b/ogcapi-features @@ -1 +1 @@ -Subproject commit d7aa4e4174528f1bee8b0149ac171811aa4a93dd +Subproject commit 0c508be34aaca0d9cf5e05722276a0ee10585d61 diff --git a/src/lib.rs b/src/lib.rs index c33f17a..56a518b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,91 +1,94 @@ +use boon::{Compiler, SchemaIndex, Schemas}; use geozero::geojson::GeoJsonWriter; -use pest::iterators::{Pairs, Pair}; +use geozero::wkt::Wkt; +use geozero::{CoordDimensions, GeozeroGeometry, ToJson}; +use pest::iterators::{Pair, Pairs}; use pest::pratt_parser::PrattParser; use pest::Parser; -use serde_json; -use serde_derive::{Serialize, Deserialize}; +use serde_derive::{Deserialize, Serialize}; use std::fs; -use boon::{Schemas, Compiler, SchemaIndex}; -use geozero::wkt::Wkt; -use geozero::{ToJson, CoordDimensions, GeozeroGeometry}; pub struct Validator { schemas: Schemas, - index: SchemaIndex + index: SchemaIndex, } impl Validator { - pub fn new() -> Validator{ + pub fn new() -> Validator { let mut schemas = Schemas::new(); let mut compiler = Compiler::new(); - let schema_json = serde_json::from_str(include_str!("../ogcapi-features/cql2/standard/schema/cql2.json")).expect("Could not parse schema to json"); - compiler.add_resource("/tmp/cql2.json", schema_json).expect("Could not add schema to compiler"); - let index = compiler.compile( - "/tmp/cql2.json", - &mut schemas - ).expect("Could not compile schema"); - Validator{schemas,index} - + let schema_json = serde_json::from_str(include_str!( + "../ogcapi-features/cql2/standard/schema/cql2.json" + )) + .expect("Could not parse schema to json"); + compiler + .add_resource("/tmp/cql2.json", schema_json) + .expect("Could not add schema to compiler"); + let index = compiler + .compile("/tmp/cql2.json", &mut schemas) + .expect("Could not compile schema"); + Validator { schemas, index } } + pub fn validate(self, obj: serde_json::Value) -> bool { let valid = self.schemas.validate(&obj, self.index); match valid { Ok(()) => true, Err(e) => { - let debug_level: &str = &std::env::var("CQL2_DEBUG_LEVEL").unwrap_or("1".to_string()); + let debug_level: &str = + &std::env::var("CQL2_DEBUG_LEVEL").unwrap_or("1".to_string()); match debug_level { - "3" => { println!("-----------\n{e:#?}\n---------------")}, - "2" => { println!("-----------\n{e:?}\n---------------")}, - "1" => { println!("-----------\n{e}\n---------------")}, - _ => { println!("-----------\nCQL2 Is Invalid!\n---------------")}, + "3" => { + println!("-----------\n{e:#?}\n---------------") + } + "2" => { + println!("-----------\n{e:?}\n---------------") + } + "1" => { + println!("-----------\n{e}\n---------------") + } + _ => { + println!("-----------\nCQL2 Is Invalid!\n---------------") + } } false } } } - pub fn validate_str(self, obj: &str) -> bool{ + pub fn validate_str(self, obj: &str) -> bool { self.validate(serde_json::from_str(obj).expect("Could not convert string to json.")) } } +impl Default for Validator { + fn default() -> Self { + Self::new() + } +} #[derive(pest_derive::Parser)] #[grammar = "cql2.pest"] pub struct CQL2Parser; - - #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] pub enum Expr { - Operation { - op: String, - args: Vec>, - }, - Interval { - interval: Vec>, - }, - Timestamp { - timestamp: Box, - }, - Date { - date: Box, - }, + Operation { op: String, args: Vec> }, + Interval { interval: Vec> }, + Timestamp { timestamp: Box }, + Date { date: Box }, // #[serde(serialize_with = "serialize_geometry", deserialize_with = "deserialize_geometry")] Geometry(serde_json::Value), ArithValue(u64), FloatValue(f64), LiteralValue(String), BoolConst(bool), - Property { - property: String, - }, + Property { property: String }, ArrayValue(Vec>), Coord(Vec>), PCoordList(Vec), PCoordListList(Vec>), PCoordListListList(Vec>), - } impl Expr { @@ -96,10 +99,10 @@ impl Expr { return "sql".to_string(); } */ pub fn as_json(&self) -> String { - return serde_json::to_string(&self).unwrap(); + serde_json::to_string(&self).unwrap() } pub fn as_json_pretty(&self) -> String { - return serde_json::to_string_pretty(&self).unwrap(); + serde_json::to_string_pretty(&self).unwrap() } pub fn validate(&self) -> bool { Validator::new().validate_str(&self.as_json()) @@ -148,18 +151,17 @@ pub fn normalize_op(op: &str) -> String { "eq" => "=", _ => &oper, }; - return operator.to_string(); + operator.to_string() } pub fn strip_quotes(quoted_string: &str) -> String { let len = quoted_string.len(); let bytes = quoted_string.as_bytes(); - if - (bytes[0] == b'"' && bytes[len-1] == b'"') - || (bytes[0] == b'\'' && bytes[len-1] == b'\'') { - Some("ed_string[1..len-1]).unwrap().to_string() - } - else { + if (bytes[0] == b'"' && bytes[len - 1] == b'"') + || (bytes[0] == b'\'' && bytes[len - 1] == b'\'') + { + quoted_string[1..len - 1].to_string() + } else { quoted_string.to_string() } } @@ -168,7 +170,6 @@ pub fn opstr(op: Pair) -> String { return normalize_op(op.as_str()); } - fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { PRATT_PARSER .map_primary(|primary| match primary.as_rule() { @@ -176,22 +177,18 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { Rule::Unsigned => { let u64_value = primary.as_str().parse::().unwrap(); Expr::ArithValue(u64_value) - }, + } Rule::DECIMAL => { let f64_value = primary.as_str().parse::().unwrap(); Expr::FloatValue(f64_value) - }, - Rule::SingleQuotedString => { - Expr::LiteralValue(strip_quotes(primary.as_str())) - }, + } + Rule::SingleQuotedString => Expr::LiteralValue(strip_quotes(primary.as_str())), Rule::True | Rule::False => { let bool_value = primary.as_str().to_lowercase().parse::().unwrap(); Expr::BoolConst(bool_value) - }, - Rule::Identifier => { - Expr::Property { - property: strip_quotes(primary.as_str()), - } + } + Rule::Identifier => Expr::Property { + property: strip_quotes(primary.as_str()), }, Rule::GEOMETRY => { let geom_wkt = Wkt(primary.as_str()); @@ -199,8 +196,7 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { let mut p = GeoJsonWriter::with_dims(&mut out, CoordDimensions::xyz()); let _ = geom_wkt.process_geom(&mut p); Expr::Geometry(serde_json::from_str(&geom_wkt.to_json().unwrap()).unwrap()) - - }, + } Rule::Function => { let mut pairs = primary.into_inner(); let op = strip_quotes(pairs.next().unwrap().as_str()).to_lowercase(); @@ -209,12 +205,16 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { args.push(Box::new(parse_expr(pair.into_inner()))) } match op.as_str() { - "interval" => Expr::Interval{interval: args}, - "date" => Expr::Date{date: args.into_iter().nth(0).unwrap()}, - "timestamp" => Expr::Timestamp{timestamp: args.into_iter().nth(0).unwrap()}, - _ => Expr::Operation{ op:op, args:args }, + "interval" => Expr::Interval { interval: args }, + "date" => Expr::Date { + date: args.into_iter().next().unwrap(), + }, + "timestamp" => Expr::Timestamp { + timestamp: args.into_iter().next().unwrap(), + }, + _ => Expr::Operation { op, args }, } - }, + } Rule::Array => { let pairs = primary.into_inner(); let mut array_elements = Vec::new(); @@ -222,21 +222,20 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { array_elements.push(Box::new(parse_expr(pair.into_inner()))) } Expr::ArrayValue(array_elements) - - }, + } rule => unreachable!("Expr::parse expected atomic rule, found {:?}", rule), }) .map_infix(|lhs, op, rhs| { let mut opstring = opstr(op); - let mut notflag: bool= false; - if opstring.starts_with("not"){ - opstring = opstring.replace("not ",""); - notflag=true; + let mut notflag: bool = false; + if opstring.starts_with("not") { + opstring = opstring.replace("not ", ""); + notflag = true; } - let origargs = vec![Box::new(lhs.clone()),Box::new(rhs.clone())]; + let origargs = vec![Box::new(lhs.clone()), Box::new(rhs.clone())]; let mut retexpr; let mut lhsclone = lhs.clone(); let rhsclone = rhs.clone(); @@ -244,75 +243,94 @@ fn parse_expr(expression_pairs: Pairs<'_, Rule>) -> Expr { let mut outargs: Vec> = Vec::new(); match lhsclone { - Expr::Operation{ref op, ref args} if *op == "and".to_string() => { - for arg in args.into_iter(){ + Expr::Operation { ref op, ref args } if *op == "and" => { + for arg in args.iter() { outargs.push(arg.clone()); } outargs.push(Box::new(rhsclone)); //retexpr = Expr::Operation{op, args: outargs}; - return Expr::Operation{op: "and".to_string(), args:outargs}; - }, - _=>() + return Expr::Operation { + op: "and".to_string(), + args: outargs, + }; + } + _ => (), } - if opstring == "between".to_string() { + if opstring == "between" { match lhsclone { - Expr::Operation{op, args} if op == "not".to_string() => { + Expr::Operation { op, args } if op == "not" => { let mut lhsargs = args.into_iter(); lhsclone = *lhsargs.next().unwrap(); - notflag=true; - }, - _=>() + notflag = true; + } + _ => (), } - match rhs { - Expr::Operation{op,args} if op == "and".to_string() => { + Expr::Operation { op, args } if op == "and" => { let mut rhsargs = args.into_iter(); - retexpr = Expr::Operation{ op: opstring, args: vec![Box::new(lhsclone), rhsargs.next().unwrap(), rhsargs.next().unwrap()]}; + retexpr = Expr::Operation { + op: opstring, + args: vec![ + Box::new(lhsclone), + rhsargs.next().unwrap(), + rhsargs.next().unwrap(), + ], + }; if rhsargs.len() >= 1 { let mut newargs = vec![Box::new(retexpr)]; - for rhsarg in rhsargs{ + for rhsarg in rhsargs { newargs.push(rhsarg); } - retexpr = Expr::Operation{ op: "and".to_string(), args: newargs}; + retexpr = Expr::Operation { + op: "and".to_string(), + args: newargs, + }; } - }, - _ => unreachable!("RHS of between must be And Statement") + } + _ => unreachable!("RHS of between must be And Statement"), } } else { retexpr = Expr::Operation { op: opstring, - args: origargs + args: origargs, }; } - if notflag { - return Expr::Operation{ op: "not".to_string(), args:vec!(Box::new(retexpr))}; + return Expr::Operation { + op: "not".to_string(), + args: vec![Box::new(retexpr)], + }; } - return retexpr; - - + retexpr }) .map_prefix(|op, child| match op.as_rule() { - Rule::UnaryNot => Expr::Operation { op: "not".to_string(), args: vec![Box::new(child)] } , - Rule::Negative => Expr::Operation { op: "*".to_string(), args: vec![Box::new(Expr::FloatValue(-1.0)),Box::new(child)] }, + Rule::UnaryNot => Expr::Operation { + op: "not".to_string(), + args: vec![Box::new(child)], + }, + Rule::Negative => Expr::Operation { + op: "*".to_string(), + args: vec![Box::new(Expr::FloatValue(-1.0)), Box::new(child)], + }, rule => unreachable!("Expr::parse expected prefix operator, found {:?}", rule), }) .map_postfix(|child, op| match op.as_rule() { - Rule::IsNullPostfix => Expr::Operation { op: "isNull".to_string(), args: vec![Box::new(child)] } , + Rule::IsNullPostfix => Expr::Operation { + op: "isNull".to_string(), + args: vec![Box::new(child)], + }, rule => unreachable!("Expr::parse expected postfix operator, found {:?}", rule), }) .parse(expression_pairs) } - - -pub fn parse(cql2: &str) -> Expr{ - if cql2.starts_with("{"){ +pub fn parse(cql2: &str) -> Expr { + if cql2.starts_with('{') { let expr: Expr = serde_json::from_str(cql2).unwrap(); - return expr; + expr } else { let mut pairs = CQL2Parser::parse(Rule::Expr, cql2).unwrap(); return parse_expr(pairs.next().unwrap().into_inner()); @@ -321,5 +339,5 @@ pub fn parse(cql2: &str) -> Expr{ pub fn parse_file(f: &str) -> Expr { let cql2 = fs::read_to_string(f).unwrap(); - return parse(&cql2) + parse(&cql2) } From c226ecce8d43de9eea6fd4e1170607bc39e1e3d4 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 23 Jul 2024 12:53:26 -0400 Subject: [PATCH 2/4] fmt --- src/main.rs | 4 +--- tests/ogc_tests.rs | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0694699..8cc872d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ - -use std::io::{self, BufRead}; use cql2_rs::parse; +use std::io::{self, BufRead}; fn main() -> io::Result<()> { for line in io::stdin().lock().lines() { let parsed = parse(&line?); @@ -9,7 +8,6 @@ fn main() -> io::Result<()> { println!("{}", parsed.as_json()); parsed.validate(); - } Ok(()) } diff --git a/tests/ogc_tests.rs b/tests/ogc_tests.rs index dddf952..90b74e1 100644 --- a/tests/ogc_tests.rs +++ b/tests/ogc_tests.rs @@ -1,12 +1,9 @@ -use cql2_rs::{parse,Validator}; +use cql2_rs::{parse, Validator}; +use rstest::rstest; use std::fs; use std::path::PathBuf; -use rstest::rstest; - - - -pub fn validate_file(f: &str){ +pub fn validate_file(f: &str) { //println!("Current Directory: {:#?}", env::current_dir()); println!("File Path: {:#?}", f); let cql2 = fs::read_to_string(f).unwrap(); @@ -24,7 +21,9 @@ pub fn validate_file(f: &str){ // } #[rstest] -fn json_examples_are_valid(#[files("ogcapi-features/cql2/standard/schema/examples/json/*.json")] path: PathBuf){ +fn json_examples_are_valid( + #[files("ogcapi-features/cql2/standard/schema/examples/json/*.json")] path: PathBuf, +) { let cql2 = fs::read_to_string(path).unwrap(); let validator = Validator::new(); let result = validator.validate_str(&cql2); @@ -32,12 +31,16 @@ fn json_examples_are_valid(#[files("ogcapi-features/cql2/standard/schema/example } #[rstest] -fn for_each_text_file(#[files("ogcapi-features/cql2/standard/schema/examples/text/*.txt")] path: PathBuf){ +fn for_each_text_file( + #[files("ogcapi-features/cql2/standard/schema/examples/text/*.txt")] path: PathBuf, +) { validate_file(path.to_str().expect("reason")); } #[rstest] -fn for_each_json_file(#[files("ogcapi-features/cql2/standard/schema/examples/json/*.json")] path: PathBuf){ +fn for_each_json_file( + #[files("ogcapi-features/cql2/standard/schema/examples/json/*.json")] path: PathBuf, +) { validate_file(path.to_str().expect("reason")); } From cedbc9b3664d36a3b51021b473d21a43451ba20b Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 23 Jul 2024 13:04:44 -0400 Subject: [PATCH 3/4] remove geos --- Cargo.lock | 100 ----------------------------------------------------- Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc4c476..03882bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,12 +86,6 @@ dependencies = [ "url", ] -[[package]] -name = "c_vec" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd7a427adc0135366d99db65b36dae9237130997e560ed61118041fb72be6e8" - [[package]] name = "cfg-if" version = "1.0.0" @@ -295,29 +289,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "geos" -version = "9.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "468b7ac7233c0f417b2c161d28b6d1e88c025eaf79303b69e6e1aa40a2ac1367" -dependencies = [ - "c_vec", - "geos-sys", - "libc", - "num", -] - -[[package]] -name = "geos-sys" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc873d24aefc72aa94c3c1c251afb82beb7be5926002746c0e1f585fef9854c" -dependencies = [ - "libc", - "pkg-config", - "semver", -] - [[package]] name = "geozero" version = "0.13.0" @@ -325,7 +296,6 @@ source = "git+https://github.com/bitner/geozero.git?rev=45828d7#45828d7e966496e8 dependencies = [ "geo-types", "geojson", - "geos", "log", "serde_json", "thiserror", @@ -411,70 +381,6 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -554,12 +460,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - [[package]] name = "proc-macro-crate" version = "3.1.0" diff --git a/Cargo.toml b/Cargo.toml index 9f95405..a73dd2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +geozero = { git = "https://github.com/bitner/geozero.git", rev = "45828d7" } boon = "0.6.0" glob = "0.3.1" lazy_static = "1.5.0" @@ -13,4 +14,3 @@ rstest = "0.21.0" serde = "1.0.204" serde_derive = "1.0.204" serde_json = "1.0.120" -geozero = { git = "https://github.com/bitner/geozero.git", rev = "45828d7", features = ["with-geos"] } From 74406e1fea34fc1129cd98f5cfcdb9d45eee0913 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Tue, 23 Jul 2024 13:24:31 -0400 Subject: [PATCH 4/4] fetch submodule --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f38e8cc..2e57fcc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + submodules: true - name: Install Rust uses: dtolnay/rust-toolchain@stable