Skip to content

Commit eb0f17c

Browse files
committed
feat: add integration and pipeline tests for set_filter_state
- Add 3 integration tests for xDS config parsing (Istio waypoint) - Add 5 HTTP pipeline tests for filter application - Fix skip_if_empty to handle '-' default values - Add :authority and :method pseudo-headers to RequestContext Signed-off-by: Eeshu-Yadav <[email protected]>
1 parent 781f711 commit eb0f17c

File tree

5 files changed

+665
-380
lines changed

5 files changed

+665
-380
lines changed

orion-configuration/src/config/network_filters/http_connection_manager/http_filters/set_filter_state.rs

Lines changed: 49 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
2121
use super::super::is_default;
2222

2323
/// Set Filter State HTTP filter configuration
24-
///
24+
///
2525
/// This filter dynamically sets filter state objects based on request data.
2626
/// Filter state can be used for routing decisions, metadata propagation,
2727
/// and internal connection handling.
@@ -36,33 +36,33 @@ pub struct SetFilterState {
3636
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
3737
pub struct FilterStateValue {
3838
/// Filter state object key (required)
39-
///
39+
///
4040
/// Examples:
4141
/// - "io.istio.connect_authority" (Istio HBONE)
4242
/// - "envoy.filters.listener.original_dst.local_ip"
4343
/// - "envoy.tcp_proxy.cluster"
4444
pub object_key: CompactString,
45-
45+
4646
/// Optional factory key for object creation
4747
/// If not specified, object_key is used for factory lookup
4848
#[serde(skip_serializing_if = "Option::is_none", default)]
4949
pub factory_key: Option<CompactString>,
50-
50+
5151
/// Format string to generate the value
5252
/// Supports Envoy substitution format strings like:
5353
/// - %REQ(:authority)% - Request header
5454
/// - %DOWNSTREAM_REMOTE_ADDRESS% - Client IP
5555
/// - %UPSTREAM_HOST% - Selected upstream
5656
pub format_string: FormatString,
57-
57+
5858
/// Make this value read-only (cannot be overridden by other filters)
5959
#[serde(skip_serializing_if = "std::ops::Not::not", default)]
6060
pub read_only: bool,
61-
61+
6262
/// Share with upstream internal connections
6363
#[serde(skip_serializing_if = "is_default", default)]
6464
pub shared_with_upstream: SharedWithUpstream,
65-
65+
6666
/// Skip setting the value if it evaluates to empty string
6767
#[serde(skip_serializing_if = "std::ops::Not::not", default)]
6868
pub skip_if_empty: bool,
@@ -88,7 +88,7 @@ pub enum FormatString {
8888
/// Plain text format string with command operators
8989
/// Example: "%REQ(:authority)%"
9090
Text(CompactString),
91-
91+
9292
/// Structured format (JSON, etc.) - future extension
9393
Structured {
9494
format: CompactString,
@@ -101,60 +101,53 @@ pub enum FormatString {
101101
mod envoy_conversions {
102102
use super::*;
103103
use crate::config::common::*;
104-
use orion_data_plane_api::envoy_data_plane_api::{
105-
envoy::{
106-
config::core::v3::SubstitutionFormatString as EnvoySubstitutionFormatString,
107-
extensions::filters::{
108-
common::set_filter_state::v3::{
109-
FilterStateValue as EnvoyFilterStateValue,
110-
filter_state_value::{
111-
Key as EnvoyKey,
112-
Value as EnvoyValue,
113-
SharedWithUpstream as EnvoySharedWithUpstream
114-
},
104+
use orion_data_plane_api::envoy_data_plane_api::envoy::{
105+
config::core::v3::SubstitutionFormatString as EnvoySubstitutionFormatString,
106+
extensions::filters::{
107+
common::set_filter_state::v3::{
108+
filter_state_value::{
109+
Key as EnvoyKey, SharedWithUpstream as EnvoySharedWithUpstream, Value as EnvoyValue,
115110
},
116-
http::set_filter_state::v3::Config as EnvoySetFilterStateConfig,
111+
FilterStateValue as EnvoyFilterStateValue,
117112
},
113+
http::set_filter_state::v3::Config as EnvoySetFilterStateConfig,
118114
},
119115
};
120-
116+
121117
impl TryFrom<EnvoySetFilterStateConfig> for SetFilterState {
122118
type Error = GenericError;
123-
119+
124120
fn try_from(envoy: EnvoySetFilterStateConfig) -> Result<Self, Self::Error> {
125-
let on_request_headers = envoy.on_request_headers
121+
let on_request_headers = envoy
122+
.on_request_headers
126123
.into_iter()
127124
.map(FilterStateValue::try_from)
128125
.collect::<Result<Vec<_>, _>>()
129126
.with_node("on_request_headers")?;
130-
127+
131128
Ok(Self { on_request_headers })
132129
}
133130
}
134-
131+
135132
impl TryFrom<EnvoyFilterStateValue> for FilterStateValue {
136133
type Error = GenericError;
137-
134+
138135
fn try_from(envoy: EnvoyFilterStateValue) -> Result<Self, Self::Error> {
139136
let object_key = match envoy.key {
140137
Some(EnvoyKey::ObjectKey(key)) => CompactString::from(key),
141138
None => return Err(GenericError::from_msg("missing object_key in FilterStateValue")),
142139
};
143-
144-
let factory_key = if envoy.factory_key.is_empty() {
145-
None
146-
} else {
147-
Some(envoy.factory_key.into())
148-
};
149-
140+
141+
let factory_key = (!envoy.factory_key.is_empty()).then(|| envoy.factory_key.into());
142+
150143
let format_string = match envoy.value {
151144
Some(EnvoyValue::FormatString(fs)) => FormatString::try_from(fs).with_node("format_string")?,
152145
None => return Err(GenericError::from_msg("missing format_string in FilterStateValue")),
153146
};
154-
155-
let shared_with_upstream = SharedWithUpstream::try_from(envoy.shared_with_upstream)
156-
.with_node("shared_with_upstream")?;
157-
147+
148+
let shared_with_upstream =
149+
SharedWithUpstream::try_from(envoy.shared_with_upstream).with_node("shared_with_upstream")?;
150+
158151
Ok(Self {
159152
object_key,
160153
factory_key,
@@ -165,34 +158,31 @@ mod envoy_conversions {
165158
})
166159
}
167160
}
168-
161+
169162
impl TryFrom<EnvoySubstitutionFormatString> for FormatString {
170163
type Error = GenericError;
171-
164+
172165
fn try_from(envoy: EnvoySubstitutionFormatString) -> Result<Self, Self::Error> {
173166
use orion_data_plane_api::envoy_data_plane_api::envoy::config::core::v3::{
174-
substitution_format_string::Format,
175-
data_source::Specifier,
167+
data_source::Specifier, substitution_format_string::Format,
176168
};
177-
169+
178170
match envoy.format {
179171
Some(Format::TextFormat(text)) => Ok(FormatString::Text(text.into())),
180-
Some(Format::TextFormatSource(source)) => {
181-
match source.specifier {
182-
Some(Specifier::InlineString(s)) => Ok(FormatString::Text(s.into())),
183-
Some(Specifier::InlineBytes(b)) => {
184-
let s = String::from_utf8(b)
185-
.map_err(|e| GenericError::from_msg(format!("Invalid UTF-8 in format string: {}", e)))?;
186-
Ok(FormatString::Text(s.into()))
187-
},
188-
Some(Specifier::Filename(_)) => {
189-
Err(GenericError::unsupported_variant("filename format strings not supported"))
190-
},
191-
Some(Specifier::EnvironmentVariable(_)) => {
192-
Err(GenericError::unsupported_variant("environment variable format strings not supported"))
193-
},
194-
None => Err(GenericError::from_msg("missing format string specifier")),
195-
}
172+
Some(Format::TextFormatSource(source)) => match source.specifier {
173+
Some(Specifier::InlineString(s)) => Ok(FormatString::Text(s.into())),
174+
Some(Specifier::InlineBytes(b)) => {
175+
let s = String::from_utf8(b)
176+
.map_err(|e| GenericError::from_msg(format!("Invalid UTF-8 in format string: {}", e)))?;
177+
Ok(FormatString::Text(s.into()))
178+
},
179+
Some(Specifier::Filename(_)) => {
180+
Err(GenericError::unsupported_variant("filename format strings not supported"))
181+
},
182+
Some(Specifier::EnvironmentVariable(_)) => {
183+
Err(GenericError::unsupported_variant("environment variable format strings not supported"))
184+
},
185+
None => Err(GenericError::from_msg("missing format string specifier")),
196186
},
197187
Some(Format::JsonFormat(_)) => {
198188
// JSON format not yet supported - would need structured logging
@@ -204,7 +194,7 @@ mod envoy_conversions {
204194
}
205195
impl TryFrom<i32> for SharedWithUpstream {
206196
type Error = GenericError;
207-
197+
208198
fn try_from(value: i32) -> Result<Self, Self::Error> {
209199
match EnvoySharedWithUpstream::try_from(value) {
210200
Ok(EnvoySharedWithUpstream::None) => Ok(Self::None),

0 commit comments

Comments
 (0)