Skip to content

Commit

Permalink
Merge pull request #65 from Kuadrant/attribute-parsing
Browse files Browse the repository at this point in the history
Move attribute parsing to attribute trait
  • Loading branch information
adam-cattermole authored Aug 13, 2024
2 parents 6519ce5 + e5a20de commit c19464b
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 57 deletions.
119 changes: 119 additions & 0 deletions src/attribute.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) -> Result<Self, String>
where
Self: Sized;
}

impl Attribute for String {
fn parse(raw_attribute: Vec<u8>) -> Result<Self, String> {
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<u8>) -> Result<Self, String> {
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<u8>) -> Result<Self, String> {
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<u8>) -> Result<Self, String> {
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<u8> {
fn parse(raw_attribute: Vec<u8>) -> Result<Self, String> {
Ok(raw_attribute)
}
}

impl Attribute for bool {
fn parse(raw_attribute: Vec<u8>) -> Result<Self, String> {
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<FixedOffset> {
fn parse(raw_attribute: Vec<u8>) -> Result<Self, String> {
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<T>(f: &Filter, attr: &str) -> Result<T, String>
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),
}
}
62 changes: 15 additions & 47 deletions src/configuration.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -168,47 +170,13 @@ impl PatternExpression {
pub fn eval(&self, raw_attribute: Vec<u8>) -> Result<bool, String> {
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<FixedOffset> = 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 => {}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod attribute;
mod configuration;
mod envoy;
mod filter;
Expand Down
15 changes: 5 additions & 10 deletions src/policy.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit c19464b

Please sign in to comment.