Repository: exein-io/pulsar
Pull request #338: Improve error handling in engine_api client using thiserror. Fixes #326
Author: login: zedddie
Labels: name: enhancement
description: New feature or request
Created: 2025-04-12T03:34:40Z | Merged: 2025-04-24T16:31:39Z

# Improve engine_api errors

Improve errors in engine_api [#326](https://github.com/exein-io/pulsar/issues/326)

(this is my second time trying to contribute, feedback is welcome)

- [x] run `cargo fmt`;
- [x] run `cargo clippy`;
- [x] run `cargo test`and all tests pass;
- [x] linked to the originating issue (if applicable).


Linked issue #326: improve errors in `engine_api` module
define a proper error with `thiserror` in `engine_api` client

Diff:
diff --git a/crates/engine-api/src/client.rs b/crates/engine-api/src/client.rs
index 3b73698b..f65c061d 100644
--- a/crates/engine-api/src/client.rs
+++ b/crates/engine-api/src/client.rs
@@ -1,6 +1,5 @@
 use std::{ffi::CString, os::unix::prelude::FileTypeExt};
 
-use anyhow::{Context, Result, anyhow, bail, ensure};
 use futures::{Stream, StreamExt};
 use http_body_util::{BodyExt, Either, Empty, Full};
 use hyper::body::{Buf, Bytes};
@@ -13,7 +12,7 @@ use tokio_tungstenite::{client_async, tungstenite::Message};
 
 use crate::{
     dto::{ConfigKV, ModuleConfigKVs},
-    error::WebsocketError,
+    error::{EngineClientError, WebsocketError},
 };
 
 #[derive(Debug, Clone)]
@@ -23,44 +22,43 @@ pub struct EngineApiClient {
 }
 
 impl EngineApiClient {
-    pub fn new() -> Result<Self> {
+    pub fn new() -> Result<Self, EngineClientError> {
         Self::unix(super::DEFAULT_UDS.to_owned())
     }
 
-    pub fn unix(socket: String) -> Result<Self> {
+    pub fn unix(socket: String) -> Result<Self, EngineClientError> {
         // The is a bug using Metadata in combination with OpenOption when using pulsar cli via sudo so I had to use the libc to verify the permissions
         // Probably https://stackoverflow.com/questions/71505367/no-such-a-file-or-directory-even-path-is-file-true it's a reference
         // TODO: Should be investigated
 
         // Check if input exists and if it is a unix socket
         match std::fs::metadata(&socket) {
-            Err(err) => match err.kind() {
-                std::io::ErrorKind::NotFound => {
-                    bail!("'{socket}' not found. Check if the daemon is running")
-                }
-                std::io::ErrorKind::PermissionDenied => {
-                    bail!("No write permission on '{socket}'")
-                }
-                _ => {
-                    bail!(
-                        anyhow::Error::new(err)
-                            .context(format!("Failed to get '{socket}' metadata"))
-                    )
-                }
-            },
+            Err(err) => {
+                return match err.kind() {
+                    std::io::ErrorKind::NotFound => Err(EngineClientError::SocketNotFound(socket)),
+                    std::io::ErrorKind::PermissionDenied => {
+                        Err(EngineClientError::NoReadPermission(socket))
+                    }
+                    _ => Err(EngineClientError::FailedToGetMetadata(socket)),
+                };
+            }
             Ok(metadata) => {
-                ensure!(
-                    metadata.file_type().is_socket(),
-                    "'{socket}' is not a unix socket"
-                );
+                if !metadata.file_type().is_socket() {
+                    return Err(EngineClientError::NotASocket(socket));
+                }
             }
         };
 
         // Check for write permission on socket
-        let cstring = CString::new(socket.as_str())
-            .with_context(|| format!("Can't convert '{socket}' to a valid string "))?;
+        let cstring = match CString::new(socket.as_str()) {
+            Ok(cs) => cs,
+            Err(err) => return Err(EngineClientError::CStringConversion(err)),
+        };
+
         let write_permission = unsafe { libc::access(cstring.as_ptr(), libc::W_OK) } == 0;
-        ensure!(write_permission, "No write permission on '{socket}'");
+        if !write_permission {
+            return Err(EngineClientError::NoWritePermission(socket));
+        }
 
         Ok(Self {
             socket,
@@ -72,52 +70,82 @@ impl EngineApiClient {
         hyperlocal::Uri::new(self.socket.clone(), path.as_ref()).into()
     }
 
-    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T> {
+    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T, EngineClientError> {
         let req = Request::builder()
             .method(Method::GET)
             .uri(uri)
             .body(Either::Right(Empty::<Bytes>::new()))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::RequestBuilderError)?;
 
         let res = self
             .client
             .request(req)
             .await
-            .map_err(|err| anyhow!("Error during the http request: reason {}", err))?;
+            .map_err(EngineClientError::HyperError)?;
 
-        let buf = res.collect().await?.aggregate();
+        let status = res.status();
+
+        match status.is_success() {
+            true => {
+                let buf = res
+                    .collect()
+                    .await
+                    .map_err(EngineClientError::CollectResponseError)?
+                    .aggregate();
+
+                let output = serde_json::from_reader(buf.reader())
+                    .map_err(|err| EngineClientError::DeserializeError(err.to_string()))?;
+
+                Ok(output)
+            }
+            false => {
+                let error = res
+                    .collect()
+                    .await
+                    .map_err(EngineClientError::CollectResponseError)?
+                    .to_bytes();
 
-        let output = serde_json::from_reader(buf.reader())?;
+                let error_str =
+                    std::str::from_utf8(&error).map_err(EngineClientError::Utf8Error)?;
 
-        Ok(output)
+                Err(EngineClientError::UnexpectedResponse(format!(
+                    "HTTP error {}: {}",
+                    status.as_u16(),
+                    error_str
+                )))
+            }
+        }
     }
 
-    pub async fn list_modules(&self) -> Result<Vec<ModuleOverview>> {
+    pub async fn list_modules(&self) -> Result<Vec<ModuleOverview>, EngineClientError> {
         let url = self.uri("/modules");
         self.get(url).await
     }
 
-    pub async fn get_configs(&self) -> Result<Vec<ModuleConfigKVs>> {
+    pub async fn get_configs(&self) -> Result<Vec<ModuleConfigKVs>, EngineClientError> {
         let url = self.uri("/configs");
         self.get(url).await
     }
 
-    pub async fn start(&self, module_name: &str) -> Result<()> {
+    pub async fn start(&self, module_name: &str) -> Result<(), EngineClientError> {
         let url = self.uri(format!("/modules/{module_name}/start"));
         self.empty_post(url).await
     }
 
-    pub async fn stop(&self, module_name: &str) -> Result<()> {
+    pub async fn stop(&self, module_name: &str) -> Result<(), EngineClientError> {
         let url = self.uri(format!("/modules/{module_name}/stop"));
         self.empty_post(url).await
     }
 
-    pub async fn restart(&self, module_name: &str) -> Result<()> {
+    pub async fn restart(&self, module_name: &str) -> Result<(), EngineClientError> {
         let url = self.uri(format!("/modules/{module_name}/restart"));
         self.empty_post(url).await
     }
 
-    pub async fn get_module_config(&self, module_name: &str) -> Result<Vec<ConfigKV>> {
+    pub async fn get_module_config(
+        &self,
+        module_name: &str,
+    ) -> Result<Vec<ConfigKV>, EngineClientError> {
         let url = self.uri(format!("/modules/{module_name}/config"));
         self.get(url).await
     }
@@ -127,27 +155,27 @@ impl EngineApiClient {
         module_name: &str,
         config_key: String,
         config_value: String,
-    ) -> Result<()> {
+    ) -> Result<(), EngineClientError> {
         let url = self.uri(format!("/modules/{module_name}/config"));
 
         let body_string = serde_json::to_string(&ConfigKV {
             key: config_key,
             value: config_value,
         })
-        .map_err(|err| anyhow!("Error during object serialization. Reason: {err}"))?;
+        .map_err(|err| EngineClientError::SerializeError(err.to_string()))?;
 
         let req = Request::builder()
             .method(Method::PATCH)
             .uri(url)
             .header("content-type", "application/json")
             .body(Either::Left(Full::from(body_string)))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::RequestBuilderError)?;
 
         let res = self
             .client
             .request(req)
             .await
-            .map_err(|err| anyhow!("Error during the http request. Reason: {}", err))?;
+            .map_err(EngineClientError::HyperError)?;
 
         let status = res.status();
 
@@ -157,27 +185,29 @@ impl EngineApiClient {
                 let error = res
                     .collect()
                     .await
-                    .map_err(|err| anyhow!("Error to bytes. Reason: {}", err))?
+                    .map_err(EngineClientError::CollectResponseError)?
                     .to_bytes();
-                let error = std::str::from_utf8(&error)
-                    .map_err(|err| anyhow!("Cannot parse error str. Reason: {}", err))?;
-                Err(anyhow!("Error during request. {error}"))
+
+                let error_str =
+                    std::str::from_utf8(&error).map_err(EngineClientError::Utf8Error)?;
+
+                Err(EngineClientError::UnexpectedResponse(error_str.to_string()))
             }
         }
     }
 
-    async fn empty_post(&self, uri: Uri) -> Result<()> {
+    async fn empty_post(&self, uri: Uri) -> Result<(), EngineClientError> {
         let req = Request::builder()
             .method(Method::POST)
             .uri(uri)
             .body(Either::Right(Empty::<Bytes>::new()))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::RequestBuilderError)?;
 
         let res = self
             .client
             .request(req)
             .await
-            .map_err(|err| anyhow!("Error during the http request. Reason: {}", err))?;
+            .map_err(EngineClientError::HyperError)?;
 
         let status = res.status();
 
@@ -187,25 +217,33 @@ impl EngineApiClient {
                 let error = res
                     .collect()
                     .await
-                    .map_err(|err| anyhow!("Error to bytes. Reason: {}", err))?
+                    .map_err(EngineClientError::CollectResponseError)?
                     .to_bytes();
-                let error = std::str::from_utf8(&error)
-                    .map_err(|err| anyhow!("Cannot parse error str. Reason: {}", err))?;
-                Err(anyhow!("Error during request. {error}"))
+
+                let error_str =
+                    std::str::from_utf8(&error).map_err(EngineClientError::Utf8Error)?;
+
+                Err(EngineClientError::UnexpectedResponse(error_str.to_string()))
             }
         }
     }
 
-    pub async fn event_monitor(&self) -> Result<impl Stream<Item = Result<Event, WebsocketError>>> {
-        let stream = tokio::net::UnixStream::connect(&self.socket).await?;
+    pub async fn event_monitor(
+        &self,
+    ) -> Result<impl Stream<Item = Result<Event, WebsocketError>>, EngineClientError> {
+        let stream = tokio::net::UnixStream::connect(&self.socket)
+            .await
+            .map_err(EngineClientError::SocketConnectionError)?;
 
         // The `localhost` domain is simply a placeholder for the url. It's not used because is already present a stream
-        let (ws_stream, _) = client_async("ws://localhost/monitor", stream).await?;
+        let (ws_stream, _) = client_async("ws://localhost/monitor", stream)
+            .await
+            .map_err(EngineClientError::from)?;
 
         let (_, read_stream) = ws_stream.split();
 
         let events_stream = read_stream.map(|item| {
-            item.map_err(|e| e.into()).and_then(|msg| {
+            item.map_err(|err| err.into()).and_then(|msg| {
                 if let Message::Text(json) = msg {
                     let event: Event =
                         serde_json::from_str(&json).map_err(WebsocketError::JsonError)?;
diff --git a/crates/engine-api/src/error.rs b/crates/engine-api/src/error.rs
index 561f2e1b..75012aeb 100644
--- a/crates/engine-api/src/error.rs
+++ b/crates/engine-api/src/error.rs
@@ -1,7 +1,8 @@
-use std::fmt::Display;
-
 use axum::{http::StatusCode, response::IntoResponse};
+use hyper::http;
 use pulsar_core::pdk::PulsarDaemonError;
+use std::fmt::Display;
+use std::str::Utf8Error;
 use thiserror::Error;
 
 #[derive(Debug)]
@@ -11,14 +12,107 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(#[from] std::ffi::NulError),
+
+    /// Hyper error
+    #[error("Hyper client error: {0}")]
+    HyperError(#[from] hyper_util::client::legacy::Error), // For client.request() errors
+
+    /// Collect Response Error
+    #[error("Error collecting response: {0}")]
+    CollectResponseError(hyper::Error), // For res.collect() errors
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(#[from] Utf8Error),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// Socket connection error
+    #[error("Failed to connect to socket: {0}")]
+    SocketConnectionError(std::io::Error),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+/// WebSocket related errors
 #[derive(Debug, Error)]
 pub enum WebsocketError {
-    #[error(transparent)]
+    /// JSON error
+    #[error("JSON error: {0}")]
     JsonError(#[from] serde_json::Error),
-    #[error(transparent)]
-    ConnectionError(#[from] tokio_tungstenite::tungstenite::Error),
-    #[error("unsupported message type")]
+
+    /// Unsupported message type
+    #[error("Unsupported message type")]
     UnsupportedMessageType,
+
+    /// Connection error
+    #[error("WebSocket connection error: {0}")]
+    ConnectionError(#[from] tokio_tungstenite::tungstenite::Error),
 }
 
 impl Display for EngineApiError {


Reviews:
user: login: banditopazzo
body: Hey, thank you for your PR! 👋

In general, it's fine. I pointed out some small things that would be great if you could address before merging. Let me know if anything is unclear or if you need a hand with it—happy to help!

Thanks again for your contribution! 😊
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-15T14:12:07Z
user: login: banditopazzo
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-16T14:39:21Z
user: login: zedddie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:32:14Z
user: login: zedddie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T15:45:16Z
user: login: zedddie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T17:06:21Z
user: login: zedddie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T17:31:23Z
user: login: banditopazzo
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-23T13:45:56Z
user: login: banditopazzo
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T16:30:48Z

Review comments:
diff_hunk: @@ -1,6 +1,6 @@
 use std::{ffi::CString, os::unix::prelude::FileTypeExt};
 
-use anyhow::{Context, Result, anyhow, bail, ensure};
+use anyhow::Result;
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: maybe this can be safely removed
created_at: 2025-04-15T13:32:56Z
author_association: MEMBER
diff_hunk: @@ -36,31 +36,32 @@ impl EngineApiClient {
         match std::fs::metadata(&socket) {
             Err(err) => match err.kind() {
                 std::io::ErrorKind::NotFound => {
-                    bail!("'{socket}' not found. Check if the daemon is running")
+                    return Err(EngineClientError::SocketNotFound(socket));
                 }
                 std::io::ErrorKind::PermissionDenied => {
-                    bail!("No write permission on '{socket}'")
+                    return Err(EngineClientError::NoWritePermission(socket));
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: this is not a write permission error, but is a read permission error.

this wrong error was already here before your pr, but now is a good time to fix it
created_at: 2025-04-15T13:40:58Z
author_association: MEMBER
diff_hunk: @@ -72,52 +73,60 @@ impl EngineApiClient {
         hyperlocal::Uri::new(self.socket.clone(), path.as_ref()).into()
     }
 
-    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T> {
+    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T, EngineClientError> {
         let req = Request::builder()
             .method(Method::GET)
             .uri(uri)
             .body(Either::Right(Empty::<Bytes>::new()))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::HttpError)?;
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: Probably `HttpError` is not a good error here, it seems more a request builder error
created_at: 2025-04-15T13:42:20Z
author_association: MEMBER
diff_hunk: @@ -72,52 +73,60 @@ impl EngineApiClient {
         hyperlocal::Uri::new(self.socket.clone(), path.as_ref()).into()
     }
 
-    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T> {
+    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T, EngineClientError> {
         let req = Request::builder()
             .method(Method::GET)
             .uri(uri)
             .body(Either::Right(Empty::<Bytes>::new()))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::HttpError)?;
 
         let res = self
             .client
             .request(req)
             .await
-            .map_err(|err| anyhow!("Error during the http request: reason {}", err))?;
+            .map_err(|err| EngineClientError::HyperRequest(err.to_string()))?;
 
-        let buf = res.collect().await?.aggregate();
+        let buf = res
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: I noticed that before parsing the json there is no check on the status like 
```rust
        let status = res.status();

        match status {
            StatusCode::OK => Ok(()),
            _ => {
            // ...
            }
```

same as before now is a good time to fix it
created_at: 2025-04-15T13:45:12Z
author_association: MEMBER
diff_hunk: @@ -72,52 +73,60 @@ impl EngineApiClient {
         hyperlocal::Uri::new(self.socket.clone(), path.as_ref()).into()
     }
 
-    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T> {
+    async fn get<T: DeserializeOwned>(&self, uri: Uri) -> Result<T, EngineClientError> {
         let req = Request::builder()
             .method(Method::GET)
             .uri(uri)
             .body(Either::Right(Empty::<Bytes>::new()))
-            .map_err(|err| anyhow!("Error building the request. Reason: {}", err))?;
+            .map_err(EngineClientError::HttpError)?;
 
         let res = self
             .client
             .request(req)
             .await
-            .map_err(|err| anyhow!("Error during the http request: reason {}", err))?;
+            .map_err(|err| EngineClientError::HyperRequest(err.to_string()))?;
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: probably is better an enum variant with a `#[from]` for "hyper errors" (I don't remember the exact type) instead of manually crafting a string
created_at: 2025-04-15T13:47:37Z
author_association: MEMBER
diff_hunk: @@ -187,25 +205,44 @@ impl EngineApiClient {
                 let error = res
                     .collect()
                     .await
-                    .map_err(|err| anyhow!("Error to bytes. Reason: {}", err))?
+                    .map_err(|err| {
+                        EngineClientError::HyperRequest(format!(
+                            "Error collecting response: {}",
+                            err
+                        ))
+                    })?
                     .to_bytes();
-                let error = std::str::from_utf8(&error)
-                    .map_err(|err| anyhow!("Cannot parse error str. Reason: {}", err))?;
-                Err(anyhow!("Error during request. {error}"))
+
+                let error_str = match std::str::from_utf8(&error) {
+                    Ok(s) => s,
+                    Err(err) => return Err(EngineClientError::Utf8Error(err.to_string())),
+                };
+
+                Err(EngineClientError::UnexpectedResponse(error_str.to_string()))
             }
         }
     }
 
-    pub async fn event_monitor(&self) -> Result<impl Stream<Item = Result<Event, WebsocketError>>> {
-        let stream = tokio::net::UnixStream::connect(&self.socket).await?;
+    pub async fn event_monitor(
+        &self,
+    ) -> Result<impl Stream<Item = Result<Event, WebsocketError>>, EngineClientError> {
+        let stream = tokio::net::UnixStream::connect(&self.socket)
+            .await
+            .map_err(|err| {
+                EngineClientError::IoError(format!("Failed to connect to socket: {}", err))
+            })?;
 
         // The `localhost` domain is simply a placeholder for the url. It's not used because is already present a stream
-        let (ws_stream, _) = client_async("ws://localhost/monitor", stream).await?;
+        let (ws_stream, _) = client_async("ws://localhost/monitor", stream)
+            .await
+            .map_err(|err| {
+                EngineClientError::WebSocketError(format!("WebSocket initialization failed: {}", err))
+            })?;
path: crates/engine-api/src/client.rs
user: login: banditopazzo
body: here probably we should use a variant with `#[from] tokio_tungstenite::tungstenite::Error`.

you can move the unused `WebsocketError::ConnectionError` from `WebsocketError` to `EngineClientErrot`
created_at: 2025-04-15T13:52:47Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for EngineClientError {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Self::Utf8Error(err.to_string())
+    }
+}
+
+impl From<io::Error> for EngineClientError {
+    fn from(err: io::Error) -> Self {
+        Self::IoError(err.to_string())
+    }
+}
+
+impl EngineClientError {
+    pub fn request_builder_error(err: http::Error) -> Self {
+        Self::RequestBuilderError(err)
+    }
+
+    pub fn hyper_request_error<E: std::fmt::Display>(err: E) -> Self {
+        Self::HyperRequestError(err.to_string())
+    }
+}
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: we don't need this functions
created_at: 2025-04-16T14:33:24Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for EngineClientError {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Self::Utf8Error(err.to_string())
+    }
+}
+
+impl From<io::Error> for EngineClientError {
+    fn from(err: io::Error) -> Self {
+        Self::IoError(err.to_string())
+    }
+}
+
+impl EngineClientError {
+    pub fn request_builder_error(err: http::Error) -> Self {
+        Self::RequestBuilderError(err)
+    }
+
+    pub fn hyper_request_error<E: std::fmt::Display>(err: E) -> Self {
+        Self::HyperRequestError(err.to_string())
+    }
+}
+
+/// WebSocket related errors
 #[derive(Debug, Error)]
 pub enum WebsocketError {
-    #[error(transparent)]
+    /// JSON error
+    #[error("JSON error: {0}")]
     JsonError(#[from] serde_json::Error),
-    #[error(transparent)]
-    ConnectionError(#[from] tokio_tungstenite::tungstenite::Error),
-    #[error("unsupported message type")]
+
+    /// Unsupported message type
+    #[error("Unsupported message type")]
     UnsupportedMessageType,
+
+    /// Connection error
+    #[error("WebSocket connection error: {0}")]
+    ConnectionError(String),
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: this is not used anymore right? you moved to `EngineClientError`
created_at: 2025-04-16T14:34:48Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for EngineClientError {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Self::Utf8Error(err.to_string())
+    }
+}
+
+impl From<io::Error> for EngineClientError {
+    fn from(err: io::Error) -> Self {
+        Self::IoError(err.to_string())
+    }
+}
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: this can be implemented as a `#from` with `thiserror`
created_at: 2025-04-16T14:35:52Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for EngineClientError {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Self::Utf8Error(err.to_string())
+    }
+}
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: this can be implemented as a `#from` with `thiserror`
created_at: 2025-04-16T14:36:23Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: not sure if this trait impl is necessary
created_at: 2025-04-16T14:37:25Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: should be removed right?
created_at: 2025-04-16T14:38:08Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: is it possible to merge this 2?
created_at: 2025-04-16T14:38:52Z
author_association: MEMBER
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(http::Error),
+
+    /// HTTP error
+    #[error("HTTP error: {0}")]
+    HttpError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(String),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// I/O error
+    #[error("I/O error: {0}")]
+    IoError(String),
+
+    /// WebSocket error
+    #[error("WebSocket error: {0}")]
+    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
+
+    /// Unknown error
+    #[error("Unknown error: {0}")]
+    Other(String),
+}
+
+impl From<std::ffi::NulError> for EngineClientError {
+    fn from(err: std::ffi::NulError) -> Self {
+        Self::CStringConversion(err.to_string())
+    }
+}
+
+impl From<serde_json::Error> for EngineClientError {
+    fn from(err: serde_json::Error) -> Self {
+        match err.classify() {
+            serde_json::error::Category::Io => {
+                Self::SerializeError(format!("IO error during serialization: {}", err))
+            }
+            serde_json::error::Category::Syntax => {
+                Self::DeserializeError(format!("JSON syntax error: {}", err))
+            }
+            serde_json::error::Category::Data => {
+                Self::DeserializeError(format!("JSON data error: {}", err))
+            }
+            serde_json::error::Category::Eof => {
+                Self::DeserializeError(format!("Unexpected end of JSON input: {}", err))
+            }
+        }
+    }
+}
+
+impl From<std::str::Utf8Error> for EngineClientError {
+    fn from(err: std::str::Utf8Error) -> Self {
+        Self::Utf8Error(err.to_string())
+    }
+}
+
+impl From<io::Error> for EngineClientError {
+    fn from(err: io::Error) -> Self {
+        Self::IoError(err.to_string())
+    }
+}
+
+impl EngineClientError {
+    pub fn request_builder_error(err: http::Error) -> Self {
+        Self::RequestBuilderError(err)
+    }
+
+    pub fn hyper_request_error<E: std::fmt::Display>(err: E) -> Self {
+        Self::HyperRequestError(err.to_string())
+    }
+}
+
+/// WebSocket related errors
 #[derive(Debug, Error)]
 pub enum WebsocketError {
-    #[error(transparent)]
+    /// JSON error
+    #[error("JSON error: {0}")]
     JsonError(#[from] serde_json::Error),
-    #[error(transparent)]
-    ConnectionError(#[from] tokio_tungstenite::tungstenite::Error),
-    #[error("unsupported message type")]
+
+    /// Unsupported message type
+    #[error("Unsupported message type")]
     UnsupportedMessageType,
+
+    /// Connection error
+    #[error("WebSocket connection error: {0}")]
+    ConnectionError(String),
path: crates/engine-api/src/error.rs
user: login: zedddie
body: it is used in line 260 client.rs ``` events_stream ```  when I delete it from errors it leads to more problems in event stream error handling
                 
created_at: 2025-04-16T15:32:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
path: crates/engine-api/src/error.rs
user: login: zedddie
body: yes, but I thought HyperClientError and HyperRequestError are different, I can just do one HyperError then instead of these two
created_at: 2025-04-16T15:45:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
path: crates/engine-api/src/error.rs
user: login: zedddie
body: seems like not, HyperClientError is for requesting from client
```rust
let res = self
            .client
            .request(req)
            .await
            .map_err(EngineClientError::HyperError)?;
 ```
            
while HyperRequestError is for collecting value from request, so I am not sure I can merge them into one, because they are different types actually
created_at: 2025-04-16T17:06:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,14 +14,146 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(String),
+
+    /// Hyper client error
+    #[error("Hyper client error: {0}")]
+    HyperClientError(#[from] HyperError),
+
+    /// Hyper request error
+    #[error("Hyper request error: {0}")]
+    HyperRequestError(String),
path: crates/engine-api/src/error.rs
user: login: zedddie
body: so there is will be `RequestBuilderError` for builders;
`CollectResponseError` for collecting response;
and `HyperError` for error during sending  constructed request.
is it okay?
created_at: 2025-04-16T17:31:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,14 +12,107 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(#[from] std::ffi::NulError),
+
+    /// Hyper error
+    #[error("Hyper client error: {0}")]
+    HyperError(#[from] hyper_util::client::legacy::Error), // For client.request() errors
+
+    /// Collect Response Error
+    #[error("Error collecting response: {0}")]
+    CollectResponseError(#[from] hyper::Error), // For res.collect() errors
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(#[from] http::Error),
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: this now is fine , but should not have a `#from` because semantically you can't transform http errors in request builder error 
created_at: 2025-04-23T13:34:40Z
author_association: MEMBER
diff_hunk: @@ -11,14 +12,107 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(#[from] std::ffi::NulError),
+
+    /// Hyper error
+    #[error("Hyper client error: {0}")]
+    HyperError(#[from] hyper_util::client::legacy::Error), // For client.request() errors
+
+    /// Collect Response Error
+    #[error("Error collecting response: {0}")]
+    CollectResponseError(#[from] hyper::Error), // For res.collect() errors
+
+    /// Error during request building
+    #[error("Failed to build request: {0}")]
+    RequestBuilderError(#[from] http::Error),
+
+    /// Serialization error
+    #[error("Serialization error: {0}")]
+    SerializeError(String),
+
+    /// Deserialization error
+    #[error("Deserialization error: {0}")]
+    DeserializeError(String),
+
+    /// UTF-8 error
+    #[error("UTF-8 error: {0}")]
+    Utf8Error(#[from] Utf8Error),
+
+    /// Unexpected response
+    #[error("Unexpected response: {0}")]
+    UnexpectedResponse(String),
+
+    /// Socket connection error
+    #[error("Failed to connect to socket: {0}")]
+    SocketConnectionError(#[from] std::io::Error),
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: this is fine , but should not have a `#from` you don't want to transform every io error in socket connection error. If you need to catch generic io errors you should re introduce IoError variant and that can have a `#from`  
created_at: 2025-04-23T13:37:01Z
author_association: MEMBER
diff_hunk: @@ -11,14 +12,107 @@ pub enum EngineApiError {
     ServiceUnavailable,
 }
 
+/// Error types for the Engine API client
+#[derive(Debug, Error)]
+pub enum EngineClientError {
+    /// Socket not found
+    #[error("Unix socket not found: {0}")]
+    SocketNotFound(String),
+
+    /// Failed to get metadata
+    #[error("Failed to get metadata: {0}")]
+    FailedToGetMetadata(String),
+
+    /// No write permission
+    #[error("No write permission: {0}")]
+    NoWritePermission(String),
+
+    /// No read permission
+    #[error("No read permission: {0}")]
+    NoReadPermission(String),
+
+    /// Not a unix socket
+    #[error("Not a unix socket: {0}")]
+    NotASocket(String),
+
+    /// C string conversion error
+    #[error("C string conversion error: {0}")]
+    CStringConversion(#[from] std::ffi::NulError),
+
+    /// Hyper error
+    #[error("Hyper client error: {0}")]
+    HyperError(#[from] hyper_util::client::legacy::Error), // For client.request() errors
+
+    /// Collect Response Error
+    #[error("Error collecting response: {0}")]
+    CollectResponseError(#[from] hyper::Error), // For res.collect() errors
path: crates/engine-api/src/error.rs
user: login: banditopazzo
body: please remove the `#from`, we don't want to eventually catch every hyper error in collect response error, but only few specific cases
created_at: 2025-04-23T13:43:28Z
author_association: MEMBER

PR comments:
user: login: zedddie
created_at: 2025-04-15T20:44:01Z
body: i hope i did requested changes well :p 
author_association: CONTRIBUTOR
user: login: zedddie
created_at: 2025-04-16T16:00:46Z
body: I removed everything you said, but have a problem, as I said earlier on line 260 `events_stream` is returning `<Event, WebsocketError>` so we cant convert `Error` to `WebsocketError` when there is no `WebSocketConnectionError`(which represent any `WebsocketError` `(tokio_tungstenite::tungstenite::Error)`) in our `WebsocketError`

author_association: CONTRIBUTOR
user: login: zedddie
created_at: 2025-04-16T17:51:55Z
body: to delete` WebsocketConnectionError` I will need to change `fn event_monitor `to return `Result<Event, EngineClientError>>, EngineClientError>` instead of `Result<Event, WebsocketError>>, EngineClientError>`, which seems to be strange, and will require to move all `WebsocketError` enums into `EngineClientError`
author_association: CONTRIBUTOR
user: login: banditopazzo
created_at: 2025-04-23T13:41:16Z
body: > to delete` WebsocketConnectionError` I will need to change `fn event_monitor `to return `Result<Event, EngineClientError>>, EngineClientError>` instead of `Result<Event, WebsocketError>>, EngineClientError>`, which seems to be strange, and will require to move all `WebsocketError` enums into `EngineClientError`

right, never mind keep the code as it is
author_association: MEMBER
user: login: banditopazzo
created_at: 2025-04-23T13:49:03Z
body: please rebase also on `main`
author_association: MEMBER
user: login: zedddie
created_at: 2025-04-28T05:49:13Z
body: thanks for your guidance 😊
author_association: CONTRIBUTOR