diff --git a/src/lib.rs b/src/lib.rs index e78355a..4ca5b58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,14 @@ +/*! +PGXN Metadata validation. + +This crate uses JSON Schema to validate PGXN Meta Spec `META.json` files. +It supports both the [v1] and [v2] specs. + + +[v1]: https://rfcs.pgxn.org/0001-meta-spec-v1.html +[v2]: https://rfcs.pgxn.org/0003-meta-spec-v2.html + +*/ + pub mod valid; pub use valid::*; diff --git a/src/valid/mod.rs b/src/valid/mod.rs index e4fdfb4..6f21198 100644 --- a/src/valid/mod.rs +++ b/src/valid/mod.rs @@ -149,7 +149,7 @@ mod tests { env!("CARGO_MANIFEST_DIR"), "tests", "corpus", - "v1", + "v2", "valid.txt", ] .iter() diff --git a/tests/corpus/v2/invalid.txt b/tests/corpus/v2/invalid.txt new file mode 100644 index 0000000..0cccc65 --- /dev/null +++ b/tests/corpus/v2/invalid.txt @@ -0,0 +1 @@ +{"test":"no_version","error":"missing properties 'version'","meta":{"name":"pair","abstract":"A key/value pair data type","maintainer":"David E. Wheeler ","license":"postgresql","provides":{"pair":{"abstract":"A key/value pair data type","file":"sql/pair.sql","docfile":"doc/pair.md","version":"0.1.0"}},"meta-spec":{"version":"1.0.0","url":"https://pgxn.org/meta/spec.txt"}}} diff --git a/tests/corpus/v2/valid.txt b/tests/corpus/v2/valid.txt new file mode 100644 index 0000000..f381857 --- /dev/null +++ b/tests/corpus/v2/valid.txt @@ -0,0 +1,6 @@ +{"test":"minimal","meta":{"name":"pair","abstract":"A key/value pair data type","version":"0.1.8","maintainers":[{"name":"David E. Wheeler","email":"david@justatheory.com"}],"license":"PostgreSQL","contents":{"extensions":{"pair":{"sql":"sql/pair.sql","control":"pair.control"}}},"meta-spec":{"version":"2.0.0"}}} +{"test":"typical sql","meta":{"name":"pair","abstract":"A key/value pair data type","description":"This library contains a single PostgreSQL extension, a key/value pair data type called “pair”, along with a convenience function for constructing key/value pairs.","version":"0.1.8","maintainers":[{"name":"David E. Wheeler","email":"david@justatheory.com"}],"license":"PostgreSQL","contents":{"extensions":{"pair":{"abstract":"A key/value pair data type","sql":"sql/pair.sql","doc":"doc/pair.md","control":"pair.control","tle":true}}},"dependencies":{"postgres":{"version":"9.1.0"}},"resources":{"issues":"https://github.com/theory/kv-pair/issues/","repository":"https://github.com/theory/kv-pair/","badges":[{"alt":"Test Status","src":"https://github.com/theory/kv-pair/workflows/CI/badge.svg","url":"https://github.com/theory/kv-pair/actions"}]},"producer":"David E. Wheeler","meta-spec":{"version":"2.0.0","url":"https://rfcs.pgxn.org/0003-meta-spec-v2.html"},"classifications":{"categories":["Data and Transformations"],"tags":["variadic function","ordered pair","pair","key value","key value pair"]},"artifacts":[{"type":"source","url":"https://github.com/theory/kv-pair/releases/download/v0.1.7/pair-0.1.7.zip","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}} +{"test":"typical c","meta":{"name":"envvar","abstract":"Get the value of a server environment variable","description":"Provides a function that returns the value of an environment variable set on the Postgres server.","version":"1.0.0","maintainers":[{"name":"David E. Wheeler","email":"david@justatheory.com"}],"license":"PostgreSQL","contents":{"extensions":{"envvar":{"abstract":"Get the value of a server environment variable","control":"envvar.control","sql":"sql/envvar.sql","doc":"doc/envvar.md"}},"modules":{"envvar":{"type":"extension","lib":"src/envvar"}}},"dependencies":{"postgres":{"version":"9.1.0"}},"resources":{"issues":"https://github.com/theory/pg-envvar/issues/","repository":"https://github.com/theory/pg-envvar/","badges":[{"alt":"CI Status","src":"https://github.com/theory/pg-envvar/actions/workflows/ci.yml/badge.svg","url":"https://github.com/theory/pg-envvar/actions/workflows/ci.yml"}]},"producer":"David E. Wheeler","meta-spec":{"version":"2.0.0","url":"https://rfcs.pgxn.org/0003-meta-spec-v2.html"},"classifications":{"categories":["Data and Transformations"],"tags":["environment variable","environment","server"]},"artifacts":[{"type":"source","url":"https://github.com/theory/pg-envvar/releases/download/v1.0.0/envvar-1.0.0.zip","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}} +{"test":"typical pgrx","meta":{"name":"jsonschema","abstract":"JSON Schema validation functions for PostgreSQL","description":"The jsonschema extension provides JSON Schema validation functions supporting drafts 2020-12, 2019-09, 7, 6, and 4","version":"0.1.1","maintainers":[{"name":"David E. Wheeler","email":"david@justatheory.com"},{"name":"Tembo","email":"admin+pgxn@tembo.io"}],"license":"MIT","contents":{"extensions":{"jsonschema":{"abstract":"JSON Schema validation functions for PostgreSQL","doc":"doc/jsonschema.md","x_comment":"Paths non-deterministic until pgrx ","sql":"target/release/**/jsonschema--0.1.1.sql","control":"target/release/**/jsonschema.control"}},"modules":{"libjsonschema":{"type":"extension","lib":"target/release/libjsonschema"}}},"dependencies":{"pipeline":"pgrx","postgres":{"version":"12.0"}},"resources":{"issues":"https://github.com/tembo-io/pg-jsonschema-boon/issues/","repository":"https://github.com/tembo-io/pg-jsonschema-boon/","badges":[{"alt":"⚖️ MIT License","src":"https://img.shields.io/badge/License-MIT-blue.svg","url":"https://github.com/tembo-io/pg-jsonschema-boon/blob/main/LICENSE.md"},{"alt":"🧪 Lint and Test","src":"https://github.com/tembo-io/pg-jsonschema-boon/actions/workflows/lint-and-test.yml/badge.svg","url":"https://github.com/tembo-io/pg-jsonschema-boon/actions/workflows/lint-and-test.yml"},{"alt":"📊 Code Coverage","src":"https://codecov.io/gh/tembo-io/pg-jsonschema-boon/graph/badge.svg","url":"https://codecov.io/gh/tembo-io/pg-jsonschema-boon"}]},"producer":"David E. Wheeler","meta-spec":{"version":"2.0.0","url":"https://rfcs.pgxn.org/0003-meta-spec-v2.html"},"classifications":{"categories":["Data and Transformations"],"tags":["jsonschema","validation","json","schema","constraint","pgrx","rust"]},"artifacts":[{"type":"source","url":"https://github.com/tembo-io/pg-jsonschema-boon/releases/download/v0.1.1/jsonschema-0.1.1.zip","sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"}]}} +{"test":"pg_partman","meta":{"name":"pg_partman","abstract":"Extension to manage partitioned tables by time or ID","version":"5.1.0","maintainers":[{"name":"Keith Fiske","email":"keith@keithf4.com"}],"license":"PostgreSQL","contents":{"extensions":{"pg_partman":{"control":"pg_partman.control","sql":"sql/types/types.sql","doc":"doc/pg_partman.md","abstract":"Extension to manage partitioned tables by time or ID"}},"modules":{"pg_partman_bgw":{"type":"bgw","lib":"src/pg_partman_bgw","preload":"server"}},"apps":{"check_unique_constraint":{"lang":"python","bin":"bin/common/check_unique_constraint.py","abstract":"Check that all rows in a partition set are unique for the given columns"},"dump_partition":{"lang":"python","bin":"bin/common/dump_partition.py","abstract":"Dump out and then drop all tables contained in a schema."},"vacuum_maintenance":{"lang":"python","bin":"bin/common/vacuum_maintenance.py","abstract":"Performing vacuum maintenance on to avoid excess vacuuming and transaction id wraparound issues"}}},"dependencies":{"postgres":{"version":"14.0"},"packages":{"run":{"requires":{"pkg:generic/python":"2.0","pkg:pypi/psycopg2":0},"recommends":{"pkg:pgxn/pg_jobmon":"1.4.1"}}}},"resources":{"issues":"https://github.com/theory/pg-envvar/issues/","repository":"https://github.com/theory/pg-envvar/","badges":[{"alt":"CI Status","src":"https://github.com/theory/pg-envvar/actions/workflows/ci.yml/badge.svg","url":"https://github.com/theory/pg-envvar/actions/workflows/ci.yml"}]},"producer":"David E. Wheeler","meta-spec":{"version":"2.0.0","url":"https://rfcs.pgxn.org/0003-meta-spec-v2.html"},"classifications":{"categories":["Orchestration"],"tags":["partition","partitions","partitioning","table","tables","bgw","background worker","custom background worker"]}}} +{"test":"postgresml","meta":{"name":"pgml","version":"2.8.2","abstract":"pgml: Created by the PostgresML team","maintainers":[{"name":"the PostgresML team","url":"https://github.com/postgresml/postgresml/"}],"license":"MIT","contents":{"extensions":{"pgml":{"sql":"target/release/**/pgml--2.7.13.sql","control":"target/release/**/pgml.control"}},"modules":{"pgml":{"type":"extension","lib":"target/release/pgml","preload":"server"}}},"dependencies":{"postgres":{"version":"9.1.0"},"pipeline":"pgrx","packages":{"configure":{"requires":{"pkg:generic/cmake":0,"pkg:cargo/cargo-pgrx":"0.11.2"}},"build":{"requires":{"pkg:generic/cargo":0,"pkg:generic/bison":0,"pkg:generic/flex":0,"pkg:generic/clang":0,"pkg:generic/openblas":0,"pkg:generic/python3":"3.7","pkg:generic/readline":0,"pkg:generic/openssl":0,"pkg:generic/pkg-config":0}},"run":{"requires":{"pkg:generic/openblas":0,"pkg:generic/python3":"3.7","pkg:generic/readline":0,"pkg:generic/openssl":0},"recommends":{"pkg:pypi/pyarrow":"11.0.0","pkg:pypi/catboost":0,"pkg:pypi/lightgbm":0,"pkg:pypi/torch":0,"pkg:pypi/torchaudio":0,"pkg:pypi/torchvision":0,"pkg:pypi/xgboost":0,"pkg:pypi/accelerate":0,"pkg:pypi/bitsandbytes":0,"pkg:pypi/ctransformers":0,"pkg:pypi/huggingface-hub":0,"pkg:pypi/deepspeed":0,"pkg:pypi/einops":0,"pkg:pypi/optimum":0,"pkg:pypi/peft":0,"pkg:pypi/tokenizers":0,"pkg:pypi/transformers":0,"pkg:pypi/transformers-stream-generator":0,"pkg:pypi/InstructorEmbedding":0,"pkg:pypi/sentence-transformers":0,"pkg:pypi/rouge":0,"pkg:pypi/sacrebleu":0,"pkg:pypi/sacremoses":0,"pkg:pypi/datasets":0,"pkg:pypi/orjson":0,"pkg:pypi/langchain":0}}},"variations":[{"where":{"platforms":["linux"]},"dependencies":{"packages":{"run":{"recommends":{"pkg:pypi/auto-gptq":0,"pkg:pypi/xformers":0}}}}}]},"resources":{"homepage":"https://postgresml.org/","issues":"https://github.com/postgresml/postgresml/issues","docs":"https://postgresml.org/docs/","support":"https://discord.com/invite/DmyJP3qJ7U","repository":"https://github.com/postgresml/postgresml","badges":[{"alt":"Tests Passing","src":"https://github.com/postgresml/postgresml/actions/workflows/ci.yml/badge.svg","url":"https://github.com/postgresml/postgresml/actions/workflows/ci.yml"},{"alt":"Chat Activity","src":"https://img.shields.io/discord/1013868243036930099","url":"https://discord.gg/DmyJP3qJ7U"}]},"producer":"David E. Wheeler","meta-spec":{"version":"2.0.0","url":"https://rfcs.pgxn.org/0003-meta-spec-v2.html"},"classifications":{"tags":["machine learning","ml","transformers"],"categories":["Machine Learning"]},"artifacts":[{"type":"source","url":"https://github.com/postgresml/postgresml/archive/refs/tags/v2.8.2.zip","sha256":"2b9d2416096d2930be51e5332b70bcd97846947777a93e4a3d65fe1b5fd7b004"},{"type":"source","url":"https://github.com/postgresml/postgresml/archive/refs/tags/v2.8.2.tar.gz","sha256":"845f28339c6159ac32daccea1cd17b386ea083c3e60bb8d58fb737725afe7eb5"}]}} diff --git a/tests/v2_schema_test.rs b/tests/v2_schema_test.rs index bf56246..ed22e02 100644 --- a/tests/v2_schema_test.rs +++ b/tests/v2_schema_test.rs @@ -1,6 +1,11 @@ use std::error::Error; +use std::fs::File; +use std::io::BufRead; +use std::io::BufReader; +use std::path::PathBuf; use boon::Schemas; +use serde::{Deserialize, Serialize}; use serde_json::{json, Map, Value}; // importing common module. @@ -8,6 +13,7 @@ mod common; use common::*; use pgxn_meta::valid::_test_support::*; +use pgxn_meta::*; const SCHEMA_VERSION: u8 = 2; @@ -3333,3 +3339,37 @@ fn test_v2_distribution() -> Result<(), Box> { Ok(()) } + +#[derive(Deserialize, Serialize)] +struct CorpusCase { + test: String, + error: Option, + meta: Value, +} + +#[test] +fn test_corpus_v2_valid() -> Result<(), Box> { + let schemas_dir: PathBuf = [env!("CARGO_MANIFEST_DIR"), "schema"].iter().collect(); + + let mut validator = Validator::new(schemas_dir)?; + let valid_file: PathBuf = [ + env!("CARGO_MANIFEST_DIR"), + "tests", + "corpus", + "v2", + "valid.txt", + ] + .iter() + .collect(); + let file = File::open(&valid_file)?; + let reader = BufReader::new(file); + for line in reader.lines() { + let tc: CorpusCase = serde_json::from_str(&line?)?; + if let Err(e) = validator.validate(&tc.meta) { + panic!("{} failed: {e}", &tc.test); + } + println!("Example {} ok", &tc.test); + } + + Ok(()) +}