245 lines
8.9 KiB
Swift
245 lines
8.9 KiB
Swift
// Copyright 2024 The NATS Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import Foundation
|
|
|
|
public protocol NatsErrorProtocol: Error, CustomStringConvertible {}
|
|
|
|
public enum NatsError {
|
|
public enum ServerError: NatsErrorProtocol, Equatable {
|
|
case staleConnection
|
|
case maxConnectionsExceeded
|
|
case authorizationViolation
|
|
case authenticationExpired
|
|
case authenticationRevoked
|
|
case authenticationTimeout
|
|
case permissionsViolation(Operation, String, String?)
|
|
case proto(String)
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .staleConnection:
|
|
return "nats: stale connection"
|
|
case .maxConnectionsExceeded:
|
|
return "nats: maximum connections exceeded"
|
|
case .authorizationViolation:
|
|
return "nats: authorization violation"
|
|
case .authenticationExpired:
|
|
return "nats: authentication expired"
|
|
case .authenticationRevoked:
|
|
return "nats: authentication revoked"
|
|
case .authenticationTimeout:
|
|
return "nats: authentication timeout"
|
|
case .permissionsViolation(let operation, let subject, let queue):
|
|
if let queue {
|
|
return
|
|
"nats: permissions violation for operation \"\(operation)\" on subject \"\(subject)\" using queue \"\(queue)\""
|
|
} else {
|
|
return
|
|
"nats: permissions violation for operation \"\(operation)\" on subject \"\(subject)\""
|
|
}
|
|
case .proto(let error):
|
|
return "nats: \(error)"
|
|
}
|
|
}
|
|
var normalizedError: String {
|
|
return description.trimWhitespacesAndApostrophes().lowercased()
|
|
}
|
|
init(_ error: String) {
|
|
let normalizedError = error.trimWhitespacesAndApostrophes().lowercased()
|
|
if normalizedError.contains("stale connection") {
|
|
self = .staleConnection
|
|
} else if normalizedError.contains("maximum connections exceeded") {
|
|
self = .maxConnectionsExceeded
|
|
} else if normalizedError.contains("authorization violation") {
|
|
self = .authorizationViolation
|
|
} else if normalizedError.contains("authentication expired") {
|
|
self = .authenticationExpired
|
|
} else if normalizedError.contains("authentication revoked") {
|
|
self = .authenticationRevoked
|
|
} else if normalizedError.contains("authentication timeout") {
|
|
self = .authenticationTimeout
|
|
} else if normalizedError.contains("permissions violation") {
|
|
if let (operation, subject, queue) = NatsError.ServerError.parsePermissions(
|
|
error: error)
|
|
{
|
|
self = .permissionsViolation(operation, subject, queue)
|
|
} else {
|
|
self = .proto(error)
|
|
}
|
|
} else {
|
|
self = .proto(error)
|
|
}
|
|
}
|
|
|
|
public enum Operation: String, Equatable {
|
|
case publish = "Publish"
|
|
case subscribe = "Subscription"
|
|
}
|
|
|
|
internal static func parsePermissions(error: String) -> (Operation, String, String?)? {
|
|
let pattern = "(Publish|Subscription) to \"(\\S+)\""
|
|
let regex = try! NSRegularExpression(pattern: pattern)
|
|
let matches = regex.matches(
|
|
in: error, options: [], range: NSRange(location: 0, length: error.utf16.count))
|
|
|
|
guard let match = matches.first else {
|
|
return nil
|
|
}
|
|
|
|
var operation: Operation?
|
|
if let operationRange = Range(match.range(at: 1), in: error) {
|
|
let operationString = String(error[operationRange])
|
|
operation = Operation(rawValue: operationString)
|
|
}
|
|
|
|
var subject: String?
|
|
if let subjectRange = Range(match.range(at: 2), in: error) {
|
|
subject = String(error[subjectRange])
|
|
}
|
|
|
|
let queuePattern = "using queue \"(\\S+)\""
|
|
let queueRegex = try! NSRegularExpression(pattern: queuePattern)
|
|
let queueMatches = queueRegex.matches(
|
|
in: error, options: [], range: NSRange(location: 0, length: error.utf16.count))
|
|
|
|
var queue: String?
|
|
if let match = queueMatches.first, let queueRange = Range(match.range(at: 1), in: error)
|
|
{
|
|
queue = String(error[queueRange])
|
|
}
|
|
|
|
if let operation, let subject {
|
|
return (operation, subject, queue)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ProtocolError: NatsErrorProtocol, Equatable {
|
|
case invalidOperation(String)
|
|
case parserFailure(String)
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .invalidOperation(let op):
|
|
return "nats: unknown server operation: \(op)"
|
|
case .parserFailure(let cause):
|
|
return "nats: parser failure: \(cause)"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ClientError: NatsErrorProtocol {
|
|
case internalError(String)
|
|
case maxReconnects
|
|
case connectionClosed
|
|
case io(Error)
|
|
case invalidConnection(String)
|
|
case cancelled
|
|
case alreadyConnected
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .internalError(let error):
|
|
return "nats: internal error: \(error)"
|
|
case .maxReconnects:
|
|
return "nats: max reconnects exceeded"
|
|
case .connectionClosed:
|
|
return "nats: connection is closed"
|
|
case .io(let error):
|
|
return "nats: IO error: \(error)"
|
|
case .invalidConnection(let error):
|
|
return "nats: \(error)"
|
|
case .cancelled:
|
|
return "nats: operation cancelled"
|
|
case .alreadyConnected:
|
|
return "nats: client is already connected or connecting"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ConnectError: NatsErrorProtocol {
|
|
case invalidConfig(String)
|
|
case tlsFailure(Error)
|
|
case timeout
|
|
case dns(Error)
|
|
case io(Error)
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .invalidConfig(let error):
|
|
return "nats: invalid client configuration: \(error)"
|
|
case .tlsFailure(let error):
|
|
return "nats: TLS error: \(error)"
|
|
case .timeout:
|
|
return "nats: timed out waiting for connection"
|
|
case .dns(let error):
|
|
return "nats: DNS lookup error: \(error)"
|
|
case .io(let error):
|
|
return "nats: error establishing connection: \(error)"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum RequestError: NatsErrorProtocol, Equatable {
|
|
case noResponders
|
|
case timeout
|
|
case permissionDenied
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .noResponders:
|
|
return "nats: no responders available for request"
|
|
case .timeout:
|
|
return "nats: request timed out"
|
|
case .permissionDenied:
|
|
return "nats: permission denied"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum SubscriptionError: NatsErrorProtocol, Equatable {
|
|
case invalidSubject
|
|
case invalidQueue
|
|
case permissionDenied
|
|
case subscriptionClosed
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .invalidSubject:
|
|
return "nats: invalid subject name"
|
|
case .invalidQueue:
|
|
return "nats: invalid queue group name"
|
|
case .permissionDenied:
|
|
return "nats: permission denied"
|
|
case .subscriptionClosed:
|
|
return "nats: subscription closed"
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum ParseHeaderError: NatsErrorProtocol, Equatable {
|
|
case invalidCharacter
|
|
|
|
public var description: String {
|
|
switch self {
|
|
case .invalidCharacter:
|
|
return
|
|
"nats: invalid header name (name cannot contain non-ascii alphanumeric characters other than '-')"
|
|
}
|
|
}
|
|
}
|
|
}
|