Skip to content

Commit

Permalink
docs: improve structure and documentation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ifiokjr committed Aug 1, 2022
1 parent 931629a commit fe1c12e
Show file tree
Hide file tree
Showing 11 changed files with 137 additions and 48 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:

jobs:
test:
name: test rust $${{matrix.rust}}
name: test rust ${{matrix.rust}}
strategy:
fail-fast: false
matrix:
Expand All @@ -35,7 +35,7 @@ jobs:

- name: 👩‍⚕️ Lint
if: matrix.rust == 'stable'
run: cargo clippy
run: cargo clippy -- -D warnings

- name: ✅ Test
run: |
Expand Down
15 changes: 11 additions & 4 deletions Cargo.lock

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

31 changes: 19 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,33 @@
name = "package_json_schema"
version = "0.0.0"
authors = ["Ifiok Jr. <[email protected]>"]
categories = ["encoding", "config"]
documentation = "https://docs.rs/package_json_schema"
edition = "2021"
include = ["src/**/*.rs", "Cargo.toml", "readme.md"]
repository = "https://github.com/skribbledev/skribble/tree/main/crates/package_json_schema"
description = "Parse widely used JSON file formats to their equivalent rust structs."
homepage = "https://github.com/ifiokjr/package_json_schema"
include = ["src/**/*.rs", "Cargo.toml", "readme.md", "license"]
keywords = ["schema", "package.json", "npm", "node"]
license = "Unlicense"
license-file = "license"
readme = "readme.md"
repository = "https://github.com/ifiokjr/package_json_schema"
description = "Parse content from a local `package.json` file and consume the result as a well PackageJson struct."

[lib]
crate-type = ["lib"]

[dependencies]
cfg-if = "1.0.0"
indexmap = { version = "1", features = ["serde-1"] }
lazy_static = "1"
regex = "1"
semver = "1"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order", "std"] }
thiserror = "1"
typed-builder = "0.10"
validator = { version = "0.16", features = ["derive"], optional = true }
doc-comment = "0.3.3"
indexmap = { version = "1.9.1", features = ["serde-1"] }
lazy_static = "1.4.0"
regex = "1.6.0"
semver = "1.0.12"
serde = { version = "1.0.141", features = ["derive"] }
serde_json = { version = "1.0.82", features = ["preserve_order", "std"] }
thiserror = "1.0.31"
typed-builder = "0.10.0"
validator = { version = "0.16.0", features = ["derive"], optional = true }

[dev-dependencies]
insta = "1.17.1"
Expand Down
2 changes: 1 addition & 1 deletion dprint.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"excludes": ["**/fixtures", "**/snapshots"],
"plugins": [
"https://plugins.dprint.dev/rustfmt-0.6.2.json@886c6f3161cf020c2d75160262b0f56d74a521e05cfb91ec4f956650c8ca76ca",
"https://plugins.dprint.dev/typescript-0.71.1.wasm",
"https://plugins.dprint.dev/typescript-0.71.2.wasm",
"https://plugins.dprint.dev/json-0.15.4.wasm",
"https://plugins.dprint.dev/markdown-0.13.3.wasm",
"https://plugins.dprint.dev/toml-0.5.4.wasm"
Expand Down
File renamed without changes.
50 changes: 43 additions & 7 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# package_json_schema

> Load json content `package.json` file
> Load a `package.json` file as a PackageJson `struct`.
![example workflow](https://github.com/ifiokjr/package_json_schema/actions/workflows/ci/badge.svg)

## Why?

Expand Down Expand Up @@ -38,19 +40,32 @@ let contents = r###"
"aws-sdk": "2.1185.0"
}
}
"###
"###;

let package_json = PackageJson::try_from(contents).unwrap();
assert_eq!(package_json.name, "my-package");
assert_eq!(package_json.version, "0.1.0");
assert_eq!(package_json.name.unwrap(), "my-package");
assert_eq!(package_json.version.unwrap(), "0.1.0");
```

This crate leaves it to the user to load the `package.json` content from the filesystem. Here is an example of loading the file contents and parsing the contents into a struct.

```rust
use std::fs::read_to_string;
use package_json_schema::PackageJson;

