Skip to content

Commit 96f814b

Browse files
authored
Merge pull request #355 from RedisLabsModules/improve-the-info-handler
Improves the info handler API.
2 parents 4e9ccd7 + 334f06b commit 96f814b

21 files changed

+906
-81
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Generated by Cargo
22
# will have compiled files and executables
3-
/target/
3+
**/target/
44

55
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
66
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

Cargo.toml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,22 @@ crate-type = ["cdylib"]
8080
name = "test_helper"
8181
crate-type = ["cdylib"]
8282

83+
[[example]]
84+
name = "info_handler_macro"
85+
crate-type = ["cdylib"]
86+
87+
[[example]]
88+
name = "info_handler_builder"
89+
crate-type = ["cdylib"]
90+
91+
[[example]]
92+
name = "info_handler_struct"
93+
crate-type = ["cdylib"]
94+
95+
[[example]]
96+
name = "info_handler_multiple_sections"
97+
crate-type = ["cdylib"]
98+
8399
[[example]]
84100
name = "info"
85101
crate-type = ["cdylib"]
@@ -119,7 +135,7 @@ redis-module-macros = { path = "./redismodule-rs-macros"}
119135
redis-module = { path = "./", default-features = false, features = ["min-redis-compatibility-version-7-2"] }
120136

121137
[build-dependencies]
122-
bindgen = "0.65"
138+
bindgen = "0.66"
123139
cc = "1"
124140

125141
[features]

examples/info_handler_builder.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use redis_module::InfoContext;
2+
use redis_module::{redis_module, RedisResult};
3+
use redis_module_macros::info_command_handler;
4+
5+
#[info_command_handler]
6+
fn add_info(ctx: &InfoContext, _for_crash_report: bool) -> RedisResult<()> {
7+
ctx.builder()
8+
.add_section("info")
9+
.field("field", "value")?
10+
.add_dictionary("dictionary")
11+
.field("key", "value")?
12+
.build_dictionary()?
13+
.build_section()?
14+
.build_info()?;
15+
16+
Ok(())
17+
}
18+
19+
//////////////////////////////////////////////////////
20+
21+
redis_module! {
22+
name: "info_handler_builder",
23+
version: 1,
24+
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
25+
data_types: [],
26+
commands: [],
27+
}

