sha1_macros/
lib.rs

1//! Macros for computing SHA1 hashes at compile-time
2//!
3//! # Examples
4//! ```rust
5//! # use sha1_macros::*;
6//! # use hex_literal::hex;
7//! assert_eq!(sha1_hex!("this is a test"), "fa26be19de6bff93f70bc2308434e4a440bbad02");
8//! assert_eq!(sha1_bytes!("this is a test"), hex!("fa26be19de6bff93f70bc2308434e4a440bbad02"));
9//! ```
10
11use proc_macro::{TokenStream, TokenTree, Literal, Spacing, Punct};
12use syn::{LitStr, LitByteStr, parse_macro_input};
13use syn::parse::{self, Parse, ParseStream};
14use sha1::{Digest, Sha1};
15
16enum Input {
17    String(LitStr),
18    Bytes(LitByteStr)
19}
20
21impl Input {
22    pub fn to_bytes(&self) -> Vec<u8> {
23        match self {
24            Self::String(x) => x.value().into_bytes(),
25            Self::Bytes(x) => x.value()
26        }
27    }
28}
29
30impl Parse for Input {
31    fn parse(input: ParseStream) -> parse::Result<Self> {
32        if input.peek(LitStr) {
33            Ok(Input::String(input.parse()?))
34        } else if input.peek(LitByteStr) {
35            Ok(Input::Bytes(input.parse()?))
36        } else {
37            Err(input.error("expected a string or byte literal"))
38        }
39    }
40}
41
42/// Computes the SHA1 hash as a hexadecimal string
43///
44/// The resulting value is of type `&'static str`.
45/// ```rust
46/// # use sha1_macros::sha1_hex;
47/// assert_eq!(sha1_hex!("this is a test"), "fa26be19de6bff93f70bc2308434e4a440bbad02");
48/// ```
49#[proc_macro]
50pub fn sha1_hex(tokens: TokenStream) -> TokenStream { 
51    sha1_impl(tokens, |hash| {
52        let hash = hex::encode(hash);
53        TokenTree::Literal(Literal::string(hash.as_ref())).into()
54    })
55}
56
57/// Computes the SHA1 hash as a base64 unpadded string
58///
59/// The resulting value is of type `&'static str`.
60/// ```rust
61/// # use sha1_macros::sha1_base64;
62/// assert_eq!(sha1_base64!("this is a test"), "+ia+Gd5r/5P3C8IwhDTkpEC7rQI");
63/// ```
64#[proc_macro]
65pub fn sha1_base64(tokens: TokenStream) -> TokenStream {
66    use base64::engine::general_purpose::STANDARD_NO_PAD;
67    use base64::Engine;
68    
69    sha1_impl(tokens, |hash| {
70        let hash = STANDARD_NO_PAD.encode(hash);
71        TokenTree::Literal(Literal::string(hash.as_ref())).into()
72    })
73}
74
75/// Computes the SHA1 hash as a byte array
76///
77/// The resulting value is of type `[u8; 20]`.
78/// ```rust
79/// # use sha1_macros::sha1_bytes;
80/// # use hex_literal::hex;
81/// assert_eq!(sha1_bytes!("this is a test"), hex!("fa26be19de6bff93f70bc2308434e4a440bbad02"));
82/// ```
83#[proc_macro]
84pub fn sha1_bytes(tokens: TokenStream) -> TokenStream {
85    sha1_impl(tokens, |hash| {
86        TokenStream::from_iter([TokenTree::Punct(Punct::new('*', Spacing::Joint)), Literal::byte_string(hash).into()])
87    })
88}
89
90
91fn sha1_impl(tokens: TokenStream, f: impl FnOnce(&[u8]) -> TokenStream) -> TokenStream {
92    let input = parse_macro_input!(tokens as Input);
93    let bytes = input.to_bytes();
94
95    let mut hasher = Sha1::new();
96    hasher.update(bytes.as_slice());
97
98    let hash = hasher.finalize();
99    f(hash.as_ref())
100}