Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #pragma once
18 : :
19 : : #include <boost/asio/error.hpp>
20 : : #include <boost/system/error_code.hpp>
21 : : #include <optional>
22 : : #include <string>
23 : :
24 : : #include "wirestead/base/error_codes.hpp"
25 : : #include "wirestead/diagnostics/error_types.hpp"
26 : : #include "wirestead/wrapper/context.hpp"
27 : :
28 : : namespace wirestead {
29 : : namespace diagnostics {
30 : :
31 : : /**
32 : : * @brief Maps boost::system::error_code to Wirestead ErrorCode
33 : : */
34 : 33 : inline ErrorCode to_wirestead_error_code(const boost::system::error_code& ec) {
35 [ + + ]: 33 : if (!ec) [[likely]] {
36 : 1 : return ErrorCode::Success;
37 : : }
38 : :
39 : : // Check specific boost error codes
40 [ + + ]: 32 : if (ec == boost::asio::error::connection_refused) [[unlikely]] {
41 : 7 : return ErrorCode::ConnectionRefused;
42 : : }
43 [ + + ]: 25 : if (ec == boost::asio::error::timed_out) [[unlikely]] {
44 : 3 : return ErrorCode::TimedOut;
45 : : }
46 [ + + ]: 22 : if (ec == boost::asio::error::connection_reset) [[unlikely]] {
47 : 2 : return ErrorCode::ConnectionReset;
48 : : }
49 [ + + ]: 20 : if (ec == boost::asio::error::connection_aborted) [[unlikely]] {
50 : 1 : return ErrorCode::ConnectionAborted;
51 : : }
52 [ + + + + : 19 : if (ec == boost::asio::error::network_unreachable || ec == boost::asio::error::host_unreachable) [[unlikely]] {
+ + ]
53 : 3 : return ErrorCode::NotConnected;
54 : : }
55 [ + + ]: 16 : if (ec == boost::asio::error::already_connected) [[unlikely]] {
56 : 1 : return ErrorCode::AlreadyConnected;
57 : : }
58 [ + + ]: 15 : if (ec == boost::asio::error::address_in_use) [[unlikely]] {
59 : 5 : return ErrorCode::PortInUse;
60 : : }
61 [ + + ]: 10 : if (ec == boost::asio::error::access_denied) [[unlikely]] {
62 : 2 : return ErrorCode::AccessDenied;
63 : : }
64 : :
65 : : // Fallback to generic IoError for other network errors
66 : 8 : return ErrorCode::IoError;
67 : : }
68 : :
69 : : /**
70 : : * @brief Determines if a TCP connection error is retryable
71 : : */
72 : 115 : inline bool is_retryable_tcp_connect_error(const boost::system::error_code& ec) {
73 [ + + ]: 115 : if (!ec) [[likely]]
74 : 2 : return false;
75 : :
76 : : // Connection refused is temporary (server might be starting up)
77 [ + + ]: 113 : if (ec == boost::asio::error::connection_refused) return true;
78 : : // Timeouts are temporary network glitches
79 [ + + ]: 56 : if (ec == boost::asio::error::timed_out) return true;
80 : : // Reset connection might be temporary
81 [ + + ]: 50 : if (ec == boost::asio::error::connection_reset) return true;
82 : : // Unreachable might be temporary routing issue
83 [ + + + + : 48 : if (ec == boost::asio::error::network_unreachable || ec == boost::asio::error::host_unreachable) return true;
+ + ]
84 : : // Resource temporarily unavailable
85 [ + + ]: 45 : if (ec == boost::asio::error::try_again) return true;
86 : :
87 : : // Aborted usually means stopped by user, not retryable automatically
88 [ + + ]: 44 : if (ec == boost::asio::error::operation_aborted) return false;
89 : :
90 : : // Unknown network errors are treated as transient to err on the side of resilience.
91 : 42 : return true;
92 : : }
93 : :
94 : : /**
95 : : * @brief Determines if a UDS connection error is retryable
96 : : */
97 : 12 : inline bool is_retryable_uds_connect_error(const boost::system::error_code& ec) {
98 [ + + ]: 12 : if (!ec) return false;
99 : :
100 : : // For UDS, "connection refused" often means the socket file exists but no one is listening.
101 : : // "no such file or directory" means the server hasn't created the socket yet.
102 : : // Both are retryable.
103 [ + + ]: 11 : if (ec == boost::asio::error::connection_refused) return true;
104 [ + + ]: 6 : if (ec == boost::system::errc::no_such_file_or_directory) return true;
105 [ + + ]: 4 : if (ec == boost::asio::error::timed_out) return true;
106 [ + + ]: 3 : if (ec == boost::asio::error::try_again) return true;
107 : :
108 [ + + ]: 2 : if (ec == boost::asio::error::operation_aborted) return false;
109 : :
110 : 1 : return true;
111 : : }
112 : :
113 : : /**
114 : : * @brief Converts ErrorInfo to wrapper::ErrorContext
115 : : */
116 : 26 : inline wrapper::ErrorContext to_error_context(const diagnostics::ErrorInfo& info,
117 : : std::optional<size_t> client_id = std::nullopt) {
118 : 26 : ErrorCode code = ErrorCode::IoError;
119 : :
120 [ + + ]: 26 : if (info.boost_error) {
121 : 17 : code = to_wirestead_error_code(info.boost_error);
122 : : } else {
123 : : // Map category if boost error is not present
124 [ + + + + ]: 9 : switch (info.category) {
125 : 2 : case ErrorCategory::CONNECTION:
126 : 2 : code = ErrorCode::NotConnected;
127 : 2 : break;
128 : 5 : case ErrorCategory::CONFIGURATION:
129 : 5 : code = ErrorCode::InvalidConfiguration;
130 : 5 : break;
131 : 1 : case ErrorCategory::SYSTEM:
132 : 1 : code = ErrorCode::InternalError;
133 : 1 : break;
134 : 1 : default:
135 : 1 : code = ErrorCode::IoError;
136 : 1 : break;
137 : : }
138 : : }
139 : :
140 : : // Use the message from ErrorInfo
141 : 26 : return wrapper::ErrorContext(code, info.message, client_id);
142 : : }
143 : :
144 : : } // namespace diagnostics
145 : : } // namespace wirestead
|