vrl 0.37.0

Vector Remap Language
Documentation
use crate::compiler::{
    Context, Expression,
    expression::{Container, Resolved, Variable},
    parser::ast::Ident,
    state::ExternalEnv,
    state::{TypeInfo, TypeState},
    type_def::Details,
};
use crate::path::{OwnedTargetPath, OwnedValuePath, PathPrefix};
use crate::value::Value;
use std::fmt;

#[derive(Clone, PartialEq)]
pub struct Query {
    target: Target,
    // Only external queries use the prefix; other targets use the value path.
    path: OwnedTargetPath,
}

impl Query {
    // TODO:
    // - error when trying to index into object
    // - error when trying to path into array
    #[must_use]
    pub fn new(target: Target, path: OwnedValuePath) -> Self {
        let prefix = match &target {
            Target::External(prefix) => *prefix,
            _ => PathPrefix::Event,
        };
        let path = OwnedTargetPath { prefix, path };
        Query { target, path }
    }

    #[must_use]
    pub fn path(&self) -> &OwnedValuePath {
        &self.path.path
    }

    #[must_use]
    pub fn target(&self) -> &Target {
        &self.target
    }

    #[must_use]
    pub fn is_external(&self) -> bool {
        matches!(self.target, Target::External(_))
    }

    #[must_use]
    pub fn external_path(&self) -> Option<&OwnedTargetPath> {
        match self.target {
            Target::External(_) => Some(&self.path),
            _ => None,
        }
    }

    #[must_use]
    pub fn as_variable(&self) -> Option<&Variable> {
        match &self.target {
            Target::Internal(variable) => Some(variable),
            _ => None,
        }
    }

    #[must_use]
    pub fn variable_ident(&self) -> Option<&Ident> {
        match &self.target {
            Target::Internal(v) => Some(v.ident()),
            _ => None,
        }
    }

    #[must_use]
    pub fn expression_target(&self) -> Option<&dyn Expression> {
        match &self.target {
            Target::FunctionCall(expr) => Some(expr),
            Target::Container(expr) => Some(expr),
            _ => None,
        }
    }

    // Only "external" paths are supported. Non external paths are ignored
    // see: https://github.com/vectordotdev/vector/issues/11246
    pub fn delete_type_def(&self, external: &mut ExternalEnv, compact: bool) {
        if let Some(target_path) = self.external_path() {
            match target_path.prefix {
                PathPrefix::Event => {
                    let mut type_def = external.target().type_def.clone();
                    type_def.remove(&target_path.path, compact);
                    external.update_target(Details {
                        type_def,
                        value: None,
                    });
                }
                PathPrefix::Metadata => {
                    let mut kind = external.metadata_kind().clone();
                    kind.remove(&target_path.path, compact);
                    external.update_metadata(kind);
                }
            }
        }
    }
}

impl Expression for Query {
    fn resolve(&self, ctx: &mut Context) -> Resolved {
        use Target::{Container, External, FunctionCall, Internal};

        let owned_value;
        let value = match &self.target {
            External(_) => {
                return Ok(ctx
                    .target()
                    .target_get(&self.path)
                    .ok()
                    .flatten()
                    .cloned()
                    .unwrap_or(Value::Null));
            }
            Internal(variable) => ctx
                .state()
                .variable(variable.ident())
                .unwrap_or(&Value::Null),
            FunctionCall(call) => {
                owned_value = call.resolve(ctx)?;
                &owned_value
            }
            Container(container) => {
                owned_value = container.resolve(ctx)?;
                &owned_value
            }
        };

        Ok(value.get(self.path()).cloned().unwrap_or(Value::Null))
    }

    fn resolve_constant(&self, state: &TypeState) -> Option<Value> {
        match self.target {
            Target::Internal(ref variable) => variable
                .resolve_constant(state)
                .and_then(|v| v.get(self.path()).cloned()),
            _ => None,
        }
    }

    fn type_info(&self, state: &TypeState) -> TypeInfo {
        use Target::{Container, External, FunctionCall, Internal};

        match &self.target {
            External(prefix) => {
                let result = state.external.kind(*prefix).at_path(self.path()).into();
                TypeInfo::new(state, result)
            }
            Internal(variable) => {
                let result = variable.type_def(state).at_path(self.path());
                TypeInfo::new(state, result)
            }
            FunctionCall(call) => call
                .type_info(state)
                .map_result(|result| result.at_path(self.path())),
            Container(container) => container
                .type_info(state)
                .map_result(|result| result.at_path(self.path())),
        }
    }
}

impl fmt::Display for Query {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self.target {
            Target::Internal(_)
                if !self.path().is_root() && !self.path().segments.first().unwrap().is_index() =>
            {
                write!(f, "{}.{}", self.target, self.path())
            }
            _ => write!(f, "{}{}", self.target, self.path()),
        }
    }
}

impl fmt::Debug for Query {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Query({:?}, {:?})", self.target, self.path())
    }
}

#[derive(Clone, PartialEq)]
pub enum Target {
    Internal(Variable),
    External(PathPrefix),
    FunctionCall(crate::compiler::expression::FunctionCall),
    Container(Container),
}

impl fmt::Display for Target {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Target::{Container, External, FunctionCall, Internal};

        match self {
            Internal(v) => v.fmt(f),
            External(prefix) => match prefix {
                PathPrefix::Event => write!(f, "."),
                PathPrefix::Metadata => write!(f, "%"),
            },
            FunctionCall(v) => v.fmt(f),
            Container(v) => v.fmt(f),
        }
    }
}

impl fmt::Debug for Target {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Target::{Container, External, FunctionCall, Internal};

        match self {
            Internal(v) => write!(f, "Internal({v:?})"),
            External(prefix) => match prefix {
                PathPrefix::Event => f.write_str("External(Event)"),
                PathPrefix::Metadata => f.write_str("External(Metadata)"),
            },
            FunctionCall(v) => v.fmt(f),
            Container(v) => v.fmt(f),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::compiler::{
        TimeZone, TypeDef,
        state::{LocalEnv, RuntimeState},
    };
    use crate::owned_value_path;

    #[test]
    fn variable_query_returns_null_when_runtime_variable_is_missing() {
        let ident = Ident::new("foo");
        let mut local = LocalEnv::default();
        local.insert_variable(
            ident.clone(),
            Details {
                type_def: TypeDef::any(),
                value: None,
            },
        );
        let variable = Variable::new((0, 0).into(), ident, &local).unwrap();
        let query = Query::new(Target::Internal(variable), owned_value_path!("field"));
        let mut state = RuntimeState::default();
        let mut target = Value::Null;
        let timezone = TimeZone::default();
        let mut ctx = Context::new(&mut target, &mut state, &timezone);

        assert_eq!(query.resolve(&mut ctx), Ok(Value::Null));
    }

    #[test]
    fn external_root_query_has_infallible_object_type() {
        let query = Query::new(Target::External(PathPrefix::Event), OwnedValuePath::root());

        let state = TypeState::default();
        let type_def = query.type_info(&state).result;

        assert!(type_def.is_infallible());
        assert!(type_def.is_object());

        let object = type_def.as_object().unwrap();

        assert!(object.is_any());
    }
}