rdb/
lib.rs

1//! rdb - Parse, analyze and dump RDB files
2//!
3//! A RDB file is a binary representation of the in-memory data of Redis.
4//! This binary file is sufficient to completely restore Redis’ state.
5//!
6//! This library provides the methods to parse and analyze a RDB file
7//! and to reformat and dump it in another format such as JSON or
8//! RESP, the Redis Serialization.
9//!
10//! You can depend on this library via Cargo:
11//!
12//! ```ini
13//! [dependencies]
14//! rdb = "*"
15//! ```
16//!
17//! # Basic operation
18//!
19//! rdb-rs exposes just one important method: `parse`.
20//! This methods takes care of reading the RDB from a stream,
21//! parsing the containted data and calling the provided formatter with already-parsed values.
22//!
23//! ```rust,no_run
24//! # #![allow(unstable)]
25//! # use std::io::BufReader;
26//! # use std::fs::File;
27//! # use std::path::Path;
28//! let file = File::open(&Path::new("dump.rdb")).unwrap();
29//! let reader = BufReader::new(file);
30//! rdb::parse(reader, rdb::formatter::JSON::new(None), rdb::filter::Simple::new());
31//! ```
32//!
33//! # Formatter
34//!
35//! rdb-rs brings 4 pre-defined formatters, which can be used:
36//!
37//! * `PlainFormatter`: Just plain output for testing
38//! * `JSONFormatter`: JSON-encoded output
39//! * `NilFormatter`: Surpresses all output
40//! * `ProtocolFormatter`: Formats the data in [RESP](http://redis.io/topics/protocol),
41//!   the Redis Serialization Protocol
42//!
43//! These formatters adhere to the `RdbParseFormatter` trait
44//! and supply a method for each possible datatype or opcode.
45//! Its up to the formatter to correctly handle all provided data such as
46//! lists, sets, hashes, expires and metadata.
47//!
48//! # Command-line
49//!
50//! rdb-rs brings a Command Line application as well.
51//!
52//! This application will take a RDB file as input and format it in the specified format (JSON by
53//! default).
54//!
55//! Example:
56//!
57//! ```shell,no_compile
58//! $ rdb --format json dump.rdb
59//! [{"key":"value"}]
60//! $ rdb --format protocol dump.rdb
61//! *2
62//! $6
63//! SELECT
64//! $1
65//! 0
66//! *3
67//! $3
68//! SET
69//! $3
70//! key
71//! $5
72//! value
73//! ```
74
75#[cfg(feature = "python")]
76use pyo3::exceptions::PyValueError;
77#[cfg(feature = "python")]
78use pyo3::prelude::*;
79
80use std::io::Read;
81
82#[doc(hidden)]
83pub use types::{RdbError, RdbOk, RdbResult, Type};
84
85pub mod constants;
86pub mod decoder;
87pub mod filter;
88pub mod formatter;
89pub mod types;
90
91pub use decoder::RdbDecoder;
92pub use filter::{Filter, Simple};
93pub use formatter::{Formatter, FormatterType};
94
95// Main entry point for parsing RDB files
96pub struct RdbParser<R: Read, L: Filter, F: Formatter> {
97    decoder: RdbDecoder<R, L>,
98    formatter: Option<F>,
99}
100
101impl<R: Read, L: Filter, F: Formatter> RdbParser<R, L, F> {
102    pub fn builder() -> RdbParserBuilder<R, L, F> {
103        RdbParserBuilder {
104            reader: None,
105            filter: None,
106            formatter: None,
107        }
108    }
109
110    //pub fn into_iter(self) -> RdbDecoder<R, L> {
111    //    self.decoder
112    //}
113}
114
115#[derive(Default)]
116pub struct RdbParserBuilder<R: Read, L: Filter, F: Formatter> {
117    reader: Option<R>,
118    filter: Option<L>,
119    formatter: Option<F>,
120}
121
122impl<R: Read, L: Filter + Default, F: Formatter> RdbParserBuilder<R, L, F> {
123    pub fn build(self) -> RdbParser<R, L, F> {
124        let reader = self.reader.unwrap();
125        let filter = self.filter.unwrap_or_default();
126        let formatter = self.formatter;
127        RdbParser {
128            decoder: RdbDecoder::new(reader, filter).unwrap(),
129            formatter,
130        }
131    }
132
133    pub fn with_reader(mut self, reader: R) -> Self {
134        self.reader = Some(reader);
135        self
136    }
137
138    pub fn with_filter(mut self, filter: L) -> Self {
139        self.filter = Some(filter);
140        self
141    }
142
143    pub fn with_formatter(mut self, formatter: F) -> Self {
144        self.formatter = Some(formatter);
145        self
146    }
147}
148
149impl<R: Read, L: Filter, F: Formatter> RdbParser<R, L, F> {
150    pub fn parse(self) -> RdbResult<()> {
151        if let Some(mut formatter) = self.formatter {
152            formatter.start_rdb();
153            for value in self.decoder {
154                formatter.format(&value?)?;
155            }
156            formatter.end_rdb();
157        }
158        Ok(())
159    }
160}
161
162pub fn parse<R: Read, L: Filter + Default, F: Formatter>(
163    reader: R,
164    formatter: F,
165    filter: L,
166) -> RdbResult<()> {
167    let parser = RdbParser::builder()
168        .with_reader(reader)
169        .with_filter(filter)
170        .with_formatter(formatter)
171        .build();
172    parser.parse()
173}
174
175#[cfg(feature = "python")]
176#[pyclass(name = "RdbDecoder")]
177pub struct PyRdbDecoder {
178    decoder: RdbDecoder<std::fs::File, Simple>,
179}
180
181#[cfg(feature = "python")]
182#[pymethods]
183impl PyRdbDecoder {
184    #[new]
185    pub fn new(path: &str) -> PyResult<Self> {
186        let file = std::fs::File::open(path)
187            .map_err(|e| PyValueError::new_err(format!("Failed to open file: {}", e)))?;
188
189        let mut filter = Simple::new();
190
191        for t in [
192            Type::Hash,
193            Type::String,
194            Type::List,
195            Type::Set,
196            Type::SortedSet,
197        ] {
198            filter.add_type(t);
199        }
200
201        let decoder = RdbDecoder::new(file, filter)
202            .map_err(|e| PyValueError::new_err(format!("Failed to create decoder: {}", e)))?;
203
204        Ok(PyRdbDecoder { decoder })
205    }
206
207    fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
208        slf
209    }
210
211    fn __next__(mut slf: PyRefMut<'_, Self>) -> PyResult<Option<PyObject>> {
212        match slf.decoder.next() {
213            Some(Ok(value)) => Python::with_gil(|py| {
214                value
215                    .into_pyobject(py)
216                    .map(|obj| Some(obj.into()))
217                    .map_err(|e| PyValueError::new_err(format!("Conversion error: {}", e)))
218            }),
219            Some(Err(e)) => Err(PyValueError::new_err(format!("Parsing error: {}", e))),
220            None => Ok(None),
221        }
222    }
223}
224
225#[cfg(feature = "python")]
226#[pymodule(name = "rdb")]
227fn rdb_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
228    m.add_class::<PyRdbDecoder>()?;
229    Ok(())
230}