Skip to content

Commit 065fcce

Browse files
lewandomMarcin Lewandowski
and
Marcin Lewandowski
authored
feat: Add output to file option (#221)
* feat: Add output to file option * Fix message * Address unreachable code * Make less changes * Test saving JWT to a file * fmt * Address code review comments - moved tempdir to dev dependencies - removed old style import - refactored exit code logic into main - covered print_decoded_token in tests * Remove unnecessary exit call Co-authored-by: Marcin Lewandowski <[email protected]>
1 parent 0e887ec commit 065fcce

File tree

8 files changed

+232
-30
lines changed

8 files changed

+232
-30
lines changed

Cargo.lock

+63
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ chrono = "0.4"
2323
parse_duration = "2.1.1"
2424
atty = "0.2"
2525
base64 = "0.21.0"
26+
27+
[dev-dependencies]
28+
tempdir = "0.3.7"

src/cli_config.rs

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::translators::{PayloadItem, SupportedTypes};
22
use crate::utils::parse_duration_string;
33
use clap::{Parser, Subcommand, ValueEnum};
44
use jsonwebtoken::Algorithm;
5+
use std::path::PathBuf;
56

67
#[derive(Parser, Debug)]
78
#[clap(name = "jwt")]
@@ -94,6 +95,11 @@ pub struct EncodeArgs {
9495
#[clap(long, short = 'S')]
9596
#[clap(value_parser)]
9697
pub secret: String,
98+
99+
/// The path of the file to write the result to (suppresses default standard output)
100+
#[clap(long = "out", short = 'o')]
101+
#[clap(value_parser)]
102+
pub output_path: Option<PathBuf>,
97103
}
98104

99105
#[derive(Debug, Clone, Parser)]
@@ -130,6 +136,11 @@ pub struct DecodeArgs {
130136
#[clap(long = "ignore-exp")]
131137
#[clap(value_parser)]
132138
pub ignore_exp: bool,
139+
140+
/// The path of the file to write the result to (suppresses default standard output, implies JSON format)
141+
#[clap(long = "out", short = 'o')]
142+
#[clap(value_parser)]
143+
pub output_path: Option<PathBuf>,
133144
}
134145

135146
#[allow(clippy::upper_case_acronyms)]

src/main.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clap::Parser;
22
use cli_config::{App, Commands, EncodeArgs};
3+
use std::process::exit;
34
use translators::decode::{decode_token, print_decoded_token};
45
use translators::encode::{encode_token, print_encoded_token};
56

@@ -22,13 +23,23 @@ fn main() {
2223
warn_unsupported(arguments);
2324

2425
let token = encode_token(arguments);
26+
let output_path = &arguments.output_path;
2527

26-
print_encoded_token(token);
28+
exit(match print_encoded_token(token, output_path) {
29+
Ok(_) => 0,
30+
_ => 1,
31+
});
2732
}
2833
Commands::Decode(arguments) => {
2934
let (validated_token, token_data, format) = decode_token(arguments);
35+
let output_path = &arguments.output_path;
3036

31-
print_decoded_token(validated_token, token_data, format);
37+
exit(
38+
match print_decoded_token(validated_token, token_data, format, output_path) {
39+
Ok(_) => 0,
40+
_ => 1,
41+
},
42+
);
3243
}
33-
}
44+
};
3445
}

src/translators/decode.rs

+19-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::cli_config::{translate_algorithm, DecodeArgs};
22
use crate::translators::Payload;
3-
use crate::utils::slurp_file;
3+
use crate::utils::{slurp_file, write_file};
44
use base64::engine::general_purpose::STANDARD as base64_engine;
55
use base64::Engine as _;
66
use jsonwebtoken::errors::{ErrorKind, Result as JWTResult};
@@ -9,7 +9,7 @@ use serde_derive::{Deserialize, Serialize};
99
use serde_json::to_string_pretty;
1010
use std::collections::HashSet;
1111
use std::io;
12-
use std::process::exit;
12+
use std::path::PathBuf;
1313

