redis-args-impl 0.22.1

Derive macro for reading and writing types for redis usage, either through serde or format args - proc-macro implementation
Documentation
// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use quote::quote;

pub(crate) fn from_redis_value(input: TokenStream) -> TokenStream {
    let ast = syn::parse_macro_input!(input as syn::DeriveInput);

    match try_from_redis_value(ast) {
        Ok(k) => k,
        Err(err) => TokenStream::from(err.to_compile_error()),
    }
}

fn try_from_redis_value(ast: syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    let parameter = FromRedisArgsParameters::from_derive_input(&ast)?;

    match parameter.conversion {
        FromRedisValueConversion::Serde => impl_from_redis_value_serde(&ast),
        FromRedisValueConversion::FromStr => impl_from_redis_value_from_str(&ast),
    }
}

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(from_redis_value))]
struct FromRedisArgsParameters {
    #[darling(flatten)]
    conversion: FromRedisValueConversion,
}

#[derive(Debug, PartialEq, Eq, FromMeta)]
enum FromRedisValueConversion {
    Serde,
    #[darling(rename = "FromStr")]
    FromStr,
}

fn impl_from_redis_value_serde(input: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    let generics = &input.generics;
    let ident = &input.ident;

    let expanded = quote! {
        impl #generics ::redis_args::__exports::redis::FromRedisValue for #ident #generics {
            fn from_redis_value(v: ::redis_args::__exports::redis::Value) -> ::core::result::Result::<Self, ::redis_args::__exports::redis::ParsingError> {
                match v {
                    ::redis_args::__exports::redis::Value::BulkString(ref bytes) => ::redis_args::__exports::serde_json::from_slice(bytes).map_err(|_| {
                        ::redis_args::__exports::redis::ParsingError::from(
                            "invalid data content"
                        )
                    }),
                    _ => ::core::result::Result::Err(
                        ::redis_args::__exports::redis::ParsingError::from(
                            "invalid data type"
                        )
                    ),
                }
            }
        }
    };
    Ok(TokenStream::from(expanded))
}

fn impl_from_redis_value_from_str(input: &syn::DeriveInput) -> Result<TokenStream, syn::Error> {
    let generics = &input.generics;
    let ident = &input.ident;

    let expanded = quote! {
        impl #generics ::redis_args::__exports::redis::FromRedisValue for #ident #generics {
            fn from_redis_value(v: ::redis_args::__exports::redis::Value) -> ::core::result::Result::<Self, ::redis_args::__exports::redis::ParsingError> {
                match v {
                    ::redis_args::__exports::redis::Value::BulkString(ref bytes) => {
                        let s = std::str::from_utf8(bytes).map_err(|_| {
                            ::redis_args::__exports::redis::ParsingError::from(
                                "string is not utf8"
                            )
                        })?;

                        ::core::str::FromStr::from_str(s).map_err(|_| {
                            ::redis_args::__exports::redis::ParsingError::from(
                                "invalid data type"
                            )
                        })
                    },
                    _ => ::core::result::Result::Err(
                        ::redis_args::__exports::redis::ParsingError::from(
                            "invalid data type"
                        )
                    ),
                }
            }
        }
    };
    Ok(TokenStream::from(expanded))
}