h3o_zip/
lib.rs

1//! H3 compression algorithms.
2//!
3//! This library allows to compress an H3 cell set into a compacted
4//! space-efficient representation.
5//!
6//! This is especially useful for on-disk storage or on-wire transmission.
7
8// Lints {{{
9
10#![deny(
11    nonstandard_style,
12    rust_2018_idioms,
13    rust_2021_compatibility,
14    future_incompatible,
15    rustdoc::all,
16    rustdoc::missing_crate_level_docs,
17    missing_docs,
18    unsafe_code,
19    unused,
20    unused_import_braces,
21    unused_lifetimes,
22    unused_qualifications,
23    variant_size_differences,
24    warnings,
25    clippy::all,
26    clippy::cargo,
27    clippy::pedantic,
28    clippy::allow_attributes_without_reason,
29    clippy::as_underscore,
30    clippy::branches_sharing_code,
31    clippy::clone_on_ref_ptr,
32    clippy::cognitive_complexity,
33    clippy::create_dir,
34    clippy::dbg_macro,
35    clippy::debug_assert_with_mut_call,
36    clippy::decimal_literal_representation,
37    clippy::default_union_representation,
38    clippy::derive_partial_eq_without_eq,
39    clippy::empty_drop,
40    clippy::empty_line_after_outer_attr,
41    clippy::empty_structs_with_brackets,
42    clippy::equatable_if_let,
43    clippy::exhaustive_enums,
44    clippy::exit,
45    clippy::filetype_is_file,
46    clippy::float_cmp_const,
47    clippy::fn_to_numeric_cast_any,
48    clippy::format_push_string,
49    clippy::future_not_send,
50    clippy::get_unwrap,
51    clippy::if_then_some_else_none,
52    clippy::imprecise_flops,
53    clippy::iter_on_empty_collections,
54    clippy::iter_on_single_items,
55    clippy::iter_with_drain,
56    clippy::large_include_file,
57    clippy::let_underscore_must_use,
58    clippy::lossy_float_literal,
59    clippy::mem_forget,
60    clippy::missing_const_for_fn,
61    clippy::mixed_read_write_in_expression,
62    clippy::multiple_inherent_impl,
63    clippy::mutex_atomic,
64    clippy::mutex_integer,
65    clippy::needless_collect,
66    clippy::non_send_fields_in_send_ty,
67    clippy::nonstandard_macro_braces,
68    clippy::option_if_let_else,
69    clippy::or_fun_call,
70    clippy::panic,
71    clippy::path_buf_push_overwrite,
72    clippy::pattern_type_mismatch,
73    clippy::print_stderr,
74    clippy::print_stdout,
75    clippy::rc_buffer,
76    clippy::rc_mutex,
77    clippy::redundant_pub_crate,
78    clippy::rest_pat_in_fully_bound_structs,
79    clippy::same_name_method,
80    clippy::self_named_module_files,
81    clippy::significant_drop_in_scrutinee,
82    clippy::str_to_string,
83    clippy::string_add,
84    clippy::string_lit_as_bytes,
85    clippy::string_slice,
86    clippy::string_to_string,
87    clippy::suboptimal_flops,
88    clippy::suspicious_operation_groupings,
89    clippy::todo,
90    clippy::trailing_empty_array,
91    clippy::trait_duplication_in_bounds,
92    clippy::transmute_undefined_repr,
93    clippy::trivial_regex,
94    clippy::try_err,
95    clippy::type_repetition_in_bounds,
96    clippy::undocumented_unsafe_blocks,
97    clippy::unimplemented,
98    clippy::unnecessary_self_imports,
99    clippy::unneeded_field_pattern,
100    clippy::unseparated_literal_suffix,
101    clippy::unused_peekable,
102    clippy::unused_rounding,
103    clippy::unwrap_used,
104    clippy::use_debug,
105    clippy::use_self,
106    clippy::useless_let_if_seq,
107    clippy::verbose_file_reads
108)]
109#![allow(
110    // The 90’s called and wanted their charset back.
111    clippy::non_ascii_literal,
112    // "It requires the user to type the module name twice."
113    // => not true here since internal modules are hidden from the users.
114    clippy::module_name_repetitions,
115    // Usually yes, but not really applicable for most literals in this crate.
116    clippy::unreadable_literal,
117    reason = "allow some exceptions"
118)]
119
120// }}}
121
122mod cht;
123mod error;
124mod header;
125
126use either::Either;
127use h3o::CellIndex;
128use header::Header;
129use std::io::{self, Write};
130
131pub use error::DecodingError;
132
133/// Compress a sorted stream of cell indexes.
134///
135/// # Preconditions
136///
137/// The stream of cell indexes must be sorted and without duplicates.
138///
139/// # Errors
140///
141/// Returns [`io::Error`] if writes to the writer fails.
142///
143/// # Examples
144///
145/// ```
146/// use h3o::CellIndex;
147/// use std::io::Cursor;
148///
149/// let cells = vec![
150///     0x8b184584a21efff,
151///     0x8b184584a246fff,
152///     0x8b184584a2a8fff,
153///     0x8b184584a2cbfff,
154///     0x8b184584a329fff,
155///     0x8b184584a366fff,
156///     0x8b184584a389fff,
157/// ].iter().copied().map(CellIndex::try_from).collect::<Result<Vec<_>, _>>()?;
158///
159/// let mut buffer = Cursor::new(vec![]);
160/// h3o_zip::compress(&mut buffer, cells.clone()).expect("compress");
161///
162/// let bytes = buffer.into_inner();
163///
164/// # Ok::<(), Box<dyn std::error::Error>>(())
165/// ```
166pub fn compress<W: Write>(
167    writer: &mut W,
168    cells: impl IntoIterator<Item = CellIndex>,
169) -> Result<(), io::Error> {
170    Header::cht_v1().write(writer)?;
171    cht::encode(writer, cells)
172}
173
174/// Decompress the bytes into a stream of sorted cell indexes.
175///
176/// # Errors
177///
178/// Returns a [`DecodingError`] if the compressed payload cannot be decoded.
179///
180/// # Examples
181///
182/// ```
183/// let bytes = [
184///     0x01, 0x1b, 0x05, 0x03, 0x41, 0x21, 0x05, 0x05, 0x09, 0x05, 0xff, 0x11,
185///     0x81, 0x06, 0x02, 0x05, 0x0d, 0x28, 0x88, 0x10, 0x54, 0x20, 0x24, 0x50,
186///     0x41, 0x81, 0x00
187/// ];
188///
189/// let cells = h3o_zip::decompress(bytes.as_slice())
190///     .collect::<Result<Vec<_>, _>>()?;
191///
192/// # Ok::<(), Box<dyn std::error::Error>>(())
193/// ```
194pub fn decompress(
195    bytes: &[u8],
196) -> impl Iterator<Item = Result<CellIndex, DecodingError>> + '_ {
197    Header::from_bytes(bytes).map_or_else(
198        |err| Either::Left(std::iter::once(Err(err))),
199        |header| match header {
200            Header::ChtV1 => Either::Right(cht::decode(&bytes[header.len()..])),
201        },
202    )
203}
204
205// -----------------------------------------------------------------------------
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use std::io::Cursor;
211
212    #[test]
213    fn rountrip() {
214        let cells = vec![
215            CellIndex::try_from(0x8b184584a21efff).expect("valid cell"),
216            CellIndex::try_from(0x8b184584a246fff).expect("valid cell"),
217            CellIndex::try_from(0x8b184584a2a8fff).expect("valid cell"),
218            CellIndex::try_from(0x8b184584a2cbfff).expect("valid cell"),
219            CellIndex::try_from(0x8b184584a329fff).expect("valid cell"),
220            CellIndex::try_from(0x8b184584a366fff).expect("valid cell"),
221            CellIndex::try_from(0x8b184584a389fff).expect("valid cell"),
222        ];
223
224        // Compress.
225        let mut buffer = Cursor::new(vec![]);
226        compress(&mut buffer, cells.clone()).expect("compress");
227        let bytes = buffer.into_inner();
228
229        // Decompress.
230        let result = decompress(bytes.as_slice())
231            .collect::<Result<Vec<_>, _>>()
232            .expect("valid input");
233        assert_eq!(result, cells);
234    }
235
236    #[test]
237    fn bad_header() {
238        let bytes = [0x81, 0x23];
239
240        let error = decompress(bytes.as_slice())
241            .collect::<Result<Vec<_>, _>>()
242            .expect_err("invalid input");
243        assert!(matches!(error, DecodingError::InvalidHeader(_)));
244    }
245}