diff --git a/examples/rust/jsonschema/Cargo.toml b/examples/rust/jsonschema/Cargo.toml new file mode 100644 index 0000000..780d6c4 --- /dev/null +++ b/examples/rust/jsonschema/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "extension" +version = "0.1.0" +edition = "2018" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen-rust = { git = "https://github.com/bytecodealliance/wit-bindgen.git", rev = "60e3c5b41e616fee239304d92128e117dd9be0a7" } +boon = { version = "0.5.1" } +serde_json = { version = "1" } diff --git a/examples/rust/jsonschema/README.md b/examples/rust/jsonschema/README.md new file mode 100644 index 0000000..ee665df --- /dev/null +++ b/examples/rust/jsonschema/README.md @@ -0,0 +1,158 @@ +# ss_jsonschema: JSON Schema support for SingleStoreDB + +**ss_jsonschema** adds [JSON schema](https://json-schema.org/) validation support for json data types. Since this is a Wasm extension, this will take `string` as input and parse as Json in the validation process. See examples below. + +## Contents + +### `match_schema (schema: string, instance: string) -> bool` +Returns true if the given instance matches the schema provided + +### `is_valid_schema (schema: string) -> bool` +Returns true if the given schema is valid + +## Building +``` +# Install the WASI cargo extension. +cargo install cargo-wasi + +# Compile the Wasm module. +cargo wasi build --release +``` +## Deployment to SingleStoreDB + +To install these functions using the MySQL client, use the following commands. This command assumes you have built the Wasm module and your current directory is the root of this Git repo. Replace `$DBUSER`, `$DBHOST`, `$DBPORT`, and `$DBNAME` with, respectively, your database username, hostname, port, and the name of the database where you want to deploy the functions. + +```bash +cat < bool +is-valid-schema: func(schema: string) -> bool diff --git a/examples/rust/jsonschema/src/lib.rs b/examples/rust/jsonschema/src/lib.rs new file mode 100644 index 0000000..2771f66 --- /dev/null +++ b/examples/rust/jsonschema/src/lib.rs @@ -0,0 +1,122 @@ +wit_bindgen_rust::export!("extension.wit"); +use boon::{Compiler, Schemas}; +use serde_json::Value; + +struct Extension; + +const DEFAULT_SCHEMA_URL: &str = "http://tmp/schema.json"; + +impl extension::Extension for Extension { + fn match_schema(schema_str: String, instance_str: String) -> bool { + let schema: Value = serde_json::from_str(schema_str.as_str()).unwrap(); + let instance: Value = serde_json::from_str(instance_str.as_str()).unwrap(); + + let mut schemas = Schemas::new(); + let mut compiler = Compiler::new(); + compiler.add_resource(DEFAULT_SCHEMA_URL, schema).unwrap(); + let sch_index = compiler.compile(DEFAULT_SCHEMA_URL, &mut schemas).unwrap(); + let result = schemas.validate(&instance, sch_index); + if !result.is_err() { + return true; + } + false + } + + fn is_valid_schema(schema_str: String) -> bool { + let schema: Value = serde_json::from_str(schema_str.as_str()).unwrap(); + let mut compiler = Compiler::new(); + let is_valid = compiler.add_resource(DEFAULT_SCHEMA_URL, schema); + if !is_valid.is_err() { + return true; + } + false + } +} + +// sanity tests +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_from_string_naive() { + let naive_schema = r#"{"type": "object"}"#.to_string(); + let naive_input = r#"{"foo": "bar"}"#.to_string(); + assert_eq!( + ::match_schema(naive_schema, naive_input), + true + ); + } + + #[test] + fn test_from_string_number() { + let number_schema = r#"{"type": "number"}"#.to_string(); + let number_str_pass = "123".to_string(); + let number_str_fail = r#""meow""#.to_string(); + + assert_eq!( + ::match_schema( + number_schema.clone(), + number_str_pass + ), + true + ); + assert_eq!( + ::match_schema(number_schema, number_str_fail), + false + ); + } + + #[test] + fn test_from_string_required_field() { + let required_schema = r#"{ + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": ["foo"], + "additionalProperties": false + }"# + .to_string(); + let required_input_pass = r#"{"foo": "bar"}"#.to_string(); + let required_input_fail = r#"{"hi" : "bar"}"#.to_string(); + let required_input_fail_2 = r#"{"foo": "bar", "hi": "bar"}"#.to_string(); + assert_eq!( + ::match_schema( + required_schema.clone(), + required_input_pass + ), + true + ); + assert_eq!( + ::match_schema( + required_schema.clone(), + required_input_fail + ), + false + ); + assert_eq!( + ::match_schema( + required_schema, + required_input_fail_2 + ), + false + ); + } + + #[test] + fn test_is_valid_schema() { + let valid_schema = r#"{"type": "integer"}"#.to_string(); + let not_valid_schema = r#"{"type": "int"}"#.to_string(); + assert_eq!( + ::is_valid_schema(valid_schema), + true + ); + assert_eq!( + ::is_valid_schema(not_valid_schema), + false + ); + } +}