diff --git a/src/attribute.rs b/src/attribute.rs new file mode 100644 index 00000000..aada6176 --- /dev/null +++ b/src/attribute.rs @@ -0,0 +1,119 @@ +use crate::configuration::Path; +use crate::filter::http_context::Filter; +use chrono::{DateTime, FixedOffset}; +use proxy_wasm::traits::Context; + +pub trait Attribute { + fn parse(raw_attribute: Vec) -> Result + where + Self: Sized; +} + +impl Attribute for String { + fn parse(raw_attribute: Vec) -> Result { + String::from_utf8(raw_attribute).map_err(|err| { + format!( + "parse: failed to parse selector String value, error: {}", + err + ) + }) + } +} + +impl Attribute for i64 { + fn parse(raw_attribute: Vec) -> Result { + if raw_attribute.len() != 8 { + return Err(format!( + "parse: Int value expected to be 8 bytes, but got {}", + raw_attribute.len() + )); + } + Ok(i64::from_le_bytes( + raw_attribute[..8] + .try_into() + .expect("This has to be 8 bytes long!"), + )) + } +} + +impl Attribute for u64 { + fn parse(raw_attribute: Vec) -> Result { + if raw_attribute.len() != 8 { + return Err(format!( + "parse: UInt value expected to be 8 bytes, but got {}", + raw_attribute.len() + )); + } + Ok(u64::from_le_bytes( + raw_attribute[..8] + .try_into() + .expect("This has to be 8 bytes long!"), + )) + } +} + +impl Attribute for f64 { + fn parse(raw_attribute: Vec) -> Result { + if raw_attribute.len() != 8 { + return Err(format!( + "parse: Float value expected to be 8 bytes, but got {}", + raw_attribute.len() + )); + } + Ok(f64::from_le_bytes( + raw_attribute[..8] + .try_into() + .expect("This has to be 8 bytes long!"), + )) + } +} + +impl Attribute for Vec { + fn parse(raw_attribute: Vec) -> Result { + Ok(raw_attribute) + } +} + +impl Attribute for bool { + fn parse(raw_attribute: Vec) -> Result { + if raw_attribute.len() != 1 { + return Err(format!( + "parse: Bool value expected to be 1 byte, but got {}", + raw_attribute.len() + )); + } + Ok(raw_attribute[0] & 1 == 1) + } +} + +impl Attribute for DateTime { + fn parse(raw_attribute: Vec) -> Result { + if raw_attribute.len() != 8 { + return Err(format!( + "parse: Timestamp expected to be 8 bytes, but got {}", + raw_attribute.len() + )); + } + + let nanos = i64::from_le_bytes( + raw_attribute.as_slice()[..8] + .try_into() + .expect("This has to be 8 bytes long!"), + ); + Ok(DateTime::from_timestamp_nanos(nanos).into()) + } +} + +#[allow(dead_code)] +pub fn get_attribute(f: &Filter, attr: &str) -> Result +where + T: Attribute, +{ + match f.get_property(Path::from(attr).tokens()) { + None => Err(format!( + "#{} get_attribute: not found: {}", + f.context_id, attr + )), + Some(attribute_bytes) => T::parse(attribute_bytes), + } +} diff --git a/src/configuration.rs b/src/configuration.rs index 7c815943..d7495ef0 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -1,13 +1,15 @@ -use crate::policy::Policy; -use crate::policy_index::PolicyIndex; +use std::cell::OnceCell; +use std::fmt::{Debug, Display, Formatter}; +use std::sync::Arc; + use cel_interpreter::objects::ValueType; use cel_interpreter::{Context, Expression, Value}; use cel_parser::{Atom, RelationOp}; -use chrono::{DateTime, FixedOffset}; use serde::Deserialize; -use std::cell::OnceCell; -use std::fmt::{Debug, Display, Formatter}; -use std::sync::Arc; + +use crate::attribute::Attribute; +use crate::policy::Policy; +use crate::policy_index::PolicyIndex; #[derive(Deserialize, Debug, Clone)] pub struct SelectorItem { @@ -168,47 +170,13 @@ impl PatternExpression { pub fn eval(&self, raw_attribute: Vec) -> Result { let cel_type = &self.compiled.get().unwrap().cel_type; let value = match cel_type { - ValueType::String => - Value::String(String::from_utf8(raw_attribute).map_err(|err| format!( - "pattern_expression_applies: failed to parse selector String value: {}, error: {}", - self.selector, err))?.into()), - ValueType::Int => { - if raw_attribute.len() != 8 { - return Err(format!("Int value expected to be 8 bytes, but got {}", raw_attribute.len())); - } - Value::Int(i64::from_le_bytes(raw_attribute[..8].try_into().expect("This has to be 8 bytes long!"))) - }, - ValueType::UInt => { - { - if raw_attribute.len() != 8 { - return Err(format!("UInt value expected to be 8 bytes, but got {}", raw_attribute.len())); - } - Value::UInt(u64::from_le_bytes(raw_attribute[..8].try_into().expect("This has to be 8 bytes long!"))) - } - }, - ValueType::Float => { - if raw_attribute.len() != 8 { - return Err(format!("Float value expected to be 8 bytes, but got {}", raw_attribute.len())); - } - Value::Float(f64::from_le_bytes(raw_attribute[..8].try_into().expect("This has to be 8 bytes long!"))) - }, - ValueType::Bytes => Value::Bytes(raw_attribute.into()), - ValueType::Bool => { - if raw_attribute.len() != 1 { - return Err(format!("Bool value expected to be 1 byte, but got {}", raw_attribute.len())); - } - Value::Bool(raw_attribute[0] & 1 == 1) - } - ValueType::Timestamp => { - { - if raw_attribute.len() != 8 { - return Err(format!("Timestamp expected to be 8 bytes, but got {}", raw_attribute.len())); - } - let nanos = i64::from_le_bytes(raw_attribute[..8].try_into().expect("This has to be 8 bytes long!")); - let time: DateTime = DateTime::from_timestamp_nanos(nanos).into(); - Value::Timestamp(time) - } - } + ValueType::String => Value::String(Arc::new(Attribute::parse(raw_attribute)?)), + ValueType::Int => Value::Int(Attribute::parse(raw_attribute)?), + ValueType::UInt => Value::UInt(Attribute::parse(raw_attribute)?), + ValueType::Float => Value::Float(Attribute::parse(raw_attribute)?), + ValueType::Bytes => Value::Bytes(Arc::new(Attribute::parse(raw_attribute)?)), + ValueType::Bool => Value::Bool(Attribute::parse(raw_attribute)?), + ValueType::Timestamp => Value::Timestamp(Attribute::parse(raw_attribute)?), // todo: Impl support for parsing these two types… Tho List/Map of what? // ValueType::List => {} // ValueType::Map => {} diff --git a/src/lib.rs b/src/lib.rs index 417ec5f5..8ee6c311 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +mod attribute; mod configuration; mod envoy; mod filter; diff --git a/src/policy.rs b/src/policy.rs index aa46a38d..7b894c16 100644 --- a/src/policy.rs +++ b/src/policy.rs @@ -1,3 +1,4 @@ +use crate::attribute::Attribute; use crate::configuration::{DataItem, DataType, PatternExpression}; use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry}; use crate::filter::http_context::Filter; @@ -139,16 +140,10 @@ impl Policy { } // TODO(eastizle): not all fields are strings // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes - Some(attribute_bytes) => match String::from_utf8(attribute_bytes) { - Err(e) => { - debug!( - "#{} build_single_descriptor: failed to parse selector value: {}, error: {}", - filter.context_id, attribute_path, e - ); - return None; - } - Ok(attribute_value) => attribute_value, - }, + Some(attribute_bytes) => Attribute::parse(attribute_bytes) + .inspect_err(|e| debug!("#{} build_single_descriptor: failed to parse selector value: {}, error: {}", + filter.context_id, attribute_path, e)) + .ok()?, }; let mut descriptor_entry = RateLimitDescriptor_Entry::new(); descriptor_entry.set_key(descriptor_key);