let contents = read_to_string("./tests/fixtures/1/package.json").unwrap();
let package_json = PackageJson::try_from(contents).unwrap();

assert_eq!(package_json.name.unwrap(), "test");
```

A `package.json` file can also be created from a builder.

```rust
use package_json_schema::PackageJson;
use index_map::IndexMap;
use package_json::Person;
use package_json_schema::AdditionalFields;
use package_json_schema::Person;
use indexmap::IndexMap;

let mut additional_fields: AdditionalFields = IndexMap::new();
additional_fields.insert("custom".into(), "value".into());
Expand All @@ -68,6 +83,27 @@ assert_eq!(
);
```

To validate the `package.json` fields, enable the `validate` feature.

```toml
package_json_schema = { version = "0.1.0", features = ["validate"] }
```

And then use the `validate` method.

```rust
use std::fs::read_to_string;
use package_json_schema::PackageJson;
#[cfg(feature = "validate")]
use validator::Validate;

let contents = read_to_string("./tests/fixtures/1/package.json").unwrap();
let package_json = PackageJson::try_from(contents).unwrap();

#[cfg(feature = "validate")]
package_json.validate().unwrap();
```

## License

This project is licensed under the [Unlicense license](LICENSE).
This project is licensed under the [Unlicense license](license).
2 changes: 1 addition & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// The errors resulting from parsing files.
///
/// Structure taken from https://kazlauskas.me/entries/errors
/// Structure taken from [here](https://kazlauskas.me/entries/errors).
#[non_exhaustive]
#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand Down
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(clippy::nursery)]
#![deny(clippy::cargo)]

doc_comment::doc_comment! {
include_str!("../readme.md")
}

pub mod error;
pub mod package_json;
#[cfg(feature = "validate")]
Expand Down
47 changes: 38 additions & 9 deletions src/package_json.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
#![allow(clippy::use_self)]

use std::fmt::Display;

use cfg_if::cfg_if;
use indexmap::IndexMap;
use serde::Deserialize;
Expand All @@ -22,13 +25,37 @@ cfg_if! {
/// Capture fields that aren't defined in the default implementation.
pub type AdditionalFields = IndexMap<String, Value>;

/// Rust schema for NPM `package.json` files.
/// This is the rust schema for npm `package.json` files.
///
/// ```
/// use package_json_schema::PackageJson;
///
/// let contents = r###"
/// {
/// "name": "my-package",
/// "version": "0.1.0",
/// "dependencies": {
/// "@sveltejs/kit": "1.0.0-next.396"
/// },
/// "peerDependencies": {
/// "aws-sdk": "2.1185.0"
/// }
/// }
/// "###;
///
/// let package_json = PackageJson::try_from(contents).unwrap();
/// assert_eq!(package_json.name.unwrap(), "my-package");
/// assert_eq!(package_json.version.unwrap(), "0.1.0");
/// ```
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct PackageJson {
/// The name of the package.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "validate", validate(length(min = 1, max = 214), regex = "PACKAGE_NAME_REGEX"))]
#[cfg_attr(
feature = "validate",
validate(length(min = 1, max = 214), regex = "PACKAGE_NAME_REGEX")
)]
#[builder(default, setter(into, strip_option))]
pub name: Option<String>,

Expand Down Expand Up @@ -309,8 +336,6 @@ pub struct PackageJson {
pub other: Option<AdditionalFields>,
}

impl PackageJson {}

