Replace ID command dictionary with Identification struct

This commit is contained in:
Oliver Drobnik
2025-07-22 16:25:22 +02:00
parent c7082a5aa2
commit f8b60fb85e
5 changed files with 102 additions and 41 deletions
@@ -4,17 +4,17 @@ import OrderedCollections
/// Command for IMAP ID.
struct IDCommand: IMAPCommand {
typealias ResultType = IDResponse
typealias ResultType = Identification
typealias HandlerType = IDHandler
/// Client identification parameters.
let parameters: OrderedDictionary<String, String?>
let identification: Identification
init(parameters: OrderedDictionary<String, String?> = [:]) {
self.parameters = parameters
init(identification: Identification = Identification()) {
self.identification = identification
}
func toTaggedCommand(tag: String) -> TaggedCommand {
TaggedCommand(tag: tag, command: .id(parameters))
TaggedCommand(tag: tag, command: .id(identification.nioParameters))
}
}
@@ -6,7 +6,7 @@ import NIOConcurrencyHelpers
import OrderedCollections
/// Handler for the IMAP ID command.
final class IDHandler: BaseIMAPCommandHandler<IDResponse>, IMAPCommandHandler, @unchecked Sendable {
final class IDHandler: BaseIMAPCommandHandler<Identification>, IMAPCommandHandler, @unchecked Sendable {
private var responseParams: OrderedDictionary<String, String?> = [:]
override func handleUntaggedResponse(_ response: Response) -> Bool {
@@ -19,7 +19,7 @@ final class IDHandler: BaseIMAPCommandHandler<IDResponse>, IMAPCommandHandler, @
override func handleTaggedOKResponse(_ response: TaggedResponse) {
let params = lock.withLock { responseParams }
succeedWithResult(IDResponse(parameters: params))
succeedWithResult(Identification(parameters: params))
}
override func handleTaggedErrorResponse(_ response: TaggedResponse) {
+3 -7
View File
@@ -212,19 +212,15 @@ public actor IMAPServer {
}
/// Identify the client to the server using the `ID` command.
/// - Parameter parameters: Key/value pairs describing the client. Pass an empty dictionary to send no information.
/// - Parameter identification: Information describing the client. Pass the default value to send no information.
/// - Returns: Information returned by the server.
/// - Throws: ``IMAPError.commandNotSupported`` if the server does not support the command or ``IMAPError.commandFailed`` on failure.
public func id(parameters: [String: String?] = [:]) async throws -> IDResponse {
public func id(_ identification: Identification = Identification()) async throws -> Identification {
guard capabilities.contains(.id) else {
throw IMAPError.commandNotSupported("ID command not supported by server")
}
var ordered = OrderedDictionary<String, String?>()
for (key, value) in parameters {
ordered[key] = value
}
let command = IDCommand(parameters: ordered)
let command = IDCommand(identification: identification)
return try await executeCommand(command)
}
@@ -1,27 +0,0 @@
import Foundation
import OrderedCollections
/// Information returned by the IMAP `ID` command.
///
/// The server may return any key/value pairs describing its
/// implementation. Values may be `nil` if the server omits them.
public struct IDResponse: Sendable {
/// Raw key/value pairs returned by the server.
public var parameters: OrderedDictionary<String, String?>
/// Create a new response with the provided parameters.
public init(parameters: OrderedDictionary<String, String?> = [:]) {
self.parameters = parameters
}
/// Access a value by key.
public subscript(key: String) -> String? {
parameters[key] ?? nil
}
/// Commonly used name field of the server.
public var name: String? { self["name"] }
/// Server version string if provided.
public var version: String? { self["version"] }
}
@@ -0,0 +1,92 @@
import Foundation
import OrderedCollections
/// Client or server identification information used with the IMAP `ID` command.
///
/// The most common fields defined by RFC 2971 are provided as stored
/// properties. Additional parameters that do not match these fields can
/// be accessed via the subscript operator.
public struct Identification: Sendable {
/// Product name of the client or server.
public var name: String?
/// Product version string.
public var version: String?
/// Operating system name.
public var os: String?
/// Operating system version string.
public var osVersion: String?
/// Name of the vendor.
public var vendor: String?
/// Support contact URL.
public var supportURL: String?
/// Any additional parameters returned by the server.
public var additional: [String: String?]
/// Create a new identification value.
public init(
name: String? = nil,
version: String? = nil,
os: String? = nil,
osVersion: String? = nil,
vendor: String? = nil,
supportURL: String? = nil,
additional: [String: String?] = [:]
) {
self.name = name
self.version = version
self.os = os
self.osVersion = osVersion
self.vendor = vendor
self.supportURL = supportURL
self.additional = additional
}
/// Create an Identification from raw parameters received from NIOIMAP.
internal init(parameters: OrderedDictionary<String, String?>) {
self.name = parameters["name"] ?? nil
self.version = parameters["version"] ?? nil
self.os = parameters["os"] ?? nil
self.osVersion = parameters["os-version"] ?? nil
self.vendor = parameters["vendor"] ?? nil
self.supportURL = parameters["support-url"] ?? nil
var other: [String: String?] = [:]
for (key, value) in parameters where !Self.knownKeys.contains(key) {
other[key] = value
}
self.additional = other
}
/// Access a parameter by key.
public subscript(key: String) -> String? {
switch key {
case "name": return name
case "version": return version
case "os": return os
case "os-version": return osVersion
case "vendor": return vendor
case "support-url": return supportURL
default: return additional[key] ?? nil
}
}
/// Convert this Identification into the ordered dictionary format expected by NIOIMAP.
internal var nioParameters: OrderedDictionary<String, String?> {
var params: OrderedDictionary<String, String?> = [:]
params["name"] = name
params["version"] = version
params["os"] = os
params["os-version"] = osVersion
params["vendor"] = vendor
params["support-url"] = supportURL
for (k, v) in additional {
params[k] = v
}
return params
}
/// Keys that map directly to stored properties.
private static let knownKeys: Set<String> = [
"name", "version", "os", "os-version", "vendor", "support-url"
]
}