Implement unified and type safe error messages (#186)

* Implement unified and type safe error messages

* Fix Apple error handling

* improve linux exception handling

* improve web exception handling

* fix window imports

* update pigeon generated files

* Fix compilaton issue

* Fix windows error parsing

* Fix windows compilation

* Improve error handling on dart side

* rename _wrapFuture to _executeWithErrorHandling

* Add pigeon generation script and format generated dart file

* Update changelog

* Update readme

---------

Co-authored-by: Foti Dim <foti@navideck.com>
This commit is contained in:
Rohit Sangwan
2025-11-12 00:04:29 +05:30
committed by GitHub
parent b52ca9ee84
commit acfa3200ca
32 changed files with 3399 additions and 1721 deletions
+6 -2
View File
@@ -1,6 +1,10 @@
#pragma once
#include <string>
#include <optional>
#include "helper/universal_enum.h"
#include "helper/universal_ble_base.h"
#include "generated/universal_ble.g.h"
namespace universal_ble
{
@@ -46,9 +50,9 @@ namespace universal_ble
return std::nullopt;
}
inline std::optional<std::string> parse_pairing_fail_error(const DevicePairingResult& result)
inline std::optional<std::string> device_pairing_result_to_string(const DevicePairingResultStatus result)
{
switch (result.Status())
switch (result)
{
case DevicePairingResultStatus::Paired: return std::nullopt;
case DevicePairingResultStatus::AlreadyPaired: return "AlreadyPaired";
+22 -12
View File
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v22.6.1), do not edit directly.
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#undef _HAS_EXCEPTIONS
@@ -425,21 +425,26 @@ EncodableValue PigeonInternalCodecSerializer::ReadValueOfType(
flutter::ByteStreamReader* stream) const {
switch (type) {
case 129: {
return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
const auto& encodable_enum_arg = ReadValue(stream);
const int64_t enum_arg_value = encodable_enum_arg.IsNull() ? 0 : encodable_enum_arg.LongValue();
return encodable_enum_arg.IsNull() ? EncodableValue() : CustomEncodableValue(static_cast<UniversalBleErrorCode>(enum_arg_value));
}
case 130: {
return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleScanResult::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 131: {
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleService::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 132: {
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalBleCharacteristic::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 133: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
return CustomEncodableValue(UniversalScanFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 134: {
return CustomEncodableValue(UniversalManufacturerDataFilter::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
case 135: {
return CustomEncodableValue(UniversalManufacturerData::FromEncodableList(std::get<EncodableList>(ReadValue(stream))));
}
default:
@@ -451,33 +456,38 @@ void PigeonInternalCodecSerializer::WriteValue(
const EncodableValue& value,
flutter::ByteStreamWriter* stream) const {
if (const CustomEncodableValue* custom_value = std::get_if<CustomEncodableValue>(&value)) {
if (custom_value->type() == typeid(UniversalBleScanResult)) {
if (custom_value->type() == typeid(UniversalBleErrorCode)) {
stream->WriteByte(129);
WriteValue(EncodableValue(static_cast<int>(std::any_cast<UniversalBleErrorCode>(*custom_value))), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleScanResult)) {
stream->WriteByte(130);
WriteValue(EncodableValue(std::any_cast<UniversalBleScanResult>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleService)) {
stream->WriteByte(130);
stream->WriteByte(131);
WriteValue(EncodableValue(std::any_cast<UniversalBleService>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalBleCharacteristic)) {
stream->WriteByte(131);
stream->WriteByte(132);
WriteValue(EncodableValue(std::any_cast<UniversalBleCharacteristic>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalScanFilter)) {
stream->WriteByte(132);
stream->WriteByte(133);
WriteValue(EncodableValue(std::any_cast<UniversalScanFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerDataFilter)) {
stream->WriteByte(133);
stream->WriteByte(134);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerDataFilter>(*custom_value).ToEncodableList()), stream);
return;
}
if (custom_value->type() == typeid(UniversalManufacturerData)) {
stream->WriteByte(134);
stream->WriteByte(135);
WriteValue(EncodableValue(std::any_cast<UniversalManufacturerData>(*custom_value).ToEncodableList()), stream);
return;
}
+66 -18
View File
@@ -1,4 +1,4 @@
// Autogenerated from Pigeon (v22.6.1), do not edit directly.
// Autogenerated from Pigeon (v26.0.5), do not edit directly.
// See also: https://pub.dev/packages/pigeon
#ifndef PIGEON_UNIVERSAL_BLE_G_H_
@@ -57,6 +57,71 @@ template<class T> class ErrorOr {
};
// Unified error codes for all platforms
enum class UniversalBleErrorCode {
kUnknownError = 0,
kFailed = 1,
kNotSupported = 2,
kNotImplemented = 3,
kChannelError = 4,
kBluetoothNotAvailable = 5,
kBluetoothNotEnabled = 6,
kBluetoothNotAllowed = 7,
kBluetoothUnauthorized = 8,
kDeviceDisconnected = 9,
kConnectionTimeout = 10,
kConnectionFailed = 11,
kConnectionRejected = 12,
kConnectionLimitExceeded = 13,
kConnectionAlreadyExists = 14,
kConnectionTerminated = 15,
kConnectionInProgress = 16,
kIllegalArgument = 17,
kDeviceNotFound = 18,
kServiceNotFound = 19,
kCharacteristicNotFound = 20,
kInvalidServiceUuid = 21,
kInvalidCharacteristicUuid = 22,
kInvalidOffset = 23,
kInvalidAttributeLength = 24,
kInvalidPdu = 25,
kInvalidHandle = 26,
kReadFailed = 27,
kReadNotPermitted = 28,
kWriteFailed = 29,
kWriteNotPermitted = 30,
kWriteRequestBusy = 31,
kInvalidAction = 32,
kOperationNotSupported = 33,
kOperationTimeout = 34,
kOperationCancelled = 35,
kOperationInProgress = 36,
kCharacteristicDoesNotSupportRead = 37,
kCharacteristicDoesNotSupportWrite = 38,
kCharacteristicDoesNotSupportWriteWithoutResponse = 39,
kCharacteristicDoesNotSupportNotify = 40,
kCharacteristicDoesNotSupportIndicate = 41,
kNotPaired = 42,
kNotPairable = 43,
kAlreadyPaired = 44,
kPairingFailed = 45,
kPairingCancelled = 46,
kPairingTimeout = 47,
kPairingNotAllowed = 48,
kAuthenticationFailure = 49,
kInsufficientAuthentication = 50,
kInsufficientAuthorization = 51,
kInsufficientEncryption = 52,
kInsufficientKeySize = 53,
kProtectionLevelNotMet = 54,
kAccessDenied = 55,
kUnpairingFailed = 56,
kAlreadyUnpaired = 57,
kScanFailed = 58,
kStoppingScanInProgress = 59,
kWebBluetoothGloballyDisabled = 60
};
// Generated class from Pigeon that represents data sent in messages.
class UniversalBleScanResult {
@@ -96,7 +161,6 @@ class UniversalBleScanResult {
void set_services(const flutter::EncodableList* value_arg);
void set_services(const flutter::EncodableList& value_arg);
private:
static UniversalBleScanResult FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -109,7 +173,6 @@ class UniversalBleScanResult {
std::optional<int64_t> rssi_;
std::optional<flutter::EncodableList> manufacturer_data_list_;
std::optional<flutter::EncodableList> services_;
};
@@ -131,7 +194,6 @@ class UniversalBleService {
void set_characteristics(const flutter::EncodableList* value_arg);
void set_characteristics(const flutter::EncodableList& value_arg);
private:
static UniversalBleService FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -140,7 +202,6 @@ class UniversalBleService {
friend class PigeonInternalCodecSerializer;
std::string uuid_;
std::optional<flutter::EncodableList> characteristics_;
};
@@ -158,7 +219,6 @@ class UniversalBleCharacteristic {
const flutter::EncodableList& properties() const;
void set_properties(const flutter::EncodableList& value_arg);
private:
static UniversalBleCharacteristic FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -167,7 +227,6 @@ class UniversalBleCharacteristic {
friend class PigeonInternalCodecSerializer;
std::string uuid_;
flutter::EncodableList properties_;
};
@@ -191,7 +250,6 @@ class UniversalScanFilter {
const flutter::EncodableList& with_manufacturer_data() const;
void set_with_manufacturer_data(const flutter::EncodableList& value_arg);
private:
static UniversalScanFilter FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -201,7 +259,6 @@ class UniversalScanFilter {
flutter::EncodableList with_services_;
flutter::EncodableList with_name_prefix_;
flutter::EncodableList with_manufacturer_data_;
};
@@ -228,7 +285,6 @@ class UniversalManufacturerDataFilter {
void set_mask(const std::vector<uint8_t>* value_arg);
void set_mask(const std::vector<uint8_t>& value_arg);
private:
static UniversalManufacturerDataFilter FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -238,7 +294,6 @@ class UniversalManufacturerDataFilter {
int64_t company_identifier_;
std::optional<std::vector<uint8_t>> data_;
std::optional<std::vector<uint8_t>> mask_;
};
@@ -256,7 +311,6 @@ class UniversalManufacturerData {
const std::vector<uint8_t>& data() const;
void set_data(const std::vector<uint8_t>& value_arg);
private:
static UniversalManufacturerData FromEncodableList(const flutter::EncodableList& list);
flutter::EncodableList ToEncodableList() const;
@@ -265,7 +319,6 @@ class UniversalManufacturerData {
friend class PigeonInternalCodecSerializer;
int64_t company_identifier_;
std::vector<uint8_t> data_;
};
@@ -280,12 +333,10 @@ class PigeonInternalCodecSerializer : public flutter::StandardCodecSerializer {
void WriteValue(
const flutter::EncodableValue& value,
flutter::ByteStreamWriter* stream) const override;
protected:
flutter::EncodableValue ReadValueOfType(
uint8_t type,
flutter::ByteStreamReader* stream) const override;
};
// Flutter -> Native
@@ -352,10 +403,8 @@ class UniversalBlePlatformChannel {
const std::string& message_channel_suffix);
static flutter::EncodableValue WrapError(std::string_view error_message);
static flutter::EncodableValue WrapError(const FlutterError& error);
protected:
UniversalBlePlatformChannel() = default;
};
// Native -> Flutter
//
@@ -393,7 +442,6 @@ class UniversalBleCallbackChannel {
const std::string* error,
std::function<void(void)>&& on_success,
std::function<void(const FlutterError&)>&& on_error);
private:
flutter::BinaryMessenger* binary_messenger_;
std::string message_channel_suffix_;
+142 -1
View File
@@ -1,8 +1,11 @@
#include "Utils.h"
#include "utils.h"
#include "../generated/universal_ble.g.h"
#include "../enum_parser.h"
#include <iomanip>
#include <iostream>
#include <sstream>
#include <algorithm>
#include <windows.h>
#include <stdio.h>
#include <sdkddkver.h>
@@ -158,4 +161,142 @@ namespace universal_ble
return rove.dwMajorVersion == 10 && rove.dwBuildNumber >= 22000;
}
FlutterError create_flutter_error(
UniversalBleErrorCode code,
const std::string& message,
const std::string& details
)
{
// Pass the enum's underlying integer value as string in code, and enum name or details in details
std::string code_str = std::to_string(static_cast<int>(code));
std::string details_str = details.empty() ? std::to_string(static_cast<int>(code)) : details;
return FlutterError(code_str, message, details_str);
}
FlutterError create_flutter_error_from_gatt_communication_status(
GattCommunicationStatus status,
const std::string& message
)
{
if (status == GattCommunicationStatus::Success) {
// Success case - shouldn't normally create an error, but if called, return unknown
return create_flutter_error(UniversalBleErrorCode::kUnknownError, message);
}
UniversalBleErrorCode error_code;
switch (status)
{
case GattCommunicationStatus::Unreachable:
case GattCommunicationStatus::ProtocolError:
case GattCommunicationStatus::AccessDenied:
default:
error_code = UniversalBleErrorCode::kFailed;
break;
}
auto error_message = gatt_communication_status_to_error(status);
std::string final_message = message.empty() && error_message.has_value()
? error_message.value()
: message;
return create_flutter_error(error_code, final_message);
}
FlutterError create_flutter_error_from_pairing_status(
DevicePairingResultStatus status,
const std::string& message
)
{
if (status == DevicePairingResultStatus::Paired) {
// Success case - shouldn't normally create an error, but if called, return unknown
return create_flutter_error(UniversalBleErrorCode::kUnknownError, message);
}
UniversalBleErrorCode error_code;
switch (status)
{
case DevicePairingResultStatus::AlreadyPaired:
error_code = UniversalBleErrorCode::kAlreadyPaired;
break;
case DevicePairingResultStatus::ConnectionRejected:
error_code = UniversalBleErrorCode::kConnectionRejected;
break;
case DevicePairingResultStatus::NotPaired:
error_code = UniversalBleErrorCode::kNotPaired;
break;
case DevicePairingResultStatus::NotReadyToPair:
case DevicePairingResultStatus::TooManyConnections:
case DevicePairingResultStatus::HardwareFailure:
case DevicePairingResultStatus::NoSupportedProfiles:
case DevicePairingResultStatus::InvalidCeremonyData:
case DevicePairingResultStatus::RequiredHandlerNotRegistered:
case DevicePairingResultStatus::RejectedByHandler:
case DevicePairingResultStatus::RemoteDeviceHasAssociation:
error_code = UniversalBleErrorCode::kPairingFailed;
break;
case DevicePairingResultStatus::AuthenticationTimeout:
case DevicePairingResultStatus::AuthenticationNotAllowed:
case DevicePairingResultStatus::AuthenticationFailure:
error_code = UniversalBleErrorCode::kAuthenticationFailure;
break;
case DevicePairingResultStatus::ProtectionLevelCouldNotBeMet:
error_code = UniversalBleErrorCode::kProtectionLevelNotMet;
break;
case DevicePairingResultStatus::AccessDenied:
error_code = UniversalBleErrorCode::kAccessDenied;
break;
case DevicePairingResultStatus::PairingCanceled:
error_code = UniversalBleErrorCode::kPairingCancelled;
break;
case DevicePairingResultStatus::OperationAlreadyInProgress:
error_code = UniversalBleErrorCode::kOperationInProgress;
break;
default:
error_code = UniversalBleErrorCode::kPairingFailed;
break;
}
auto error_message = device_pairing_result_to_string(status);
std::string final_message = message.empty() && error_message.has_value()
? error_message.value()
: message;
return create_flutter_error(error_code, final_message);
}
FlutterError create_flutter_error_from_unpairing_status(
DeviceUnpairingResultStatus status,
const std::string& message
)
{
if (status == DeviceUnpairingResultStatus::Unpaired) {
// Success case - shouldn't normally create an error, but if called, return unknown
return create_flutter_error(UniversalBleErrorCode::kUnknownError, message);
}
UniversalBleErrorCode error_code;
switch (status)
{
case DeviceUnpairingResultStatus::Failed:
error_code = UniversalBleErrorCode::kUnpairingFailed;
break;
case DeviceUnpairingResultStatus::AlreadyUnpaired:
error_code = UniversalBleErrorCode::kAlreadyUnpaired;
break;
case DeviceUnpairingResultStatus::AccessDenied:
error_code = UniversalBleErrorCode::kAccessDenied;
break;
case DeviceUnpairingResultStatus::OperationAlreadyInProgress:
error_code = UniversalBleErrorCode::kOperationInProgress;
break;
default:
error_code = UniversalBleErrorCode::kUnpairingFailed;
break;
}
auto error_message = device_unpairing_result_to_string(status);
std::string final_message = message.empty() && error_message.has_value()
? error_message.value()
: message;
return create_flutter_error(error_code, final_message);
}
} // namespace universal_ble
+38 -2
View File
@@ -6,8 +6,10 @@
#include "winrt/Windows.Foundation.h"
#include "winrt/Windows.Storage.Streams.h"
#include "winrt/Windows.Devices.Bluetooth.GenericAttributeProfile.h"
#include "winrt/base.h"
#include "universal_ble_base.h"
#include "../generated/universal_ble.g.h"
constexpr uint32_t TEN_SECONDS_IN_MSECS = 10000;
@@ -28,6 +30,40 @@ namespace universal_ble
bool is_little_endian();
bool is_windows11_or_greater();
/// Creates a FlutterError with the error code enum in details
FlutterError create_flutter_error(
UniversalBleErrorCode code,
const std::string& message = "",
const std::string& details = ""
);
// Error creation functions
/// Creates a FlutterError from GATT communication status
FlutterError create_flutter_error_from_gatt_communication_status(
GattCommunicationStatus status,
const std::string& message = ""
);
/// Creates a FlutterError from device pairing result status
FlutterError create_flutter_error_from_pairing_status(
DevicePairingResultStatus status,
const std::string& message = ""
);
/// Creates a FlutterError from device unpairing result status
FlutterError create_flutter_error_from_unpairing_status(
DeviceUnpairingResultStatus status,
const std::string& message = ""
);
// Create Flutter unknown error
inline FlutterError create_flutter_unknown_error(
const std::string& message = "Unknown error"
) {
return create_flutter_error(UniversalBleErrorCode::kUnknownError, message);
}
/// To call async functions synchronously
template <typename AsyncT>
static auto async_get(AsyncT const &async)
@@ -42,11 +78,11 @@ namespace universal_ble
}
catch (const hresult_error &err)
{
throw FlutterError("Failed", to_string(err.message()));
throw create_flutter_error(UniversalBleErrorCode::kFailed, to_string(err.message()));
}
catch (...)
{
throw FlutterError("Failed", "Unknown error");
throw create_flutter_unknown_error();
}
}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -61,11 +61,11 @@ namespace universal_ble
{
if (gatt_map.count(service_uuid) == 0)
{
throw FlutterError("IllegalArgument", "Service not found");
throw create_flutter_error(UniversalBleErrorCode::kServiceNotFound, "Service not found");
}
if (gatt_map[service_uuid].characteristics.count(characteristic_uuid) == 0)
{
throw FlutterError("IllegalArgument", "Characteristic not found");
throw create_flutter_error(UniversalBleErrorCode::kCharacteristicNotFound, "Characteristic not found");
}
return gatt_map[service_uuid].characteristics.at(characteristic_uuid);
}