-
Notifications
You must be signed in to change notification settings - Fork 252
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Nest authored test functions for safety
Like `tokio::test` et. al., nest the authored test functions and call them, which helps maintain code safety by separating contexts as recommended for all macros.
- Loading branch information
Showing
6 changed files
with
240 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
use azure_core::headers::{AsHeaders, HeaderName, HeaderValue}; | ||
use serde::Serialize; | ||
use std::{ | ||
convert::Infallible, | ||
fmt, | ||
iter::{once, Once}, | ||
}; | ||
|
||
/// Default sanitization replacement value, "Sanitized"; | ||
pub const SANITIZED_VALUE: &str = "Sanitized"; | ||
const ABSTRACTION_IDENTIFIER: HeaderName = HeaderName::from_static("x-abstraction-identifier"); | ||
|
||
/// Represents a sanitizer. | ||
pub trait Sanitizer: AsHeaders + fmt::Debug + Serialize {} | ||
|
||
macro_rules! impl_sanitizer { | ||
($name:ident) => { | ||
impl Sanitizer for $name {} | ||
|
||
impl AsHeaders for $name { | ||
type Error = Infallible; | ||
type Iter = Once<(HeaderName, HeaderValue)>; | ||
fn as_headers(&self) -> Result<Self::Iter, Self::Error> { | ||
Ok(once(( | ||
ABSTRACTION_IDENTIFIER, | ||
HeaderValue::from_static(stringify!($name)), | ||
))) | ||
} | ||
} | ||
}; | ||
|
||
($($name:ident),+) => { | ||
$(impl_sanitizer!($name))* | ||
|
||
}; | ||
} | ||
|
||
/// This sanitizer offers regular expression replacements within a returned JSON body for a specific JSONPath. | ||
/// | ||
/// This sanitizer only applies to JSON bodies. | ||
#[derive(Clone, Debug, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BodyKeySanitizer { | ||
/// The JSONPath that will be checked for replacements. | ||
pub json_path: String, | ||
|
||
/// The substitution value. The default is [`SANITIZED_VALUE`]. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub value: Option<String>, | ||
|
||
/// The regular expression to search for. | ||
/// | ||
/// Can be defined as a simple regular expression replacement or, if [`BodyKeySanitizer::group_for_replace`] is set, a substitution operation. | ||
/// Defaults to replacing the entire string. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub regex: Option<String>, | ||
|
||
/// The regular expression capture group to substitute. | ||
/// | ||
/// Do not set if you're invoking a simple replacement operation. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub group_for_replace: Option<String>, | ||
} | ||
impl_sanitizer!(BodyKeySanitizer); | ||
|
||
#[test] | ||
fn test_body_key_sanitizer_as_headers() { | ||
let sut = BodyKeySanitizer { | ||
json_path: String::from("$.values"), | ||
value: None, | ||
regex: None, | ||
group_for_replace: None, | ||
}; | ||
let headers = sut.as_headers().expect("expect headers"); | ||
headers.for_each(|(h, v)| { | ||
assert_eq!(h.as_str(), "x-abstraction-identifier"); | ||
assert_eq!(v.as_str(), "BodyKeySanitizer"); | ||
}); | ||
} | ||
|
||
/// This sanitizer offers regular expression replacements within raw request and response bodies. | ||
/// | ||
/// Specifically, this means the regular expression applies to the raw JSON. | ||
/// If you are attempting to simply replace a specific JSON key, the [`BodyKeySanitizer`] is probably what you want to use. | ||
#[derive(Clone, Debug, Default, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct BodyRegexSanitizer { | ||
/// The substitution value. The default is [`SANITIZED_VALUE`]. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub value: Option<String>, | ||
|
||
/// The regular expression to search for or the entire body if `None`. | ||
/// | ||
/// Can be defined as a simple regular expression replacement or, if [`BodyRegexSanitizer::group_for_replace`] is set, a substitution operation. | ||
/// Defaults to replacing the entire string. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub regex: Option<String>, | ||
|
||
/// The regular expression capture group to substitute. | ||
/// | ||
/// Do not set if you're invoking a simple replacement operation. | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub group_for_replace: Option<String>, | ||
} | ||
impl_sanitizer!(BodyRegexSanitizer); | ||
|
||
#[test] | ||
fn test_body_regex_sanitizer_as_headers() { | ||
let sut = BodyRegexSanitizer::default(); | ||
let headers = sut.as_headers().expect("expect headers"); | ||
headers.for_each(|(h, v)| { | ||
assert_eq!(h.as_str(), "x-abstraction-identifier"); | ||
assert_eq!(v.as_str(), "BodyRegexSanitizer"); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
use async_trait::async_trait; | ||
use azure_core::{test::TestMode, Context, Policy, PolicyResult, Request, TransportOptions}; | ||
use std::sync::Arc; | ||
use tracing::{debug_span, Instrument}; | ||
|
||
/// Wraps the original [`TransportOptions`] and records or plays back session records for testing. | ||
#[derive(Debug)] | ||
pub struct ProxyTransportPolicy { | ||
pub(crate) inner: TransportOptions, | ||
pub(crate) mode: TestMode, | ||
} | ||
|
||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] | ||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)] | ||
impl Policy for ProxyTransportPolicy { | ||
async fn send( | ||
&self, | ||
ctx: &Context, | ||
request: &mut Request, | ||
next: &[Arc<dyn Policy>], | ||
) -> PolicyResult { | ||
// There must be no other policies since we're encapsulating the original TransportPolicy. | ||
assert_eq!(0, next.len()); | ||
|
||
let span = debug_span!("test-proxy", mode = ?self.mode); | ||
async move { { self.inner.send(ctx, request) }.await } | ||
.instrument(span) | ||
.await | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters