vrl/stdlib/
parse_proto.rs1use super::util::example_path_or_basename;
2use crate::compiler::prelude::*;
3use crate::protobuf::descriptor::get_message_descriptor;
4use crate::protobuf::parse::parse_proto;
5use crate::stdlib::json_utils::json_type_def::json_type_def;
6use prost_reflect::MessageDescriptor;
7use std::ffi::OsString;
8use std::path::Path;
9use std::path::PathBuf;
10use std::sync::LazyLock;
11
12#[derive(Clone, Copy, Debug)]
13pub struct ParseProto;
14
15static EXAMPLE_PARSE_PROTO_EXPR: LazyLock<&str> = LazyLock::new(|| {
18 let path = example_path_or_basename("protobuf/test_protobuf/v1/test_protobuf.desc");
19
20 Box::leak(
21 format!(
22 r#"parse_proto!(decode_base64!("Cgdzb21lb25lIggKBjEyMzQ1Ng=="), "{path}", "test_protobuf.v1.Person")"#
23 )
24 .into_boxed_str(),
25 )
26});
27
28static EXAMPLES: LazyLock<Vec<Example>> = LazyLock::new(|| {
29 vec![example! {
30 title: "Parse proto",
31 source: &EXAMPLE_PARSE_PROTO_EXPR,
32 result: Ok(r#"{ "name": "someone", "phones": [{"number": "123456"}] }"#),
33 }]
34});
35
36impl Function for ParseProto {
37 fn identifier(&self) -> &'static str {
38 "parse_proto"
39 }
40
41 fn summary(&self) -> &'static str {
42 "parse a string to a protobuf based type"
43 }
44
45 fn usage(&self) -> &'static str {
46 "Parses the `value` as a protocol buffer payload."
47 }
48
49 fn category(&self) -> &'static str {
50 Category::Parse.as_ref()
51 }
52
53 fn internal_failure_reasons(&self) -> &'static [&'static str] {
54 &[
55 "`value` is not a valid proto payload.",
56 "`desc_file` file does not exist.",
57 "`message_type` message type does not exist in the descriptor file.",
58 ]
59 }
60
61 fn return_kind(&self) -> u16 {
62 kind::OBJECT
63 }
64
65 fn notices(&self) -> &'static [&'static str] {
66 &["Only proto messages are parsed and returned."]
67 }
68
69 fn parameters(&self) -> &'static [Parameter] {
70 const PARAMETERS: &[Parameter] = &[
71 Parameter::required(
72 "value",
73 kind::BYTES,
74 "The protocol buffer payload to parse.",
75 ),
76 Parameter::required(
77 "desc_file",
78 kind::BYTES,
79 "The path to the protobuf descriptor set file. Must be a literal string.
80
81This file is the output of protoc -o <path> ...",
82 ),
83 Parameter::required(
84 "message_type",
85 kind::BYTES,
86 "The name of the message type to use for serializing.
87
88Must be a literal string.",
89 ),
90 ];
91 PARAMETERS
92 }
93
94 fn examples(&self) -> &'static [Example] {
95 EXAMPLES.as_slice()
96 }
97
98 fn compile(
99 &self,
100 state: &state::TypeState,
101 _ctx: &mut FunctionCompileContext,
102 arguments: ArgumentList,
103 ) -> Compiled {
104 let value = arguments.required("value");
105 let desc_file = arguments.required_literal("desc_file", state)?;
106 let desc_file_str = desc_file
107 .try_bytes_utf8_lossy()
108 .expect("descriptor file must be a string");
109 let message_type = arguments.required_literal("message_type", state)?;
110 let message_type_str = message_type
111 .try_bytes_utf8_lossy()
112 .expect("message_type must be a string");
113 let os_string: OsString = desc_file_str.into_owned().into();
114 let path_buf = PathBuf::from(os_string);
115 let path = Path::new(&path_buf);
116 let descriptor =
117 get_message_descriptor(path, &message_type_str).expect("message type not found");
118
119 Ok(ParseProtoFn { descriptor, value }.as_expr())
120 }
121}
122
123#[derive(Debug, Clone)]
124struct ParseProtoFn {
125 descriptor: MessageDescriptor,
126 value: Box<dyn Expression>,
127}
128
129impl FunctionExpression for ParseProtoFn {
130 fn resolve(&self, ctx: &mut Context) -> Resolved {
131 let value = self.value.resolve(ctx)?;
132 parse_proto(&self.descriptor, value)
133 }
134
135 fn type_def(&self, _: &state::TypeState) -> TypeDef {
136 json_type_def()
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::value;
144 use std::{env, fs};
145
146 fn test_data_dir() -> PathBuf {
147 PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("tests/data/protobuf")
148 }
149
150 fn read_pb_file(protobuf_bin_message_path: &str) -> String {
151 fs::read_to_string(test_data_dir().join(protobuf_bin_message_path)).unwrap()
152 }
153
154 test_function![
155 parse_proto => ParseProto;
156
157 parses {
158 args: func_args![ value: read_pb_file("test_protobuf/v1/input/person_someone.pb"),
159 desc_file: test_data_dir().join("test_protobuf/v1/test_protobuf.desc").to_str().unwrap().to_owned(),
160 message_type: "test_protobuf.v1.Person"],
161 want: Ok(value!({ name: "Someone", phones: [{number: "123-456"}] })),
162 tdef: json_type_def(),
163 }
164
165 parses_proto3 {
166 args: func_args![ value: read_pb_file("test_protobuf3/v1/input/person_someone.pb"),
167 desc_file: test_data_dir().join("test_protobuf3/v1/test_protobuf3.desc").to_str().unwrap().to_owned(),
168 message_type: "test_protobuf3.v1.Person"],
169 want: Ok(value!({ name: "Someone", phones: [{number: "123-456", type: "PHONE_TYPE_MOBILE"}] })),
170 tdef: json_type_def(),
171 }
172 ];
173}