vrl/stdlib/
encode_gzip.rs

1use std::io::Read;
2
3use flate2::read::GzEncoder;
4use nom::AsBytes;
5
6use crate::compiler::prelude::*;
7
8const MAX_COMPRESSION_LEVEL: u32 = 10;
9
10fn encode_gzip(value: Value, compression_level: Option<Value>) -> Resolved {
11    let compression_level = match compression_level {
12        None => flate2::Compression::default(),
13        Some(value) => {
14            // TODO consider removal options
15            #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
16            let level = value.try_integer()? as u32;
17            if level > MAX_COMPRESSION_LEVEL {
18                return Err(format!("compression level must be <= {MAX_COMPRESSION_LEVEL}").into());
19            }
20            flate2::Compression::new(level)
21        }
22    };
23
24    let value = value.try_bytes()?;
25    let mut buf = Vec::new();
26    // We can safely ignore the error here because the value being read from, `Bytes`, never fails a `read()` call and the value being written to, a `Vec`, never fails a `write()` call
27    GzEncoder::new(value.as_bytes(), compression_level)
28        .read_to_end(&mut buf)
29        .expect("gzip compression failed, please report");
30
31    Ok(Value::Bytes(buf.into()))
32}
33
34#[derive(Clone, Copy, Debug)]
35pub struct EncodeGzip;
36
37impl Function for EncodeGzip {
38    fn identifier(&self) -> &'static str {
39        "encode_gzip"
40    }
41
42    fn usage(&self) -> &'static str {
43        "Encodes the `value` to [Gzip](https://www.gzip.org/)."
44    }
45
46    fn examples(&self) -> &'static [Example] {
47        &[example! {
48            title: "Encode to Gzip",
49            source: r#"encode_base64(encode_gzip("please encode me"))"#,
50            result: Ok("H4sIAAAAAAAA/yvISU0sTlVIzUvOT0lVyE0FAI4R4vcQAAAA"),
51        }]
52    }
53
54    fn compile(
55        &self,
56        _state: &state::TypeState,
57        _ctx: &mut FunctionCompileContext,
58        arguments: ArgumentList,
59    ) -> Compiled {
60        let value = arguments.required("value");
61        let compression_level = arguments.optional("compression_level");
62
63        Ok(EncodeGzipFn {
64            value,
65            compression_level,
66        }
67        .as_expr())
68    }
69
70    fn parameters(&self) -> &'static [Parameter] {
71        &[
72            Parameter {
73                keyword: "value",
74                kind: kind::BYTES,
75                required: true,
76            },
77            Parameter {
78                keyword: "compression_level",
79                kind: kind::INTEGER,
80                required: false,
81            },
82        ]
83    }
84}
85
86#[derive(Clone, Debug)]
87struct EncodeGzipFn {
88    value: Box<dyn Expression>,
89    compression_level: Option<Box<dyn Expression>>,
90}
91
92impl FunctionExpression for EncodeGzipFn {
93    fn resolve(&self, ctx: &mut Context) -> Resolved {
94        let value = self.value.resolve(ctx)?;
95
96        let compression_level = self
97            .compression_level
98            .as_ref()
99            .map(|expr| expr.resolve(ctx))
100            .transpose()?;
101
102        encode_gzip(value, compression_level)
103    }
104
105    fn type_def(&self, state: &state::TypeState) -> TypeDef {
106        let is_compression_level_valid_constant = if let Some(level) = &self.compression_level {
107            match level.resolve_constant(state) {
108                Some(Value::Integer(level)) => level <= i64::from(MAX_COMPRESSION_LEVEL),
109                _ => false,
110            }
111        } else {
112            true
113        };
114
115        TypeDef::bytes().maybe_fallible(!is_compression_level_valid_constant)
116    }
117}
118
119#[cfg(test)]
120mod test {
121    use crate::value;
122
123    use super::*;
124
125    fn encode(text: &str, level: flate2::Compression) -> Vec<u8> {
126        let mut encoder = GzEncoder::new(text.as_bytes(), level);
127        let mut output = vec![];
128        encoder.read_to_end(&mut output).unwrap();
129        output
130    }
131
132    test_function![
133        encode_gzip => EncodeGzip;
134
135        with_defaults {
136            args: func_args![value: value!("you_have_successfully_decoded_me.congratulations.you_are_breathtaking.")],
137            want: Ok(value!(encode("you_have_successfully_decoded_me.congratulations.you_are_breathtaking.", flate2::Compression::default()).as_bytes())),
138            tdef: TypeDef::bytes().infallible(),
139        }
140
141        with_custom_compression_level {
142            args: func_args![value: value!("you_have_successfully_decoded_me.congratulations.you_are_breathtaking."), compression_level: 9],
143            want: Ok(value!(encode("you_have_successfully_decoded_me.congratulations.you_are_breathtaking.", flate2::Compression::new(9)).as_bytes())),
144            tdef: TypeDef::bytes().infallible(),
145        }
146
147        invalid_constant_compression {
148            args: func_args![value: value!("test"), compression_level: 11],
149            want: Err("compression level must be <= 10"),
150            tdef: TypeDef::bytes().fallible(),
151        }
152    ];
153}