impl TryFrom<&str> for PackageJson {
type Error = crate::error::Error;

Expand All @@ -332,6 +357,10 @@ impl TryFrom<String> for PackageJson {

impl PackageJson {
/// Convert the [`PackageJson`] to a [Result] containing [`String`].
///
/// # Errors
///
/// This will return an error when the [`PackageJson`] cannot be serialized.
pub fn try_to_string(&self) -> Result<String, crate::error::Error> {
let content = serde_json::to_string(self).map_err(crate::Error::SerializePackageJson)?;
Ok(content)
Expand Down Expand Up @@ -471,7 +500,7 @@ pub enum Type {

impl Default for Type {
fn default() -> Self {
Type::CommonJS
Self::CommonJS
}
}

Expand Down Expand Up @@ -515,7 +544,7 @@ cfg_if! {

match self {
Person::Object(person) => ValidationErrors::merge(result, "Person", person.validate()),
_ => result,
Person::String(_) => result,
}
}
}
Expand All @@ -526,7 +555,7 @@ cfg_if! {
/// issues should be reported. These are helpful for people who encounter issues
/// with your package.
#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize,Deserialize, Debug, Clone)]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct BugObject {
/// The url to your project's issue tracker.
#[cfg_attr(feature = "validate", validate(url))]
Expand Down Expand Up @@ -669,7 +698,7 @@ cfg_if! {
}

#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize,Deserialize, Debug, Clone)]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct ExportsObject {
/// The module path that is resolved when this specifier is imported as a
/// CommonJS module using the `require(...)` function.
Expand Down Expand Up @@ -705,7 +734,7 @@ pub struct ExportsObject {
}

#[cfg_attr(feature = "validate", derive(Validate))]
#[derive(TypedBuilder, Serialize,Deserialize, Debug, Clone)]
#[derive(TypedBuilder, Serialize, Deserialize, Debug, Clone)]
pub struct Directories {
/// If you specify a 'bin' directory, then all the files in that folder will
/// be used as the 'bin' hash.
Expand Down
22 changes: 11 additions & 11 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@ use validator::validate_url;
use validator::ValidationError;

lazy_static! {
pub(crate) static ref PACKAGE_NAME_REGEX: Regex =
pub static ref PACKAGE_NAME_REGEX: Regex =
Regex::new(r#"^(?:@[a-z0-9-*~][a-z0-9-*._~]*/)?[a-z0-9-~][a-z0-9-._~]*$"#).unwrap();
pub(crate) static ref PACKAGE_MANAGER_REGEX: Regex =
pub static ref PACKAGE_MANAGER_REGEX: Regex =
Regex::new(r#"(npm|pnpm|yarn)@\d+\.\d+\.\d+(-.+)?"#).unwrap();
}

pub(crate) fn validate_version(version: &str) -> Result<(), ValidationError> {
pub fn validate_version(version: &str) -> Result<(), ValidationError> {
VersionReq::parse(version)
.map_err(|_| ValidationError::new("version must be a valid semver string"))?;

Ok(())
}

pub(crate) fn validate_exports_path(path: &str) -> Result<(), ValidationError> {
if !path.starts_with("./") {
Err(ValidationError::new("exports path must start with './'"))
} else {
pub fn validate_exports_path(path: &str) -> Result<(), ValidationError> {
if path.starts_with("./") {
Ok(())
} else {
Err(ValidationError::new("exports path must start with './'"))
}
}

pub(crate) fn validate_email_or_url(email_or_url: &str) -> Result<(), ValidationError> {
if !(validate_email(email_or_url) || validate_url(email_or_url)) {
Err(ValidationError::new("invalid email or url"))
} else {
pub fn validate_email_or_url(email_or_url: &str) -> Result<(), ValidationError> {
if validate_email(email_or_url) || validate_url(email_or_url) {
Ok(())
} else {
Err(ValidationError::new("invalid email or url"))
}
}
3 changes: 2 additions & 1 deletion tests/package_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ fn create_package_json_file_with_builder_pattern() {
"author": "Tester",
"custom": "value"
}
"###)
"###
);
}

#[cfg(feature = "validate")]
Expand Down

0 comments on commit fe1c12e

Please sign in to comment.