Skip to main content

sift_query_sqlx/web/
mod.rs

1//! PostgreSQL web adapter primitives shared by axum, actix-web, and poem.
2//!
3//! Request parsing is kept aligned with the portable common adapter, while
4//! this module maps PostgreSQL's richer internal error enum to public HTTP
5//! errors.
6
7use sift_query_core::{ParamError, QueryLimits, RawParams};
8
9use crate::PgSearchError;
10
11mod search;
12pub use search::{SearchParamConfig, SearchParams};
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct QueryParams(pub RawParams);
16
17impl QueryParams {
18    pub fn parse(query: Option<&str>) -> Result<Self, ParamError> {
19        RawParams::parse(query.unwrap_or_default()).map(Self)
20    }
21
22    pub fn parse_with_limits(
23        query: Option<&str>,
24        limits: &QueryLimits,
25    ) -> Result<Self, ParamError> {
26        RawParams::parse_with_limits(query.unwrap_or_default(), limits).map(Self)
27    }
28
29    #[must_use]
30    pub fn raw(&self) -> &RawParams {
31        &self.0
32    }
33}
34
35/// Safe default HTTP error adapter. Applications can override the mapping
36/// using `public_error` without publishing the underlying database error.
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct WebError(pub PublicError);
39
40impl std::fmt::Display for WebError {
41    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        formatter.write_str(self.0.code)
43    }
44}
45impl std::error::Error for WebError {}
46impl From<PgSearchError> for WebError {
47    fn from(error: PgSearchError) -> Self {
48        Self(public_error(&error))
49    }
50}
51impl From<ParamError> for WebError {
52    fn from(error: ParamError) -> Self {
53        if error == ParamError::QueryTooLong {
54            return search::error(413, "query_too_long");
55        }
56        Self(PublicError {
57            status: 400,
58            code: "invalid_query",
59        })
60    }
61}
62impl PublicError {
63    #[must_use]
64    pub fn body(self) -> String {
65        format!("{{\"error\":{{\"code\":\"{}\"}}}}", self.code)
66    }
67}
68
69#[cfg(feature = "actix-web")]
70mod actix;
71#[cfg(feature = "axum")]
72mod axum;
73#[cfg(feature = "poem")]
74mod poem;
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct PublicError {
78    pub status: u16,
79    pub code: &'static str,
80}
81
82#[must_use]
83pub fn public_error(error: &PgSearchError) -> PublicError {
84    match error {
85        PgSearchError::Param(ParamError::QueryTooLong)
86        | PgSearchError::Plan(sift_query_core::PlanError::Param(ParamError::QueryTooLong)) => {
87            WebError::from(ParamError::QueryTooLong).0
88        }
89        PgSearchError::Param(_) | PgSearchError::Plan(sift_query_core::PlanError::Param(_)) => {
90            PublicError {
91                status: 400,
92                code: "invalid_query",
93            }
94        }
95        PgSearchError::Timeout(_)
96        | PgSearchError::Extension(crate::ExtensionError::LabelTimeout) => PublicError {
97            status: 504,
98            code: "query_timeout",
99        },
100        PgSearchError::ScanLimitExceeded { .. } => PublicError {
101            status: 413,
102            code: "scan_limit_exceeded",
103        },
104        _ => PublicError {
105            status: 500,
106            code: "query_failed",
107        },
108    }
109}