vrl/cli/
cmd.rs

1use crate::compiler::TargetValueRef;
2use std::{
3    collections::BTreeMap,
4    fs::File,
5    io::{self, Read},
6    iter::IntoIterator,
7    path::PathBuf,
8};
9
10use crate::compiler::TimeZone;
11use crate::compiler::runtime::Runtime;
12use crate::compiler::state::RuntimeState;
13use crate::compiler::{
14    CompilationResult, CompileConfig, Function, Program, Target, TypeState, VrlRuntime,
15    compile_with_state,
16};
17use crate::diagnostic::Formatter;
18use crate::owned_metadata_path;
19use crate::value::Secrets;
20use crate::value::Value;
21use clap::Parser;
22
23use super::Error;
24use super::repl;
25
26#[derive(Parser, Debug)]
27#[command(name = "VRL", about = "Vector Remap Language CLI")]
28pub struct Opts {
29    /// The VRL program to execute. The program ".foo = true", for example, sets the event object's
30    /// `foo` field to `true`.
31    #[arg(id = "PROGRAM")]
32    program: Option<String>,
33
34    /// The file containing the event object(s) to handle. JSON events should be one per line..
35    #[arg(short, long = "input")]
36    input_file: Option<PathBuf>,
37
38    /// The file containing the VRL program to execute. This can be used instead of `PROGRAM`.
39    #[arg(short, long = "program", conflicts_with("PROGRAM"))]
40    program_file: Option<PathBuf>,
41
42    // Don't print banner and other help messages on startup
43    #[arg(short = 'q', long)]
44    quiet: bool,
45
46    /// Print the (modified) event object instead of the result of the final expression. Setting
47    /// this flag is equivalent to using `.` as the final expression.
48    #[arg(short = 'o', long)]
49    print_object: bool,
50
51    /// The timezone used to parse dates.
52    #[arg(short = 'z', long)]
53    timezone: Option<String>,
54
55    /// Should we use the VM to evaluate the VRL
56    #[arg(short, long = "runtime", default_value_t)]
57    runtime: VrlRuntime,
58
59    // Should the CLI emit warnings
60    #[arg(long = "print-warnings")]
61    print_warnings: bool,
62}
63
64impl Opts {
65    fn timezone(&self) -> Result<TimeZone, Error> {
66        if let Some(ref tz) = self.timezone {
67            TimeZone::parse(tz)
68                .ok_or_else(|| Error::Parse(format!("unable to parse timezone: {tz}")))
69        } else {
70            Ok(TimeZone::default())
71        }
72    }
73
74    fn read_program(&self) -> Result<String, Error> {
75        match self.program.as_ref() {
76            Some(source) => Ok(source.clone()),
77            None => match self.program_file.as_ref() {
78                Some(path) => read(File::open(path)?),
79                None => Ok(String::new()),
80            },
81        }
82    }
83
84    fn read_into_objects(&self) -> Result<Vec<Value>, Error> {
85        let input = match self.input_file.as_ref() {
86            Some(path) => read(File::open(path)?),
87            None => read(io::stdin()),
88        }?;
89
90        match input.as_str() {
91            "" => Ok(vec![Value::Object(BTreeMap::default())]),
92            _ => input
93                .lines()
94                .map(|line| Ok(serde_to_vrl(serde_json::from_str(line)?)))
95                .collect::<Result<Vec<Value>, Error>>(),
96        }
97    }
98
99    fn should_open_repl(&self) -> bool {
100        self.program.is_none() && self.program_file.is_none()
101    }
102}
103
104#[must_use]
105pub fn cmd(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> exitcode::ExitCode {
106    match run(opts, stdlib_functions) {
107        Ok(()) => exitcode::OK,
108        Err(err) => {
109            #[allow(clippy::print_stderr)]
110            {
111                eprintln!("{err}");
112            }
113            exitcode::SOFTWARE
114        }
115    }
116}
117
118fn run(opts: &Opts, stdlib_functions: Vec<Box<dyn Function>>) -> Result<(), Error> {
119    let tz = opts.timezone()?;
120    // Run the REPL if no program or program file is specified
121    if opts.should_open_repl() {
122        // If an input file is provided, use that for the REPL objects, otherwise provide a
123        // generic default object.
124        let repl_objects = if opts.input_file.is_some() {
125            opts.read_into_objects()?
126        } else {
127            default_objects()
128        };
129
130        repl(opts.quiet, repl_objects, tz, opts.runtime, stdlib_functions)
131    } else {
132        let objects = opts.read_into_objects()?;
133        let source = opts.read_program()?;
134
135        // The CLI should be moved out of the "vrl" module, and then it can use the `vector-core::compile_vrl` function which includes this automatically
136        let mut config = CompileConfig::default();
137        config.set_read_only_path(owned_metadata_path!("vector"), true);
138
139        let state = TypeState::default();
140
141        let CompilationResult {
142            program,
143            warnings,
144            config: _,
145        } = compile_with_state(
146            &source,
147            &crate::stdlib::all(),
148            &state,
149            CompileConfig::default(),
150        )
151        .map_err(|diagnostics| {
152            Error::Parse(Formatter::new(&source, diagnostics).colored().to_string())
153        })?;
154
155        #[allow(clippy::print_stderr)]
156        if opts.print_warnings {
157            let warnings = Formatter::new(&source, warnings).colored().to_string();
158            eprintln!("{warnings}");
159        }
160
161        for mut object in objects {
162            let mut metadata = Value::Object(BTreeMap::new());
163            let mut secrets = Secrets::new();
164            let mut target = TargetValueRef {
165                value: &mut object,
166                metadata: &mut metadata,
167                secrets: &mut secrets,
168            };
169            let state = RuntimeState::default();
170            let runtime = Runtime::new(state);
171
172            let result = execute(&mut target, &program, tz, runtime, opts.runtime).map(|v| {
173                if opts.print_object {
174                    object.to_string()
175                } else {
176                    v.to_string()
177                }
178            });
179
180            #[allow(clippy::print_stdout)]
181            #[allow(clippy::print_stderr)]
182            match result {
183                Ok(ok) => println!("{ok}"),
184                Err(err) => eprintln!("{err}"),
185            }
186        }
187
188        Ok(())
189    }
190}
191
192#[allow(clippy::unnecessary_wraps)]
193fn repl(
194    quiet: bool,
195    objects: Vec<Value>,
196    timezone: TimeZone,
197    vrl_runtime: VrlRuntime,
198    stdlib_functions: Vec<Box<dyn Function>>,
199) -> Result<(), Error> {
200    use crate::compiler::TargetValue;
201
202    let objects = objects
203        .into_iter()
204        .map(|value| TargetValue {
205            value,
206            metadata: Value::Object(BTreeMap::new()),
207            secrets: Secrets::new(),
208        })
209        .collect();
210
211    repl::run(quiet, objects, timezone, vrl_runtime, stdlib_functions).map_err(Into::into)
212}
213
214fn execute(
215    object: &mut impl Target,
216    program: &Program,
217    timezone: TimeZone,
218    mut runtime: Runtime,
219    vrl_runtime: VrlRuntime,
220) -> Result<Value, Error> {
221    match vrl_runtime {
222        VrlRuntime::Ast => runtime
223            .resolve(object, program, &timezone)
224            .map_err(Error::Runtime),
225    }
226}
227
228fn serde_to_vrl(value: serde_json::Value) -> Value {
229    use serde_json::Value as JsonValue;
230
231    match value {
232        JsonValue::Null => crate::value::Value::Null,
233        JsonValue::Object(v) => v
234            .into_iter()
235            .map(|(k, v)| (k.into(), serde_to_vrl(v)))
236            .collect::<BTreeMap<_, _>>()
237            .into(),
238        JsonValue::Bool(v) => v.into(),
239        JsonValue::Number(v) if v.is_f64() => Value::from_f64_or_zero(v.as_f64().unwrap()),
240        JsonValue::Number(v) => v.as_i64().unwrap_or(i64::MAX).into(),
241        JsonValue::String(v) => v.into(),
242        JsonValue::Array(v) => v.into_iter().map(serde_to_vrl).collect::<Vec<_>>().into(),
243    }
244}
245
246fn read<R: Read>(mut reader: R) -> Result<String, Error> {
247    let mut buffer = String::new();
248    reader.read_to_string(&mut buffer)?;
249
250    Ok(buffer)
251}
252
253fn default_objects() -> Vec<Value> {
254    vec![Value::Object(BTreeMap::new())]
255}