Skip to content

Commit

Permalink
add new cddl-linter tool, cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Leshiy committed Aug 21, 2024
1 parent 9281a88 commit e8ba753
Show file tree
Hide file tree
Showing 12 changed files with 234 additions and 16 deletions.
File renamed without changes.
2 changes: 2 additions & 0 deletions rust/cbork/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[workspace]
package.edition = "2021"
package.license = "MIT OR Apache-2.0"
resolver = "2"
members = [
"cddl-parser",
"abnf-parser",
"cddl-linter",
]

[workspace.lints.rust]
Expand Down
2 changes: 1 addition & 1 deletion rust/cbork/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ IMPORT github.com/input-output-hk/catalyst-ci/earthly/rust:v3.1.24 AS rust-ci
builder:
DO rust-ci+SETUP

COPY --dir .cargo .config cddl-parser abnf-parser .
COPY --dir .cargo .config cddl-parser abnf-parser cddl-linter .
COPY Cargo.toml clippy.toml deny.toml rustfmt.toml .

# RUN cargo generate-lockfile
Expand Down
3 changes: 2 additions & 1 deletion rust/cbork/abnf-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name = "abnf-parser"
version = "0.1.0"
edition.workspace = true
license = "MIT OR Apache-2.0"
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -15,3 +15,4 @@ workspace = true
derive_more = "0.99.17"
pest = { version = "2.7.2", features = ["std", "pretty-print", "memchr", "const_prec_climber"] }
pest_derive = { version = "2.7.2", features = ["grammar-extras"] }
thiserror = "1.0.56"
14 changes: 6 additions & 8 deletions rust/cbork/abnf-parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
//! A parser for ABNF, utilized for parsing in accordance with RFC 5234.

// cspell: words Naur

#![allow(missing_docs)] // TODO(apskhem): Temporary, to bo removed in a subsequent PR

//! A parser for ABNF, utilized for parsing in accordance with RFC 5234.

use std::fmt::Debug;

use derive_more::{Display, From};
use derive_more::From;
pub use pest::Parser;
use pest::{error::Error, iterators::Pairs};

Expand All @@ -27,14 +25,14 @@ pub mod abnf_test {
pub struct ABNFTestParser;
}

/// Abstract Syntax Tree (AST) representing parsed ABNF syntax.
#[derive(Debug)]
#[allow(dead_code)]
/// Abstract Syntax Tree (AST) representing parsed ABNF syntax.
pub struct AST<'a>(Pairs<'a, abnf::Rule>);

/// Represents an error that may occur during ABNF parsing.
#[derive(Display, Debug, From)]
/// Error type for ABNF parsing.
#[derive(thiserror::Error, Debug, From)]
#[error("{0}")]
pub struct ABNFError(Error<abnf::Rule>);

/// Parses the input string containing ABNF (Augmented Backus-Naur Form) syntax and
Expand Down
17 changes: 17 additions & 0 deletions rust/cbork/cddl-linter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "cddl-linter"
version = "0.1.0"
edition.workspace = true
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lints]
workspace = true

[dependencies]
cddl-parser = { path = "../cddl-parser", version = "0.1.0" }
clap = { version = "4.5.3", features = ["derive", "env"] }
anyhow = "1.0.71"
console = "0.15.8"

13 changes: 13 additions & 0 deletions rust/cbork/cddl-linter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# CDDL linter

[CDDL](https://datatracker.ietf.org/doc/html/rfc8610) (Concise Data Definition Language)
linting cli tool,
enabling users to check their CDDL code for errors, inconsistencies, and compliance with the CDDL specification.

## Install

To install this tool run

```shell
cargo install --git https://github.com/input-output-hk/hermes.git cddl-linter
```
88 changes: 88 additions & 0 deletions rust/cbork/cddl-linter/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//! CLI interpreter for the cbork lint tool

use std::{path::PathBuf, process::exit};

use clap::Parser;
use console::{style, Emoji};

/// CDDL linter cli tool
#[derive(Parser)]
pub(crate) struct Cli {
/// Path to the CDDL files definition.
/// It could path to the standalone file, or to the directory.
/// So all files with the `.cddl` extension inside the directory will be linted.
path: PathBuf,
}

impl Cli {
/// Execute the CLI
pub(crate) fn exec(self) {
let res = if self.path.is_file() {
check_file_with_print(&self.path)
} else {
check_dir_with_print(&self.path)
};

if !res {
exit(1);
}
}
}

/// Check the CDDL file, return any errors
fn check_file(file_path: &PathBuf) -> anyhow::Result<()> {
let mut content = std::fs::read_to_string(file_path)?;
cddl_parser::parse_cddl(&mut content, &cddl_parser::Extension::CDDLParser)?;
Ok(())
}

/// Check the CDDL file, prints any errors into the stdout
fn check_file_with_print(file_path: &PathBuf) -> bool {
if let Err(e) = check_file(file_path) {
println!(
"{} {}:\n{}",
Emoji::new("🚨", "Errors"),
file_path.display(),
style(e).red()
);
false
} else {
println!("{} {}", Emoji::new("✅", "Success"), file_path.display(),);
true
}
}

/// CDDL file extension. Filter directory files to apply the linter only on the CDDL
/// files.
const CDDL_FILE_EXTENSION: &str = "cddl";

/// Check the directory, prints any errors into the stdout
fn check_dir_with_print(dir_path: &PathBuf) -> bool {
let fun = |dir_path| -> anyhow::Result<bool> {
let mut res = true;
for entry in std::fs::read_dir(dir_path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if path.extension().is_some_and(|e| e.eq(CDDL_FILE_EXTENSION)) {
res = check_file_with_print(&path);
}
} else if path.is_dir() {
res = check_dir_with_print(&path);
}
}
Ok(res)
};

if let Err(e) = fun(dir_path) {
println!(
"{} {}:\n{}",
Emoji::new("🚨", "Errors"),
dir_path.display(),
style(e).red()
);
false
} else {
true
}
}
90 changes: 90 additions & 0 deletions rust/cbork/cddl-linter/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! Errors module.

