cageforge_policy/error.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! Typed failures raised by policy construction, normalization, and queries.
4//!
5//! [`crate::PolicyError`] is the boundary between invalid portable policy data
6//! and the valid values consumed by composition or a native backend.
7
8use crate::AccessMode;
9use std::net::SocketAddr;
10use std::path::PathBuf;
11use thiserror::Error;
12
13/// Errors returned when a policy value cannot represent a safe request.
14#[derive(Debug, Error, Clone, PartialEq, Eq)]
15pub enum PolicyError {
16 /// A path argument was empty.
17 #[error("path cannot be empty")]
18 EmptyPath,
19 /// A path that must be absolute was relative.
20 #[error("path must be absolute: {}", path.display())]
21 ExpectedAbsolute {
22 /// The path that was supplied by the caller.
23 path: PathBuf,
24 },
25 /// A path that must be workspace-relative was absolute.
26 #[error("path must be workspace-relative: {}", path.display())]
27 ExpectedRelative {
28 /// The path that was supplied by the caller.
29 path: PathBuf,
30 },
31 /// A path contained a NUL character that an operating-system backend cannot use.
32 #[error("path must not contain a NUL character: {}", path.display())]
33 PathContainsNul {
34 /// The path that was supplied by the caller.
35 path: PathBuf,
36 },
37 /// A workspace-relative path attempts to escape its root.
38 #[error(
39 "workspace-relative path cannot contain parent traversal: {}",
40 path.display()
41 )]
42 ParentTraversal {
43 /// The workspace-relative path that attempted to escape its root.
44 path: PathBuf,
45 },
46 /// A domain pattern is empty or malformed.
47 #[error("invalid domain pattern: {pattern}")]
48 InvalidDomainPattern {
49 /// The domain pattern that failed validation.
50 pattern: String,
51 },
52 /// An IP-literal target claimed a resolved address for a different IP.
53 #[error("resolved address {address} does not match IP literal {literal}")]
54 ResolvedAddressMismatch {
55 /// The normalized literal supplied as the target host.
56 literal: String,
57 /// The inconsistent address in the resolution snapshot.
58 address: SocketAddr,
59 },
60 /// A filesystem glob is empty or malformed.
61 #[error("invalid glob pattern {pattern:?}: {reason}")]
62 InvalidGlobPattern {
63 /// The glob pattern that failed validation.
64 pattern: String,
65 /// The reason the pattern is invalid.
66 reason: String,
67 },
68 /// A glob requested an access mode that is not portable across backends.
69 #[error("filesystem glob rules support only deny access; requested {access:?}")]
70 UnsupportedGlobAccess {
71 /// The access mode requested for the glob.
72 access: AccessMode,
73 },
74 /// A protected relative path is empty or unsafe.
75 #[error("invalid protected relative path {path:?}: {reason}")]
76 InvalidProtectedPath {
77 /// The protected path that failed validation.
78 path: PathBuf,
79 /// The reason the path is invalid.
80 reason: String,
81 },
82 /// A path resolution context contains an invalid value.
83 #[error("{message}")]
84 InvalidContext {
85 /// A human-readable explanation of the invalid context.
86 message: String,
87 },
88 /// A policy rule is internally inconsistent.
89 #[error("{message}")]
90 InvalidRule {
91 /// A human-readable explanation of the inconsistent rule.
92 message: String,
93 },
94 /// A local-IPC endpoint is malformed or cannot be represented safely.
95 #[error("invalid local-IPC endpoint {endpoint:?}: {reason}")]
96 InvalidLocalIpcEndpoint {
97 /// The rejected endpoint spelling.
98 endpoint: String,
99 /// The validation rule that rejected it.
100 reason: &'static str,
101 },
102}