use syn::{Block, Expr, ExprIf, Item, Pat, Stmt, Type};
const REPLAY_GUARD_IDENT: &str = "__AUTUMN_IDEMPOTENCY_REPLAY_GUARD";
pub fn block_has_replay_guard(block: &Block) -> bool {
block_has_generated_replay_guard(block)
}
pub fn should_own_replay(input_fn: &syn::ItemFn) -> bool {
!crate::param_helpers::has_any_guard_gate_param(input_fn)
&& !block_has_replay_guard(&input_fn.block)
&& !has_pending_authorize_attr(input_fn)
}
pub fn owned_replay_check_tokens() -> proc_macro2::TokenStream {
quote::quote! {
let __autumn_idempotency_replay = parts
.extensions
.get::<::autumn_web::idempotency::IdempotencyReplayResponse>()
.cloned()
.map(::autumn_web::reexports::axum::extract::Extension);
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return ::core::result::Result::Err(__autumn_response);
}
}
}
fn has_pending_authorize_attr(input_fn: &syn::ItemFn) -> bool {
input_fn
.attrs
.iter()
.any(|attr| crate::authorize::attr_is_authorize_shaped(attr, input_fn))
}
fn block_has_generated_replay_guard(block: &Block) -> bool {
let mut index = 0;
while let Some(stmt) = block.stmts.get(index) {
if stmt_is_replay_guard_marker(stmt)
&& block
.stmts
.get(index + 1)
.is_some_and(stmt_is_generated_replay_guard)
{
return true;
}
if stmt_is_generated_auth_prologue(stmt)
|| stmt_is_generated_throttle_prologue(stmt)
|| stmt_is_generated_sunset_check_prologue(stmt)
{
index += 1;
continue;
}
if let Some(nested) = generated_nested_response_body(block, index) {
return block_has_generated_replay_guard(nested);
}
return false;
}
false
}
fn stmt_is_replay_guard_marker(stmt: &Stmt) -> bool {
matches!(
stmt,
Stmt::Item(Item::Const(item)) if item.ident == REPLAY_GUARD_IDENT
)
}
fn stmt_is_generated_replay_guard(stmt: &Stmt) -> bool {
let Stmt::Expr(Expr::If(expr_if), _) = stmt else {
return false;
};
if_let_replays_and_returns(expr_if)
}
fn stmt_is_generated_auth_prologue(stmt: &Stmt) -> bool {
if matches!(
stmt,
Stmt::Item(Item::Const(item))
if item.ident == "__AUTUMN_SECURED_ROLES"
|| item.ident == "__AUTUMN_SECURED_SCOPES"
|| item.ident == "__AUTUMN_AUTHORIZE_BINDINGS"
) {
return true;
}
let Stmt::Expr(Expr::If(expr_if), _) = stmt else {
return false;
};
if_let_generated_check_returns_error(expr_if)
}
fn stmt_is_generated_throttle_prologue(stmt: &Stmt) -> bool {
if matches!(
stmt,
Stmt::Item(Item::Const(item)) if item.ident == "__AUTUMN_THROTTLE_ROUTE_ID"
) {
return true;
}
let Stmt::Expr(Expr::If(expr_if), _) = stmt else {
return false;
};
if_let_throttle_check_returns(expr_if)
}
fn stmt_is_generated_sunset_check_prologue(stmt: &Stmt) -> bool {
let Stmt::Expr(Expr::If(expr_if), _) = stmt else {
return false;
};
if_let_sunset_check(expr_if)
}
fn if_let_sunset_check(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
expr_if.else_branch.is_none()
&& pat_is_some_route_version_extension(&expr_let.pat)
&& expr_is_ref_to_ident(&expr_let.expr, "__autumn_route_version")
&& block_is_sunset_check_body(&expr_if.then_branch)
}
fn pat_is_some_route_version_extension(pat: &Pat) -> bool {
let Pat::TupleStruct(tuple) = pat else {
return false;
};
path_matches(&tuple.path, &["core", "option", "Option", "Some"])
&& tuple.elems.len() == 1
&& pat_is_extension_binding(&tuple.elems[0], "__autumn_meta")
}
fn pat_is_extension_binding(pat: &Pat, expected: &str) -> bool {
let Pat::TupleStruct(tuple) = pat else {
return false;
};
path_ends_with(&tuple.path, "Extension")
&& tuple.elems.len() == 1
&& pat_binds_ident(&tuple.elems[0], expected)
}
fn block_is_sunset_check_body(block: &Block) -> bool {
match block.stmts.as_slice() {
[Stmt::Expr(Expr::If(inner_if), _)] => if_let_sunset_response_returns(inner_if),
_ => false,
}
}
fn if_let_sunset_response_returns(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
expr_if.else_branch.is_none()
&& pat_is_some_replay_response(&expr_let.pat)
&& expr_is_check_sunset_call(&expr_let.expr)
&& block_returns_ident(&expr_if.then_branch, "__autumn_response")
}
fn expr_is_check_sunset_call(expr: &Expr) -> bool {
match expr {
Expr::Call(call) => {
path_expr_matches(&call.func, &["autumn_web", "__private", "check_sunset"])
}
Expr::Group(group) => expr_is_check_sunset_call(&group.expr),
Expr::Paren(paren) => expr_is_check_sunset_call(&paren.expr),
_ => false,
}
}
fn if_let_throttle_check_returns(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
pat_is_err_throttle_response(&expr_let.pat)
&& expr_is_generated_throttle_check_call(&expr_let.expr)
&& block_returns_ident(&expr_if.then_branch, "__autumn_throttle_response")
}
fn pat_is_err_throttle_response(pat: &Pat) -> bool {
match pat {
Pat::TupleStruct(tuple) => {
path_matches(&tuple.path, &["core", "result", "Result", "Err"])
&& tuple.elems.len() == 1
&& pat_binds_ident(&tuple.elems[0], "__autumn_throttle_response")
}
_ => false,
}
}
fn expr_is_generated_throttle_check_call(expr: &Expr) -> bool {
match expr {
Expr::Await(await_expr) => expr_is_generated_throttle_check_call(&await_expr.base),
Expr::Call(call) => {
path_expr_matches(&call.func, &["autumn_web", "security", "__check_throttle"])
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_state"))
}
Expr::Group(group) => expr_is_generated_throttle_check_call(&group.expr),
Expr::Paren(paren) => expr_is_generated_throttle_check_call(&paren.expr),
_ => false,
}
}
fn if_let_replays_and_returns(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
pat_is_some_replay_response(&expr_let.pat)
&& expr_is_replay_response_call(&expr_let.expr)
&& block_returns_ident(&expr_if.then_branch, "__autumn_response")
}
fn if_let_generated_check_returns_error(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
pat_is_err_autumn_error(&expr_let.pat)
&& expr_is_generated_auth_check_call(&expr_let.expr)
&& block_is_generated_auth_failure_response(&expr_if.then_branch)
}
fn pat_is_some_replay_response(pat: &Pat) -> bool {
match pat {
Pat::TupleStruct(tuple) => {
path_matches(&tuple.path, &["core", "option", "Option", "Some"])
&& tuple.elems.len() == 1
&& pat_binds_ident(&tuple.elems[0], "__autumn_response")
}
_ => false,
}
}
fn pat_is_err_autumn_error(pat: &Pat) -> bool {
match pat {
Pat::TupleStruct(tuple) => {
path_matches(&tuple.path, &["core", "result", "Result", "Err"])
&& tuple.elems.len() == 1
&& pat_binds_ident(&tuple.elems[0], "__autumn_error")
}
_ => false,
}
}
fn pat_binds_ident(pat: &Pat, expected: &str) -> bool {
matches!(pat, Pat::Ident(ident) if ident.ident == expected)
}
fn expr_is_replay_response_call(expr: &Expr) -> bool {
match expr {
Expr::Call(call) => path_expr_matches(
&call.func,
&["autumn_web", "idempotency", "__replay_response"],
),
Expr::Group(group) => expr_is_replay_response_call(&group.expr),
Expr::Paren(paren) => expr_is_replay_response_call(&paren.expr),
_ => false,
}
}
fn expr_is_generated_auth_check_call(expr: &Expr) -> bool {
match expr {
Expr::Await(await_expr) => expr_is_generated_auth_check_call(&await_expr.base),
Expr::Call(call)
if path_expr_matches(
&call.func,
&["autumn_web", "auth", "__check_secured_with_key"],
) =>
{
call.args.len() == 3
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_session"))
&& call
.args
.iter()
.nth(1)
.is_some_and(expr_is_auth_session_key_call)
&& call
.args
.iter()
.nth(2)
.is_some_and(|arg| path_expr_ends_with(arg, "__AUTUMN_SECURED_ROLES"))
}
Expr::Call(call)
if path_expr_matches(
&call.func,
&["autumn_web", "auth", "__check_secured_scopes"],
) =>
{
call.args.len() == 2
&& call
.args
.iter()
.nth(1)
.is_some_and(|arg| path_expr_ends_with(arg, "__AUTUMN_SECURED_SCOPES"))
}
Expr::Call(call)
if path_expr_matches(
&call.func,
&["autumn_web", "authorization", "__check_policy"],
) =>
{
call.args.len() == 4
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_state"))
&& call
.args
.iter()
.nth(1)
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_session"))
&& call.args.iter().nth(2).is_some_and(expr_is_string_literal)
&& call.args.iter().nth(3).is_some_and(expr_is_ref_to_path)
}
Expr::Call(call)
if path_expr_matches(
&call.func,
&["autumn_web", "authorization", "__check_policy_scoped"],
) =>
{
call.args.len() == 5
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_state"))
&& call
.args
.iter()
.nth(1)
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_session"))
&& call.args.iter().nth(2).is_some_and(expr_is_scopes_map_arg)
&& call.args.iter().nth(3).is_some_and(expr_is_string_literal)
&& call.args.iter().nth(4).is_some_and(expr_is_ref_to_path)
}
Expr::Group(group) => expr_is_generated_auth_check_call(&group.expr),
Expr::Paren(paren) => expr_is_generated_auth_check_call(&paren.expr),
_ => false,
}
}
fn expr_is_scopes_map_arg(expr: &Expr) -> bool {
let Expr::MethodCall(outer) = expr else {
return false;
};
if outer.method != "map" {
return false;
}
let Expr::MethodCall(inner) = outer.receiver.as_ref() else {
return false;
};
inner.method == "as_ref" && path_expr_ends_with(&inner.receiver, "__autumn_token_scopes")
}
fn block_returns_ident(block: &Block, expected: &str) -> bool {
block.stmts.iter().any(|stmt| match stmt {
Stmt::Expr(Expr::Return(ret), _) => ret
.expr
.as_ref()
.is_some_and(|expr| path_expr_ends_with(expr, expected)),
_ => false,
})
}
fn block_is_generated_auth_failure_response(block: &Block) -> bool {
match block.stmts.as_slice() {
[Stmt::Expr(Expr::Return(ret), _)] => ret
.expr
.as_ref()
.is_some_and(|expr| expr_is_autumn_error_response(expr)),
[
Stmt::Expr(Expr::If(replay_if), _),
Stmt::Expr(Expr::Return(ret), _),
] => {
if_let_replays_finalized_session_response(replay_if)
&& ret
.expr
.as_ref()
.is_some_and(|expr| expr_is_autumn_error_response(expr))
}
_ => false,
}
}
fn expr_is_autumn_error_response(expr: &Expr) -> bool {
match expr {
Expr::Call(call) => {
path_expr_matches(
&call.func,
&[
"autumn_web",
"reexports",
"axum",
"response",
"IntoResponse",
"into_response",
],
) && call.args.len() == 1
&& call
.args
.first()
.is_some_and(|arg| path_expr_ends_with(arg, "__autumn_error"))
}
Expr::Group(group) => expr_is_autumn_error_response(&group.expr),
Expr::Paren(paren) => expr_is_autumn_error_response(&paren.expr),
_ => false,
}
}
fn if_let_replays_finalized_session_response(expr_if: &ExprIf) -> bool {
let Expr::Let(expr_let) = expr_if.cond.as_ref() else {
return false;
};
pat_is_some_replay_response(&expr_let.pat)
&& expr_is_finalized_session_replay_call(&expr_let.expr)
&& block_returns_ident(&expr_if.then_branch, "__autumn_response")
}
fn expr_is_finalized_session_replay_call(expr: &Expr) -> bool {
match expr {
Expr::Await(await_expr) => expr_is_finalized_session_replay_call(&await_expr.base),
Expr::Call(call)
if path_expr_matches(
&call.func,
&[
"autumn_web",
"idempotency",
"__replay_finalized_session_response",
],
) =>
{
call.args.len() == 1
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_idempotency_replay"))
}
Expr::Call(call)
if path_expr_matches(
&call.func,
&[
"autumn_web",
"idempotency",
"__replay_finalized_session_response_for_anonymous",
],
) =>
{
call.args.len() == 3
&& call
.args
.first()
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_session"))
&& call
.args
.iter()
.nth(1)
.is_some_and(expr_is_auth_session_key_call)
&& call
.args
.iter()
.nth(2)
.is_some_and(|arg| expr_is_ref_to_ident(arg, "__autumn_idempotency_replay"))
}
Expr::Group(group) => expr_is_finalized_session_replay_call(&group.expr),
Expr::Paren(paren) => expr_is_finalized_session_replay_call(&paren.expr),
_ => false,
}
}
fn expr_is_ref_to_ident(expr: &Expr, expected: &str) -> bool {
let Expr::Reference(reference) = expr else {
return false;
};
path_expr_ends_with(&reference.expr, expected)
}
fn expr_is_ref_to_path(expr: &Expr) -> bool {
matches!(expr, Expr::Reference(reference) if matches!(reference.expr.as_ref(), Expr::Path(_)))
}
fn expr_is_auth_session_key_call(expr: &Expr) -> bool {
let Expr::MethodCall(call) = expr else {
return false;
};
call.method == "auth_session_key"
&& call.args.is_empty()
&& path_expr_ends_with(&call.receiver, "__autumn_state")
}
const fn expr_is_string_literal(expr: &Expr) -> bool {
matches!(
expr,
Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(_),
..
})
)
}
fn generated_nested_response_body(block: &Block, index: usize) -> Option<&Block> {
let stmt = block.stmts.get(index)?;
match stmt {
Stmt::Local(local) if pat_binds_inner_response(&local.pat) => local
.init
.as_ref()
.and_then(|init| expr_nested_async_body(&init.expr))
.filter(|_| {
index + 2 == block.stmts.len()
&& block
.stmts
.get(index + 1)
.is_some_and(stmt_is_inner_response_tail)
}),
Stmt::Expr(expr, None) if index + 1 == block.stmts.len() => expr_nested_async_body(expr),
_ => None,
}
}
fn pat_binds_inner_response(pat: &Pat) -> bool {
match pat {
Pat::Ident(ident) => ident.ident == "__autumn_inner",
Pat::Type(typed) => pat_binds_inner_response(&typed.pat),
_ => false,
}
}
pub fn expr_nested_async_body(expr: &Expr) -> Option<&Block> {
match expr {
Expr::Async(expr_async) => Some(&expr_async.block),
Expr::Await(await_expr) => expr_nested_async_body(&await_expr.base),
Expr::Call(call)
if path_expr_matches(
&call.func,
&[
"autumn_web",
"reexports",
"axum",
"response",
"IntoResponse",
"into_response",
],
) && call.args.len() == 1 =>
{
call.args.first().and_then(expr_nested_async_body)
}
Expr::Call(call) if call.args.is_empty() => match unwrap_group_paren(&call.func) {
Expr::Closure(closure) => expr_nested_async_body(&closure.body),
_ => None,
},
Expr::Group(group) => expr_nested_async_body(&group.expr),
Expr::Paren(paren) => expr_nested_async_body(&paren.expr),
_ => None,
}
}
fn unwrap_group_paren(expr: &Expr) -> &Expr {
match expr {
Expr::Group(group) => unwrap_group_paren(&group.expr),
Expr::Paren(paren) => unwrap_group_paren(&paren.expr),
other => other,
}
}
const RESPONSE_REWRITING_GUARD_MARKERS: &[&str] = &[
"__AUTUMN_SECURED_ROLES",
"__AUTUMN_STEP_UP_MAX_AGE",
"__AUTUMN_THROTTLE_ROUTE_ID",
"__AUTUMN_AUTHORIZE_BINDINGS",
];
fn stmts_have_response_rewriting_guard_marker(stmts: &[Stmt]) -> bool {
stmts.iter().any(|stmt| {
matches!(
stmt,
Stmt::Item(Item::Const(item))
if RESPONSE_REWRITING_GUARD_MARKERS.contains(&item.ident.to_string().as_str())
)
})
}
pub fn generated_inner_response_binding(
block: &Block,
markerless_gate_budget: usize,
) -> Option<(&Type, &Expr, usize)> {
let len = block.stmts.len();
if len < 2 {
return None;
}
let index = len - 2;
let Stmt::Local(local) = &block.stmts[index] else {
return None;
};
if !pat_binds_inner_response(&local.pat) {
return None;
}
let Pat::Type(pat_type) = &local.pat else {
return None;
};
if !stmt_is_inner_response_tail(&block.stmts[index + 1]) {
return None;
}
let remaining_budget = if stmts_have_response_rewriting_guard_marker(&block.stmts[..index]) {
markerless_gate_budget
} else if markerless_gate_budget > 0 {
markerless_gate_budget - 1
} else {
return None;
};
let init_expr = &local.init.as_ref()?.expr;
Some((&pat_type.ty, init_expr, remaining_budget))
}
fn stmt_is_inner_response_tail(stmt: &Stmt) -> bool {
let Stmt::Expr(Expr::Call(call), None) = stmt else {
return false;
};
path_expr_matches(
&call.func,
&[
"autumn_web",
"reexports",
"axum",
"response",
"IntoResponse",
"into_response",
],
) && call.args.len() == 1
&& call
.args
.first()
.is_some_and(|arg| path_expr_ends_with(arg, "__autumn_inner"))
}
fn path_expr_ends_with(expr: &Expr, expected: &str) -> bool {
let Expr::Path(path) = expr else {
return false;
};
path_ends_with(&path.path, expected)
}
fn path_expr_matches(expr: &Expr, expected: &[&str]) -> bool {
let Expr::Path(path) = expr else {
return false;
};
path_matches(&path.path, expected)
}
fn path_matches(path: &syn::Path, expected: &[&str]) -> bool {
if path.segments.len() != expected.len() {
return false;
}
let root_matches = if expected.first() == Some(&"autumn_web") {
path.segments.first().is_some_and(|segment| {
segment.ident == autumn_macros_support::crate_path::current_target_path_segment()
})
} else {
path.segments
.first()
.zip(expected.first())
.is_some_and(|(segment, expected)| segment.ident == *expected)
};
root_matches
&& path
.segments
.iter()
.skip(1)
.zip(&expected[1..])
.all(|(segment, expected)| segment.ident == *expected)
}
fn path_ends_with(path: &syn::Path, expected: &str) -> bool {
path.segments
.last()
.is_some_and(|segment| segment.ident == expected)
}
#[cfg(test)]
mod tests {
use super::{block_has_replay_guard, should_own_replay};
#[test]
fn string_literal_does_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
let _ = "__AUTUMN_IDEMPOTENCY_REPLAY_GUARD";
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn marker_const_without_replay_call_does_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
let _ = "plain user const";
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn marker_const_and_non_returned_replay_call_do_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
let _ignored =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay);
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn generated_shape_after_user_statement_does_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
mutate_before_replay_stop();
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn nested_generated_shape_with_semicolon_does_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
::autumn_web::reexports::axum::response::IntoResponse::into_response(
(async move {
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
})
.await,
);
mutate_after_dropped_replay_response();
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn nested_generated_shape_without_tail_response_does_not_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
let __autumn_inner: ::autumn_web::reexports::axum::response::Response = (async move {
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
})
.await;
mutate_after_nested_replay_response();
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn nested_guard_in_non_autumn_into_response_does_not_count() {
let block: syn::Block = syn::parse_quote!({
evil::IntoResponse::into_response(
(async move {
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
})
.await,
)
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn nested_guard_in_extra_into_response_argument_does_not_count() {
let block: syn::Block = syn::parse_quote!({
::autumn_web::reexports::axum::response::IntoResponse::into_response(
side_effect_before_replay_stop(),
(async move {
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
})
.await,
)
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn secured_prologue_with_side_effect_argument_does_not_count() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_SECURED_ROLES: &[&str] = &["admin"];
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::auth::__check_secured_with_key(
side_effect_before_replay_stop(),
__autumn_state.auth_session_key(),
__AUTUMN_SECURED_ROLES,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn authorize_prologue_with_side_effect_argument_does_not_count() {
let block: syn::Block = syn::parse_quote!({
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::authorization::__check_policy::<Post>(
&__autumn_state,
&__autumn_session,
side_effect_before_replay_stop(),
&post,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn generated_const_and_replay_call_count_as_replay_guard() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn generated_throttle_prologue_before_replay_guard_counts() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_THROTTLE_ROUTE_ID: &str =
::core::concat!(::core::module_path!(), "::", "handler");
if let ::core::result::Result::Err(__autumn_throttle_response) =
::autumn_web::security::__check_throttle(
&__autumn_state,
__AUTUMN_THROTTLE_ROUTE_ID,
::autumn_web::security::ThrottleSpec::Inline {
limit: 1,
per_secs: 60,
key: ::core::option::Option::None,
},
&__autumn_throttle_headers,
__autumn_throttle_peer.as_ref().map(|ext| ext.0.0),
__autumn_throttle_principal.as_ref().map(|e| &e.0),
__autumn_throttle_session.as_ref().map(|e| &e.0),
__autumn_throttle_exempt.is_some(),
)
.await
{
return __autumn_throttle_response;
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn throttle_prologue_with_side_effect_argument_does_not_count() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_THROTTLE_ROUTE_ID: &str = "handler";
if let ::core::result::Result::Err(__autumn_throttle_response) =
::autumn_web::security::__check_throttle(
side_effect_before_replay_stop(),
__AUTUMN_THROTTLE_ROUTE_ID,
)
.await
{
return __autumn_throttle_response;
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(!block_has_replay_guard(&block));
}
#[test]
fn generated_secured_prologue_before_replay_guard_counts() {
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_SECURED_ROLES: &[&str] = &["admin"];
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::auth::__check_secured_with_key(
&__autumn_session,
__autumn_state.auth_session_key(),
__AUTUMN_SECURED_ROLES,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn generated_authorize_prologue_with_anonymous_session_replay_counts() {
let block: syn::Block = syn::parse_quote!({
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::authorization::__check_policy::<Post>(
&__autumn_state,
&__autumn_session,
"update",
&post,
)
.await
{
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_finalized_session_response_for_anonymous(
&__autumn_session,
__autumn_state.auth_session_key(),
&__autumn_idempotency_replay,
)
.await
{
return __autumn_response;
}
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn generated_authorize_scoped_prologue_before_replay_guard_counts() {
let block: syn::Block = syn::parse_quote!({
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::authorization::__check_policy_scoped::<Post>(
&__autumn_state,
&__autumn_session,
__autumn_token_scopes.as_ref().map(|__e| &__e.0),
"update",
&post,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn generated_authorize_wrapper_can_find_nested_secured_replay_guard() {
let block: syn::Block = syn::parse_quote!({
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::authorization::__check_policy::<Post>(
&__autumn_state,
&__autumn_session,
"update",
&post,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
let __autumn_inner: ::autumn_web::reexports::axum::response::Response = (async move {
const __AUTUMN_SECURED_ROLES: &[&str] = &["admin"];
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::auth::__check_secured_with_key(
&__autumn_session,
__autumn_state.auth_session_key(),
__AUTUMN_SECURED_ROLES,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
})
.await;
::autumn_web::reexports::axum::response::IntoResponse::into_response(__autumn_inner)
});
assert!(block_has_replay_guard(&block));
}
#[test]
fn generated_authorize_bindings_marker_does_not_hide_replay_guard() {
let generated = crate::authorize::authorize_macro(
quote::quote! { "update", resource = Post },
quote::quote! {
async fn update_post(post: Post) -> &'static str { "ok" }
},
);
let generated_fn: syn::ItemFn =
syn::parse2(generated).expect("#[authorize] must emit a parseable function");
assert!(
matches!(
generated_fn.block.stmts.first(),
Some(syn::Stmt::Item(syn::Item::Const(item)))
if item.ident == "__AUTUMN_AUTHORIZE_BINDINGS"
),
"the reduced block's FIRST statement must keep matching what #[authorize] emits \
(the rest of the real expansion is deliberately not walked here — see above)"
);
let block: syn::Block = syn::parse_quote!({
const __AUTUMN_AUTHORIZE_BINDINGS: &[(&str, &str)] = &[("update", "Post")];
if let ::core::result::Result::Err(__autumn_error) =
::autumn_web::authorization::__check_policy_scoped::<Post>(
&__autumn_state,
&__autumn_session,
__autumn_token_scopes.as_ref().map(|__e| &__e.0),
"update",
&post,
)
.await
{
return ::autumn_web::reexports::axum::response::IntoResponse::into_response(
__autumn_error,
);
}
const __AUTUMN_IDEMPOTENCY_REPLAY_GUARD: () = ();
if let ::core::option::Option::Some(__autumn_response) =
::autumn_web::idempotency::__replay_response(&__autumn_idempotency_replay)
{
return __autumn_response;
}
});
assert!(block_has_replay_guard(&block));
}
fn final_replay_marker_count(gate_item: &str, final_fn: &proc_macro2::TokenStream) -> usize {
format!("{gate_item} {final_fn}")
.matches("__AUTUMN_IDEMPOTENCY_REPLAY_GUARD")
.count()
}
#[test]
fn secured_above_authorize_emits_exactly_one_replay_guard() {
let secured_input = quote::quote! {
#[authorize("update", resource = Note)]
async fn update_note(note: Note) -> &'static str { "ok" }
};
let after_secured = crate::secured::secured_macro(quote::quote! { "admin" }, secured_input);
let mut fn_after_secured: syn::ItemFn =
crate::param_helpers::extract_fn_item(after_secured.clone(), "update_note");
fn_after_secured
.attrs
.retain(|a| !a.path().is_ident("authorize"));
let after_authorize = crate::authorize::authorize_macro(
quote::quote! { "update", resource = Note },
quote::quote! { #fn_after_secured },
);
let gate_item = after_secured
.to_string()
.split("async fn update_note")
.next()
.expect("the gate struct+impl precedes the fn in secured's output")
.to_string();
assert_eq!(
final_replay_marker_count(&gate_item, &after_authorize),
1,
"exactly one guard must own replay-serving end to end: \
gate:\n{gate_item}\n\nfinal fn:\n{after_authorize}"
);
}
#[test]
fn authorize_above_secured_emits_exactly_one_replay_guard() {
let authorize_input = quote::quote! {
#[secured("admin")]
async fn update_note(note: Note) -> &'static str { "ok" }
};
let after_authorize = crate::authorize::authorize_macro(
quote::quote! { "update", resource = Note },
authorize_input,
);
let mut fn_after_authorize: syn::ItemFn =
crate::param_helpers::extract_fn_item(after_authorize, "update_note");
fn_after_authorize
.attrs
.retain(|a| !a.path().is_ident("secured"));
let after_secured = crate::secured::secured_macro(
quote::quote! { "admin" },
quote::quote! { #fn_after_authorize },
);
assert_eq!(
after_secured
.to_string()
.matches("__AUTUMN_IDEMPOTENCY_REPLAY_GUARD")
.count(),
1,
"exactly one guard must own replay-serving end to end: {after_secured}"
);
}
#[test]
fn should_own_replay_defers_to_authorize_across_the_sunset_check() {
let generated = crate::authorize::authorize_macro(
quote::quote! { "update", resource = Post },
quote::quote! {
async fn update_post(post: Post) -> &'static str { "ok" }
},
);
let generated_fn: syn::ItemFn =
syn::parse2(generated).expect("#[authorize] must emit a parseable function");
assert!(
block_has_replay_guard(&generated_fn.block),
"the real #[authorize] expansion already owns replay-serving; the scan must \
see past the sunset-check statement to find it"
);
assert!(
!should_own_replay(&generated_fn),
"a gate macro stacking on top of real #[authorize] output must defer replay \
ownership to authorize, not claim it for a pre-body gate"
);
}
}