#![allow(dead_code)]

use std::{error::Error, fmt::Display};

/// Errors struct which holds a collection of errors
#[derive(Debug)]
pub(crate) struct Errors(Vec<anyhow::Error>);

impl Error for Errors {}

impl Display for Errors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for err in &self.0 {
write!(f, "- ")?;
let err_str = err.to_string();
let mut err_lines = err_str.lines();
if let Some(first_line) = err_lines.next() {
writeln!(f, "{first_line}")?;
for line in err_lines {
writeln!(f, " {line}")?;
}
}
}
Ok(())
}
}

impl Errors {
/// Create a new empty `Errors`
pub(crate) fn new() -> Self {
Self(Vec::new())
}

/// Returns `true` if the `Errors` contains no elements.
pub(crate) fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Add an error to the `Errors`
pub(crate) fn add_err<E>(&mut self, err: E)
where E: Into<anyhow::Error> {
let err = err.into();
match err.downcast::<Errors>() {
Ok(errs) => self.0.extend(errs.0),
Err(err) => self.0.push(err),
}
}

/// Return a closure that adds an error to the `Errors`
pub(crate) fn get_add_err_fn<E>(&mut self) -> impl FnOnce(E) + '_
where E: Into<anyhow::Error> {
|err| self.add_err(err)
}

/// Return errors if `Errors` is not empty or return `Ok(val)`
pub(crate) fn return_result<T>(self, val: T) -> anyhow::Result<T> {
if self.0.is_empty() {
Ok(val)
} else {
Err(self.into())
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_errors() {
let mut errors_1 = Errors::new();
errors_1.add_err(anyhow::anyhow!("error 1"));
errors_1.add_err(anyhow::anyhow!("error 2"));

let mut errors_2 = Errors::new();
errors_2.add_err(anyhow::anyhow!("error 3"));
errors_2.add_err(anyhow::anyhow!("error 4"));

let mut combined_errors = Errors::new();
combined_errors.add_err(errors_1);
combined_errors.add_err(errors_2);

assert_eq!(
combined_errors.to_string(),
"- error 1\n- error 2\n- error 3\n- error 4\n"
);
}
}
9 changes: 9 additions & 0 deletions rust/cbork/cddl-linter/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
//! CDDL linter cli tool

mod cli;

fn main() {
use clap::Parser;

cli::Cli::parse().exec();
}
3 changes: 2 additions & 1 deletion rust/cbork/cddl-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
name = "cddl-parser"
version = "0.1.0"
edition.workspace = true
license = "MIT OR Apache-2.0"
license.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand All @@ -15,3 +15,4 @@ workspace = true
derive_more = "0.99.17"
pest = { version = "2.7.2", features = ["std", "pretty-print", "memchr", "const_prec_climber"] }
pest_derive = { version = "2.7.2", features = ["grammar-extras"] }
thiserror = "1.0.56"
9 changes: 4 additions & 5 deletions rust/cbork/cddl-parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
#![allow(missing_docs)] // TODO(apskhem): Temporary, to bo removed in a subsequent PR

//! A parser for CDDL, utilized for parsing in accordance with RFC 8610.

use std::fmt::Debug;
#![allow(missing_docs)] // TODO(apskhem): Temporary, to bo removed in a subsequent PR

use derive_more::{Display, From};
pub use pest::Parser;
Expand Down Expand Up @@ -60,9 +58,9 @@ pub enum Extension {
// CDDL Standard Postlude - read from an external file
pub const POSTLUDE: &str = include_str!("grammar/postlude.cddl");

/// Abstract Syntax Tree (AST) representing parsed CDDL syntax.
// TODO: this is temporary. need to add more pragmatic nodes
#[derive(Debug)]
/// Abstract Syntax Tree (AST) representing parsed CDDL syntax.
pub enum AST<'a> {
/// Represents the AST for RFC 8610 CDDL rules.
RFC8610(Pairs<'a, rfc_8610::Rule>),
Expand All @@ -84,7 +82,8 @@ pub enum CDDLErrorType {
}

/// Represents an error that may occur during CDDL parsing.
#[derive(Display, Debug, From)]
#[derive(thiserror::Error, Debug, From)]
#[error("{0}")]
pub struct CDDLError(CDDLErrorType);

/// Parses and checks semantically a CDDL input string.
Expand Down

0 comments on commit e8ba753

Please sign in to comment.