diff --git a/schema/v2/resources.schema.json b/schema/v2/resources.schema.json new file mode 100644 index 0000000..c697f6d --- /dev/null +++ b/schema/v2/resources.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://pgxn.org/meta/v2/resources.schema.json", + "title": "Resources", + "description": "*Resources* provide external information about the package provided by the distribution. Consumers **MAY** use this data for links and displaying useful information about the package.", + "type": "object", + "properties": { + "homepage": { + "type": "string", + "format": "uri", + "description": "A URI for the official home of this project on the web." + }, + "issues": { + "type": "string", + "format": "uri", + "description": "A URI for the package’s issue tracking system." + }, + "repository": { + "type": "string", + "format": "uri", + "description": "A URI for the package’s source code repository." + }, + "docs": { + "type": "string", + "format": "uri", + "description": "A URI for the package’s documentation." + }, + "support": { + "type": "string", + "format": "uri", + "description": "A URI for support resources and contacts for the package" + }, + "badges": { "$ref": "badges.schema.json" } + }, + "patternProperties": { "^[xX]_.": { "description": "Custom key" } }, + "additionalProperties": false, + "anyOf": [ + { "required": ["homepage"] }, + { "required": ["issues"] }, + { "required": ["repository"] }, + { "required": ["docs"] }, + { "required": ["support"] }, + { "required": ["docs"] }, + { "required": ["badges"] } + ], + "examples": [ + { + "homepage": "https://pair.example.com", + "issues": "https://github.com/example/pair/issues", + "docs": "https://pair.example.com/docs", + "support": "https://github.com/example/pair/discussions", + "repository": "https://github.com/example/pair", + "badges": [ + { + "alt": "Test Status", + "src": "https://test.packages.postgresql.org/github.com/example/pair.svg" + } + ] + } + ] +} diff --git a/tests/v2_schema_test.rs b/tests/v2_schema_test.rs index 18fc50b..1a0f86f 100644 --- a/tests/v2_schema_test.rs +++ b/tests/v2_schema_test.rs @@ -44,7 +44,7 @@ fn test_v2_semver() -> Result<(), Box> { panic!("{} unexpectedly passed!", valid_version) } } else if let Err(e) = schemas.validate(&vv, idx) { - panic!("semver {} failed: {e}", valid_version); + panic!("{} failed: {e}", valid_version); } } @@ -75,7 +75,7 @@ fn test_v2_path() -> Result<(), Box> { json!("this\\\\and\\\\that.txt"), ] { if let Err(e) = schemas.validate(&valid, idx) { - panic!("path {} failed: {e}", valid); + panic!("{} failed: {e}", valid); } } @@ -121,7 +121,7 @@ fn test_v2_glob() -> Result<(), Box> { json!("this\\\\and\\\\that.txt"), ] { if let Err(e) = schemas.validate(&valid, idx) { - panic!("glob {} failed: {e}", valid); + panic!("{} failed: {e}", valid); } } @@ -160,7 +160,7 @@ fn test_v2_version_range() -> Result<(), Box> { ] { let range = json!(format!("{}{}{}", op, valid_version, append)); if let Err(e) = schemas.validate(&range, idx) { - panic!("range {} failed: {e}", range); + panic!("{} failed: {e}", range); } // Version zero must not appear in a range. @@ -183,14 +183,14 @@ fn test_v2_version_range() -> Result<(), Box> { // Bare integer 0 allowed. let zero = json!(0); if let Err(e) = schemas.validate(&zero, idx) { - panic!("range {} failed: {e}", zero); + panic!("{} failed: {e}", zero); } // But version 0 cannot appear with any range operator or in any range. for op in ["", "==", "!=", ">", "<", ">=", "<="] { let range = json!(format!("{op}0")); if let Err(e) = schemas.validate(&range, idx) { - panic!("range {} failed: {e}", range); + panic!("{} failed: {e}", range); } } @@ -237,7 +237,7 @@ fn test_v2_license() -> Result<(), Box> { json!("DocumentRef-spdx-tool-1.2:LicenseRef-MIT-Style-2"), ] { if let Err(e) = schemas.validate(&valid_license, idx) { - panic!("license {} failed: {e}", valid_license); + panic!("{} failed: {e}", valid_license); } } @@ -279,7 +279,7 @@ fn test_v2_purl() -> Result<(), Box> { json!("pkg:type/namespace/name@version?qualifiers#subpath"), ] { if let Err(e) = schemas.validate(&valid_purl, idx) { - panic!("purl {} failed: {e}", valid_purl); + panic!("{} failed: {e}", valid_purl); } } @@ -332,7 +332,7 @@ fn test_v2_platforms() -> Result<(), Box> { ] { let platform = json!([os]); if let Err(e) = schemas.validate(&platform, idx) { - panic!("platform {} failed: {e}", platform); + panic!("{} failed: {e}", platform); } let architectures = [ @@ -343,7 +343,7 @@ fn test_v2_platforms() -> Result<(), Box> { for arch in architectures { let platform = json!([format!("{os}-{arch}")]); if let Err(e) = schemas.validate(&platform, idx) { - panic!("platform pattern {} failed: {e}", platform); + panic!("{} failed: {e}", platform); } } @@ -358,13 +358,13 @@ fn test_v2_platforms() -> Result<(), Box> { } let platform = json!([format!("{os}-{version}")]); if let Err(e) = schemas.validate(&platform, idx) { - panic!("platform pattern {} failed: {e}", platform); + panic!("{} failed: {e}", platform); } for arch in architectures { let platform = json!([format!("{os}-{version}-{arch}")]); if let Err(e) = schemas.validate(&platform, idx) { - panic!("platform pattern {} failed: {e}", platform); + panic!("{} failed: {e}", platform); } } } @@ -382,7 +382,7 @@ fn test_v2_platforms() -> Result<(), Box> { ("full", json!(["musllinux-2.5-amd64", "gnulinux-3.3-amd64"])), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("platform {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -477,7 +477,7 @@ fn test_v2_maintainers() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_maintainer.1, idx) { - panic!("maintainers {} failed: {e}", valid_maintainer.0); + panic!("{} failed: {e}", valid_maintainer.0); } } @@ -600,7 +600,7 @@ fn test_v2_extension() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_extension.1, idx) { - panic!("extension {} failed: {e}", valid_extension.0); + panic!("{} failed: {e}", valid_extension.0); } } @@ -763,7 +763,7 @@ fn test_v2_module() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_module.1, idx) { - panic!("extension {} failed: {e}", valid_module.0); + panic!("{} failed: {e}", valid_module.0); } } @@ -929,7 +929,7 @@ fn test_v2_app() -> Result<(), Box> { ("X field", json!({"bin": "bog", "X_bar": 42})), ] { if let Err(e) = schemas.validate(&valid_app.1, idx) { - panic!("extension {} failed: {e}", valid_app.0); + panic!("{} failed: {e}", valid_app.0); } } @@ -1078,7 +1078,7 @@ fn test_v2_contents() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -1168,7 +1168,7 @@ fn test_v2_meta_spec() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_meta_spec.1, idx) { - panic!("extension {} failed: {e}", valid_meta_spec.0); + panic!("{} failed: {e}", valid_meta_spec.0); } } @@ -1238,7 +1238,7 @@ fn test_v2_categories() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_cats.1, idx) { - panic!("extension {} failed: {e}", valid_cats.0); + panic!("{} failed: {e}", valid_cats.0); } } @@ -1299,7 +1299,7 @@ fn test_v2_classifications() -> Result<(), Box> { ("X field", json!({"tags": ["xy"], "X_bar": 42})), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -1370,7 +1370,7 @@ fn test_v2_ignore() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -1468,7 +1468,7 @@ fn test_v2_phase() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid_prereq_phase.1, idx) { - panic!("extension {} failed: {e}", valid_prereq_phase.0); + panic!("{} failed: {e}", valid_prereq_phase.0); } } @@ -1609,7 +1609,7 @@ fn test_v2_packages() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -1697,7 +1697,7 @@ fn test_v2_postgres() -> Result<(), Box> { ("custom X_", json!({"version": "14.0", "X_z": true})), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -1761,7 +1761,7 @@ fn test_v2_pipeline() -> Result<(), Box> { json!("cargo"), ] { if let Err(e) = schemas.validate(&valid, idx) { - panic!("pipeline {} failed: {e}", valid); + panic!("{} failed: {e}", valid); } } @@ -1874,7 +1874,7 @@ fn test_v2_dependencies() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -2088,7 +2088,7 @@ fn test_v2_variations() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -2204,7 +2204,7 @@ fn test_v2_badges() -> Result<(), Box> { ), ] { if let Err(e) = schemas.validate(&valid.1, idx) { - panic!("extension {} failed: {e}", valid.0); + panic!("{} failed: {e}", valid.0); } } @@ -2234,7 +2234,7 @@ fn test_v2_badges() -> Result<(), Box> { ("src bool", json!([{"alt": "abcd", "src": true}])), ("src number", json!([{"alt": "abcd", "src": 42}])), ("src null", json!([{"alt": "abcd", "src": null}])), - ("src invalid", json!([{"alt": "abcd", "src": "xyz"}])), + ("src empty", json!([{"alt": "abcd", "src": ""}])), ("src invalid", json!([{"alt": "abcd", "src": "not a uri"}])), // alt ("alt array", json!([{"src": "x:y", "alt": []}])), @@ -2243,7 +2243,136 @@ fn test_v2_badges() -> Result<(), Box> { ("alt bool", json!([{"src": "x:y", "alt": true}])), ("alt number", json!([{"src": "x:y", "alt": 42}])), ("alt null", json!([{"src": "x:y", "alt": null}])), - ("alt too short", json!([{"src": "x:y", "alt": ["xyz"]}])), + ("alt empty", json!([{"src": "x:y", "alt": "xyz"}])), + ("alt too short", json!([{"src": "x:y", "alt": "xyz"}])), + ] { + if schemas.validate(&invalid.1, idx).is_ok() { + panic!("{} unexpectedly passed!", invalid.0) + } + } + + Ok(()) +} + +#[test] +fn test_v2_resources() -> Result<(), Box> { + // Load the schemas and compile the maintainer schema. + let mut compiler = new_compiler("schema/v2")?; + let mut schemas = Schemas::new(); + let id = id_for(SCHEMA_VERSION, "resources"); + let idx = compiler.compile(&id, &mut schemas)?; + + for valid in [ + ("homepage", json!({"homepage": "https://example.com"})), + ("issues", json!({"issues": "https://github.com/issues"})), + ("repo", json!({"repository": "https://github.com/repo"})), + ("docs", json!({"repository": "https://example.com"})), + ("support", json!({"repository": "https://example.com"})), + ("badges", json!({"badges": [{"src": "x:y", "alt": "abcd"}]})), + ("custom x_", json!({"docs": "x:y", "x_y": 1})), + ("custom X_", json!({"docs": "x:y", "X_z": true})), + ( + "everything", + json!({ + "homepage": "https://pgtap.org", + "issues": "https://github.com/theory/pgtap/issues", + "repository": "https://github.com/theory/pgtap", + "docs": "https://pgtap.org/documentation.html", + "support": "https://github.com/theory/pgtap", + "badges": [ + { + "src": "https://camo.githubusercontent.com/6552afb9038154d801c50b6e55a76db78a6787a8d6e2b5252a44864503c52887/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d626c75652e737667", + "alt": "MIT License", + }, + { + "src": "https://github.com/theory/pgtap/actions/workflows/test.yml/badge.svg", + "alt": "Test Status", + }, + { + "src": "https://camo.githubusercontent.com/20f036be4bac5e4326d17cfd98a55b7753a6619ce463ed7482f7a1bd98978135/68747470733a2f2f636f6465636f762e696f2f67682f74656d626f2d696f2f70672d6a736f6e736368656d612d626f6f6e2f67726170682f62616467652e7376673f746f6b656e3d44494645443332345a59", + "alt": "Code Coverage", + }, + ] + }), + ), + ] { + if let Err(e) = schemas.validate(&valid.1, idx) { + panic!("{} failed: {e}", valid.0); + } + } + + for invalid in [ + ("array", json!([])), + ("string", json!("web")), + ("empty string", json!("")), + ("true", json!(true)), + ("false", json!(false)), + ("null", json!(null)), + ("empty object", json!({})), + ("only x_", json!({"x_y": 0})), + ("only X_", json!({"X_y": 0})), + ("bare x_", json!({"docs": "x:y", "x_": 0})), + ("bare X_", json!({"docs": "x:y", "x_": 0})), + ("unknown", json!({"docs": "x:y", "foo": 0})), + // homepage + ("homepage array", json!([{"homepage": []}])), + ("homepage object", json!([{"homepage": {}}])), + ("homepage empty", json!([{"homepage": ""}])), + ("homepage bool", json!([{"homepage": true}])), + ("homepage number", json!([{"homepage": 42}])), + ("homepage null", json!([{"homepage": null}])), + ("homepage empty", json!([{"homepage": ""}])), + ("homepage invalid", json!([{"homepage": "not a uri"}])), + // issues + ("issues array", json!([{"issues": []}])), + ("issues object", json!([{"issues": {}}])), + ("issues empty", json!([{"issues": ""}])), + ("issues bool", json!([{"issues": true}])), + ("issues number", json!([{"issues": 42}])), + ("issues null", json!([{"issues": null}])), + ("issues empty", json!([{"issues": ""}])), + ("issues invalid", json!([{"issues": "not a uri"}])), + // repository + ("repository array", json!([{"repository": []}])), + ("repository object", json!([{"repository": {}}])), + ("repository empty", json!([{"repository": ""}])), + ("repository bool", json!([{"repository": true}])), + ("repository number", json!([{"repository": 42}])), + ("repository null", json!([{"repository": null}])), + ("repository empty", json!([{"repository": ""}])), + ("repository invalid", json!([{"repository": "not a uri"}])), + // docs + ("docs array", json!([{"docs": []}])), + ("docs object", json!([{"docs": {}}])), + ("docs empty", json!([{"docs": ""}])), + ("docs bool", json!([{"docs": true}])), + ("docs number", json!([{"docs": 42}])), + ("docs null", json!([{"docs": null}])), + ("docs empty", json!([{"docs": ""}])), + ("docs invalid", json!([{"docs": "not a uri"}])), + // support + ("support array", json!([{"support": []}])), + ("support object", json!([{"support": {}}])), + ("support empty", json!([{"support": ""}])), + ("support bool", json!([{"support": true}])), + ("support number", json!([{"support": 42}])), + ("support null", json!([{"support": null}])), + ("support empty", json!([{"support": ""}])), + ("support invalid", json!([{"support": "not a uri"}])), + // badges + ("badges empty", json!([{"badges": []}])), + ("badges object", json!([{"badges": {}}])), + ("badges empty", json!([{"badges": ""}])), + ("badges bool", json!([{"badges": true}])), + ("badges number", json!([{"badges": 42}])), + ("badges null", json!([{"badges": null}])), + ("badges empty", json!([{"badges": ""}])), + ("badges src only", json!([{"badges": {"src": "x:y"}}])), + ("badges alt only", json!([{"badges": {"alt": "abcd"}}])), + ( + "badges src invalid", + json!([{"badges": {"alt": "abcd", "src": "not a uri"}}]), + ), ] { if schemas.validate(&invalid.1, idx).is_ok() { panic!("{} unexpectedly passed!", invalid.0)