ts-token 0.8.0

JSON web token library for my projects
Documentation
//! CLI interface for converting cryptographic keys to their JSON web key representation

use std::{
    io::{Read, stdin},
    path::{Path, PathBuf},
};

use clap::Parser;
use fs_err::read;
use ts_crypto::{SigningKey, VerifyingKey};
use ts_error_stack::Report;
use ts_token::JsonWebKey;

/// CLI for working with JSON web tokens and keys
#[derive(Parser)]
#[command(version, about = "CLI for working with JSON web keys", long_about = None)]
pub struct Args {
    /// specify a non-default algorithm for the key type, no validation is done on this value
    #[arg(short, long)]
    alg: Option<String>,

    /// the path to the public/private PEM key file, use `-` to read from stdin
    key_path: PathBuf,
}

fn main() -> Result<(), Report<CliError>> {
    let Args { alg, key_path } = Args::parse();
    let alg: Option<&str> = alg.as_deref();

    let pem = if key_path == Path::new("-") {
        let mut buf = Vec::new();
        stdin()
            .read_to_end(&mut buf)
            .map_err(|source| CreateError::ReadStdin { source })?;
        buf
    } else {
        read(key_path).map_err(|source| CreateError::ReadKeyFile { source })?
    };

    let key = if let Some(key) = VerifyingKey::from_pem(&pem) {
        key
    } else if let Some(key) = SigningKey::from_pem(&pem) {
        key.verifying_key()
    } else {
        return Err(CreateError::CannotParseKey.into());
    };

    let mut jwk = JsonWebKey::from(&key);

    if let Some(alg) = alg {
        jwk.alg = alg.to_string();
    }

    let key_json = serde_json::to_string_pretty(&jwk)
        .map_err(|source| CreateError::SerializeKey { source })?;

    println!("{key_json}");

    Ok(())
}

#[allow(missing_docs)]
#[derive(Debug, thiserror::Error)]
#[error("creating the JSON web key failed")]
pub struct CliError(#[source] CreateError);
impl From<CreateError> for CliError {
    fn from(value: CreateError) -> Self {
        Self(value)
    }
}

#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum CreateError {
    #[error("the key could not be read from stdin")]
    ReadStdin { source: std::io::Error },

    #[error("the key file could not be read")]
    ReadKeyFile { source: std::io::Error },

    #[error("the key couldn't be parsed, ensure it is PEM encoded and a supported type")]
    CannotParseKey,

    #[error("the JSON serialization failed")]
    SerializeKey { source: serde_json::Error },
}
impl From<CreateError> for Report<CliError> {
    fn from(value: CreateError) -> Self {
        Self::from(CliError::from(value))
    }
}