vrl/stdlib/
encode_gzip.rs1use 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 #[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 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}