use std::path::Path;
use schemars::JsonSchema;
use serde::de::{self, Deserializer};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use crate::envelope::{take_or_default, Envelope};
use crate::vocabulary::{Admits, Vocabulary};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(bound = "", deny_unknown_fields)]
#[schemars(
bound = "V::Source: JsonSchema, V::Fields: JsonSchema",
rename = "Filter"
)]
pub struct Filter<V: Vocabulary> {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub include: Vec<Matcher<V>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub exclude: Vec<Matcher<V>>,
}
impl<V: Vocabulary> Default for Filter<V> {
fn default() -> Self {
Self {
include: Vec::new(),
exclude: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
#[serde(bound = "")]
#[schemars(
bound = "V::Source: JsonSchema, V::Fields: JsonSchema",
rename = "Matcher"
)]
pub struct Matcher<V: Vocabulary> {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<V::Source>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub kind: Option<String>,
#[serde(flatten)]
pub fields: V::Fields,
}
impl<'de, V: Vocabulary> Deserialize<'de> for Matcher<V> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let mut fields: Map<String, Value> = Map::deserialize(deserializer)?;
let source = take_or_default(&mut fields, "source")?;
let kind = take_or_default(&mut fields, "kind")?;
let fields = serde_json::from_value(Value::Object(fields)).map_err(de::Error::custom)?;
Ok(Self {
source,
kind,
fields,
})
}
}
impl<V: Vocabulary> Default for Matcher<V> {
fn default() -> Self {
Self {
source: None,
kind: None,
fields: V::Fields::default(),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(transparent)]
pub struct LabelMatch(pub Map<String, Value>);
impl LabelMatch {
#[must_use]
pub fn with(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
self.0.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{0}")]
pub struct FilterError(pub String);
impl<V: Vocabulary> Filter<V> {
#[must_use]
pub fn everything() -> Self {
Self::default()
}
pub fn parse(spec: &str) -> Result<Self, FilterError> {
let filter: Self = serde_norway::from_str(spec)
.map_err(|failure| FilterError(format!("the event filter is unusable: {failure}")))?;
filter.validate().map_err(FilterError)?;
Ok(filter)
}
pub fn read(spec: &str) -> Result<Self, FilterError> {
if spec.trim_start().starts_with('{') {
return Self::parse(spec);
}
let document = std::fs::read_to_string(Path::new(spec)).map_err(|failure| {
FilterError(format!("cannot read the event filter {spec}: {failure}"))
})?;
Self::parse(&document)
}
#[must_use]
pub fn matches(&self, envelope: &Envelope<V>) -> bool {
self.allows(
&envelope.source,
envelope.kind.as_str(),
&envelope.dimensions,
&envelope.labels,
)
}
#[must_use]
pub fn allows(
&self,
source: &V::Source,
kind: &str,
dimensions: &V::Dimensions,
labels: &V::Labels,
) -> bool {
if self.include.is_empty() && self.exclude.is_empty() {
return true;
}
let carried = Carried::of::<V>(dimensions, labels);
if self
.exclude
.iter()
.any(|matcher| matcher.holds(source, kind, &carried))
{
return false;
}
self.include.is_empty()
|| self
.include
.iter()
.any(|matcher| matcher.holds(source, kind, &carried))
}
pub fn validate(&self) -> Result<(), String> {
for (list, matchers) in [("include", &self.include), ("exclude", &self.exclude)] {
for (at, matcher) in matchers.iter().enumerate() {
matcher.check().map_err(|why| {
format!(
"{list}[{at}] {}: {why}",
serde_json::to_string(matcher).unwrap_or_else(|_| "{}".to_owned())
)
})?;
}
}
Ok(())
}
}
struct Carried(Map<String, Value>);
impl Carried {
fn of<V: Vocabulary>(dimensions: &V::Dimensions, labels: &V::Labels) -> Self {
let mut carried = as_map(dimensions);
carried.extend(as_map(labels));
Self(carried)
}
}
fn as_map<T: Serialize>(value: &T) -> Map<String, Value> {
match serde_json::to_value(value) {
Ok(Value::Object(map)) => map,
_ => Map::new(),
}
}
impl<V: Vocabulary> Matcher<V> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn parse(spec: &str) -> Result<Self, FilterError> {
let matcher: Self = serde_json::from_str(spec)
.map_err(|failure| FilterError(format!("the matcher is unusable: {failure}")))?;
matcher
.check()
.map_err(|why| FilterError(format!("{spec}: {why}")))?;
Ok(matcher)
}
#[must_use]
pub fn source(mut self, source: V::Source) -> Self {
self.source = Some(source);
self
}
#[must_use]
pub fn kind(mut self, glob: impl Into<String>) -> Self {
self.kind = Some(glob.into());
self
}
#[must_use]
pub fn fields(mut self, fields: V::Fields) -> Self {
self.fields = fields;
self
}
fn holds(&self, source: &V::Source, kind: &str, carried: &Carried) -> bool {
if self.source.as_ref().is_some_and(|named| named != source) {
return false;
}
if self
.kind
.as_deref()
.is_some_and(|pattern| !glob(pattern, kind))
{
return false;
}
as_map(&self.fields)
.iter()
.all(|(key, asked)| carried.0.get(key) == Some(asked))
}
fn check(&self) -> Result<(), String> {
let mut named = usize::from(self.source.is_some());
let mut asked: Vec<(String, Value)> = Vec::new();
if let Some(kind) = &self.kind {
asked.push(("kind".to_owned(), Value::String(kind.clone())));
}
asked.extend(as_map(&self.fields));
for (field, value) in asked {
named += 1;
let empty = match &value {
Value::String(text) => text.trim().is_empty(),
Value::Null => true,
_ => false,
};
if empty {
return Err(format!(
"`{field}` is empty, and nothing on the stream carries an empty {field} — \
omit the field to leave it unasked"
));
}
}
if named == 0 {
let keys: Vec<&str> = ["source", "kind"]
.into_iter()
.chain(
V::DIMENSIONS
.iter()
.chain(V::RESERVED)
.filter(|reserved| reserved.admits != Admits::Integer)
.map(|reserved| reserved.key),
)
.collect();
return Err(format!(
"a matcher naming no field matches every event — name at least one of `{}`",
keys.join("`, `")
));
}
Ok(())
}
}
#[must_use]
pub fn glob(pattern: &str, text: &str) -> bool {
let pattern: Vec<char> = pattern.chars().collect();
let text: Vec<char> = text.chars().collect();
let (mut p, mut t) = (0, 0);
let (mut star, mut resume) = (None, 0);
while t < text.len() {
if pattern.get(p) == Some(&'*') {
star = Some(p);
resume = t;
p += 1;
} else if pattern.get(p) == Some(&text[t]) {
p += 1;
t += 1;
} else if let Some(at) = star {
p = at + 1;
resume += 1;
t = resume;
} else {
return false;
}
}
pattern[p..].iter().all(|character| *character == '*')
}