Skip to content

Commit 58eda22

Browse files
authored
Handle Data encodings (#57)
* Add Base64 decoding for Data. * Allow DataProtocol for Data
1 parent 894c112 commit 58eda22

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
3+
/// Decodes `String` values as a Base64-encoded `Data`.
4+
///
5+
/// Decodes strictly valid Base64. This does not handle b64url encoding, invalid padding, or unknown characters.
6+
public struct Base64Strategy<DataType: MutableDataProtocol>: DataValueCodableStrategy {
7+
public static func decode(_ value: String) throws -> DataType {
8+
if let data = Data(base64Encoded: value) {
9+
return DataType(data)
10+
} else {
11+
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Invalid Base64 Format!"))
12+
}
13+
}
14+
15+
public static func encode(_ data: DataType) -> String {
16+
Data(data).base64EncodedString()
17+
}
18+
}

Sources/BetterCodable/DataValue.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Foundation
2+
3+
/// A protocol for providing a custom strategy for encoding and decoding data as strings.
4+
///
5+
/// `DataValueCodableStrategy` provides a generic strategy type that the `DataValue` property wrapper can use to inject
6+
/// custom strategies for encoding and decoding data values.
7+
///
8+
public protocol DataValueCodableStrategy {
9+
associatedtype DataType: MutableDataProtocol
10+
static func decode(_ value: String) throws -> DataType
11+
static func encode(_ data: DataType) -> String
12+
}
13+
14+
/// Decodes and encodes data using a strategy type.
15+
///
16+
/// `@DataValue` decodes data using a `DataValueCodableStrategy` which provides custom decoding and encoding functionality.
17+
@propertyWrapper
18+
public struct DataValue<Coder: DataValueCodableStrategy> {
19+
public var wrappedValue: Coder.DataType
20+
21+
public init(wrappedValue: Coder.DataType) {
22+
self.wrappedValue = wrappedValue
23+
}
24+
}
25+
26+
extension DataValue: Decodable {
27+
public init(from decoder: Decoder) throws {
28+
self.wrappedValue = try Coder.decode(String(from: decoder))
29+
}
30+
}
31+
32+
extension DataValue: Encodable {
33+
public func encode(to encoder: Encoder) throws {
34+
try Coder.encode(wrappedValue).encode(to: encoder)
35+
}
36+
}
37+
38+
extension DataValue: Equatable where Coder.DataType: Equatable {}
39+
extension DataValue: Hashable where Coder.DataType: Hashable {}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import XCTest
2+
import BetterCodable
3+
4+
class DataValueTests: XCTestCase {
5+
func testDecodingAndEncodingBase64String() throws {
6+
struct Fixture: Codable {
7+
@DataValue<Base64Strategy> var data: Data
8+
}
9+
let jsonData = #"{"data":"QmV0dGVyQ29kYWJsZQ=="}"#.data(using: .utf8)!
10+
11+
let fixture = try JSONDecoder().decode(Fixture.self, from: jsonData)
12+
XCTAssertEqual(fixture.data, Data("BetterCodable".utf8))
13+
14+
let outputJSON = try JSONEncoder().encode(fixture)
15+
XCTAssertEqual(outputJSON, jsonData)
16+
}
17+
18+
func testDecodingMalformedBase64Fails() throws {
19+
struct Fixture: Codable {
20+
@DataValue<Base64Strategy> var data: Data
21+
}
22+
let jsonData = #"{"data":"invalidBase64!"}"#.data(using: .utf8)!
23+
24+
XCTAssertThrowsError(try JSONDecoder().decode(Fixture.self, from: jsonData))
25+
}
26+
27+
func testDecodingAndEncodingBase64StringToArray() throws {
28+
struct Fixture: Codable {
29+
@DataValue<Base64Strategy> var data: [UInt8]
30+
}
31+
let jsonData = #"{"data":"QmV0dGVyQ29kYWJsZQ=="}"#.data(using: .utf8)!
32+
33+
let fixture = try JSONDecoder().decode(Fixture.self, from: jsonData)
34+
XCTAssertEqual(fixture.data, Array("BetterCodable".utf8))
35+
36+
let outputJSON = try JSONEncoder().encode(fixture)
37+
XCTAssertEqual(outputJSON, jsonData)
38+
}
39+
40+
}

0 commit comments

Comments
 (0)