examples/info_handler_macro.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use redis_module::{redis_module, RedisResult};
2+
use redis_module::{InfoContext, Status};
3+
use redis_module_macros::info_command_handler;
4+
5+
#[info_command_handler]
6+
fn add_info(ctx: &InfoContext, _for_crash_report: bool) -> RedisResult<()> {
7+
if ctx.add_info_section(Some("info")) == Status::Ok {
8+
ctx.add_info_field_str("field", "value");
9+
}
10+
11+
Ok(())
12+
}
13+
14+
//////////////////////////////////////////////////////
15+
16+
redis_module! {
17+
name: "info_handler_macro",
18+
version: 1,
19+
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
20+
data_types: [],
21+
commands: [],
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use redis_module::InfoContext;
2+
use redis_module::{redis_module, RedisResult};
3+
use redis_module_macros::{info_command_handler, InfoSection};
4+
5+
#[derive(Debug, Clone, InfoSection)]
6+
struct InfoSection1 {
7+
field_1: String,
8+
}
9+
10+
#[derive(Debug, Clone, InfoSection)]
11+
struct InfoSection2 {
12+
field_2: String,
13+
}
14+
15+
#[info_command_handler]
16+
fn add_info(ctx: &InfoContext, _for_crash_report: bool) -> RedisResult<()> {
17+
let data = InfoSection1 {
18+
field_1: "value1".to_owned(),
19+
};
20+
let _ = ctx.build_one_section(data)?;
21+
22+
let data = InfoSection2 {
23+
field_2: "value2".to_owned(),
24+
};
25+
26+
ctx.build_one_section(data)
27+
}
28+
29+
//////////////////////////////////////////////////////
30+
31+
redis_module! {
32+
name: "info_handler_multiple_sections",
33+
version: 1,
34+
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
35+
data_types: [],
36+
commands: [],
37+
}

examples/info_handler_struct.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
use std::collections::HashMap;
2+
3+
use redis_module::InfoContext;
4+
use redis_module::{redis_module, RedisResult};
5+
use redis_module_macros::{info_command_handler, InfoSection};
6+
7+
#[derive(Debug, Clone, InfoSection)]
8+
struct Info {
9+
field: String,
10+
dictionary: HashMap<String, String>,
11+
}
12+
13+
#[info_command_handler]
14+
fn add_info(ctx: &InfoContext, _for_crash_report: bool) -> RedisResult<()> {
15+
let mut dictionary = HashMap::new();
16+
dictionary.insert("key".to_owned(), "value".into());
17+
let data = Info {
18+
field: "value".to_owned(),
19+
dictionary,
20+
};
21+
ctx.build_one_section(data)
22+
}
23+
24+
//////////////////////////////////////////////////////
25+
26+
redis_module! {
27+
name: "info_handler_struct",
28+
version: 1,
29+
allocator: (redis_module::alloc::RedisAlloc, redis_module::alloc::RedisAlloc),
30+
data_types: [],
31+
commands: [],
32+
}

examples/test_helper.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use redis_module::InfoContext;
2-
use redis_module::Status;
31
use redis_module::{redis_module, Context, RedisError, RedisResult, RedisString};
2+
use redis_module::{InfoContext, Status};
43

54
fn test_helper_version(ctx: &Context, _args: Vec<RedisString>) -> RedisResult {
65
let ver = ctx.get_redis_version()?;
@@ -33,7 +32,7 @@ fn test_helper_err(ctx: &Context, args: Vec<RedisString>) -> RedisResult {
3332

3433
fn add_info(ctx: &InfoContext, _for_crash_report: bool) {
3534
if ctx.add_info_section(Some("test_helper")) == Status::Ok {
36-
ctx.add_info_field_str("field", "test_helper_value");
35+
ctx.add_info_field_str("field", "value");
3736
}
3837
}
3938

redismodule-rs-macros/src/command.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ struct Args {
222222

223223
impl Parse for Args {
224224
fn parse(input: ParseStream) -> parse::Result<Self> {
225-
from_stream(config::JSONY, &input)
225+
from_stream(config::JSONY, input)
226226
}
227227
}
228228

@@ -241,12 +241,12 @@ pub(crate) fn redis_command(attr: TokenStream, item: TokenStream) -> TokenStream
241241
let original_function_name = func.sig.ident.clone();
242242

243243
let c_function_name = Ident::new(
244-
&format!("_inner_{}", func.sig.ident.to_string()),
244+
&format!("_inner_{}", func.sig.ident),
245245
func.sig.ident.span(),
246246
);
247247

248248
let get_command_info_function_name = Ident::new(
249-
&format!("_inner_get_command_info_{}", func.sig.ident.to_string()),
249+
&format!("_inner_get_command_info_{}", func.sig.ident),
250250
func.sig.ident.span(),
251251
);
252252

@@ -347,7 +347,7 @@ pub(crate) fn redis_command(attr: TokenStream, item: TokenStream) -> TokenStream
347347
context.reply(response.map(|v| v.into())) as i32
348348
}
349349

350-
#[linkme::distributed_slice(redis_module::commands::COMMNADS_LIST)]
350+
#[linkme::distributed_slice(redis_module::commands::COMMANDS_LIST)]
351351
fn #get_command_info_function_name() -> Result<redis_module::commands::CommandInfo, redis_module::RedisError> {
352352
let key_spec = vec![
353353
#(
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::Ident;
3+
use quote::{quote, ToTokens};
4+
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields};
5+
6+
/// Returns `true` if the `type_string` provided is a type of a map
7+
/// supported by the [`crate::InfoSection`].
8+
pub fn is_supported_map(type_string: &str) -> bool {
9+
/// A list of supported maps which can be converted to a dictionary for
10+
/// the [`redis_module::InfoContext`].
11+
const ALL: [&str; 2] = ["BTreeMap", "HashMap"];
12+
13+
ALL.iter().any(|m| type_string.contains(&m.to_lowercase()))
14+
}
15+
16+
/// Generate a [`From`] implementation for this struct so that it is
17+
/// possible to generate a [`redis_module::InfoContext`] information
18+
/// from it.
19+
///
20+
/// A struct is compatible to be used with [`crate::InfoSection`] when
21+
/// it has fields, whose types are convertible to
22+
/// [`redis_module::InfoContextBuilderFieldTopLevelValue`] and (for
23+
/// the dictionaries) if it has fields which are compatible maps of
24+
/// objects, where a key is a [`String`] and a value is any type
25+
/// convertible to
26+
/// [`redis_module::InfoContextBuilderFieldBottomLevelValue`].
27+
fn struct_info_section(struct_name: Ident, struct_data: DataStruct) -> TokenStream {
28+
let fields = match struct_data.fields {
29+
Fields::Named(f) => f,
30+
_ => {
31+
return quote! {compile_error!("The InfoSection can only be derived for structs with named fields.")}.into()
32+
}
33+
};
34+
35+
let fields = fields
36+
.named
37+
.into_iter()
38+
.map(|v| {
39+
let is_dictionary =
40+
is_supported_map(&v.ty.clone().into_token_stream().to_string().to_lowercase());
41+
let name = v.ident.ok_or(
42+
"Structs with unnamed fields are not supported by the InfoSection.".to_owned(),
43+
)?;
44+
Ok((is_dictionary, name))
45+
})
46+
.collect::<Result<Vec<_>, String>>();
47+
48+
let section_key_fields: Vec<_> = match fields {
49+
Ok(ref f) => f.iter().filter(|i| !i.0).map(|i| i.1.clone()).collect(),
50+
Err(e) => return quote! {compile_error!(#e)}.into(),
51+
};
52+
53+
let section_dictionary_fields: Vec<_> = match fields {
54+
Ok(f) => f.iter().filter(|i| i.0).map(|i| i.1.clone()).collect(),
55+
Err(e) => return quote! {compile_error!(#e)}.into(),
56+
};
57+
58+
let key_fields_names: Vec<_> = section_key_fields.iter().map(|v| v.to_string()).collect();
59+
60+
let dictionary_fields_names: Vec<_> = section_dictionary_fields
61+
.iter()
62+
.map(|v| v.to_string())
63+
.collect();
64+
65+
quote! {
66+
impl From<#struct_name> for redis_module::OneInfoSectionData {
67+
fn from(val: #struct_name) -> redis_module::OneInfoSectionData {
68+
let section_name = stringify!(#struct_name).to_owned();
69+
70+
let fields = vec![
71+
// The section's key => value pairs.
72+
#((
73+
#key_fields_names.to_owned(),
74+
redis_module::InfoContextBuilderFieldTopLevelValue::from(val.#section_key_fields)
75+
), )*
76+
77+
// The dictionaries within this section.
78+
#((
79+
#dictionary_fields_names.to_owned(),
80+
redis_module::InfoContextBuilderFieldTopLevelValue::Dictionary {
81+
name: #dictionary_fields_names.to_owned(),
82+
fields: redis_module::InfoContextFieldBottomLevelData(
83+
val.#section_dictionary_fields
84+
.into_iter()
85+
.map(|d| d.into())
86+
.collect()),
87+
}
88+
), )*
89+
];
90+
(section_name, fields)
91+
}
92+
}
93+
}
94+
.into()
95+
}
96+
97+
/// Implementation for the [`crate::info_section`] derive macro.
98+
/// Runs the relevant code generation base on the element
99+
/// the macro was used on. Currently supports `struct`s only.
100+
pub fn info_section(item: TokenStream) -> TokenStream {
101+
let input: DeriveInput = parse_macro_input!(item);
102+
let ident = input.ident;
103+
match input.data {
104+
Data::Struct(s) => struct_info_section(ident, s),
105+
_ => {
106+
quote! {compile_error!("The InfoSection derive can only be used with structs.")}.into()
107+
}
108+
}
109+
}

0 commit comments

Comments
 (0)