Skip to content

Commit

Permalink
feat: added parsing logic for plugin manifests
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickjcasey committed Aug 20, 2024
1 parent c1251d5 commit 1e1f402
Show file tree
Hide file tree
Showing 5 changed files with 513 additions and 92 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion hipcheck/src/plugin/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod download_manifest;
mod manager;
mod parser;
mod types;

use crate::plugin::manager::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,55 +1,16 @@
use super::extract_data;
use crate::plugin::parser::ParseKdlNode;
use crate::string_newtype_parse_kdl_node;
use crate::{error::Error, hc_error};
use kdl::{KdlDocument, KdlNode, KdlValue};
use std::{fmt::Display, str::FromStr};

// NOTE: the implementation in this crate was largely derived from RFD #0004

#[allow(unused)]
// Helper trait to make it easier to parse KdlNodes into our own types
trait ParseKdlNode
where
Self: Sized,
{
/// Return the name of the attribute used to identify the node pertaining to this struct
fn kdl_key() -> &'static str;

/// Attempt to convert a `kdl::KdlNode` into Self
fn parse_node(node: &KdlNode) -> Option<Self>;
}

#[allow(unused)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Url(pub String);

impl Url {
pub fn new(url: String) -> Self {
Self(url)
}
}

impl AsRef<String> for Url {
fn as_ref(&self) -> &String {
&self.0
}
}

impl ParseKdlNode for Url {
fn kdl_key() -> &'static str {
"url"
}

fn parse_node(node: &KdlNode) -> Option<Self> {
if node.name().to_string().as_str() != Self::kdl_key() {
return None;
}
// per RFD #0004, the first positional argument will be the URL and it will be a String
let url = node.entries().first()?;
match url.value() {
KdlValue::String(url) => Some(Url(url.clone())),
_ => None,
}
}
}
string_newtype_parse_kdl_node!(Url, "url");

/// Contains all of the hash algorithms supported inside of the plugin download manifest
#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -105,24 +66,15 @@ impl ParseKdlNode for HashWithDigest {
if node.name().to_string().as_str() != Self::kdl_key() {
return None;
}

let specified_algorithm = node.get("alg")?;
let hash_algorithm = match specified_algorithm.value() {
KdlValue::String(alg) => HashAlgorithm::try_from(alg.as_str()).ok()?,
_ => return None,
};

let specified_digest = node.get("digest")?;
let digest = match specified_digest.value() {
KdlValue::String(digest) => digest.clone(),
_ => return None,
};

// Per RFD #0004, the hash algorithm is of type String
let specified_algorithm = node.get("alg")?.value().as_string()?;
let hash_algorithm = HashAlgorithm::try_from(specified_algorithm).ok()?;
// Per RFD #0004, the digest is of type String
let digest = node.get("digest")?.value().as_string()?.to_string();
Some(HashWithDigest::new(hash_algorithm, digest))
}
}

#[allow(unused)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ArchiveFormat {
/// archived with tar and compressed with the XZ algorithm
Expand Down Expand Up @@ -164,7 +116,6 @@ impl TryFrom<&str> for ArchiveFormat {
}
}

#[allow(unused)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Compress {
/// compression algorithm used for the downloaded archive
Expand All @@ -188,16 +139,13 @@ impl ParseKdlNode for Compress {
if node.name().to_string().as_str() != Self::kdl_key() {
return None;
}
let specified_format = node.get("format")?;
let format = match specified_format.value() {
KdlValue::String(format) => ArchiveFormat::try_from(format.as_str()).ok()?,
_ => return None,
};
// Per RFD #0004, the format is of type String
let specified_format = node.get("format")?.value().as_string()?;
let format = ArchiveFormat::try_from(specified_format).ok()?;
Some(Compress { format })
}
}

#[allow(unused)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Size {
/// size of the downloaded artifact, in bytes
Expand Down Expand Up @@ -246,7 +194,6 @@ impl ParseKdlNode for Size {
/// size bytes=2_869_896
///}
///```
#[allow(unused)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DownloadManifestEntry {
// TODO: make this a SemVer type?
Expand All @@ -269,19 +216,6 @@ pub struct DownloadManifestEntry {
pub size: Size,
}

/// Returns the first successful node that can be parsed into T, if there is one
fn extract_data<T>(nodes: &[KdlNode]) -> Option<T>
where
T: ParseKdlNode,
{
for node in nodes {
if let Some(val) = T::parse_node(node) {
return Some(val);
}
}
None
}

impl ParseKdlNode for DownloadManifestEntry {
fn kdl_key() -> &'static str {
"plugin"
Expand All @@ -291,18 +225,10 @@ impl ParseKdlNode for DownloadManifestEntry {
if node.name().to_string().as_str() != Self::kdl_key() {
return None;
}

let version = node.get("version")?;
let version = match version.value() {
KdlValue::String(version) => version.to_string(),
_ => return None,
};

let arch = node.get("arch")?;
let arch = match arch.value() {
KdlValue::String(arch) => arch.to_string(),
_ => return None,
};
// Per RFD #0004, version is of type String
let version = node.get("version")?.value().as_string()?.to_string();
// Per RFD #0004, arch is of type String
let arch = node.get("arch")?.value().as_string()?.to_string();

// there should be one child for each plugin and it should contain the url, hash, compress
// and size information
Expand Down
77 changes: 77 additions & 0 deletions hipcheck/src/plugin/parser/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
mod download_manifest;
mod plugin_manifest;

use kdl::KdlNode;

// Helper trait to make it easier to parse KdlNodes into our own types
trait ParseKdlNode
where
Self: Sized,
{
/// Return the name of the attribute used to identify the node pertaining to this struct
fn kdl_key() -> &'static str;

/// Attempt to convert a `kdl::KdlNode` into Self
fn parse_node(node: &KdlNode) -> Option<Self>;
}

/// Returns the first successful node that can be parsed into T, if there is one
fn extract_data<T>(nodes: &[KdlNode]) -> Option<T>
where
T: ParseKdlNode,
{
for node in nodes {
if let Some(val) = T::parse_node(node) {
return Some(val);
}
}
None
}

/// Use this macro to generate the code needed to parse a KDL node that is a single string, as the
/// code is quite repetitive for this simple task.
///
/// As a bonus, the following code is also generated:
/// - AsRef<String>
/// - new(value: String) -> Self
///
/// NOTE: This only works with newtype wrappers around String!
///
/// Example:
/// publisher "mitre" can be generated by this macro!
///
/// ```rust
/// struct Publisher(pub String)
/// ```
#[macro_export]
macro_rules! string_newtype_parse_kdl_node {
($type:ty, $identifier:expr) => {
impl $type {
pub fn new(value: String) -> Self {
Self(value)
}
}

impl ParseKdlNode for $type {
fn kdl_key() -> &'static str {
$identifier
}

fn parse_node(node: &KdlNode) -> Option<Self> {
if node.name().to_string().as_str() != Self::kdl_key() {
return None;
}
// NOTE: this macro currently assumes that the first positional argument is the
// correct value to be parsing, which is true for newtype String wrappers!
let entry = node.entries().first()?.value().as_string()?.to_string();
Some(Self(entry))
}
}

impl AsRef<String> for $type {
fn as_ref(&self) -> &String {
&self.0
}
}
};
}
Loading

0 comments on commit 1e1f402

Please sign in to comment.