From 6d87b60c4d2956c0e9ed492cde6ffafb8136c752 Mon Sep 17 00:00:00 2001 From: dkz2 Date: Wed, 2 Aug 2023 07:29:07 -0500 Subject: [PATCH 1/3] init delete support --- .../Extensions/UInt8+Characters.swift | 1 + .../SwiftMemcache/MemcachedConnection.swift | 31 +++++++++++++++++++ Sources/SwiftMemcache/MemcachedRequest.swift | 5 +++ .../MemcachedRequestEncoder.swift | 13 ++++++++ .../MemcachedIntegrationTests.swift | 26 ++++++++++++++++ .../MemcachedRequestEncoderTests.swift | 17 ++++++++++ 6 files changed, 93 insertions(+) diff --git a/Sources/SwiftMemcache/Extensions/UInt8+Characters.swift b/Sources/SwiftMemcache/Extensions/UInt8+Characters.swift index b4a36c1..bda618f 100644 --- a/Sources/SwiftMemcache/Extensions/UInt8+Characters.swift +++ b/Sources/SwiftMemcache/Extensions/UInt8+Characters.swift @@ -19,6 +19,7 @@ extension UInt8 { static var m: UInt8 = .init(ascii: "m") static var s: UInt8 = .init(ascii: "s") static var g: UInt8 = .init(ascii: "g") + static var d: UInt8 = .init(ascii: "d") static var v: UInt8 = .init(ascii: "v") static var T: UInt8 = .init(ascii: "T") static var zero: UInt8 = .init(ascii: "0") diff --git a/Sources/SwiftMemcache/MemcachedConnection.swift b/Sources/SwiftMemcache/MemcachedConnection.swift index 41072a8..8ae9c11 100644 --- a/Sources/SwiftMemcache/MemcachedConnection.swift +++ b/Sources/SwiftMemcache/MemcachedConnection.swift @@ -247,4 +247,35 @@ public actor MemcachedConnection { throw MemcachedConnectionError.connectionShutdown } } + + // MARK: - Deleting a Value + + /// Delete the value for a key from the Memcache server. + /// + /// - Parameter key: The key of the item to be deleted. + /// - Returns: A boolean indicating whether the deletion was successful. + /// - Throws: A `MemcachedConnectionError.connectionShutdown` error if the connection to the Memcache server is shut down. + public func delete(_ key: String) async throws -> Bool { + switch self.state { + case .initial(_, _, _, _), + .running: + + let command = MemcachedRequest.DeleteCommand(key: key) + let request = MemcachedRequest.delete(command) + + let response = try await sendRequest(request) + + switch response.returnCode { + case .HD: + return true + case .NF: + return false + default: + return false + } + + case .finished: + throw MemcachedConnectionError.connectionShutdown + } + } } diff --git a/Sources/SwiftMemcache/MemcachedRequest.swift b/Sources/SwiftMemcache/MemcachedRequest.swift index 13a7410..232d0b7 100644 --- a/Sources/SwiftMemcache/MemcachedRequest.swift +++ b/Sources/SwiftMemcache/MemcachedRequest.swift @@ -25,6 +25,11 @@ enum MemcachedRequest { var flags: MemcachedFlags } + struct DeleteCommand { + let key: String + } + case set(SetCommand) case get(GetCommand) + case delete(DeleteCommand) } diff --git a/Sources/SwiftMemcache/MemcachedRequestEncoder.swift b/Sources/SwiftMemcache/MemcachedRequestEncoder.swift index 101e184..1389bf0 100644 --- a/Sources/SwiftMemcache/MemcachedRequestEncoder.swift +++ b/Sources/SwiftMemcache/MemcachedRequestEncoder.swift @@ -60,6 +60,19 @@ struct MemcachedRequestEncoder: MessageToByteEncoder { // write flags if there are any out.writeMemcachedFlags(flags: command.flags) + // write separator + out.writeInteger(UInt8.carriageReturn) + out.writeInteger(UInt8.newline) + + case .delete(let command): + precondition(!command.key.isEmpty, "Key must not be empty") + + // write command and key + out.writeInteger(UInt8.m) + out.writeInteger(UInt8.d) + out.writeInteger(UInt8.whitespace) + out.writeBytes(command.key.utf8) + // write separator out.writeInteger(UInt8.carriageReturn) out.writeInteger(UInt8.newline) diff --git a/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift b/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift index 4772173..73ba62c 100644 --- a/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift +++ b/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift @@ -230,6 +230,32 @@ final class MemcachedIntegrationTest: XCTestCase { } } + func testDeleteValue() async throws { + let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try! group.syncShutdownGracefully()) + } + let memcachedConnection = MemcachedConnection(host: "memcached", port: 11211, eventLoopGroup: group) + + try await withThrowingTaskGroup(of: Void.self) { group in + group.addTask { try await memcachedConnection.run() } + + // Set key and value + let setValue = "foo" + try await memcachedConnection.set("bar", value: setValue) + + // Delete the key + let deletionSuccess = try await memcachedConnection.delete("bar") + XCTAssertTrue(deletionSuccess, "Deletion should be successful") + + // Try to delete the key again + let secondDeletionAttempt = try await memcachedConnection.delete("bar") + XCTAssertFalse(secondDeletionAttempt, "Second deletion attempt should be unsuccessful") + + group.cancelAll() + } + } + func testMemcachedConnectionWithUInt() async throws { let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { diff --git a/Tests/SwiftMemcacheTests/UnitTest/MemcachedRequestEncoderTests.swift b/Tests/SwiftMemcacheTests/UnitTest/MemcachedRequestEncoderTests.swift index d06d0e6..8dad036 100644 --- a/Tests/SwiftMemcacheTests/UnitTest/MemcachedRequestEncoderTests.swift +++ b/Tests/SwiftMemcacheTests/UnitTest/MemcachedRequestEncoderTests.swift @@ -139,4 +139,21 @@ final class MemcachedRequestEncoderTests: XCTestCase { let expectedEncodedData = "mg foo v\r\n" XCTAssertEqual(outBuffer.getString(at: 0, length: outBuffer.readableBytes), expectedEncodedData) } + + func testEncodeDeleteRequest() { + // Prepare a MemcachedRequest + let command = MemcachedRequest.DeleteCommand(key: "foo") + let request = MemcachedRequest.delete(command) + + // Pass our request through the encoder + var outBuffer = ByteBufferAllocator().buffer(capacity: 0) + do { + try self.encoder.encode(data: request, out: &outBuffer) + } catch { + XCTFail("Encoding failed with error: \(error)") + } + + let expectedEncodedData = "md foo\r\n" + XCTAssertEqual(outBuffer.getString(at: 0, length: outBuffer.readableBytes), expectedEncodedData) + } } From 24d4ad9fe0e765d5f230fda6371db675b69496a1 Mon Sep 17 00:00:00 2001 From: dkz2 Date: Wed, 2 Aug 2023 08:28:58 -0500 Subject: [PATCH 2/3] delete signature refactor --- Sources/SwiftMemcache/MemcachedConnection.swift | 10 +++++----- .../IntegrationTest/MemcachedIntegrationTests.swift | 12 +++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Sources/SwiftMemcache/MemcachedConnection.swift b/Sources/SwiftMemcache/MemcachedConnection.swift index 8ae9c11..061333d 100644 --- a/Sources/SwiftMemcache/MemcachedConnection.swift +++ b/Sources/SwiftMemcache/MemcachedConnection.swift @@ -253,9 +253,9 @@ public actor MemcachedConnection { /// Delete the value for a key from the Memcache server. /// /// - Parameter key: The key of the item to be deleted. - /// - Returns: A boolean indicating whether the deletion was successful. /// - Throws: A `MemcachedConnectionError.connectionShutdown` error if the connection to the Memcache server is shut down. - public func delete(_ key: String) async throws -> Bool { + /// - Throws: A `MemcachedConnectionError.unexpectedNilResponse` error if the key was not found or if an unexpected response code was returned. + public func delete(_ key: String) async throws { switch self.state { case .initial(_, _, _, _), .running: @@ -267,11 +267,11 @@ public actor MemcachedConnection { switch response.returnCode { case .HD: - return true + return case .NF: - return false + throw MemcachedConnectionError.unexpectedNilResponse default: - return false + throw MemcachedConnectionError.unexpectedNilResponse } case .finished: diff --git a/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift b/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift index 73ba62c..a2ed847 100644 --- a/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift +++ b/Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift @@ -245,13 +245,11 @@ final class MemcachedIntegrationTest: XCTestCase { try await memcachedConnection.set("bar", value: setValue) // Delete the key - let deletionSuccess = try await memcachedConnection.delete("bar") - XCTAssertTrue(deletionSuccess, "Deletion should be successful") - - // Try to delete the key again - let secondDeletionAttempt = try await memcachedConnection.delete("bar") - XCTAssertFalse(secondDeletionAttempt, "Second deletion attempt should be unsuccessful") - + do { + try await memcachedConnection.delete("bar") + } catch { + XCTFail("Deletion attempt should be successful, but threw: \(error)") + } group.cancelAll() } } From 5406cf4162076bca299a0ffd2d22ccae56c0733b Mon Sep 17 00:00:00 2001 From: dkz2 Date: Wed, 2 Aug 2023 09:27:07 -0500 Subject: [PATCH 3/3] NF error added --- Sources/SwiftMemcache/MemcachedConnection.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/SwiftMemcache/MemcachedConnection.swift b/Sources/SwiftMemcache/MemcachedConnection.swift index 061333d..05549b7 100644 --- a/Sources/SwiftMemcache/MemcachedConnection.swift +++ b/Sources/SwiftMemcache/MemcachedConnection.swift @@ -59,6 +59,8 @@ public actor MemcachedConnection { case connectionShutdown /// Indicates that a nil response was received from the server. case unexpectedNilResponse + /// Indicates that the key was not found. + case keyNotFound } private var state: State @@ -269,7 +271,7 @@ public actor MemcachedConnection { case .HD: return case .NF: - throw MemcachedConnectionError.unexpectedNilResponse + throw MemcachedConnectionError.keyNotFound default: throw MemcachedConnectionError.unexpectedNilResponse }