Improve md5 and sha256 code (#3841)

* Refactor Hash code to simplify md5 and sha256 implementations

Md5 and Sha256 (and other future digests) require less boilerplate code
now. Error reporting includues the name of the hash again.

* Add missing hash sha256 test
This commit is contained in:
Peter Cunderlik 2021-07-29 16:22:16 +01:00 committed by GitHub
parent 7f7af2bbaa
commit 9696e4d315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 74 deletions

View File

@ -1,40 +1,84 @@
use std::marker::PhantomData;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::ShellTypeName;
use nu_protocol::{ColumnPath, Primitive, UntaggedValue, Value};
use nu_protocol::{ColumnPath, Primitive, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{ShellTypeName, Signature};
use nu_source::Tag;
pub fn run<D>(args: CommandArgs) -> Result<OutputStream, ShellError>
pub trait HashDigest: digest::Digest {
fn name() -> &'static str;
fn examples() -> Vec<Example>;
}
pub struct SubCommand<D: HashDigest> {
name_string: String,
usage_string: String,
phantom: PhantomData<D>,
}
impl<D: HashDigest> Default for SubCommand<D> {
fn default() -> Self {
Self {
name_string: format!("hash {}", D::name()),
usage_string: format!("{} encode a value", D::name()),
phantom: PhantomData,
}
}
}
impl<D> WholeStreamCommand for SubCommand<D>
where
D: digest::Digest,
D: HashDigest + Send + Sync,
digest::Output<D>: core::fmt::LowerHex,
{
let column_paths: Vec<ColumnPath> = args.rest(0)?;
fn name(&self) -> &str {
&self.name_string
}
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action::<D>(&v, v.tag())
} else {
let mut ret = v;
fn signature(&self) -> Signature {
Signature::build(self.name()).rest(
SyntaxShape::ColumnPath,
format!("optionally {} encode data by column paths", D::name()),
)
}
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action::<D>(old, old.tag())),
)?;
fn usage(&self) -> &str {
&self.usage_string
}
fn examples(&self) -> Vec<Example> {
D::examples()
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let column_paths: Vec<ColumnPath> = args.rest(0)?;
Ok(args
.input
.map(move |v| {
if column_paths.is_empty() {
action::<D>(&v, v.tag())
} else {
let mut ret = v;
for path in &column_paths {
ret = ret.swap_data_by_column_path(
path,
Box::new(move |old| action::<D>(old, old.tag())),
)?;
}
Ok(ret)
}
Ok(ret)
}
})
.into_input_stream())
})
.into_input_stream())
}
}
pub fn action<D>(input: &Value, tag: Tag) -> Result<Value, ShellError>
where
D: digest::Digest,
D: HashDigest,
digest::Output<D>: core::fmt::LowerHex,
{
match &input.value {
@ -49,7 +93,7 @@ where
other => {
let got = format!("got {}", other.type_name());
Err(ShellError::labeled_error(
"value is not supported for hashing",
format!("value is not supported for hashing as {}", D::name()),
got,
tag.span,
))

View File

@ -1,34 +1,17 @@
use crate::prelude::*;
use md5::Md5;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_protocol::UntaggedValue;
use super::generic_digest;
use super::generic_digest::{self, HashDigest};
pub struct SubCommand;
pub type SubCommand = generic_digest::SubCommand<Md5>;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"hash md5"
impl HashDigest for Md5 {
fn name() -> &'static str {
"md5"
}
fn signature(&self) -> Signature {
Signature::build("hash md5").rest(
SyntaxShape::ColumnPath,
"optionally md5 encode data by column paths",
)
}
fn usage(&self) -> &str {
"md5 encode a value"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
generic_digest::run::<Md5>(args)
}
fn examples(&self) -> Vec<Example> {
fn examples() -> Vec<Example> {
vec![
Example {
description: "md5 encode a string",

View File

@ -1,34 +1,17 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Signature, SyntaxShape, UntaggedValue};
use nu_protocol::UntaggedValue;
use sha2::Sha256;
use super::generic_digest;
use super::generic_digest::{self, HashDigest};
pub struct SubCommand;
pub type SubCommand = generic_digest::SubCommand<Sha256>;
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"hash sha256"
impl HashDigest for Sha256 {
fn name() -> &'static str {
"sha256"
}
fn signature(&self) -> Signature {
Signature::build("hash sha256").rest(
SyntaxShape::ColumnPath,
"optionally sha256 encode data by column paths",
)
}
fn usage(&self) -> &str {
"sha256 encode a value"
}
fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
generic_digest::run::<Sha256>(args)
}
fn examples(&self) -> Vec<Example> {
fn examples() -> Vec<Example> {
vec![
Example {
description: "sha256 encode a string",

View File

@ -84,8 +84,8 @@ pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Bo
// Text manipulation
whole_stream_command(Hash),
whole_stream_command(HashBase64),
whole_stream_command(HashMd5),
whole_stream_command(HashSha256),
whole_stream_command(HashMd5::default()),
whole_stream_command(HashSha256::default()),
whole_stream_command(Split),
whole_stream_command(SplitColumn),
whole_stream_command(SplitRow),

View File

@ -96,3 +96,19 @@ fn md5_works_with_file() {
assert_eq!(actual.out, "4de97601d232c427977ef11db396c951");
}
#[test]
fn sha256_works_with_file() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
open sample.db | hash sha256
"#
)
);
assert_eq!(
actual.out,
"2f5050e7eea415c1f3d80b5d93355efd15043ec9157a2bb167a9e73f2ae651f2"
);
}