Skip to content

Commit

Permalink
feat: "libify" the Validator
Browse files Browse the repository at this point in the history
- Returns `Result<()>` instead of bools from validation method
- Removes `validate_str`
- Adds crate-specific error based on `thiserror`
- Adds some doctests
  • Loading branch information
gadomski committed Jul 24, 2024
1 parent aadbca0 commit 04e598b
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 72 deletions.
5 changes: 3 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ rstest = "0.21.0"
serde = "1.0.204"
serde_derive = "1.0.204"
serde_json = "1.0.120"
thiserror = "1.0.63"

[[bin]]
name = "cql2-rs"
test = false
doc = false
151 changes: 100 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,82 @@
use boon::{Compiler, SchemaIndex, Schemas};
use boon::{Compiler, SchemaIndex, Schemas, ValidationError};
use geozero::geojson::GeoJsonWriter;
use geozero::wkt::Wkt;
use geozero::{CoordDimensions, GeozeroGeometry, ToJson};
use pest::iterators::{Pair, Pairs};
use pest::pratt_parser::PrattParser;
use pest::Parser;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;
use thiserror::Error;

/// Crate-specific error enum.
#[derive(Debug, Error)]
pub enum Error {
/// [boon::CompileError]
#[error(transparent)]
BoonCompile(#[from] boon::CompileError),

/// [serde_json::Error]
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
}

pub struct Validator {
schemas: Schemas,
index: SchemaIndex,
}

impl Validator {
pub fn new() -> Validator {
/// Creates a new validator.
///
/// # Examples
///
/// ```
/// use cql2_rs::Validator;
///
/// let validator = Validator::new().unwrap();
/// ```
pub fn new() -> Result<Validator, Error> {
let mut schemas = Schemas::new();
let mut compiler = Compiler::new();
let schema_json = serde_json::from_str(include_str!("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());
match debug_level {
"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 {
self.validate(serde_json::from_str(obj).expect("Could not convert string to json."))
let schema_json = serde_json::from_str(include_str!("cql2.json"))?;
// TODO make this windoze friendly
compiler.add_resource("/tmp/cql2.json", schema_json)?;
let index = compiler.compile("/tmp/cql2.json", &mut schemas)?;
Ok(Validator { schemas, index })
}
}

impl Default for Validator {
fn default() -> Self {
Self::new()
/// Validates a [serde_json::Value].
///
/// Returns either `Ok(())` or the error output as a [serde_json::Value].
/// Use [Validator::detailed] to configure whether the basic or detailed
/// output is returned.
///
/// # Examples
///
/// ```
/// use cql2_rs::Validator;
/// use serde_json::json;
///
/// let validator = Validator::new().unwrap();
///
/// let valid = json!({
/// "op": "=",
/// "args": [
/// { "property": "landsat:scene_id" },
/// "LC82030282019133LGN00"
/// ]
/// });
/// validator.validate(&valid).unwrap();
///
/// let invalid = json!({
/// "op": "not an operator!",
/// });
/// validator.validate(&invalid).unwrap_err();
/// ```
pub fn validate<'a, 'b>(&'a self, value: &'b Value) -> Result<(), ValidationError<'a, 'b>> {
self.schemas.validate(value, self.index)
}
}

Expand Down Expand Up @@ -96,14 +112,47 @@ impl Expr {
fn as_sql() -> String {
return "sql".to_string();
} */
pub fn as_json(&self) -> String {
serde_json::to_string(&self).unwrap()

/// Converts this expression to a JSON string.
///
/// # Examples
///
/// ```
/// use cql2_rs::Expr;
///
/// let expr = Expr::BoolConst(true);
/// let s = expr.to_json().unwrap();
/// ```
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&self)
}
pub fn as_json_pretty(&self) -> String {
serde_json::to_string_pretty(&self).unwrap()

/// Converts this expression to a pretty JSON string.
///
/// # Examples
///
/// ```
/// use cql2_rs::Expr;
///
/// let expr = Expr::BoolConst(true);
/// let s = expr.to_json_pretty().unwrap();
/// ```
pub fn to_json_pretty(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(&self)
}
pub fn validate(&self) -> bool {
Validator::new().validate_str(&self.as_json())

/// Converts this expression to a [serde_json::Value].
///
/// # Examples
///
/// ```
/// use cql2_rs::Expr;
///
/// let expr = Expr::BoolConst(true);
/// let value = expr.to_value().unwrap();
/// ```
pub fn to_value(&self) -> Result<Value, serde_json::Error> {
serde_json::to_value(self)
}
}

Expand Down
37 changes: 29 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
use cql2_rs::parse;
use std::io::{self, BufRead};
fn main() -> io::Result<()> {
for line in io::stdin().lock().lines() {
let parsed = parse(&line?);
use cql2_rs::Validator;
use std::io::BufRead;

fn main() {
let debug_level: u8 = std::env::var("CQL2_DEBUG_LEVEL")
.map(|s| {
s.parse()
.unwrap_or_else(|_| panic!("CQL2_DEBUG_LEVEL should be an integer: {}", s))
})
.unwrap_or(1);
let validator = Validator::new().unwrap();

let mut ok = true;
for line in std::io::stdin().lock().lines() {
let parsed = cql2_rs::parse(&line.unwrap());
println!("Parsed: {:#?}", &parsed);
println!("{}", parsed.as_json());
println!("{}", parsed.to_json().unwrap());
let value = serde_json::to_value(parsed).unwrap();

if let Err(err) = validator.validate(&value) {
match debug_level {
0 => println!("-----------\nCQL2 Is Invalid!\n---------------"),
1 => println!("-----------\n{err}\n---------------"),
2 => println!("-----------\n{err:?}\n---------------"),
_ => println!("-----------\n{err:?}\n---------------"),
}
ok = false;
}
}

parsed.validate();
if !ok {
std::process::exit(1);
}
Ok(())
}
16 changes: 5 additions & 11 deletions tests/ogc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,16 @@ use std::fs;
use std::path::PathBuf;

pub fn validate_file(f: &str) {
//println!("Current Directory: {:#?}", env::current_dir());
println!("File Path: {:#?}", f);
let cql2 = fs::read_to_string(f).unwrap();
println!("CQL2: {}", cql2);
let expr: cql2_rs::Expr = parse(&cql2);
println!("Expr: {}", expr.as_json_pretty());
let valid = expr.validate();
assert!(valid)
}
println!("Expr: {}", expr.to_json_pretty().unwrap());

#[rstest]
fn json_examples_are_valid(#[files("tests/fixtures/json/*.json")] path: PathBuf) {
let cql2 = fs::read_to_string(path).unwrap();
let validator = Validator::new();
let result = validator.validate_str(&cql2);
assert!(result)
Validator::new()
.unwrap()
.validate(&expr.to_value().unwrap())
.unwrap();
}

#[rstest]
Expand Down

0 comments on commit 04e598b

Please sign in to comment.