Skip to content

Commit 67477dd

Browse files
authored
Implements MemcachedValue Protocol and Conformances (#22)
* init MemcachedValue * added example usage * closes #21 * soundness likes some over generic type? * PR clean up * soundness * closes #22
1 parent 0e97917 commit 67477dd

File tree

6 files changed

+278
-14
lines changed

6 files changed

+278
-14
lines changed

Package.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,11 @@ let package = Package(
4747
name: "SwiftMemcacheTests",
4848
dependencies: ["SwiftMemcache"]
4949
),
50+
.executableTarget(
51+
name: "swift-memcache-gsoc-example",
52+
dependencies: [
53+
.target(name: "SwiftMemcache"),
54+
]
55+
),
5056
]
5157
)

Sources/SwiftMemcache/MemcachedConnection.swift

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ public actor MemcachedConnection {
5757
enum MemcachedConnectionError: Error {
5858
/// Indicates that the connection has shut down.
5959
case connectionShutdown
60+
/// Indicates that a nil response was received from the server.
61+
case unexpectedNilResponse
6062
}
6163

6264
private var state: State
@@ -137,8 +139,8 @@ public actor MemcachedConnection {
137139
/// Fetch the value for a key from the Memcache server.
138140
///
139141
/// - Parameter key: The key to fetch the value for.
140-
/// - Returns: A `ByteBuffer` containing the fetched value, or `nil` if no value was found.
141-
public func get(_ key: String) async throws -> ByteBuffer? {
142+
/// - Returns: A `Value` containing the fetched value, or `nil` if no value was found.
143+
public func get<Value: MemcachedValue>(_ key: String, as valueType: Value.Type = Value.self) async throws -> Value? {
142144
switch self.state {
143145
case .initial(_, _, _, let requestContinuation),
144146
.running(_, _, _, let requestContinuation):
@@ -148,7 +150,7 @@ public actor MemcachedConnection {
148150
let command = MemcachedRequest.GetCommand(key: key, flags: flags)
149151
let request = MemcachedRequest.get(command)
150152

151-
return try await withCheckedThrowingContinuation { continuation in
153+
let response = try await withCheckedThrowingContinuation { continuation in
152154
switch requestContinuation.yield((request, continuation)) {
153155
case .enqueued:
154156
break
@@ -159,6 +161,11 @@ public actor MemcachedConnection {
159161
}
160162
}.value
161163

164+
if var unwrappedResponse = response {
165+
return Value.readFromBuffer(&unwrappedResponse)
166+
} else {
167+
throw MemcachedConnectionError.unexpectedNilResponse
168+
}
162169
case .finished:
163170
throw MemcachedConnectionError.connectionShutdown
164171
}
@@ -168,19 +175,18 @@ public actor MemcachedConnection {
168175
///
169176
/// - Parameters:
170177
/// - key: The key to set the value for.
171-
/// - value: The value to set for the key.
172-
/// - Returns: A `ByteBuffer` containing the server's response to the set request.
173-
public func set(_ key: String, value: String) async throws -> ByteBuffer? {
178+
/// - value: The `Value` to set for the key.
179+
public func set(_ key: String, value: some MemcachedValue) async throws {
174180
switch self.state {
175181
case .initial(_, let bufferAllocator, _, let requestContinuation),
176182
.running(let bufferAllocator, _, _, let requestContinuation):
177183

178184
var buffer = bufferAllocator.buffer(capacity: 0)
179-
buffer.writeString(value)
185+
value.writeToBuffer(&buffer)
180186
let command = MemcachedRequest.SetCommand(key: key, value: buffer)
181187
let request = MemcachedRequest.set(command)
182188

183-
return try await withCheckedThrowingContinuation { continuation in
189+
_ = try await withCheckedThrowingContinuation { continuation in
184190
switch requestContinuation.yield((request, continuation)) {
185191
case .enqueued:
186192
break
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the swift-memcache-gsoc open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the swift-memcache-gsoc project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of swift-memcache-gsoc project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
17+
/// Protocol defining the requirements for a type that can be converted to a ByteBuffer for transmission to Memcached.
18+
public protocol MemcachedValue {
19+
/// Writes the value to a ByteBuffer.
20+
///
21+
/// - Parameter buffer: The ByteBuffer to which the value should be written.
22+
func writeToBuffer(_ buffer: inout ByteBuffer)
23+
24+
/// Reads the type from a ByteBuffer.
25+
///
26+
/// - Parameter buffer: The ByteBuffer from which the value should be read.
27+
static func readFromBuffer(_ buffer: inout ByteBuffer) -> Self?
28+
}
29+
30+
/// Extension for FixedWidthInteger types to conform to MemcachedValue.
31+
extension MemcachedValue where Self: FixedWidthInteger {
32+
/// Writes the integer to a ByteBuffer.
33+
///
34+
/// - Parameter buffer: The ByteBuffer to which the integer should be written.
35+
public func writeToBuffer(_ buffer: inout ByteBuffer) {
36+
buffer.writeInteger(self)
37+
}
38+
39+
/// Reads a FixedWidthInteger from a ByteBuffer.
40+
///
41+
/// - Parameter buffer: The ByteBuffer from which the value should be read.
42+
public static func readFromBuffer(_ buffer: inout ByteBuffer) -> Self? {
43+
return buffer.readInteger()
44+
}
45+
}
46+
47+
/// Extension for StringProtocol types to conform to MemcachedValue.
48+
extension MemcachedValue where Self: StringProtocol {
49+
/// Writes the string to a ByteBuffer.
50+
///
51+
/// - Parameter buffer: The ByteBuffer to which the string should be written.
52+
public func writeToBuffer(_ buffer: inout ByteBuffer) {
53+
buffer.writeString(String(self))
54+
}
55+
56+
/// Reads a String from a ByteBuffer.
57+
///
58+
/// - Parameter buffer: The ByteBuffer from which the value should be read.
59+
public static func readFromBuffer(_ buffer: inout ByteBuffer) -> Self? {
60+
return buffer.readString(length: buffer.readableBytes) as? Self
61+
}
62+
}
63+
64+
/// MemcachedValue conformance to several standard Swift types.
65+
extension Int: MemcachedValue {}
66+
extension Int8: MemcachedValue {}
67+
extension Int16: MemcachedValue {}
68+
extension Int32: MemcachedValue {}
69+
extension Int64: MemcachedValue {}
70+
extension UInt: MemcachedValue {}
71+
extension UInt8: MemcachedValue {}
72+
extension UInt16: MemcachedValue {}
73+
extension UInt32: MemcachedValue {}
74+
extension UInt64: MemcachedValue {}
75+
extension String: MemcachedValue {}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the swift-memcache-gsoc open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the swift-memcache-gsoc project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of swift-memcache-gsoc project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
import NIOPosix
17+
import SwiftMemcache
18+
19+
@main
20+
struct Program {
21+
// Create an event loop group with a single thread
22+
static let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
23+
24+
static func main() async throws {
25+
// Instantiate a new MemcachedConnection actor with host, port, and event loop group
26+
let memcachedConnection = MemcachedConnection(host: "127.0.0.1", port: 11211, eventLoopGroup: eventLoopGroup)
27+
28+
try await withThrowingTaskGroup(of: Void.self) { group in
29+
// Add the connection actor's run function to the task group
30+
// This opens the connection and handles requests until the task is cancelled or the connection is closed
31+
group.addTask { try await memcachedConnection.run() }
32+
33+
// Set a value for a key.
34+
let setValue = "bar"
35+
try await memcachedConnection.set("foo", value: setValue)
36+
37+
// Get the value for a key.
38+
// Specify the expected type for the value returned from Memcache.
39+
let getValue: String? = try await memcachedConnection.get("foo")
40+
41+
// Assert that the get operation was successful by comparing the value set and the value returned from Memcache.
42+
// If they are not equal, this will throw an error.
43+
assert(getValue == setValue, "Value retrieved from Memcache does not match the set value")
44+
45+
// Cancel all tasks in the task group.
46+
// This also results in the connection to Memcache being closed.
47+
group.cancelAll()
48+
}
49+
}
50+
}

Tests/SwiftMemcacheTests/IntegrationTest/MemcachedIntegrationTests.swift

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,43 @@ final class MemcachedIntegrationTest: XCTestCase {
9595
try await withThrowingTaskGroup(of: Void.self) { group in
9696
group.addTask { try await connectionActor.run() }
9797

98+
// Set key and value
9899
let setValue = "foo"
99-
var setBuffer = ByteBufferAllocator().buffer(capacity: setValue.count)
100-
setBuffer.writeString(setValue)
101-
let _ = try await connectionActor.set("bar", value: setValue)
100+
try await connectionActor.set("bar", value: setValue)
102101

103102
// Get value for key
104-
let getValue = try await connectionActor.get("bar")
105-
let getValueString = getValue?.getString(at: getValue!.readerIndex, length: getValue!.readableBytes)
106-
XCTAssertEqual(getValueString, setValue, "Received value should be the same as sent")
103+
let getValue: String? = try await connectionActor.get("bar")
104+
XCTAssertEqual(getValue, setValue, "Received value should be the same as sent")
105+
106+
group.cancelAll()
107+
}
108+
}
109+
110+
func testMemcachedConnectionActorWithUInt() async throws {
111+
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
112+
defer {
113+
XCTAssertNoThrow(try! group.syncShutdownGracefully())
114+
}
115+
let connectionActor = MemcachedConnection(host: "memcached", port: 11211, eventLoopGroup: group)
116+
117+
try await withThrowingTaskGroup(of: Void.self) { group in
118+
group.addTask { try await connectionActor.run() }
119+
120+
// Set UInt32 value for key
121+
let setUInt32Value: UInt32 = 1_234_567_890
122+
try await connectionActor.set("UInt32Key", value: setUInt32Value)
123+
124+
// Get value for UInt32 key
125+
let getUInt32Value: UInt32? = try await connectionActor.get("UInt32Key")
126+
XCTAssertEqual(getUInt32Value, setUInt32Value, "Received UInt32 value should be the same as sent")
127+
128+
// Set UInt64 value for key
129+
let setUInt64Value: UInt64 = 12_345_678_901_234_567_890
130+
let _ = try await connectionActor.set("UInt64Key", value: setUInt64Value)
131+
132+
// Get value for UInt64 key
133+
let getUInt64Value: UInt64? = try await connectionActor.get("UInt64Key")
134+
XCTAssertEqual(getUInt64Value, setUInt64Value, "Received UInt64 value should be the same as sent")
107135

108136
group.cancelAll()
109137
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the swift-memcache-gsoc open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the swift-memcache-gsoc project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of swift-memcache-gsoc project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
@testable import SwiftMemcache
17+
import XCTest
18+
19+
final class MemcachedValueTests: XCTestCase {
20+
func testMemcachedValueConformance() {
21+
var buffer = ByteBufferAllocator().buffer(capacity: 0)
22+
23+
// Test for Int
24+
let int = 100
25+
int.writeToBuffer(&buffer)
26+
let intResult = Int.readFromBuffer(&buffer)
27+
XCTAssertEqual(intResult, 100)
28+
buffer.clear()
29+
30+
// Test for Int8
31+
let int8: Int8 = 8
32+
int8.writeToBuffer(&buffer)
33+
let int8Result = Int8.readFromBuffer(&buffer)
34+
XCTAssertEqual(int8Result, 8)
35+
buffer.clear()
36+
37+
// Test for Int16
38+
let int16: Int16 = 16
39+
int16.writeToBuffer(&buffer)
40+
let int16Result = Int16.readFromBuffer(&buffer)
41+
XCTAssertEqual(int16Result, 16)
42+
buffer.clear()
43+
44+
// Test for Int32
45+
let int32: Int32 = 32
46+
int32.writeToBuffer(&buffer)
47+
let int32Result = Int32.readFromBuffer(&buffer)
48+
XCTAssertEqual(int32Result, 32)
49+
buffer.clear()
50+
51+
// Test for Int64
52+
let int64: Int64 = 64
53+
int64.writeToBuffer(&buffer)
54+
let int64Result = Int64.readFromBuffer(&buffer)
55+
XCTAssertEqual(int64Result, 64)
56+
buffer.clear()
57+
58+
// Test for UInt
59+
let uint: UInt = 200
60+
uint.writeToBuffer(&buffer)
61+
let uintResult = UInt.readFromBuffer(&buffer)
62+
XCTAssertEqual(uintResult, 200)
63+
buffer.clear()
64+
65+
// Test for UInt8
66+
let uint8: UInt8 = 9
67+
uint8.writeToBuffer(&buffer)
68+
let uint8Result = UInt8.readFromBuffer(&buffer)
69+
XCTAssertEqual(uint8Result, 9)
70+
buffer.clear()
71+
72+
// Test for UInt16
73+
let uint16: UInt16 = 17
74+
uint16.writeToBuffer(&buffer)
75+
let uint16Result = UInt16.readFromBuffer(&buffer)
76+
XCTAssertEqual(uint16Result, 17)
77+
buffer.clear()
78+
79+
// Test for UInt32
80+
let uint32: UInt32 = 33
81+
uint32.writeToBuffer(&buffer)
82+
let uint32Result = UInt32.readFromBuffer(&buffer)
83+
XCTAssertEqual(uint32Result, 33)
84+
buffer.clear()
85+
86+
// Test for UInt64
87+
let uint64: UInt64 = 65
88+
uint64.writeToBuffer(&buffer)
89+
let uint64Result = UInt64.readFromBuffer(&buffer)
90+
XCTAssertEqual(uint64Result, 65)
91+
buffer.clear()
92+
93+
// Test for String
94+
let string = "string"
95+
string.writeToBuffer(&buffer)
96+
let stringResult = String.readFromBuffer(&buffer)
97+
XCTAssertEqual(stringResult, "string")
98+
}
99+
}

0 commit comments

Comments
 (0)