Skip to content

Commit

Permalink
Implement meta module and struct
Browse files Browse the repository at this point in the history
Add the `meta` module, the new (currently un-exported) API for working
with PGXN Meta data. It defines structs and enums for all the data
stored in v2 Meta specs, and parses their contents. Tested against the
v2 corpus. Include a placeholder for v1 parsing.

Also: Support "http" as well as "https" for meta-spec URLs, since there
are quite a few `META.json` files on PGXN that use "http".
  • Loading branch information
theory committed Aug 29, 2024
1 parent b409a1d commit 99e3506
Show file tree
Hide file tree
Showing 12 changed files with 391 additions and 6 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
slug: pgxn/meta
files: target/cover/coveralls
- name: Clear Badge Cache
uses: kevincobain2000/action-camo-purge@v1
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ All notable changes to this project will be documented in this file. It uses the
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
"Semantic Versioning 2.0.0"

## [Unreleased] — Date TBD


## [v0.1.0] — 2024-08-08

The theme of this release is *Cross Compilation.*
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

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

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ exclude = [ ".github", ".vscode", ".gitignore", ".ci", ".pre-*.yaml"]
[dependencies]
boon = "0.6"
lexopt = "0.3.0"
relative-path = "1.9.3"
relative-path = { version = "1.9", features = ["serde"] }
semver = { version = "1.0", features = ["std", "serde"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
spdx = "0.10.6"
Expand Down
5 changes: 4 additions & 1 deletion schema/v1/meta-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
},
"url": {
"type": "string",
"const": "https://pgxn.org/meta/spec.txt",
"enum": [
"https://pgxn.org/meta/spec.txt",
"http://pgxn.org/meta/spec.txt"
],
"description": "The URI of the metadata specification document corresponding to the given version. This is strictly for human-consumption and should not impact the interpretation of the document."
}
},
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ assert!(validator.validate(&meta).is_ok());

mod valid;
pub use valid::{ValidationError, Validator};
mod meta;
// pub use meta::*;

#[cfg(test)]
mod tests;
261 changes: 261 additions & 0 deletions src/meta/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
use std::{collections::HashMap, error::Error, fs::File, path::PathBuf};

use relative_path::RelativePathBuf;
use semver::Version;
use serde::{Deserialize, Serialize};
use serde_json::Value;

mod v1;
mod v2;

fn meta_url() -> String {
"https://rfcs.pgxn.org/0003-meta-spec-v2.html".to_string()
}

/// Represents the `meta-spec` object in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Spec {
version: String,
#[serde(default = "meta_url")]
url: String,
}

/// Maintainer represents an object in the list of `maintainers` in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Maintainer {
name: String,
email: Option<String>,
url: Option<String>,
}

/// Describes an extension in under `extensions` in [`Contents`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Extension {
control: RelativePathBuf,
#[serde(rename = "abstract")]
abs_tract: Option<String>,
tle: Option<bool>,
sql: RelativePathBuf,
doc: Option<RelativePathBuf>,
}

/// Defines a type of module in [`Module`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum ModuleType {
#[serde(rename = "extension")]
Extension,
#[serde(rename = "hook")]
Hook,
#[serde(rename = "bgw")]
Bgw,
}

/// Defines the values for the `preload` value in [`Module`]s.
#[derive(Serialize, Deserialize, PartialEq, Debug)]
enum Preload {
#[serde(rename = "server")]
Server,
#[serde(rename = "session")]
Session,
}

/// Represents a loadable module under `modules` in [`Contents`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Module {
#[serde(rename = "type")]
kind: ModuleType,
#[serde(rename = "abstract")]
abs_tract: Option<String>,
preload: Option<Preload>,
lib: RelativePathBuf,
doc: Option<RelativePathBuf>,
}

/// Represents an app under `apps` in [`Contents`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct App {
lang: Option<String>,
#[serde(rename = "abstract")]
abs_tract: Option<String>,
bin: RelativePathBuf,
doc: Option<RelativePathBuf>,
lib: Option<RelativePathBuf>,
man: Option<RelativePathBuf>,
html: Option<RelativePathBuf>,
}

/// Represents the contents of a distribution, under `contents` in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Contents {
extensions: Option<HashMap<String, Extension>>,
modules: Option<HashMap<String, Module>>,
apps: Option<HashMap<String, App>>,
}

/// Represents the classifications of a distribution, under `classifications`
/// in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Classifications {
tags: Option<Vec<String>>,
categories: Option<Vec<String>>,
}

