Skip to content

Commit

Permalink
Serialize and deserialize custom properties
Browse files Browse the repository at this point in the history
Add (de)serialization for the v1 and v2 schemas, as well as a new test
case in the v2 corpus for these changes.
  • Loading branch information
vrmiguel authored and theory committed Sep 11, 2024
1 parent 9a45fb3 commit b57a708
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 6 deletions.
91 changes: 91 additions & 0 deletions corpus/v2/custom-fields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"name": "widget",
"abstract": "Widget for PostgreSQL",
"description": "¿A widget is just thing thing, yoŭ know?",
"version": "0.2.5",
"maintainers": [
{
"name": "David E. Wheeler",
"email": "[email protected]",
"x_looking_for_jobs": false
}
],
"license": "PostgreSQL",
"contents": {
"extensions": {
"pair": {
"sql": "sql/widget.sql",
"control": "widget.control"
}
},
"modules": {
"examples": {
"type": "extension",
"lib": "target/release/example",
"preload": "server",
"x_is_c_only": false
}
},
"x_contains_shared_objects": true
},
"resources": {
"homepage": "http://widget.example.org/",
"x_support_email": "[email protected]",
"badges": [
{
"alt": "CI Status",
"src": "https://github.com/example/pg-widget/actions/workflows/ci.yml/badge.svg",
"url": "https://github.com/example/pg-widget/actions/workflows/ci.yml",
"x_updated_every": "1h"
}
]
},
"dependencies": {
"postgres": {
"version": "14.0",
"X_RELEASE": "REL_14_BETA3"
},
"x_forbid_unsafe": true,
"packages": {
"run": {
"requires": {
"pkg:generic/python": "2.0",
"pkg:pypi/psycopg2": 0
},
"recommends": {
"pkg:pgxn/pg_jobmon": "1.4.1"
}
},
"x_requires_python": true
},
"variations": [
{
"where": {
"platforms": ["linux"]
},
"dependencies": {
"packages": {
"build": {
"x_early": true,
"requires": {
"pkg:generic/awk": 0,
"pkg:generic/perl": "5.20"
}
},
"run": {
"recommends": {
"pkg:pypi/widgets": 0
}
}
}
}
}
]
},
"classifications": {
"categories": ["Data and Transformations"],
"tags": ["variadic function"],
"x_ui_related": true
},
"meta-spec": { "version": "2.0.0" }
}
3 changes: 2 additions & 1 deletion corpus/v2/typical-pgrx.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"abstract": "JSON Schema validation functions for PostgreSQL",
"doc": "doc/jsonschema.md",
"sql": "target/release/**/jsonschema--0.1.1.sql",
"control": "target/release/**/jsonschema.control"
"control": "target/release/**/jsonschema.control",
"x_commit_hash": "4ba6b23"
}
},
"modules": {
Expand Down
62 changes: 61 additions & 1 deletion src/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::{collections::HashMap, error::Error, fs::File, path::PathBuf};
use crate::util;
use relative_path::RelativePathBuf;
use semver::Version;
use serde::{Deserialize, Serialize};
use serde::{ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;

mod v1;
Expand All @@ -35,6 +35,9 @@ pub struct Maintainer {
email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Describes an extension in under `extensions` in [`Contents`].
Expand All @@ -49,6 +52,9 @@ pub struct Extension {
sql: RelativePathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
doc: Option<RelativePathBuf>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines a type of module in [`Module`].
Expand Down Expand Up @@ -84,6 +90,9 @@ pub struct Module {
lib: RelativePathBuf,
#[serde(skip_serializing_if = "Option::is_none")]
doc: Option<RelativePathBuf>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Represents an app under `apps` in [`Contents`].
Expand All @@ -103,6 +112,9 @@ pub struct App {
man: Option<RelativePathBuf>,
#[serde(skip_serializing_if = "Option::is_none")]
html: Option<RelativePathBuf>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Represents the contents of a distribution, under `contents` in [`Meta`].
Expand All @@ -114,6 +126,9 @@ pub struct Contents {
modules: Option<HashMap<String, Module>>,
#[serde(skip_serializing_if = "Option::is_none")]
apps: Option<HashMap<String, App>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Represents the classifications of a distribution, under `classifications`
Expand All @@ -124,6 +139,9 @@ pub struct Classifications {
tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
categories: Option<Vec<String>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Represents Postgres requirements under `postgres` in [`Dependencies`].
Expand All @@ -132,6 +150,9 @@ pub struct Postgres {
version: String,
#[serde(skip_serializing_if = "Option::is_none")]
with: Option<Vec<String>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Represents the name of a build pipeline under `pipeline` in
Expand Down Expand Up @@ -176,6 +197,9 @@ pub struct Phase {
suggests: Option<HashMap<String, VersionRange>>,
#[serde(skip_serializing_if = "Option::is_none")]
conflicts: Option<HashMap<String, VersionRange>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines package dependencies for build phases under `packages` in
Expand All @@ -192,6 +216,9 @@ pub struct Packages {
run: Option<Phase>,
#[serde(skip_serializing_if = "Option::is_none")]
develop: Option<Phase>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines dependency variations under `variations`in [`Dependencies`].
Expand All @@ -200,6 +227,9 @@ pub struct Variations {
#[serde(rename = "where")]
wheres: Box<Dependencies>,
dependencies: Box<Dependencies>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines the distribution dependencies under `dependencies` in [`Meta`].
Expand All @@ -215,6 +245,9 @@ pub struct Dependencies {
packages: Option<Packages>,
#[serde(skip_serializing_if = "Option::is_none")]
variations: Option<Vec<Variations>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines the badges under `badges` in [`Resources`].
Expand All @@ -224,6 +257,9 @@ pub struct Badge {
alt: String,
#[serde(skip_serializing_if = "Option::is_none")]
url: Option<String>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines the resources under `resources` in [`Meta`].
Expand All @@ -241,6 +277,9 @@ pub struct Resources {
support: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
badges: Option<Vec<Badge>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Defines the artifacts in the array under `artifacts` in [`Meta`].
Expand All @@ -255,6 +294,9 @@ pub struct Artifact {
sha256: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
sha512: Option<String>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/**
Expand Down Expand Up @@ -285,6 +327,24 @@ pub struct Meta {
resources: Option<Resources>,
#[serde(skip_serializing_if = "Option::is_none")]
artifacts: Option<Vec<Artifact>>,
#[serde(flatten)]
#[serde(deserialize_with = "deserialize_custom_properties")]
custom_props: HashMap<String, Value>,
}

/// Deserializes extra fields starting with `X_` or `x_` into the `custom_properties` HashMap.
pub fn deserialize_custom_properties<'de, D>(
deserializer: D,
) -> Result<HashMap<String, Value>, D::Error>
where
D: Deserializer<'de>,
{
let map: HashMap<String, Value> = HashMap::deserialize(deserializer)?;

Ok(map
.into_iter()
.filter(|(key, _value)| key.starts_with("x_") || key.starts_with("X_"))
.collect())
}

impl Meta {
Expand Down
7 changes: 3 additions & 4 deletions src/valid/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,11 @@ assert!(validator.validate(&meta).is_ok());
*/
use std::{error::Error, fmt};

use crate::util;
use boon::{Compiler, Schemas};
use serde_json::Value;

use crate::util;

// Export compiler publicly only for tests.
/// Export compiler publicly only for tests.
#[cfg(test)]
pub mod compiler;

Expand Down Expand Up @@ -112,7 +111,7 @@ impl Validator {
/// validation error on failure.
///
/// See the [module docs](crate::valid) for an example.
pub fn validate<'a>(&'a mut self, meta: &'a Value) -> Result<u8, Box<dyn Error + '_>> {
pub fn validate<'a>(&'a mut self, meta: &'a Value) -> Result<u8, Box<dyn Error + 'a>> {
let v = util::get_version(meta).ok_or(ValidationError::UnknownSpec)?;
let id = format!("{SCHEMA_BASE}{v}/distribution.schema.json");

Expand Down

0 comments on commit b57a708

Please sign in to comment.