Skip to main content

osproxy_rewrite/
error.rs

1//! Failures the pure body transforms can return.
2
3use osproxy_core::json::JsonError;
4use osproxy_core::FieldName;
5use thiserror::Error;
6
7/// A failure applying a body transform.
8///
9/// These are document-shape failures (the body is not what the transform
10/// requires) or safety failures (a client field would collide with an injected
11/// tenancy field). They carry only field *names* and shapes, never values, so
12/// they are safe to surface in telemetry (NFR-S2).
13#[non_exhaustive]
14#[derive(Debug, Error, PartialEq, Eq)]
15pub enum RewriteError {
16    /// The body was expected to be a JSON object but was not.
17    #[error("document body is not a JSON object")]
18    NotAnObject,
19
20    /// The body bytes were not valid JSON.
21    #[error("document body is not valid JSON")]
22    InvalidJson,
23
24    /// A field the transform must inject already exists in the client document.
25    /// Rejected rather than overwritten: a client could otherwise spoof a
26    /// tenancy field and defeat isolation (`docs/03`).
27    #[error("client document already contains reserved field")]
28    ReservedFieldCollision {
29        /// The colliding field name.
30        field: FieldName,
31    },
32
33    /// A path referenced by a partition key or id template does not resolve to
34    /// a scalar value in the document.
35    #[error("path does not resolve to a scalar value in the document")]
36    PathNotScalar {
37        /// The dotted path that failed to resolve.
38        path: String,
39    },
40
41    /// An id template referenced a placeholder the transform does not support.
42    #[error("unsupported id-template placeholder")]
43    UnsupportedPlaceholder {
44        /// The placeholder text, without braces.
45        placeholder: String,
46    },
47
48    /// A `_bulk` action line is not a single-key `{verb: {…}}` object, names an
49    /// unknown verb, or an action that needs a source line has none.
50    #[error("malformed _bulk action line")]
51    MalformedBulkAction,
52
53    /// An id template cannot be reversed to recover a logical id on the read
54    /// path: a logical→physical mapping needs exactly one `{body.<path>}`
55    /// placeholder (the natural key), so a template with none or several is not
56    /// usable for `GetById`/`DeleteById` (`docs/03` §4, `docs/04` §5).
57    #[error("id template is not reversible: needs exactly one body placeholder")]
58    IrreversibleIdTemplate,
59
60    /// A shared-index search carries a construct that escapes the mandatory
61    /// partition filter, a `global` aggregation (which OpenSearch evaluates
62    /// against the whole index, ignoring the query) or a `suggest` block (which
63    /// runs independent of the query). Either would read across partitions, so
64    /// it is refused: the isolation boundary is filter-or-reject, never
65    /// best-effort (`docs/03` §5, NFR-S4). Carries only the construct name.
66    #[error("search construct bypasses the partition filter: {construct}")]
67    Unfilterable {
68        /// The offending construct: `"global aggregation"` or `"suggest"`.
69        construct: &'static str,
70    },
71}
72
73/// Maps a byte-scanner failure onto the transform error vocabulary, so the
74/// byte-level inject/id primitives surface the same variants as the `Value` path.
75impl From<JsonError> for RewriteError {
76    fn from(err: JsonError) -> Self {
77        match err {
78            JsonError::Invalid => Self::InvalidJson,
79            JsonError::NotAnObject => Self::NotAnObject,
80            JsonError::PathNotScalar { path } => Self::PathNotScalar { path },
81        }
82    }
83}