1414
#[derive(Debug, PartialEq, Eq)]
1515
pub enum OutputFormat {
@@ -18,9 +18,9 @@ pub enum OutputFormat {
1818
}
1919

2020
#[derive(Debug, Serialize, Deserialize, PartialEq)]
21-
struct TokenOutput {
22-
header: Header,
23-
payload: Payload,
21+
pub struct TokenOutput {
22+
pub header: Header,
23+
pub payload: Payload,
2424
}
2525

2626
impl TokenOutput {
@@ -147,7 +147,8 @@ pub fn print_decoded_token(
147147
validated_token: JWTResult<TokenData<Payload>>,
148148
token_data: JWTResult<TokenData<Payload>>,
149149
format: OutputFormat,
150-
) {
150+
output_path: &Option<PathBuf>,
151+
) -> JWTResult<()> {
151152
if let Err(err) = &validated_token {
152153
match err.kind() {
153154
ErrorKind::InvalidToken => {
@@ -193,23 +194,26 @@ pub fn print_decoded_token(
193194
err
194195
),
195196
};
197+
return Err(validated_token.err().unwrap());
196198
}
197199

198-
match (format, token_data) {
199-
(OutputFormat::Json, Ok(token)) => {
200-
println!("{}", to_string_pretty(&TokenOutput::new(token)).unwrap())
200+
match (output_path.as_ref(), format, token_data) {
201+
(Some(path), _, Ok(token)) => {
202+
let json = to_string_pretty(&TokenOutput::new(token)).unwrap();
203+
write_file(path, json.as_bytes());
204+
println!("Wrote jwt to file {}", path.display());
205+
}
206+
(None, OutputFormat::Json, Ok(token)) => {
207+
println!("{}", to_string_pretty(&TokenOutput::new(token)).unwrap());
201208
}
202-
(_, Ok(token)) => {
209+
(None, _, Ok(token)) => {
203210
bunt::println!("\n{$bold}Token header\n------------{/$}");
204211
println!("{}\n", to_string_pretty(&token.header).unwrap());
205212
bunt::println!("{$bold}Token claims\n------------{/$}");
206213
println!("{}", to_string_pretty(&token.claims).unwrap());
207214
}
208-
(_, Err(_)) => exit(1),
215+
(_, _, Err(err)) => return Err(err),
209216
}
210217

211-
exit(match validated_token {
212-
Err(_) => 1,
213-
Ok(_) => 0,
214-
})
218+
Ok(())
215219
}

src/translators/encode.rs

+16-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::cli_config::{translate_algorithm, EncodeArgs};
22
use crate::translators::{Payload, PayloadItem};
3-
use crate::utils::slurp_file;
3+
use crate::utils::{slurp_file, write_file};
44
use atty::Stream;
55
use base64::engine::general_purpose::STANDARD as base64_engine;
66
use base64::Engine as _;
@@ -9,7 +9,7 @@ use jsonwebtoken::errors::Result as JWTResult;
99
use jsonwebtoken::{encode, Algorithm, EncodingKey, Header};
1010
use serde_json::{from_str, Value};
1111
use std::io;
12-
use std::process::exit;
12+
use std::path::PathBuf;
1313

1414
fn create_header(alg: Algorithm, kid: Option<&String>) -> Header {
1515
let mut header = Header::new(alg);
@@ -113,20 +113,27 @@ pub fn encode_token(arguments: &EncodeArgs) -> JWTResult<String> {
113113
.and_then(|secret| encode(&header, &claims, &secret))
114114
}
115115

116-
pub fn print_encoded_token(token: JWTResult<String>) {
117-
match token {
118-
Ok(jwt) => {
116+
pub fn print_encoded_token(
117+
token: JWTResult<String>,
118+
output_path: &Option<PathBuf>,
119+
) -> JWTResult<()> {
120+
match (output_path.as_ref(), token) {
121+
(Some(path), Ok(jwt)) => {
122+
write_file(path, jwt.as_bytes());
123+
println!("Wrote jwt to file {}", path.display());
124+
}
125+
(None, Ok(jwt)) => {
119126
if atty::is(Stream::Stdout) {
120127
println!("{}", jwt);
121128
} else {
122129
print!("{}", jwt);
123-
}
124-
exit(0);
130+
};
125131
}
126-
Err(err) => {
132+
(_, Err(err)) => {
127133
bunt::eprintln!("{$red+bold}Something went awry creating the jwt{/$}\n");
128134
eprintln!("{}", err);
129-
exit(1);
135+
return Err(err);
130136
}
131137
}
138+
Ok(())
132139
}

src/utils.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use std::fs;
2+
use std::path::Path;
23

34
pub fn slurp_file(file_name: &str) -> Vec<u8> {
45
fs::read(file_name).unwrap_or_else(|_| panic!("Unable to read file {}", file_name))
56
}
67

8+
pub fn write_file(path: &Path, content: &[u8]) {
9+
fs::write(path, content).unwrap_or_else(|_| panic!("Unable to write file {}", path.display()))
10+
}
11+
712
pub fn parse_duration_string(val: &str) -> Result<i64, String> {
813
let mut base_val = val.replace(" ago", "");
914

0 commit comments

Comments
 (0)