/// Represents Postgres requirements under `postgres` in [`Dependencies`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Postgres {
version: String,
with: Option<Vec<String>>,
}

/// Represents the name of a build pipeline under `pipeline` in
/// [`Dependencies`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub enum Pipeline {
/// PGXS
#[serde(rename = "pgxs")]
Pgxs,
#[serde(rename = "meson")]
/// Meson
Meson,
#[serde(rename = "pgrx")]
/// pgrx
Pgrx,
/// Autoconf
#[serde(rename = "autoconf")]
Autoconf,
/// cmake
#[serde(rename = "cmake")]
Cmake,
}

/// Defines a version range for [`Phase`] dependencies.
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(untagged)]
pub enum VersionRange {
/// Represents `0` as a shorthand for "no specific version".
Integer(u8),
/// Represents a string defining a version range.
String(String),
}

/// Defines the relationships for a build phase in [`Packages`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Phase {
requires: Option<HashMap<String, VersionRange>>,
recommends: Option<HashMap<String, VersionRange>>,
suggests: Option<HashMap<String, VersionRange>>,
conflicts: Option<HashMap<String, VersionRange>>,
}

/// Defines package dependencies for build phases under `packages` in
/// [`Dependencies`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Packages {
configure: Option<Phase>,
build: Option<Phase>,
test: Option<Phase>,
run: Option<Phase>,
develop: Option<Phase>,
}

/// Defines dependency variations under `variations`in [`Dependencies`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Variations {
#[serde(rename = "where")]
wheres: Box<Dependencies>,
dependencies: Box<Dependencies>,
}

/// Defines the distribution dependencies under `dependencies` in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Dependencies {
platforms: Option<Vec<String>>,
postgres: Option<Postgres>,
pipeline: Option<Pipeline>,
packages: Option<Packages>,
variations: Option<Vec<Variations>>,
}

/// Defines the resources under `resources` in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Resources {
homepage: Option<String>,
issues: Option<String>,
repository: Option<String>,
docs: Option<String>,
support: Option<String>,
}

/// Defines the artifacts in the array under `artifacts` in [`Meta`].
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Artifact {
url: String,
#[serde(rename = "type")]
kind: String,
platform: Option<String>,
sha256: Option<String>,
sha512: Option<String>,
}

/// Represents a complete PGXN Meta definition.
#[derive(Serialize, Deserialize, PartialEq, Debug)]
pub struct Meta {
name: String,
version: Version,
#[serde(rename = "abstract")]
abs_tract: String,
description: Option<String>,
producer: Option<String>,
license: String, // use spdx::Expression.
#[serde(rename = "meta-spec")]
spec: Spec,
maintainers: Vec<Maintainer>,
classifications: Option<Classifications>,
contents: Contents,
ignore: Option<Vec<String>>,
dependencies: Option<Dependencies>,
resources: Option<Resources>,
artifacts: Option<Vec<Artifact>>,
}

impl Meta {
fn from_version(version: u8, meta: Value) -> Result<Self, Box<dyn Error>> {
match version {
1 => v1::from_value(meta),

Check warning on line 224 in src/meta/mod.rs

View check run for this annotation

Codecov / codecov/patch

src/meta/mod.rs#L224

Added line #L224 was not covered by tests
2 => v2::from_value(meta),
_ => Err(Box::from(format!("Unknown meta version {version}"))),
}
}
}

impl TryFrom<Value> for Meta {
type Error = Box<dyn Error>;
fn try_from(meta: Value) -> Result<Self, Self::Error> {
// Make sure it's valid.
let mut validator = crate::valid::Validator::new();
let version = match validator.validate(&meta) {
Err(e) => return Err(Box::from(e.to_string())),
Ok(v) => v,
};
Meta::from_version(version, meta)
}
}

impl TryFrom<&PathBuf> for Meta {
type Error = Box<dyn Error>;
fn try_from(file: &PathBuf) -> Result<Self, Self::Error> {
let meta: Value = serde_json::from_reader(File::open(file)?)?;
Meta::try_from(meta)
}
}

impl TryFrom<&String> for Meta {
type Error = Box<dyn Error>;
fn try_from(str: &String) -> Result<Self, Self::Error> {
let meta: Value = serde_json::from_str(str)?;
Meta::try_from(meta)
}
}

#[cfg(test)]
mod tests;
Loading

0 comments on commit 99e3506

Please sign in to comment.