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}