redis_module/
raw.rs

1// Allow dead code in here in case I want to publish it as a crate at some
2// point.
3#![allow(dead_code)]
4
5extern crate enum_primitive_derive;
6extern crate libc;
7extern crate num_traits;
8
9use std::cmp::Ordering;
10use std::ffi::{c_ulonglong, CStr, CString};
11use std::os::raw::{c_char, c_double, c_int, c_long, c_longlong, c_void};
12use std::ptr;
13use std::slice;
14
15use crate::RedisResult;
16use bitflags::bitflags;
17use enum_primitive_derive::Primitive;
18use libc::size_t;
19use num_traits::FromPrimitive;
20
21use crate::error::Error;
22pub use crate::redisraw::bindings::*;
23use crate::{context::StrCallArgs, Context, RedisString};
24use crate::{RedisBuffer, RedisError};
25
26const GENERIC_ERROR_MESSAGE: &str = "Generic error.";
27
28bitflags! {
29    pub struct KeyMode: c_int {
30        const READ = REDISMODULE_READ as c_int;
31        const WRITE = REDISMODULE_WRITE as c_int;
32    }
33}
34
35bitflags! {
36    pub struct ModuleOptions: c_int {
37        const HANDLE_IO_ERRORS = REDISMODULE_OPTIONS_HANDLE_IO_ERRORS as c_int;
38        const NO_IMPLICIT_SIGNAL_MODIFIED = REDISMODULE_OPTION_NO_IMPLICIT_SIGNAL_MODIFIED as c_int;
39        const HANDLE_REPL_ASYNC_LOAD = REDISMODULE_OPTIONS_HANDLE_REPL_ASYNC_LOAD as c_int;
40    }
41}
42
43#[derive(Primitive, Debug, PartialEq, Eq)]
44pub enum KeyType {
45    Empty = REDISMODULE_KEYTYPE_EMPTY,
46    String = REDISMODULE_KEYTYPE_STRING,
47    List = REDISMODULE_KEYTYPE_LIST,
48    Hash = REDISMODULE_KEYTYPE_HASH,
49    Set = REDISMODULE_KEYTYPE_SET,
50    ZSet = REDISMODULE_KEYTYPE_ZSET,
51    Module = REDISMODULE_KEYTYPE_MODULE,
52    Stream = REDISMODULE_KEYTYPE_STREAM,
53}
54
55impl From<c_int> for KeyType {
56    fn from(v: c_int) -> Self {
57        Self::from_i32(v).unwrap()
58    }
59}
60
61#[derive(Primitive, Debug, PartialEq, Eq)]
62pub enum Where {
63    ListHead = REDISMODULE_LIST_HEAD,
64    ListTail = REDISMODULE_LIST_TAIL,
65}
66
67#[derive(Primitive, Debug, PartialEq, Eq)]
68pub enum ReplyType {
69    Unknown = REDISMODULE_REPLY_UNKNOWN,
70    String = REDISMODULE_REPLY_STRING,
71    Error = REDISMODULE_REPLY_ERROR,
72    Integer = REDISMODULE_REPLY_INTEGER,
73    Array = REDISMODULE_REPLY_ARRAY,
74    Null = REDISMODULE_REPLY_NULL,
75    Map = REDISMODULE_REPLY_MAP,
76    Set = REDISMODULE_REPLY_SET,
77    Bool = REDISMODULE_REPLY_BOOL,
78    Double = REDISMODULE_REPLY_DOUBLE,
79    BigNumber = REDISMODULE_REPLY_BIG_NUMBER,
80    VerbatimString = REDISMODULE_REPLY_VERBATIM_STRING,
81}
82
83impl From<c_int> for ReplyType {
84    fn from(v: c_int) -> Self {
85        Self::from_i32(v).unwrap()
86    }
87}
88
89#[derive(Primitive, Debug, PartialEq, Eq)]
90pub enum Aux {
91    Before = REDISMODULE_AUX_BEFORE_RDB,
92    After = REDISMODULE_AUX_AFTER_RDB,
93}
94
95#[derive(Primitive, Debug, PartialEq, Eq)]
96pub enum Status {
97    Ok = REDISMODULE_OK,
98    Err = REDISMODULE_ERR,
99}
100
101impl From<Status> for RedisResult<()> {
102    fn from(value: Status) -> Self {
103        match value {
104            Status::Ok => Ok(()),
105            Status::Err => Err(RedisError::Str(GENERIC_ERROR_MESSAGE)),
106        }
107    }
108}
109
110impl From<c_int> for Status {
111    fn from(v: c_int) -> Self {
112        Self::from_i32(v).unwrap()
113    }
114}
115
116impl From<Status> for Result<(), &str> {
117    fn from(s: Status) -> Self {
118        match s {
119            Status::Ok => Ok(()),
120            Status::Err => Err(GENERIC_ERROR_MESSAGE),
121        }
122    }
123}
124
125bitflags! {
126    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
127    pub struct NotifyEvent : c_int {
128        const GENERIC = REDISMODULE_NOTIFY_GENERIC;
129        const STRING = REDISMODULE_NOTIFY_STRING;
130        const LIST = REDISMODULE_NOTIFY_LIST;
131        const SET = REDISMODULE_NOTIFY_SET;
132        const HASH = REDISMODULE_NOTIFY_HASH;
133        const ZSET = REDISMODULE_NOTIFY_ZSET;
134        const EXPIRED = REDISMODULE_NOTIFY_EXPIRED;
135        const EVICTED = REDISMODULE_NOTIFY_EVICTED;
136        const STREAM = REDISMODULE_NOTIFY_STREAM;
137        const MODULE = REDISMODULE_NOTIFY_MODULE;
138        const LOADED = REDISMODULE_NOTIFY_LOADED;
139        const MISSED = REDISMODULE_NOTIFY_KEY_MISS;
140        const ALL = REDISMODULE_NOTIFY_ALL;
141        const TRIMMED = REDISMODULE_NOTIFY_TRIMMED;
142    }
143}
144
145#[derive(Debug)]
146pub enum CommandFlag {
147    Write,
148    Readonly,
149    Denyoom,
150    Admin,
151    Pubsub,
152    Noscript,
153    Random,
154    SortForScript,
155    Loading,
156    Stale,
157    SkipMonitor,
158    Asking,
159    Fast,
160    Movablekeys,
161}
162
163const fn command_flag_repr(flag: &CommandFlag) -> &'static str {
164    use crate::raw::CommandFlag::*;
165    match flag {
166        Write => "write",
167        Readonly => "readonly",
168        Denyoom => "denyoom",
169        Admin => "admin",
170        Pubsub => "pubsub",
171        Noscript => "noscript",
172        Random => "random",
173        SortForScript => "sort_for_script",
174        Loading => "loading",
175        Stale => "stale",
176        SkipMonitor => "skip_monitor",
177        Asking => "asking",
178        Fast => "fast",
179        Movablekeys => "movablekeys",
180    }
181}
182
183// This is the one static function we need to initialize a module.
184// bindgen does not generate it for us (probably since it's defined as static in redismodule.h).
185#[allow(improper_ctypes)]
186#[link(name = "redismodule", kind = "static")]
187extern "C" {
188    pub fn Export_RedisModule_Init(
189        ctx: *mut RedisModuleCtx,
190        module_name: *const c_char,
191        module_version: c_int,
192        api_version: c_int,
193    ) -> c_int;
194
195    pub fn Export_RedisModule_InitAPI(ctx: *mut RedisModuleCtx) -> c_void;
196}
197
198///////////////////////////////////////////////////////////////
199
200pub const FMT: *const c_char = b"v\0".as_ptr().cast::<c_char>();
201
202// REDISMODULE_HASH_DELETE is defined explicitly here because bindgen cannot
203// parse typecasts in C macro constants yet.
204// See https://github.com/rust-lang/rust-bindgen/issues/316
205pub const REDISMODULE_HASH_DELETE: *const RedisModuleString = 1 as *const RedisModuleString;
206
207// Helper functions for the raw bindings.
208
209#[allow(clippy::not_unsafe_ptr_arg_deref)]
210pub fn call_reply_type(reply: *mut RedisModuleCallReply) -> ReplyType {
211    unsafe {
212        // TODO: Cache the unwrapped functions and use them instead of unwrapping every time?
213        RedisModule_CallReplyType.unwrap()(reply).into()
214    }
215}
216
217#[allow(clippy::not_unsafe_ptr_arg_deref)]
218pub fn free_call_reply(reply: *mut RedisModuleCallReply) {
219    unsafe { RedisModule_FreeCallReply.unwrap()(reply) }
220}
221
222#[allow(clippy::not_unsafe_ptr_arg_deref)]
223pub fn call_reply_integer(reply: *mut RedisModuleCallReply) -> c_longlong {
224    unsafe { RedisModule_CallReplyInteger.unwrap()(reply) }
225}
226
227/// # Panics
228///
229/// Panics if the Redis server doesn't support replying with bool (since RESP3).
230#[allow(clippy::not_unsafe_ptr_arg_deref)]
231pub fn call_reply_bool(reply: *mut RedisModuleCallReply) -> bool {
232    (unsafe { RedisModule_CallReplyBool.unwrap()(reply) } != 0)
233}
234
235/// # Panics
236///
237/// Panics if the Redis server doesn't support replying with bool (since RESP3).
238#[allow(clippy::not_unsafe_ptr_arg_deref)]
239pub fn call_reply_double(reply: *mut RedisModuleCallReply) -> f64 {
240    unsafe { RedisModule_CallReplyDouble.unwrap()(reply) }
241}
242
243/// # Panics
244///
245/// Panics if the Redis server doesn't support replying with bool (since RESP3).
246#[allow(clippy::not_unsafe_ptr_arg_deref)]
247pub fn call_reply_big_number(reply: *mut RedisModuleCallReply) -> Option<String> {
248    unsafe {
249        let mut len: size_t = 0;
250        let reply_string: *mut u8 =
251            RedisModule_CallReplyBigNumber.unwrap()(reply, &mut len) as *mut u8;
252        if reply_string.is_null() {
253            return None;
254        }
255        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
256    }
257}
258
259/// # Panics
260///
261/// Panics if the Redis server doesn't support replying with bool (since RESP3).
262#[allow(clippy::not_unsafe_ptr_arg_deref)]
263pub fn call_reply_verbatim_string(reply: *mut RedisModuleCallReply) -> Option<(String, Vec<u8>)> {
264    unsafe {
265        let mut len: size_t = 0;
266        let format: *const u8 = ptr::null();
267        let reply_string: *mut u8 =
268            RedisModule_CallReplyVerbatim.unwrap()(reply, &mut len, &mut (format as *const c_char))
269                as *mut u8;
270        if reply_string.is_null() {
271            return None;
272        }
273        Some((
274            String::from_utf8(slice::from_raw_parts(format, 3).to_vec()).ok()?,
275            slice::from_raw_parts(reply_string, len).to_vec(),
276        ))
277    }
278}
279
280#[allow(clippy::not_unsafe_ptr_arg_deref)]
281pub fn call_reply_array_element(
282    reply: *mut RedisModuleCallReply,
283    idx: usize,
284) -> *mut RedisModuleCallReply {
285    unsafe { RedisModule_CallReplyArrayElement.unwrap()(reply, idx) }
286}
287
288/// # Panics
289///
290/// Panics if the Redis server doesn't support replying with bool (since RESP3).
291#[allow(clippy::not_unsafe_ptr_arg_deref)]
292pub fn call_reply_set_element(
293    reply: *mut RedisModuleCallReply,
294    idx: usize,
295) -> *mut RedisModuleCallReply {
296    unsafe { RedisModule_CallReplySetElement.unwrap()(reply, idx) }
297}
298
299/// # Panics
300///
301/// Panics if the Redis server doesn't support replying with bool (since RESP3).
302#[allow(clippy::not_unsafe_ptr_arg_deref)]
303pub fn call_reply_map_element(
304    reply: *mut RedisModuleCallReply,
305    idx: usize,
306) -> (*mut RedisModuleCallReply, *mut RedisModuleCallReply) {
307    let mut key: *mut RedisModuleCallReply = ptr::null_mut();
308    let mut val: *mut RedisModuleCallReply = ptr::null_mut();
309    unsafe { RedisModule_CallReplyMapElement.unwrap()(reply, idx, &mut key, &mut val) };
310    (key, val)
311}
312
313#[allow(clippy::not_unsafe_ptr_arg_deref)]
314pub fn call_reply_length(reply: *mut RedisModuleCallReply) -> usize {
315    unsafe { RedisModule_CallReplyLength.unwrap()(reply) }
316}
317
318#[allow(clippy::not_unsafe_ptr_arg_deref)]
319pub fn call_reply_string_ptr(reply: *mut RedisModuleCallReply, len: *mut size_t) -> *const c_char {
320    unsafe { RedisModule_CallReplyStringPtr.unwrap()(reply, len) }
321}
322
323#[allow(clippy::not_unsafe_ptr_arg_deref)]
324pub fn call_reply_string(reply: *mut RedisModuleCallReply) -> Option<String> {
325    unsafe {
326        let mut len: size_t = 0;
327        let reply_string: *mut u8 =
328            RedisModule_CallReplyStringPtr.unwrap()(reply, &mut len) as *mut u8;
329        if reply_string.is_null() {
330            return None;
331        }
332        String::from_utf8(slice::from_raw_parts(reply_string, len).to_vec()).ok()
333    }
334}
335
336#[allow(clippy::not_unsafe_ptr_arg_deref)]
337#[inline]
338pub fn close_key(kp: *mut RedisModuleKey) {
339    unsafe { RedisModule_CloseKey.unwrap()(kp) }
340}
341
342#[allow(clippy::not_unsafe_ptr_arg_deref)]
343#[inline]
344pub fn open_key(
345    ctx: *mut RedisModuleCtx,
346    keyname: *mut RedisModuleString,
347    mode: KeyMode,
348) -> *mut RedisModuleKey {
349    unsafe { RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits()).cast::<RedisModuleKey>() }
350}
351
352#[allow(clippy::not_unsafe_ptr_arg_deref)]
353#[inline]
354pub(crate) fn open_key_with_flags(
355    ctx: *mut RedisModuleCtx,
356    keyname: *mut RedisModuleString,
357    mode: KeyMode,
358    flags: c_int,
359) -> *mut RedisModuleKey {
360    unsafe {
361        RedisModule_OpenKey.unwrap()(ctx, keyname, mode.bits() | flags).cast::<RedisModuleKey>()
362    }
363}
364
365#[allow(clippy::not_unsafe_ptr_arg_deref)]
366#[inline]
367pub fn reply_with_array(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
368    unsafe { RedisModule_ReplyWithArray.unwrap()(ctx, len).into() }
369}
370
371#[allow(clippy::not_unsafe_ptr_arg_deref)]
372#[inline]
373pub fn reply_with_map(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
374    unsafe {
375        RedisModule_ReplyWithMap
376            .map_or_else(
377                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
378                |f| f(ctx, len),
379            )
380            .into()
381    }
382}
383
384#[allow(clippy::not_unsafe_ptr_arg_deref)]
385#[inline]
386pub fn reply_with_set(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
387    unsafe {
388        RedisModule_ReplyWithSet
389            .map_or_else(
390                || RedisModule_ReplyWithArray.unwrap()(ctx, len * 2),
391                |f| f(ctx, len),
392            )
393            .into()
394    }
395}
396
397#[allow(clippy::not_unsafe_ptr_arg_deref)]
398#[inline]
399pub fn reply_with_attribute(ctx: *mut RedisModuleCtx, len: c_long) -> Status {
400    unsafe { RedisModule_ReplyWithAttribute.unwrap()(ctx, len).into() }
401}
402
403#[allow(clippy::not_unsafe_ptr_arg_deref)]
404pub fn reply_with_error(ctx: *mut RedisModuleCtx, err: *const c_char) {
405    unsafe {
406        let msg = Context::str_as_legal_resp_string(CStr::from_ptr(err).to_str().unwrap());
407        RedisModule_ReplyWithError.unwrap()(ctx, msg.as_ptr());
408    }
409}
410
411#[allow(clippy::not_unsafe_ptr_arg_deref)]
412#[inline]
413pub fn reply_with_null(ctx: *mut RedisModuleCtx) -> Status {
414    unsafe { RedisModule_ReplyWithNull.unwrap()(ctx).into() }
415}
416
417#[allow(clippy::not_unsafe_ptr_arg_deref)]
418#[inline]
419pub fn reply_with_bool(ctx: *mut RedisModuleCtx, b: c_int) -> Status {
420    unsafe { RedisModule_ReplyWithBool.unwrap()(ctx, b).into() }
421}
422
423#[allow(clippy::not_unsafe_ptr_arg_deref)]
424#[inline]
425pub fn reply_with_long_long(ctx: *mut RedisModuleCtx, ll: c_longlong) -> Status {
426    unsafe { RedisModule_ReplyWithLongLong.unwrap()(ctx, ll).into() }
427}
428
429#[allow(clippy::not_unsafe_ptr_arg_deref)]
430#[inline]
431pub fn reply_with_double(ctx: *mut RedisModuleCtx, f: c_double) -> Status {
432    unsafe { RedisModule_ReplyWithDouble.unwrap()(ctx, f).into() }
433}
434
435#[allow(clippy::not_unsafe_ptr_arg_deref)]
436#[inline]
437pub fn reply_with_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) -> Status {
438    unsafe { RedisModule_ReplyWithString.unwrap()(ctx, s).into() }
439}
440
441#[allow(clippy::not_unsafe_ptr_arg_deref)]
442#[inline]
443pub fn reply_with_simple_string(ctx: *mut RedisModuleCtx, s: *const c_char) -> Status {
444    unsafe { RedisModule_ReplyWithSimpleString.unwrap()(ctx, s).into() }
445}
446
447#[allow(clippy::not_unsafe_ptr_arg_deref)]
448#[inline]
449pub fn reply_with_string_buffer(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
450    unsafe { RedisModule_ReplyWithStringBuffer.unwrap()(ctx, s, len).into() }
451}
452
453#[allow(clippy::not_unsafe_ptr_arg_deref)]
454#[inline]
455pub fn reply_with_big_number(ctx: *mut RedisModuleCtx, s: *const c_char, len: size_t) -> Status {
456    unsafe { RedisModule_ReplyWithBigNumber.unwrap()(ctx, s, len).into() }
457}
458
459#[allow(clippy::not_unsafe_ptr_arg_deref)]
460#[inline]
461pub fn reply_with_verbatim_string(
462    ctx: *mut RedisModuleCtx,
463    s: *const c_char,
464    len: size_t,
465    format: *const c_char,
466) -> Status {
467    unsafe { RedisModule_ReplyWithVerbatimStringType.unwrap()(ctx, s, len, format).into() }
468}
469
470// Sets the expiry on a key.
471//
472// Expire is in milliseconds.
473#[allow(clippy::not_unsafe_ptr_arg_deref)]
474#[inline]
475pub fn set_expire(key: *mut RedisModuleKey, expire: c_longlong) -> Status {
476    unsafe { RedisModule_SetExpire.unwrap()(key, expire).into() }
477}
478
479#[allow(clippy::not_unsafe_ptr_arg_deref)]
480#[inline]
481pub fn string_dma(key: *mut RedisModuleKey, len: *mut size_t, mode: KeyMode) -> *mut c_char {
482    unsafe { RedisModule_StringDMA.unwrap()(key, len, mode.bits()) }
483}
484
485#[allow(clippy::not_unsafe_ptr_arg_deref)]
486#[inline]
487pub fn string_truncate(key: *mut RedisModuleKey, new_len: size_t) -> Status {
488    unsafe { RedisModule_StringTruncate.unwrap()(key, new_len).into() }
489}
490
491#[allow(clippy::not_unsafe_ptr_arg_deref)]
492pub fn hash_get_multi<T>(
493    key: *mut RedisModuleKey,
494    fields: &[T],
495    values: &mut [*mut RedisModuleString],
496) -> Result<(), RedisError>
497where
498    T: Into<Vec<u8>> + Clone,
499{
500    assert_eq!(fields.len(), values.len());
501
502    let fields = fields
503        .iter()
504        .map(|e| CString::new(e.clone()))
505        .collect::<Result<Vec<CString>, _>>()?;
506
507    let mut fi = fields.iter();
508    let mut vi = values.iter_mut();
509
510    macro_rules! rm {
511        () => { unsafe {
512            RedisModule_HashGet.unwrap()(key, REDISMODULE_HASH_CFIELDS as i32,
513                                         ptr::null::<c_char>())
514        }};
515        ($($args:expr)*) => { unsafe {
516            RedisModule_HashGet.unwrap()(
517                key, REDISMODULE_HASH_CFIELDS as i32,
518                $($args),*,
519                ptr::null::<c_char>()
520            )
521        }};
522    }
523    macro_rules! f {
524        () => {
525            fi.next().unwrap().as_ptr()
526        };
527    }
528    macro_rules! v {
529        () => {
530            vi.next().unwrap()
531        };
532    }
533
534    // This convoluted code is necessary since Redis only exposes a varargs API for HashGet
535    // to modules. Unfortunately there's no straightforward or portable way of calling a
536    // a varargs function with a variable number of arguments that is determined at runtime.
537    // See also the following Redis ticket: https://github.com/redis/redis/issues/7860
538    let res = Status::from(match fields.len() {
539        0 => rm! {},
540        1 => rm! {f!() v!()},
541        2 => rm! {f!() v!() f!() v!()},
542        3 => rm! {f!() v!() f!() v!() f!() v!()},
543        4 => rm! {f!() v!() f!() v!() f!() v!() f!() v!()},
544        5 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
545        6 => rm! {f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()},
546        7 => rm! {
547            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
548            f!() v!()
549        },
550        8 => rm! {
551            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
552            f!() v!() f!() v!()
553        },
554        9 => rm! {
555            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
556            f!() v!() f!() v!() f!() v!()
557        },
558        10 => rm! {
559            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
560            f!() v!() f!() v!() f!() v!() f!() v!()
561        },
562        11 => rm! {
563            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
564            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
565        },
566        12 => rm! {
567            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
568            f!() v!() f!() v!() f!() v!() f!() v!() f!() v!() f!() v!()
569        },
570        _ => panic!("Unsupported length"),
571    });
572
573    match res {
574        Status::Ok => Ok(()),
575        Status::Err => Err(RedisError::Str("ERR key is not a hash value")),
576    }
577}
578
579#[allow(clippy::not_unsafe_ptr_arg_deref)]
580#[inline]
581pub fn hash_set(key: *mut RedisModuleKey, field: &str, value: *mut RedisModuleString) -> Status {
582    let field = CString::new(field).unwrap();
583
584    unsafe {
585        RedisModule_HashSet.unwrap()(
586            key,
587            REDISMODULE_HASH_CFIELDS as i32,
588            field.as_ptr(),
589            value,
590            ptr::null::<c_char>(),
591        )
592        .into()
593    }
594}
595
596#[allow(clippy::not_unsafe_ptr_arg_deref)]
597#[inline]
598pub fn hash_del(key: *mut RedisModuleKey, field: &str) -> Status {
599    let field = CString::new(field).unwrap();
600
601    // TODO: Add hash_del_multi()
602    // Support to pass multiple fields is desired but is complicated.
603    // See hash_get_multi() and https://github.com/redis/redis/issues/7860
604
605    unsafe {
606        RedisModule_HashSet.unwrap()(
607            key,
608            REDISMODULE_HASH_CFIELDS as i32,
609            field.as_ptr(),
610            REDISMODULE_HASH_DELETE,
611            ptr::null::<c_char>(),
612        )
613        .into()
614    }
615}
616
617#[allow(clippy::not_unsafe_ptr_arg_deref)]
618#[inline]
619pub fn list_push(
620    key: *mut RedisModuleKey,
621    list_where: Where,
622    element: *mut RedisModuleString,
623) -> Status {
624    unsafe { RedisModule_ListPush.unwrap()(key, list_where as i32, element).into() }
625}
626
627#[allow(clippy::not_unsafe_ptr_arg_deref)]
628#[inline]
629pub fn list_pop(key: *mut RedisModuleKey, list_where: Where) -> *mut RedisModuleString {
630    unsafe { RedisModule_ListPop.unwrap()(key, list_where as i32) }
631}
632
633// Returns pointer to the C string, and sets len to its length
634#[allow(clippy::not_unsafe_ptr_arg_deref)]
635#[inline]
636pub fn string_ptr_len(s: *const RedisModuleString, len: *mut size_t) -> *const c_char {
637    unsafe { RedisModule_StringPtrLen.unwrap()(s, len) }
638}
639
640#[allow(clippy::not_unsafe_ptr_arg_deref)]
641#[inline]
642pub fn string_retain_string(ctx: *mut RedisModuleCtx, s: *mut RedisModuleString) {
643    unsafe { RedisModule_RetainString.unwrap()(ctx, s) }
644}
645
646#[allow(clippy::not_unsafe_ptr_arg_deref)]
647#[inline]
648pub fn string_to_longlong(s: *const RedisModuleString, len: *mut i64) -> Status {
649    unsafe { RedisModule_StringToLongLong.unwrap()(s, len).into() }
650}
651
652#[allow(clippy::not_unsafe_ptr_arg_deref)]
653#[inline]
654pub fn string_to_double(s: *const RedisModuleString, len: *mut f64) -> Status {
655    unsafe { RedisModule_StringToDouble.unwrap()(s, len).into() }
656}
657
658#[allow(clippy::not_unsafe_ptr_arg_deref)]
659#[inline]
660pub fn string_set(key: *mut RedisModuleKey, s: *mut RedisModuleString) -> Status {
661    unsafe { RedisModule_StringSet.unwrap()(key, s).into() }
662}
663
664#[allow(clippy::not_unsafe_ptr_arg_deref)]
665#[inline]
666pub fn replicate_verbatim(ctx: *mut RedisModuleCtx) -> Status {
667    unsafe { RedisModule_ReplicateVerbatim.unwrap()(ctx).into() }
668}
669
670fn load<F, T>(rdb: *mut RedisModuleIO, f: F) -> Result<T, Error>
671where
672    F: FnOnce(*mut RedisModuleIO) -> T,
673{
674    let res = f(rdb);
675    if is_io_error(rdb) {
676        Err(RedisError::short_read().into())
677    } else {
678        Ok(res)
679    }
680}
681
682#[allow(clippy::not_unsafe_ptr_arg_deref)]
683pub fn load_unsigned(rdb: *mut RedisModuleIO) -> Result<u64, Error> {
684    unsafe { load(rdb, |rdb| RedisModule_LoadUnsigned.unwrap()(rdb)) }
685}
686
687#[allow(clippy::not_unsafe_ptr_arg_deref)]
688pub fn load_signed(rdb: *mut RedisModuleIO) -> Result<i64, Error> {
689    unsafe { load(rdb, |rdb| RedisModule_LoadSigned.unwrap()(rdb)) }
690}
691
692#[allow(clippy::not_unsafe_ptr_arg_deref)]
693pub fn load_string(rdb: *mut RedisModuleIO) -> Result<RedisString, Error> {
694    let p = unsafe { load(rdb, |rdb| RedisModule_LoadString.unwrap()(rdb))? };
695    Ok(RedisString::from_redis_module_string(ptr::null_mut(), p))
696}
697
698#[allow(clippy::not_unsafe_ptr_arg_deref)]
699pub fn load_string_buffer(rdb: *mut RedisModuleIO) -> Result<RedisBuffer, Error> {
700    unsafe {
701        let mut len = 0;
702        let buffer = load(rdb, |rdb| {
703            RedisModule_LoadStringBuffer.unwrap()(rdb, &mut len)
704        })?;
705        Ok(RedisBuffer::new(buffer, len))
706    }
707}
708
709#[allow(clippy::not_unsafe_ptr_arg_deref)]
710pub fn replicate<'a, T: Into<StrCallArgs<'a>>>(
711    ctx: *mut RedisModuleCtx,
712    command: &str,
713    args: T,
714) -> Status {
715    let mut call_args: StrCallArgs = args.into();
716    let final_args = call_args.args_mut();
717
718    let cmd = CString::new(command).unwrap();
719
720    unsafe {
721        RedisModule_Replicate.unwrap()(
722            ctx,
723            cmd.as_ptr(),
724            FMT,
725            final_args.as_ptr(),
726            final_args.len(),
727        )
728        .into()
729    }
730}
731
732#[allow(clippy::not_unsafe_ptr_arg_deref)]
733pub fn load_double(rdb: *mut RedisModuleIO) -> Result<f64, Error> {
734    unsafe { load(rdb, |rdb| RedisModule_LoadDouble.unwrap()(rdb)) }
735}
736
737#[allow(clippy::not_unsafe_ptr_arg_deref)]
738pub fn load_float(rdb: *mut RedisModuleIO) -> Result<f32, Error> {
739    unsafe { load(rdb, |rdb| RedisModule_LoadFloat.unwrap()(rdb)) }
740}
741
742#[allow(clippy::not_unsafe_ptr_arg_deref)]
743pub fn save_string(rdb: *mut RedisModuleIO, buf: &str) {
744    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
745}
746
747#[allow(clippy::not_unsafe_ptr_arg_deref)]
748/// Save the `RedisString` into the RDB
749pub fn save_redis_string(rdb: *mut RedisModuleIO, s: &RedisString) {
750    unsafe { RedisModule_SaveString.unwrap()(rdb, s.inner) };
751}
752
753#[allow(clippy::not_unsafe_ptr_arg_deref)]
754/// Save the `&[u8]` into the RDB
755pub fn save_slice(rdb: *mut RedisModuleIO, buf: &[u8]) {
756    unsafe { RedisModule_SaveStringBuffer.unwrap()(rdb, buf.as_ptr().cast::<c_char>(), buf.len()) };
757}
758
759#[allow(clippy::not_unsafe_ptr_arg_deref)]
760pub fn save_double(rdb: *mut RedisModuleIO, val: f64) {
761    unsafe { RedisModule_SaveDouble.unwrap()(rdb, val) };
762}
763
764#[allow(clippy::not_unsafe_ptr_arg_deref)]
765pub fn save_signed(rdb: *mut RedisModuleIO, val: i64) {
766    unsafe { RedisModule_SaveSigned.unwrap()(rdb, val) };
767}
768
769#[allow(clippy::not_unsafe_ptr_arg_deref)]
770pub fn save_float(rdb: *mut RedisModuleIO, val: f32) {
771    unsafe { RedisModule_SaveFloat.unwrap()(rdb, val) };
772}
773
774#[allow(clippy::not_unsafe_ptr_arg_deref)]
775pub fn save_unsigned(rdb: *mut RedisModuleIO, val: u64) {
776    unsafe { RedisModule_SaveUnsigned.unwrap()(rdb, val) };
777}
778
779#[allow(clippy::not_unsafe_ptr_arg_deref)]
780pub fn string_compare(a: *mut RedisModuleString, b: *mut RedisModuleString) -> Ordering {
781    unsafe { RedisModule_StringCompare.unwrap()(a, b).cmp(&0) }
782}
783
784#[allow(clippy::not_unsafe_ptr_arg_deref)]
785pub fn string_append_buffer(
786    ctx: *mut RedisModuleCtx,
787    s: *mut RedisModuleString,
788    buff: &str,
789) -> Status {
790    unsafe {
791        RedisModule_StringAppendBuffer.unwrap()(ctx, s, buff.as_ptr().cast::<c_char>(), buff.len())
792            .into()
793    }
794}
795
796#[allow(clippy::not_unsafe_ptr_arg_deref)]
797pub fn subscribe_to_server_event(
798    ctx: *mut RedisModuleCtx,
799    event: RedisModuleEvent,
800    callback: RedisModuleEventCallback,
801) -> Status {
802    unsafe { RedisModule_SubscribeToServerEvent.unwrap()(ctx, event, callback).into() }
803}
804
805#[allow(clippy::not_unsafe_ptr_arg_deref)]
806pub fn register_info_function(ctx: *mut RedisModuleCtx, callback: RedisModuleInfoFunc) -> Status {
807    unsafe { RedisModule_RegisterInfoFunc.unwrap()(ctx, callback).into() }
808}
809
810#[allow(clippy::not_unsafe_ptr_arg_deref)]
811pub fn add_info_section(ctx: *mut RedisModuleInfoCtx, name: Option<&str>) -> Status {
812    name.map(|n| CString::new(n).unwrap()).map_or_else(
813        || unsafe { RedisModule_InfoAddSection.unwrap()(ctx, ptr::null_mut()).into() },
814        |n| unsafe { RedisModule_InfoAddSection.unwrap()(ctx, n.as_ptr()).into() },
815    )
816}
817
818#[allow(clippy::not_unsafe_ptr_arg_deref)]
819pub fn add_info_field_str(ctx: *mut RedisModuleInfoCtx, name: &str, content: &str) -> Status {
820    let name = CString::new(name).unwrap();
821    let content = RedisString::create(None, content);
822    unsafe { RedisModule_InfoAddFieldString.unwrap()(ctx, name.as_ptr(), content.inner).into() }
823}
824
825#[allow(clippy::not_unsafe_ptr_arg_deref)]
826pub fn add_info_field_long_long(
827    ctx: *mut RedisModuleInfoCtx,
828    name: &str,
829    value: c_longlong,
830) -> Status {
831    let name = CString::new(name).unwrap();
832    unsafe { RedisModule_InfoAddFieldLongLong.unwrap()(ctx, name.as_ptr(), value).into() }
833}
834
835#[allow(clippy::not_unsafe_ptr_arg_deref)]
836pub fn add_info_field_unsigned_long_long(
837    ctx: *mut RedisModuleInfoCtx,
838    name: &str,
839    value: c_ulonglong,
840) -> Status {
841    let name = CString::new(name).unwrap();
842    unsafe { RedisModule_InfoAddFieldULongLong.unwrap()(ctx, name.as_ptr(), value).into() }
843}
844
845#[allow(clippy::not_unsafe_ptr_arg_deref)]
846pub fn add_info_field_double(ctx: *mut RedisModuleInfoCtx, name: &str, value: c_double) -> Status {
847    let name = CString::new(name).unwrap();
848    unsafe { RedisModule_InfoAddFieldDouble.unwrap()(ctx, name.as_ptr(), value).into() }
849}
850
851#[allow(clippy::not_unsafe_ptr_arg_deref)]
852pub fn add_info_begin_dict_field(ctx: *mut RedisModuleInfoCtx, name: &str) -> Status {
853    let name = CString::new(name).unwrap();
854    unsafe { RedisModule_InfoBeginDictField.unwrap()(ctx, name.as_ptr()).into() }
855}
856
857#[allow(clippy::not_unsafe_ptr_arg_deref)]
858pub fn add_info_end_dict_field(ctx: *mut RedisModuleInfoCtx) -> Status {
859    unsafe { RedisModule_InfoEndDictField.unwrap()(ctx).into() }
860}
861
862/// # Safety
863///
864/// This function is safe to use as it doesn't perform any work with
865/// the [RedisModuleCtx] pointer except for passing it to the redis server.
866///
867/// # Panics
868///
869/// Panics when the [RedisModule_ExportSharedAPI] is unavailable.
870pub unsafe fn export_shared_api(
871    ctx: *mut RedisModuleCtx,
872    func: *const ::std::os::raw::c_void,
873    name: *const ::std::os::raw::c_char,
874) {
875    RedisModule_ExportSharedAPI.unwrap()(ctx, name, func as *mut ::std::os::raw::c_void);
876}
877
878/// # Safety
879///
880/// This function is safe to use as it doesn't perform any work with
881/// the [RedisModuleCtx] pointer except for passing it to the redis server.
882///
883/// # Panics
884///
885/// Panics when the [RedisModule_NotifyKeyspaceEvent] is unavailable.
886pub unsafe fn notify_keyspace_event(
887    ctx: *mut RedisModuleCtx,
888    event_type: NotifyEvent,
889    event: &str,
890    keyname: &RedisString,
891) -> Status {
892    let event = CString::new(event).unwrap();
893    RedisModule_NotifyKeyspaceEvent.unwrap()(ctx, event_type.bits(), event.as_ptr(), keyname.inner)
894        .into()
895}
896
897/// # Panics
898///
899/// Panics when the [RedisModule_GetNotifyKeyspaceEvents] is unavailable.
900#[must_use]
901pub fn get_keyspace_events() -> NotifyEvent {
902    unsafe {
903        let events = RedisModule_GetNotifyKeyspaceEvents.unwrap()();
904        NotifyEvent::from_bits_truncate(events)
905    }
906}
907
908#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
909pub struct Version {
910    pub major: i32,
911    pub minor: i32,
912    pub patch: i32,
913}
914
915impl From<c_int> for Version {
916    fn from(ver: c_int) -> Self {
917        // Expected format: 0x00MMmmpp for Major, minor, patch
918        Self {
919            major: (ver & 0x00FF_0000) >> 16,
920            minor: (ver & 0x0000_FF00) >> 8,
921            patch: (ver & 0x0000_00FF),
922        }
923    }
924}
925
926#[allow(clippy::not_unsafe_ptr_arg_deref)]
927pub fn is_io_error(rdb: *mut RedisModuleIO) -> bool {
928    unsafe { RedisModule_IsIOError.unwrap()(rdb) != 0 }
929}