Skip to main content

openjd_sessions/
error.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// Copyright by contributors to this project.
3// SPDX-License-Identifier: (Apache-2.0 OR MIT)
4
5//! Error types for openjd-sessions.
6
7use std::path::PathBuf;
8
9use crate::session::SessionState;
10
11/// Errors that can occur during session operations.
12///
13/// ```
14/// use openjd_sessions::SessionError;
15///
16/// let err = SessionError::Runtime("something went wrong".into());
17/// assert_eq!(err.to_string(), "something went wrong");
18/// ```
19#[derive(Debug, thiserror::Error)]
20#[non_exhaustive]
21pub enum SessionError {
22    /// Session is not in the expected state for the requested operation.
23    #[error("Session must be in {} state, current: {current}", format_expected(.expected))]
24    InvalidState {
25        expected: Vec<SessionState>,
26        current: SessionState,
27    },
28
29    /// An environment's onEnter or onExit script failed.
30    #[error("Environment '{name}' {action} failed: {reason}")]
31    EnvironmentScriptFailed {
32        name: String,
33        action: String,
34        reason: String,
35    },
36
37    /// Failed to resolve a format string expression.
38    #[error("Failed to resolve {context}: {reason}")]
39    FormatString { context: String, reason: String },
40
41    /// Failed to write an embedded file.
42    #[error("Failed to write embedded file '{name}': {source}")]
43    EmbeddedFile {
44        name: String,
45        #[source]
46        source: std::io::Error,
47    },
48
49    /// An embedded file's filename is not a safe single path component.
50    ///
51    /// Raised when a `filename` field's value contains path separators,
52    /// parent-directory components, null bytes, or is otherwise unsafe.
53    /// This is a defense-in-depth check; `openjd-model` also rejects
54    /// path separators in filenames at template validation time per the
55    /// 2023-09 spec (§6.1.1 `<Filename>`).
56    #[error("Embedded file '{name}' has unsafe filename '{filename}': {reason}")]
57    EmbeddedFilePath {
58        name: String,
59        filename: String,
60        reason: String,
61    },
62
63    /// Failed to create or access the working directory.
64    #[error("Failed to create working directory {path}: {source}")]
65    WorkingDirectory {
66        path: PathBuf,
67        #[source]
68        source: std::io::Error,
69    },
70
71    /// Subprocess failed to start.
72    #[error("Failed to start subprocess '{command}': {source}")]
73    SubprocessStart {
74        command: String,
75        #[source]
76        source: std::io::Error,
77    },
78
79    /// Failed to create or manage a temp directory.
80    #[error("Failed to create temp directory in {path}: {source}")]
81    TempDir {
82        path: PathBuf,
83        #[source]
84        source: std::io::Error,
85    },
86
87    /// A generic runtime error.
88    #[error("{0}")]
89    Runtime(String),
90
91    /// Attempted to enter an environment that is already active in this session.
92    #[error("Environment {id} has already been entered in this Session.")]
93    DuplicateEnvironment { id: String },
94
95    /// Referenced an environment identifier that does not exist in this session.
96    #[error("Unknown environment identifier: {identifier}")]
97    UnknownEnvironment { identifier: String },
98
99    /// Failed to set file ownership or permissions (chown/chmod).
100    #[error("Failed to set permissions on '{path}': {reason}")]
101    PathPermissions { path: String, reason: String },
102
103    /// Cross-user helper IPC communication failure.
104    #[error("Cross-user helper error: {0}")]
105    HelperCommunication(String),
106
107    /// Attempted to exit an environment out of LIFO order.
108    #[error(
109        "Must exit the most recently entered environment first. Expected {expected}, got {got}"
110    )]
111    LifoViolation { expected: String, got: String },
112}
113
114fn format_expected(states: &[SessionState]) -> String {
115    match states {
116        [single] => single.to_string(),
117        _ => states
118            .iter()
119            .map(|s| s.to_string())
120            .collect::<Vec<_>>()
121            .join(" or "),
122    }
123}