Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
9 changes: 4 additions & 5 deletions crates/aisix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,10 @@ pub use models::{
GuardrailExecution, GuardrailHookPoint, GuardrailKind, GuardrailMetricsSink,
GuardrailMonitorHit, KeywordConfig, KeywordPattern, McpAuthType, McpProtocolVersion,
McpRateLimit, McpServer, McpServerType, McpTransport, Model, ObservabilityExporter,
ParamConstraints, PassthroughAuthMode, PassthroughCredentialMode, PassthroughProtocol,
PassthroughRoute, PolicyScope, PolicyWindow, ProviderKey, RateLimit, RateLimitPolicy,
RequestOverrides, ResponseOverrides, Routing, RoutingStrategy, RoutingTarget, SchemaError,
StreamDoneMarker, TelemetryKind, TelemetryTags, WhenAllUnavailablePolicy,
DEFAULT_COOLDOWN_TRIGGER_STATUSES,
ParamConstraints, PassthroughAuthMode, PassthroughCredentialMode, PassthroughRoute,
PolicyScope, PolicyWindow, ProviderKey, RateLimit, RateLimitPolicy, RequestOverrides,
ResponseOverrides, Routing, RoutingStrategy, RoutingTarget, SchemaError, StreamDoneMarker,
TelemetryKind, TelemetryTags, WhenAllUnavailablePolicy, DEFAULT_COOLDOWN_TRIGGER_STATUSES,
};
pub use resource::{Resource, ResourceEntry};
pub use snapshot::{ResourceTable, SnapshotHandle};
Expand Down
4 changes: 1 addition & 3 deletions crates/aisix-core/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ pub use observability_exporter::{
ObjectStoreProvider, ObservabilityExporter, OtlpHttpConfig, SlsContentMode,
};
pub use oidc_provider::{BoundClaimExpect, OidcProvider};
pub use passthrough_route::{
PassthroughAuthMode, PassthroughCredentialMode, PassthroughProtocol, PassthroughRoute,
};
pub use passthrough_route::{PassthroughAuthMode, PassthroughCredentialMode, PassthroughRoute};
pub use policy_conditions::{
eval_condition_nodes, validate_condition_nodes, ConditionGroup, ConditionInput, ConditionLogic,
ConditionNode, ConditionOperator, ConditionValue, GroupByDimension, PolicyAction,
Expand Down
72 changes: 29 additions & 43 deletions crates/aisix-core/src/models/passthrough_route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,6 @@ pub struct PassthroughRoute {
#[schemars(length(min = 1))]
pub provider_key_id: Option<String>,

/// Body-shape hint for auditing, guardrails and usage extraction.
/// Parsing is best-effort: a body that does not match the declared
/// shape degrades to `raw` handling, it is never rejected for shape.
#[serde(default)]
pub protocol: PassthroughProtocol,

/// Relay `text/event-stream` upstream responses incrementally. When
/// `false` streaming responses are fully buffered like any other body.
#[serde(default = "default_true")]
pub streaming: bool,

/// Optional header carrying the end-user identity injected by the
/// upstream network device (e.g. `x-aisix-user`). Its value is recorded
/// on the usage event for per-employee audit attribution and stripped
Expand All @@ -135,12 +124,11 @@ pub struct PassthroughRoute {
#[schemars(regex(pattern = "^[!#$%&'*+.^_`|~0-9a-z-]+$"), length(min = 1))]
pub identity_header: Option<String>,

/// Maximum time, in milliseconds, for a non-streaming upstream
/// exchange. On a streaming route it bounds the response-header phase
/// and any non-SSE body read, but never a healthy SSE relay (which
/// ends with the upstream stream or the client hanging up). When
/// omitted, the gateway default request timeout applies to
/// non-streaming exchanges only.
/// Maximum time, in milliseconds, for the upstream exchange. Bounds
/// the response-header phase and any non-SSE body read, but never a
/// healthy SSE relay (which ends with the upstream stream or the
/// client hanging up). When omitted, the gateway default request
/// timeout applies the same way.
#[serde(default, skip_serializing_if = "Option::is_none")]
#[schemars(range(min = 1))]
pub timeout_ms: Option<u64>,
Expand Down Expand Up @@ -193,30 +181,6 @@ pub enum PassthroughCredentialMode {
ForwardClient,
}

/// Body-shape hint for a passthrough route.
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum PassthroughProtocol {
/// No parsing: bodies are opaque byte blobs (guardrails scan them as
/// one lossy-UTF-8 text).
#[default]
Raw,
/// OpenAI-compatible chat envelope (`messages`, streamed
/// `choices[].delta.content`, final-chunk / response `usage`).
OpenaiChat,
/// OpenAI-compatible legacy completions / FIM envelope (`prompt` [+
/// `suffix`], streamed `choices[].text`, `usage`).
OpenaiCompletions,
/// OpenAI Responses API envelope: `input` items on the request,
/// `output` items on the response, `response.output_text.delta`
/// events while streaming, and `usage` in the
/// `input_tokens`/`output_tokens` spelling — carried on the terminal
/// `response.completed` event when the response streams.
OpenaiResponses,
}

impl Resource for PassthroughRoute {
fn id(&self) -> &str {
&self.runtime_id
Expand Down Expand Up @@ -476,8 +440,6 @@ mod tests {
let r = minimal();
assert_eq!(r.auth_mode, PassthroughAuthMode::GatewayKey);
assert_eq!(r.credential_mode, PassthroughCredentialMode::Inject);
assert_eq!(r.protocol, PassthroughProtocol::Raw);
assert!(r.streaming);
assert!(r.enabled);
assert!(!r.preserve_host);
}
Expand Down Expand Up @@ -608,6 +570,30 @@ mod coupling_tests {
assert!(validate_passthrough_route_lenient(&doc).is_ok());
}

#[test]
fn removed_protocol_and_streaming_fields_are_unknown() {
// The pre-0.10.0 dev cycle carried `protocol` / `streaming` route
// fields; both were removed before the kind ever shipped (the
// envelope is now detected per request, SSE always relays
// incrementally). The strict write path rejects them like any
// unknown field; the lenient etcd path tolerates-and-strips.
for (field, value) in [
("protocol", json!("openai_responses")),
("streaming", json!(false)),
] {
let mut doc = base();
doc[field] = value;
assert!(
validate_passthrough_route(&doc).is_err(),
"strict must reject unknown field {field}"
);
assert!(
validate_passthrough_route_lenient(&doc).is_ok(),
"lenient must tolerate unknown field {field}"
);
}
}

#[test]
fn cross_mode_leftover_companions_are_rejected() {
// A companion outside its mode is never consulted at runtime, so
Expand Down
12 changes: 1 addition & 11 deletions crates/aisix-core/src/models/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ pub fn claim_mapping_root_schema() -> Value {
/// the [`PassthroughRoute`](crate::models::PassthroughRoute) struct. Uses the
/// nullable `Option` representation (`true`) so unset optional fields accept
/// an explicit `null` as well as being absent. The `auth_mode` /
/// `credential_mode` / `protocol` closed sets come from their enums; every
/// `credential_mode` closed sets come from their enums; every
/// cross-field invariant (match dimensions, target shape, per-mode required
/// companions) is injected as an `allOf` (see
/// [`super::passthrough_route::passthrough_route_coupling`]) so the strict
Expand Down Expand Up @@ -755,16 +755,6 @@ pub fn passthrough_route_root_schema() -> Value {
("forward_client", "Forward the caller's own credential"),
],
);
title_single_value_enum_variants(
defs,
"PassthroughProtocol",
&[
("raw", "Opaque body"),
("openai_chat", "OpenAI-compatible chat"),
("openai_completions", "OpenAI-compatible completions / FIM"),
("openai_responses", "OpenAI Responses API"),
],
);
}
schema
}
Expand Down
Loading