Skip to main content

astarte_message_hub/
error.rs

1// This file is part of Astarte.
2//
3// Copyright 2022, 2026 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Errors for the message hub.
20
21use std::io;
22use std::path::PathBuf;
23
24use astarte_device_sdk::introspection::AddInterfaceError;
25use astarte_interfaces::error::Error as InterfaceError;
26use astarte_message_hub_proto::prost::UnknownEnumValue;
27use tonic::{Code, Status};
28use tracing::error;
29use uuid::Uuid;
30
31use crate::astarte::handler::DeviceError;
32use crate::config::dynamic::http::HttpError;
33
34/// A list specifying general categories of Astarte Message Hub error.
35#[derive(thiserror::Error, Debug)]
36pub enum AstarteMessageHubError {
37    /// Error returned by the Astarte SDK
38    #[error(transparent)]
39    Astarte(#[from] astarte_device_sdk::error::Error),
40
41    /// Invalid date
42    #[error("{0}")]
43    AstarteInvalidData(String),
44
45    /// Wrapper for an io error
46    #[error(transparent)]
47    Io(#[from] std::io::Error),
48
49    /// Unrecoverable error
50    #[error("unrecoverable error ({0})")]
51    Fatal(String),
52
53    /// Fail while sending or receiving data
54    #[error(transparent)]
55    Transport(#[from] tonic::transport::Error),
56
57    /// Error returned by Zbus
58    #[error(transparent)]
59    Zbus(#[from] zbus::Error),
60
61    /// Http server error
62    #[error("HTTP server error, {0}")]
63    HttpServer(#[from] HttpError),
64
65    /// Wrapper for integer conversion errors
66    #[error("couldn't convert timestamp, {0}")]
67    Timestamp(&'static str),
68
69    /// Error returned by  the device
70    #[error("error returned by the device")]
71    Device(#[from] DeviceError),
72
73    /// Couldn't parse the node ID
74    #[error("couldn't parse node id")]
75    Uuid(#[from] uuid::Error),
76
77    /// Couldn't find the node id
78    #[error("node id not found {0}")]
79    NodeId(Uuid),
80
81    /// Failed to parse am Interface
82    #[error("failed to parse am Interface")]
83    ParseInterface(#[from] InterfaceError),
84
85    /// Failed to add interfaces while building an Astarte device
86    #[error("failed to add interfaces while building an Astarte device")]
87    AddInterface(#[from] AddInterfaceError),
88
89    /// Couldn't read the configuration
90    #[error("coudln't read the configuration")]
91    Config(#[from] ConfigError),
92
93    /// Received an invalid ownership enumeration field
94    #[error("received an invalid ownership enumeration field, {0}")]
95    InvalidProtoOwnership(#[source] UnknownEnumValue),
96
97    /// Interface not present in the node introspection
98    #[error("interface {interface} not present in the introspection of node {node_id}")]
99    MissingInterface {
100        /// The interface name
101        interface: String,
102        /// The node ID
103        node_id: Uuid,
104    },
105
106    /// Couldn't convert value from or to proto
107    #[error("couldn't convert value {ctx}")]
108    Conversion {
109        /// Reason why the conversion failed
110        ctx: &'static str,
111    },
112}
113
114impl From<AstarteMessageHubError> for Status {
115    fn from(value: AstarteMessageHubError) -> Self {
116        error!(error = %value, "grpc call failed");
117
118        let code = match value {
119            AstarteMessageHubError::Astarte(_)
120            | AstarteMessageHubError::Io(_)
121            | AstarteMessageHubError::Fatal(_)
122            | AstarteMessageHubError::Config(_)
123            | AstarteMessageHubError::Transport(_)
124            | AstarteMessageHubError::Zbus(_)
125            | AstarteMessageHubError::HttpServer(_)
126            | AstarteMessageHubError::ParseInterface(_)
127            | AstarteMessageHubError::AddInterface(_) => Code::Internal,
128            AstarteMessageHubError::Device(ref err) => err.into(),
129            AstarteMessageHubError::AstarteInvalidData(_)
130            | AstarteMessageHubError::Timestamp(_)
131            | AstarteMessageHubError::Uuid(_)
132            | AstarteMessageHubError::NodeId(_)
133            | AstarteMessageHubError::InvalidProtoOwnership(_)
134            | AstarteMessageHubError::Conversion { .. }
135            | AstarteMessageHubError::MissingInterface { .. } => Code::InvalidArgument,
136        };
137
138        Status::new(code, value.to_string())
139    }
140}
141
142/// Reason why a configuration is invalid.
143#[derive(thiserror::Error, Debug)]
144pub enum ConfigError {
145    /// Missing required field in the configuration file
146    #[error("{0} field is missing")]
147    MissingField(&'static str),
148    /// Missing both the pairing token and the credentials secret
149    #[error("either the pairing token or credential secret must be provided")]
150    Credentials,
151    /// The provided interface path is not a directory
152    #[error("interface path {0:?} is not a directory")]
153    InvalidInterfaceDirectory(Option<PathBuf>),
154    /// Couldn't deserialize the configuration file
155    #[error("coudln't deserialize the configuration file")]
156    Toml(#[from] toml::de::Error),
157    /// Couldn't read configuration file
158    #[error("couldn't read configuration file")]
159    File(#[from] io::Error),
160    /// Couldn't parse url.
161    #[error("coudldn't parse url")]
162    Url(#[from] url::ParseError),
163    /// Couldn't read the dynamic generated file.
164    #[error("coudldn't read dynamic configuration {0}")]
165    Dynamic(PathBuf),
166}