| use crate::utils::byte_size::IggyByteSize; |
| use crate::utils::topic_size::MaxTopicSize; |
| use strum::{EnumDiscriminants, FromRepr, IntoStaticStr}; |
| use thiserror::Error; |
| |
| #[derive(Clone, Debug, Error, EnumDiscriminants, IntoStaticStr, FromRepr)] |
| #[repr(u32)] |
| #[strum(serialize_all = "snake_case")] |
| #[strum_discriminants( |
| vis(pub(crate)), |
| derive(FromRepr, IntoStaticStr), |
| strum(serialize_all = "snake_case") |
| )] |
| pub enum IggyError { |
| #[error("Error")] |
| Error = 1, |
| #[error("Invalid configuration")] |
| InvalidConfiguration = 2, |
| #[error("Invalid command")] |
| InvalidCommand = 3, |
| #[error("Invalid format")] |
| InvalidFormat = 4, |
| #[error("Feature is unavailable")] |
| FeatureUnavailable = 5, |
| #[error("Invalid identifier")] |
| InvalidIdentifier = 6, |
| #[error("Invalid version: {0}")] |
| InvalidVersion(String) = 7, |
| #[error("Disconnected")] |
| Disconnected = 8, |
| #[error("Cannot establish connection")] |
| CannotEstablishConnection = 9, |
| #[error("Cannot create base directory, Path: {0}")] |
| CannotCreateBaseDirectory(String) = 10, |
| #[error("Cannot create runtime directory, Path: {0}")] |
| CannotCreateRuntimeDirectory(String) = 11, |
| #[error("Cannot remove runtime directory, Path: {0}")] |
| CannotRemoveRuntimeDirectory(String) = 12, |
| #[error("Cannot create state directory, Path: {0}")] |
| CannotCreateStateDirectory(String) = 13, |
| #[error("State file not found")] |
| StateFileNotFound = 14, |
| #[error("State file corrupted")] |
| StateFileCorrupted = 15, |
| #[error("Invalid state entry checksum: {0}, expected: {1}, for index: {2}")] |
| InvalidStateEntryChecksum(u32, u32, u64) = 16, |
| #[error("Cannot open database, Path: {0}")] |
| CannotOpenDatabase(String) = 19, |
| #[error("Resource with key: {0} was not found.")] |
| ResourceNotFound(String) = 20, |
| #[error("Stale client")] |
| StaleClient = 30, |
| #[error("TCP error")] |
| TcpError = 31, |
| #[error("QUIC error")] |
| QuicError = 32, |
| #[error("Invalid server address")] |
| InvalidServerAddress = 33, |
| #[error("Invalid client address")] |
| InvalidClientAddress = 34, |
| #[error("Unauthenticated")] |
| Unauthenticated = 40, |
| #[error("Unauthorized")] |
| Unauthorized = 41, |
| #[error("Invalid credentials")] |
| InvalidCredentials = 42, |
| #[error("Invalid username")] |
| InvalidUsername = 43, |
| #[error("Invalid password")] |
| InvalidPassword = 44, |
| #[error("Invalid user status")] |
| InvalidUserStatus = 45, |
| #[error("User already exists")] |
| UserAlreadyExists = 46, |
| #[error("User inactive")] |
| UserInactive = 47, |
| #[error("Cannot delete user with ID: {0}")] |
| CannotDeleteUser(u32) = 48, |
| #[error("Cannot change permissions for user with ID: {0}")] |
| CannotChangePermissions(u32) = 49, |
| #[error("Invalid personal access token name")] |
| InvalidPersonalAccessTokenName = 50, |
| #[error("Personal access token: {0} for user with ID: {1} already exists")] |
| PersonalAccessTokenAlreadyExists(String, u32) = 51, |
| #[error("User with ID: {0} has reached the maximum number of personal access tokens: {1}")] |
| PersonalAccessTokensLimitReached(u32, u32) = 52, |
| #[error("Invalid personal access token")] |
| InvalidPersonalAccessToken = 53, |
| #[error("Personal access token: {0} for user with ID: {1} has expired.")] |
| PersonalAccessTokenExpired(String, u32) = 54, |
| #[error("Users limit reached.")] |
| UsersLimitReached = 55, |
| #[error("Not connected")] |
| NotConnected = 61, |
| #[error("Client shutdown")] |
| ClientShutdown = 63, |
| #[error("Invalid TLS domain")] |
| InvalidTlsDomain = 64, |
| #[error("Invalid TLS certificate path")] |
| InvalidTlsCertificatePath = 65, |
| #[error("Invalid TLS certificate")] |
| InvalidTlsCertificate = 66, |
| #[error("Failed to add certificate")] |
| FailedToAddCertificate = 67, |
| #[error("Invalid encryption key")] |
| InvalidEncryptionKey = 70, |
| #[error("Cannot encrypt data")] |
| CannotEncryptData = 71, |
| #[error("Cannot decrypt data")] |
| CannotDecryptData = 72, |
| #[error("Invalid JWT algorithm: {0}")] |
| InvalidJwtAlgorithm(String) = 73, |
| #[error("Invalid JWT secret")] |
| InvalidJwtSecret = 74, |
| #[error("JWT is missing")] |
| JwtMissing = 75, |
| #[error("Cannot generate JWT")] |
| CannotGenerateJwt = 76, |
| #[error("Access token is missing")] |
| AccessTokenMissing = 77, |
| #[error("Invalid access token")] |
| InvalidAccessToken = 78, |
| #[error("Invalid size bytes")] |
| InvalidSizeBytes = 80, |
| #[error("Invalid UTF-8")] |
| InvalidUtf8 = 81, |
| #[error("Invalid number encoding")] |
| InvalidNumberEncoding = 82, |
| #[error("Invalid boolean value")] |
| InvalidBooleanValue, |
| #[error("Invalid number value")] |
| InvalidNumberValue, |
| #[error("Client with ID: {0} was not found.")] |
| ClientNotFound(u32) = 100, |
| #[error("Invalid client ID")] |
| InvalidClientId = 101, |
| #[error("Connection closed")] |
| ConnectionClosed = 206, |
| #[error("Cannot parse header kind from {0}")] |
| CannotParseHeaderKind(String) = 209, |
| #[error("HTTP response error, status: {0}, body: {1}")] |
| HttpResponseError(u16, String) = 300, |
| #[error("Invalid HTTP request")] |
| InvalidHttpRequest = 301, |
| #[error("Invalid JSON response")] |
| InvalidJsonResponse = 302, |
| #[error("Invalid bytes response")] |
| InvalidBytesResponse = 303, |
| #[error("Empty response")] |
| EmptyResponse = 304, |
| #[error("Cannot create endpoint")] |
| CannotCreateEndpoint = 305, |
| #[error("Cannot parse URL")] |
| CannotParseUrl = 306, |
| #[error("Cannot create streams directory, Path: {0}")] |
| CannotCreateStreamsDirectory(String) = 1000, |
| #[error("Cannot create stream with ID: {0} directory, Path: {1}")] |
| CannotCreateStreamDirectory(u32, String) = 1001, |
| #[error("Failed to create stream info file for stream with ID: {0}")] |
| CannotCreateStreamInfo(u32) = 1002, |
| #[error("Failed to update stream info for stream with ID: {0}")] |
| CannotUpdateStreamInfo(u32) = 1003, |
| #[error("Failed to open stream info file for stream with ID: {0}")] |
| CannotOpenStreamInfo(u32) = 1004, |
| #[error("Failed to read stream info file for stream with ID: {0}")] |
| CannotReadStreamInfo(u32) = 1005, |
| #[error("Failed to create stream with ID: {0}")] |
| CannotCreateStream(u32) = 1006, |
| #[error("Failed to delete stream with ID: {0}")] |
| CannotDeleteStream(u32) = 1007, |
| #[error("Failed to delete stream directory with ID: {0}")] |
| CannotDeleteStreamDirectory(u32) = 1008, |
| #[error("Stream with ID: {0} was not found.")] |
| StreamIdNotFound(u32) = 1009, |
| #[error("Stream with name: {0} was not found.")] |
| StreamNameNotFound(String) = 1010, |
| #[error("Stream with ID: {0} already exists.")] |
| StreamIdAlreadyExists(u32) = 1011, |
| #[error("Stream with name: {0} already exists.")] |
| StreamNameAlreadyExists(String) = 1012, |
| #[error("Invalid stream name")] |
| InvalidStreamName = 1013, |
| #[error("Invalid stream ID")] |
| InvalidStreamId = 1014, |
| #[error("Cannot read streams")] |
| CannotReadStreams = 1015, |
| #[error("Missing streams")] |
| MissingStreams = 1016, |
| #[error("Missing topics for stream with ID: {0}")] |
| MissingTopics(u32) = 1017, |
| #[error("Missing partitions for topic with ID: {0} for stream with ID: {1}.")] |
| MissingPartitions(u32, u32) = 1018, |
| #[error("Max topic size cannot be lower than segment size. Max topic size: {0} < segment size: {1}.")] |
| InvalidTopicSize(MaxTopicSize, IggyByteSize) = 1019, |
| #[error("Cannot create topics directory for stream with ID: {0}, Path: {1}")] |
| CannotCreateTopicsDirectory(u32, String) = 2000, |
| #[error( |
| "Failed to create directory for topic with ID: {0} for stream with ID: {1}, Path: {2}" |
| )] |
| CannotCreateTopicDirectory(u32, u32, String) = 2001, |
| #[error("Failed to create topic info file for topic with ID: {0} for stream with ID: {1}.")] |
| CannotCreateTopicInfo(u32, u32) = 2002, |
| #[error("Failed to update topic info for topic with ID: {0} for stream with ID: {1}.")] |
| CannotUpdateTopicInfo(u32, u32) = 2003, |
| #[error("Failed to open topic info file for topic with ID: {0} for stream with ID: {1}.")] |
| CannotOpenTopicInfo(u32, u32) = 2004, |
| #[error("Failed to read topic info file for topic with ID: {0} for stream with ID: {1}.")] |
| CannotReadTopicInfo(u32, u32) = 2005, |
| #[error("Failed to create topic with ID: {0} for stream with ID: {1}.")] |
| CannotCreateTopic(u32, u32) = 2006, |
| #[error("Failed to delete topic with ID: {0} for stream with ID: {1}.")] |
| CannotDeleteTopic(u32, u32) = 2007, |
| #[error("Failed to delete topic directory with ID: {0} for stream with ID: {1}, Path: {2}")] |
| CannotDeleteTopicDirectory(u32, u32, String) = 2008, |
| #[error("Cannot poll topic")] |
| CannotPollTopic = 2009, |
| #[error("Topic with ID: {0} for stream with ID: {1} was not found.")] |
| TopicIdNotFound(u32, u32) = 2010, |
| #[error("Topic with name: {0} for stream with ID: {1} was not found.")] |
| TopicNameNotFound(String, String) = 2011, |
| #[error("Topic with ID: {0} for stream with ID: {1} already exists.")] |
| TopicIdAlreadyExists(u32, u32) = 2012, |
| #[error("Topic with name: {0} for stream with ID: {1} already exists.")] |
| TopicNameAlreadyExists(String, u32) = 2013, |
| #[error("Invalid topic name")] |
| InvalidTopicName = 2014, |
| #[error("Too many partitions")] |
| TooManyPartitions = 2015, |
| #[error("Invalid topic ID")] |
| InvalidTopicId = 2016, |
| #[error("Cannot read topics for stream with ID: {0}")] |
| CannotReadTopics(u32) = 2017, |
| #[error("Invalid replication factor")] |
| InvalidReplicationFactor = 2018, |
| #[error("Cannot create partition with ID: {0} for stream with ID: {1} and topic with ID: {2}")] |
| CannotCreatePartition(u32, u32, u32) = 3000, |
| #[error( |
| "Failed to create directory for partitions for stream with ID: {0} and topic with ID: {1}" |
| )] |
| CannotCreatePartitionsDirectory(u32, u32) = 3001, |
| #[error("Failed to create directory for partition with ID: {0} for stream with ID: {1} and topic with ID: {2}")] |
| CannotCreatePartitionDirectory(u32, u32, u32) = 3002, |
| #[error("Cannot open partition log file")] |
| CannotOpenPartitionLogFile = 3003, |
| #[error("Cannot read partitions directories.")] |
| CannotReadPartitions = 3004, |
| #[error( |
| "Failed to delete partition with ID: {0} for stream with ID: {1} and topic with ID: {2}" |
| )] |
| CannotDeletePartition(u32, u32, u32) = 3005, |
| #[error("Failed to delete partition directory with ID: {0} for stream with ID: {1} and topic with ID: {2}")] |
| CannotDeletePartitionDirectory(u32, u32, u32) = 3006, |
| #[error( |
| "Partition with ID: {0} for topic with ID: {1} for stream with ID: {2} was not found." |
| )] |
| PartitionNotFound(u32, u32, u32) = 3007, |
| #[error("Topic with ID: {0} for stream with ID: {1} has no partitions.")] |
| NoPartitions(u32, u32) = 3008, |
| #[error("Cannot read partitions for topic with ID: {0} for stream with ID: {1}")] |
| TopicFull(u32, u32) = 3009, |
| #[error("Failed to delete consumer offsets directory for path: {0}")] |
| CannotDeleteConsumerOffsetsDirectory(String) = 3010, |
| #[error("Failed to delete consumer offset file for path: {0}")] |
| CannotDeleteConsumerOffsetFile(String) = 3011, |
| #[error("Failed to create consumer offsets directory for path: {0}")] |
| CannotCreateConsumerOffsetsDirectory(String) = 3012, |
| #[error("Failed to read consumers offsets from path: {0}")] |
| CannotReadConsumerOffsets(String) = 3020, |
| #[error("Consumer offset for consumer with ID: {0} was not found.")] |
| ConsumerOffsetNotFound(u32) = 3021, |
| #[error("Segment not found")] |
| SegmentNotFound = 4000, |
| #[error("Segment with start offset: {0} and partition with ID: {1} is closed")] |
| SegmentClosed(u64, u32) = 4001, |
| #[error("Segment size is invalid")] |
| InvalidSegmentSize(u64) = 4002, |
| #[error("Failed to create segment log file for Path: {0}.")] |
| CannotCreateSegmentLogFile(String) = 4003, |
| #[error("Failed to create segment index file for Path: {0}.")] |
| CannotCreateSegmentIndexFile(String) = 4004, |
| #[error("Failed to create segment time index file for Path: {0}.")] |
| CannotCreateSegmentTimeIndexFile(String) = 4005, |
| #[error("Cannot save messages to segment.")] |
| CannotSaveMessagesToSegment = 4006, |
| #[error("Cannot save index to segment.")] |
| CannotSaveIndexToSegment = 4007, |
| #[error("Cannot save time index to segment.")] |
| CannotSaveTimeIndexToSegment = 4008, |
| #[error("Invalid messages count")] |
| InvalidMessagesCount = 4009, |
| #[error("Cannot append message")] |
| CannotAppendMessage = 4010, |
| #[error("Cannot read message")] |
| CannotReadMessage = 4011, |
| #[error("Cannot read message ID")] |
| CannotReadMessageId = 4012, |
| #[error("Cannot read message state")] |
| CannotReadMessageState = 4013, |
| #[error("Cannot read message timestamp")] |
| CannotReadMessageTimestamp = 4014, |
| #[error("Cannot read headers length")] |
| CannotReadHeadersLength = 4015, |
| #[error("Cannot read headers payload")] |
| CannotReadHeadersPayload = 4016, |
| #[error("Too big headers payload")] |
| TooBigHeadersPayload = 4017, |
| #[error("Invalid header key")] |
| InvalidHeaderKey = 4018, |
| #[error("Invalid header value")] |
| InvalidHeaderValue = 4019, |
| #[error("Cannot read message length")] |
| CannotReadMessageLength = 4020, |
| #[error("Cannot save messages to segment")] |
| CannotReadMessagePayload = 4021, |
| #[error("Too big message payload")] |
| TooBigMessagePayload = 4022, |
| #[error("Too many messages")] |
| TooManyMessages = 4023, |
| #[error("Empty message payload")] |
| EmptyMessagePayload = 4024, |
| #[error("Invalid message payload length")] |
| InvalidMessagePayloadLength = 4025, |
| #[error("Cannot read message checksum")] |
| CannotReadMessageChecksum = 4026, |
| #[error("Invalid message checksum: {0}, expected: {1}, for offset: {2}")] |
| InvalidMessageChecksum(u32, u32, u64) = 4027, |
| #[error("Invalid key value length")] |
| InvalidKeyValueLength = 4028, |
| #[error("Command length error: {0}")] |
| CommandLengthError(String) = 4029, |
| #[error("Non-zero offset: {0} at index: {1}")] |
| NonZeroOffset(u64, u32) = 4030, |
| #[error("Non-zero timestamp: {0} at index: {1}")] |
| NonZeroTimestamp(u64, u32) = 4031, |
| #[error("Missing index: {0}")] |
| MissingIndex(u32) = 4032, |
| #[error("Invalid indexes byte size: {0}B, should be divisible by 16")] |
| InvalidIndexesByteSize(u32) = 4034, |
| #[error("Invalid indexes count: {0}, expected: {1}")] |
| InvalidIndexesCount(u32, u32) = 4035, |
| #[error("Invalid messages byte size: {0}B, expected: {1}B")] |
| InvalidMessagesSize(u32, u32) = 4036, |
| #[error("Cannot sed messages due to client disconnection")] |
| CannotSendMessagesDueToClientDisconnection = 4050, |
| #[error("Invalid offset: {0}")] |
| InvalidOffset(u64) = 4100, |
| #[error("Consumer group with ID: {0} for topic with ID: {1} was not found.")] |
| ConsumerGroupIdNotFound(u32, u32) = 5000, |
| #[error("Consumer group with ID: {0} for topic with ID: {1} already exists.")] |
| ConsumerGroupIdAlreadyExists(u32, u32) = 5001, |
| #[error("Invalid consumer group ID")] |
| InvalidConsumerGroupId = 5002, |
| #[error("Consumer group with name: {0} for topic with ID: {1} was not found.")] |
| ConsumerGroupNameNotFound(String, String) = 5003, |
| #[error("Consumer group with name: {0} for topic with ID: {1} already exists.")] |
| ConsumerGroupNameAlreadyExists(String, u32) = 5004, |
| #[error("Invalid consumer group name")] |
| InvalidConsumerGroupName = 5005, |
| #[error("Consumer group member with ID: {0} for group with ID: {1} for topic with ID: {2} was not found.")] |
| ConsumerGroupMemberNotFound(u32, u32, u32) = 5006, |
| #[error("Failed to create consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.")] |
| CannotCreateConsumerGroupInfo(u32, u32, u32) = 5007, |
| #[error("Failed to delete consumer group info file for ID: {0} for topic with ID: {1} for stream with ID: {2}.")] |
| CannotDeleteConsumerGroupInfo(u32, u32, u32) = 5008, |
| #[error("Base offset is missing")] |
| MissingBaseOffsetRetainedMessageBatch = 6000, |
| #[error("Last offset delta is missing")] |
| MissingLastOffsetDeltaRetainedMessageBatch = 6001, |
| #[error("Max timestamp is missing")] |
| MissingMaxTimestampRetainedMessageBatch = 6002, |
| #[error("Length is missing")] |
| MissingLengthRetainedMessageBatch = 6003, |
| #[error("Payload is missing")] |
| MissingPayloadRetainedMessageBatch = 6004, |
| #[error("Cannot read batch base offset")] |
| CannotReadBatchBaseOffset = 7000, |
| #[error("Cannot read batch length")] |
| CannotReadBatchLength = 7001, |
| #[error("Cannot read batch last offset delta")] |
| CannotReadLastOffsetDelta = 7002, |
| #[error("Cannot read batch maximum timestamp")] |
| CannotReadMaxTimestamp = 7003, |
| #[error("Cannot read batch payload")] |
| CannotReadBatchPayload = 7004, |
| #[error("Invalid connection string")] |
| InvalidConnectionString = 8000, |
| #[error("Snapshot file completion failed")] |
| SnapshotFileCompletionFailed = 9000, |
| #[error("Cannot serialize resource")] |
| CannotSerializeResource = 10000, |
| #[error("Cannot deserialize resource")] |
| CannotDeserializeResource = 10001, |
| #[error("Cannot read file")] |
| CannotReadFile = 10002, |
| #[error("Cannot read file metadata")] |
| CannotReadFileMetadata = 10003, |
| #[error("Cannot seek file")] |
| CannotSeekFile = 10004, |
| #[error("Cannot append to file")] |
| CannotAppendToFile = 10005, |
| #[error("Cannot write to file")] |
| CannotWriteToFile = 10006, |
| #[error("Cannot overwrite file")] |
| CannotOverwriteFile = 10007, |
| #[error("Cannot delete file")] |
| CannotDeleteFile = 10008, |
| #[error("Cannot sync file")] |
| CannotSyncFile = 10009, |
| #[error("Cannot read index offset")] |
| CannotReadIndexOffset = 10010, |
| #[error("Cannot read index position")] |
| CannotReadIndexPosition = 10011, |
| #[error("Cannot read index timestamp")] |
| CannotReadIndexTimestamp = 10012, |
| } |
| |
| impl IggyError { |
| pub fn as_code(&self) -> u32 { |
| // SAFETY: SdkError specifies #[repr(u32)] representation. |
| // https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting |
| unsafe { *(self as *const Self as *const u32) } |
| } |
| |
| pub fn as_string(&self) -> &'static str { |
| self.into() |
| } |
| |
| pub fn from_code(code: u32) -> Self { |
| IggyError::from_repr(code).unwrap_or(IggyError::Error) |
| } |
| |
| pub fn from_code_as_string(code: u32) -> &'static str { |
| IggyErrorDiscriminants::from_repr(code) |
| .map(|discriminant| discriminant.into()) |
| .unwrap_or("unknown error code") |
| } |
| } |
| |
| impl PartialEq for IggyError { |
| fn eq(&self, other: &Self) -> bool { |
| self.as_code() == other.as_code() |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| |
| const GROUP_NAME_ERROR_CODE: u32 = 5005; |
| |
| #[test] |
| fn derived_sdk_error_discriminant_keeps_codes() { |
| assert_eq!( |
| GROUP_NAME_ERROR_CODE, |
| IggyError::InvalidConsumerGroupName.as_code() |
| ); |
| assert_eq!( |
| GROUP_NAME_ERROR_CODE, |
| IggyErrorDiscriminants::InvalidConsumerGroupName as u32 |
| ); |
| } |
| |
| #[test] |
| fn static_str_uses_kebab_case() { |
| assert_eq!( |
| "invalid_consumer_group_name", |
| IggyError::InvalidConsumerGroupName.as_string() |
| ) |
| } |
| |
| #[test] |
| fn gets_string_from_code() { |
| assert_eq!( |
| IggyError::InvalidConsumerGroupName.as_string(), |
| IggyError::from_code_as_string(GROUP_NAME_ERROR_CODE) |
| ) |
| } |
| } |