conmonrs/streaming_server/remotecommand/
status.rs1use super::{SUCCESS_STATUS, Version};
4use anyhow::{Context, Result};
5use serde::Serialize;
6use std::fmt;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub(crate) enum Operation {
11 Exec,
12 Attach,
13}
14
15impl Operation {
16 fn context(self) -> &'static str {
18 match self {
19 Self::Exec => "error executing command in container",
20 Self::Attach => "error attaching to container",
21 }
22 }
23}
24
25impl fmt::Display for Operation {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 f.write_str(match self {
28 Self::Exec => "exec",
29 Self::Attach => "attach",
30 })
31 }
32}
33
34#[derive(Debug, PartialEq)]
36pub(crate) enum Status {
37 Success,
38 ExitCode(i32),
39
40 Error(String),
44}
45
46impl Status {
47 pub(crate) fn exited(code: i32) -> Self {
51 match code {
52 0 => Self::Success,
53 1.. => Self::ExitCode(code),
54 _ => Self::failed(
55 Operation::Exec,
56 format_args!("unable to get the exit code of the command ({code})"),
57 ),
58 }
59 }
60
61 pub(crate) fn failed(operation: Operation, error: impl fmt::Display) -> Self {
63 Self::Error(format!(
64 "Internal error occurred: {}: {error}",
65 operation.context()
66 ))
67 }
68
69 pub(super) fn encode(&self, version: Version) -> Result<Option<Vec<u8>>> {
71 if version.json_status() {
72 return match self {
73 Self::Success => Ok(Some(SUCCESS_STATUS.to_vec())),
74 Self::ExitCode(code) => Self::json(&ErrorMessage::new(code)).map(Some),
75 Self::Error(message) => {
76 Self::json(&ErrorMessage::internal(message.clone())).map(Some)
77 }
78 };
79 }
80 Ok(match self {
81 Self::Success => None,
82 Self::ExitCode(code) => Some(
83 format!("command terminated with non-zero exit code: exit status {code}")
84 .into_bytes(),
85 ),
86 Self::Error(message) => Some(message.clone().into_bytes()),
87 })
88 }
89
90 fn json(status: &impl Serialize) -> Result<Vec<u8>> {
91 serde_json::to_vec(status).context("serialize status message")
92 }
93}
94
95#[derive(Debug, Serialize)]
98pub(super) struct ErrorMessage {
99 status: &'static str,
100 message: String,
101 reason: &'static str,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 details: Option<ErrorDetails>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 code: Option<u16>,
106}
107
108impl ErrorMessage {
109 pub(super) fn new<T>(exit_code: T) -> Self
112 where
113 T: fmt::Display,
114 {
115 Self {
116 status: "Failure",
117 message: format!("command terminated with non-zero exit code: exit status {exit_code}"),
118 reason: "NonZeroExitCode",
119 details: Some(ErrorDetails {
120 causes: vec![ErrorCause {
121 reason: "ExitCode",
122 message: exit_code.to_string(),
123 }],
124 }),
125 code: None,
126 }
127 }
128
129 fn internal(message: String) -> Self {
132 Self {
133 status: "Failure",
134 message,
135 reason: "InternalError",
136 details: None,
137 code: Some(500),
138 }
139 }
140}
141
142#[derive(Debug, Serialize)]
144struct ErrorDetails {
145 causes: Vec<ErrorCause>,
146}
147
148#[derive(Debug, Serialize)]
150struct ErrorCause {
151 reason: &'static str,
152 message: String,
153}