Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move attribute parsing to attribute trait #65

Merged
merged 4 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions src/attribute.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could "just" be the TryInto trait, but it is that against the orphan rules

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!(
"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!(
"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!(
"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!(
"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())
}
}

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 All @@ -22,7 +24,7 @@
// An optional value to use if the selector is not found in the context.
// If not set and the selector is not found in the context, then no data is generated.
#[serde(default)]
pub default: Option<String>,

Check warning on line 27 in src/configuration.rs

View workflow job for this annotation

GitHub Actions / Test Suite

field `default` is never read

Check warning on line 27 in src/configuration.rs

View workflow job for this annotation

GitHub Actions / Rustfmt

field `default` is never read

Check warning on line 27 in src/configuration.rs

View workflow job for this annotation

GitHub Actions / Check

field `default` is never read

Check failure on line 27 in src/configuration.rs

View workflow job for this annotation

GitHub Actions / Clippy

field `default` is never read

#[serde(skip_deserializing)]
path: OnceCell<Path>,
Expand Down Expand Up @@ -168,47 +170,13 @@
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)?),
Comment on lines +173 to +179
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice ❤️

// 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
27 changes: 2 additions & 25 deletions src/policy.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::attribute::get_attribute;
use crate::configuration::{DataItem, DataType, PatternExpression};
use crate::envoy::{RateLimitDescriptor, RateLimitDescriptor_Entry};
use crate::filter::http_context::Filter;
Expand Down Expand Up @@ -125,31 +126,7 @@ impl Policy {
Some(key) => key.to_owned(),
};

let attribute_path = selector_item.path();
let value = match filter.get_property(attribute_path.tokens()) {
None => {
debug!(
"#{} build_single_descriptor: selector not found: {}",
filter.context_id, attribute_path
);
match &selector_item.default {
None => return None, // skipping the entire descriptor
Some(default_value) => default_value.clone(),
}
adam-cattermole marked this conversation as resolved.
Show resolved Hide resolved
}
// 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,
},
};
let value = get_attribute(filter, selector_item.selector.as_str()).ok()?;
let mut descriptor_entry = RateLimitDescriptor_Entry::new();
descriptor_entry.set_key(descriptor_key);
descriptor_entry.set_value(value);
Expand Down
Loading