Skip to content

Commit 87b72d8

Browse files
- feat: ✨ CryptoField追加、onLostKeyValue対応のCryptoFieldableプロトコル導入
1 parent 16601b5 commit 87b72d8

File tree

7 files changed

+105
-64
lines changed

7 files changed

+105
-64
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,11 @@
2020
## サポート
2121

2222
- macOS >= 13
23-
- 6.0 > Swift >= 5.10
23+
- Swift >= 6.0
2424

2525
## 特徴
2626
- AES-GCMでJSONプロパティを暗号化・復号
27-
- 暗号化対象のプロパティは`Sendable``Codable`、および`Hashable`に準拠
28-
- 暗号化対象プロパティは`Optional`型である必要がある
27+
- 暗号化対象のプロパティは`Sendable``CryptoFieldable`に準拠している必要がある
2928

3029
## インストール
3130

@@ -67,12 +66,13 @@ import Foundation
6766

6867
struct Event: Hashable, Codable, Sendable {
6968
var id: UUID
70-
@CryptoField var 個人情報: Self.個人情報?
71-
struct 個人情報: Hashable, Codable, Sendable {
72-
var 氏名: String
73-
var 誕生日: Date
74-
var 年齢: Int
75-
}
69+
var 職業: String
70+
@CryptoField var 氏名: String
71+
@CryptoField var LINEやってる: Bool
72+
@CryptoField var 誕生日: Date
73+
@CryptoField var 年齢: Int
74+
@CryptoField var 身長: Double
75+
@CryptoField var 体重: Double?
7676
}
7777
```
7878

@@ -103,8 +103,8 @@ let event: Event = try CryptoConfigContainer.$key.withValue(key) {
103103
```
104104

105105
## 暗号化可能なプロパティの条件
106-
- プロパティの型は`Sendable``Codable``Hashable`に準拠している必要があります。
107-
- プロパティは`Optional`型である必要があります。
106+
- プロパティの型は`Sendable``CryptoFieldable`に準拠している必要があります。
107+
- Int, String, Double, Bool, Date, Optional型はデフォルトで準拠している
108108

109109
## 暗号鍵が設定されていない場合
110110
暗号鍵が設定されていない場合、プロパティには`nil`が設定されますが、デコード自体は成功します。

Sources/CryptoCodable/CryptoConfigContainer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Foundation
33

44
/// 暗号鍵等の設定を保持するための名前空間
55
public struct CryptoConfigContainer: Sendable {
6-
/// OptionalCryptoFieldのエンコード/デコード時に使用する暗号鍵
6+
/// ``CryptoField``のエンコード/デコード時に使用する暗号鍵
77
///
88
/// 暗号化・復号時に事前にセットする必要がある
99
///
@@ -25,12 +25,12 @@ public struct CryptoConfigContainer: Sendable {
2525
/// ```
2626
@TaskLocal public static var key: SymmetricKey?
2727

28-
/// OptionalCryptoFieldのエンコード/デコード時に使用するJSONEncoder
28+
/// CryptoFieldのエンコード/デコード時に使用するJSONEncoder
2929
///
3030
/// カスタマイズしたい場合のみ設定が必要
3131
@TaskLocal public static var encoder: JSONEncoder = .init()
3232

33-
/// OptionalCryptoFieldのエンコード/デコード時に使用するJSONDecoder
33+
/// CryptoFieldのエンコード/デコード時に使用するJSONDecoder
3434
///
3535
/// カスタマイズしたい場合のみ設定が必要
3636
@TaskLocal public static var decoder: JSONDecoder = .init()

Sources/CryptoCodable/OptionalCryptoField.swift renamed to Sources/CryptoCodable/CryptoField.swift

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,21 @@ import Foundation
55
///
66
/// ## 使用方法
77
///
8-
/// 1. OptionalCryptoFieldプロパティラッパーを使ったCodableな型を定義する
8+
/// 1. ``CryptoField``プロパティラッパーを使った``Codable``な型を定義する
99
///
1010
/// ```swift
1111
/// import CryptoCodable
1212
/// import Foundation
1313
///
1414
/// struct Event: Hashable, Codable, Sendable {
15-
/// var id: UUID
16-
/// @OptionalCryptoField var 個人情報: Self.個人情報?
17-
/// struct 個人情報: Hashable, Codable, Sendable {
18-
/// var 氏名: String
19-
/// var 誕生日: Date
20-
/// var 年齢: Int
21-
/// }
15+
/// var id: UUID
16+
/// var 職業: String
17+
/// @CryptoField var 氏名: String
18+
/// @CryptoField var LINEやってる: Bool
19+
/// @CryptoField var 誕生日: Date
20+
/// @CryptoField var 年齢: Int
21+
/// @CryptoField var 身長: Double
22+
/// @CryptoField var 体重: Double?
2223
/// }
2324
/// ```
2425
///
@@ -48,20 +49,21 @@ import Foundation
4849
///
4950
/// ## 暗号化可能なプロパティの条件
5051
///
51-
/// - プロパティの型はSendable, Codable, Hashbleに準拠している
52-
/// - プロパティはOptional型である
52+
/// - プロパティの型は``Sendable``, ``CryptoFieldable``に準拠している
53+
/// - Int, String, Double, Bool, Date, Optional型はデフォルトで``CryptoFieldable``準拠している
5354
///
5455
/// ## 暗号鍵が設定されていない場合
5556
///
56-
/// 暗号鍵が存在しない場合、プロパティにnilが設定され、デコード自体は成功します。
57+
/// 暗号鍵が存在しない場合、プロパティにonLostKeyValue値が設定され、デコード自体は成功します。
5758
///
58-
/// - throws: `DecryptFailure` 暗号鍵が異なる場合
59+
/// - throws: ``DecryptFailure`` 暗号鍵が異なる場合
5960
///
6061
@propertyWrapper
61-
public struct OptionalCryptoField<T>: Codable, Sendable, Hashable where T: Sendable & Codable & Hashable {
62-
public var wrappedValue: T?
62+
public struct CryptoField<T>: Codable, Sendable, Hashable
63+
where T: Sendable & Codable & Hashable & CryptoFieldable {
64+
public var wrappedValue: T
6365

64-
public init(wrappedValue: T?) {
66+
public init(wrappedValue: T) {
6567
self.wrappedValue = wrappedValue
6668
}
6769

@@ -72,7 +74,7 @@ public struct OptionalCryptoField<T>: Codable, Sendable, Hashable where T: Senda
7274

7375
public init(from decoder: any Decoder) throws {
7476
guard let key = CryptoConfigContainer.key else {
75-
wrappedValue = nil
77+
wrappedValue = T.onLostKeyValue
7678
return
7779
}
7880
let container = try decoder.singleValueContainer()
@@ -92,7 +94,7 @@ public struct OptionalCryptoField<T>: Codable, Sendable, Hashable where T: Senda
9294
get throws {
9395
let json = try CryptoConfigContainer.encoder.encode(wrappedValue)
9496
guard let key = CryptoConfigContainer.key else {
95-
fatalError("暗号鍵が設定されていません。OptionalCryptoFieldKey.keyに暗号鍵を設定してください。")
97+
fatalError("暗号鍵が設定されていません。CryptoFieldKey.keyに暗号鍵を設定してください。")
9698
}
9799
let sealedBox = try AES.GCM.seal(json, using: key)
98100
guard let combined = sealedBox.combined else {
@@ -108,7 +110,7 @@ public struct OptionalCryptoField<T>: Codable, Sendable, Hashable where T: Senda
108110
/// 基本的に投げられることはないため、エラー処理は不要
109111
public struct EncryptIllegalSizeNounceError: Error, Hashable, Codable, Sendable {}
110112

111-
/// デコード(復号)時にOptionalCryptoFieldプロパティが復号に失敗した際に投げられるエラー
113+
/// デコード(復号)時にCryptoFieldプロパティが復号に失敗した際に投げられるエラー
112114
///
113115
/// 暗号鍵が誤っている場合に投げられる
114116
public struct DecryptFailure: Error, Hashable, Codable, Sendable {}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Foundation
2+
3+
/// 鍵が無い場合のデフォルト値を持つことができるプロトコル
4+
public protocol CryptoFieldable: Hashable, Codable {
5+
static var onLostKeyValue: Self { get }
6+
}
7+
extension Int: CryptoFieldable {
8+
/// 鍵が紛失した場合のデフォルト値
9+
@TaskLocal public static var onLostKeyValue = 1
10+
}
11+
extension String: CryptoFieldable {
12+
/// 鍵が紛失した場合のデフォルト値
13+
@TaskLocal public static var onLostKeyValue = "Lost"
14+
}
15+
extension Double: CryptoFieldable {
16+
/// 鍵が紛失した場合のデフォルト値
17+
@TaskLocal public static var onLostKeyValue = 1.0
18+
}
19+
extension Bool: CryptoFieldable {
20+
/// 鍵が紛失した場合のデフォルト値
21+
@TaskLocal public static var onLostKeyValue = false
22+
}
23+
extension Date: CryptoFieldable {
24+
/// 鍵が紛失した場合のデフォルト値
25+
@TaskLocal public static var onLostKeyValue = Date(timeIntervalSince1970: 0)
26+
}
27+
extension Optional: CryptoFieldable where Wrapped: Hashable & Codable {
28+
/// 鍵が紛失した場合のデフォルト値
29+
public static var onLostKeyValue: Self { nil }
30+
}

Sources/CryptoCodable/SymmetricKey+data.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Crypto
22
import Foundation
33

44
extension SymmetricKey {
5-
/// `SymmetricKey`を`Data`に変換する
5+
/// ``SymmetricKey``を``Data``に変換する
66
///
77
/// この`Data`を保存することで後から再度デコード・復号することが可能
88
///
@@ -14,7 +14,7 @@ extension SymmetricKey {
1414
/// let data = key.data // このデータを永続化する
1515
/// ```
1616
///
17-
/// dataは標準イニシャライザで`SymmetricKey`に戻すことができる
17+
/// dataは標準イニシャライザで``SymmetricKey``に戻すことができる
1818
///
1919
/// ```swift
2020
/// import Crypto

Tests/CryptoCodableTests/OptionalCryptoFieldTests.swift renamed to Tests/CryptoCodableTests/CryptoFieldTests.swift

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,29 @@ import CryptoCodable
33
import Foundation
44
import Testing
55

6-
@Suite struct OptionalCryptoFieldTests {
6+
@Suite struct CryptoFieldTests {
77
struct Event: Hashable, Codable, Sendable {
88
var id: UUID
99
var 職業: String
10-
@OptionalCryptoField var 個人情報: Self.個人情報?
11-
struct 個人情報: Hashable, Codable, Sendable {
12-
var 氏名: String
13-
var 誕生日: Date
14-
var 年齢: Int
15-
}
10+
@CryptoField var 氏名: String
11+
@CryptoField var LINEやってる: Bool
12+
@CryptoField var 誕生日: Date
13+
@CryptoField var 年齢: Int
14+
@CryptoField var 身長: Double
15+
@CryptoField var 体重: Double?
1616
}
1717

1818
@Test func 暗号化・復号できる() throws {
1919
// Arrange
2020
let event = Event(
2121
id: UUID(),
2222
職業: "暗号専門家",
23-
個人情報: .init(
24-
氏名: "アリス",
25-
誕生日: Date(timeIntervalSince1970: 54),
26-
年齢: 777
27-
)
23+
氏名: "佐藤",
24+
LINEやってる: true,
25+
誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!,
26+
年齢: 24,
27+
身長: 168.3,
28+
体重: 32.5
2829
)
2930

3031
try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) {
@@ -37,16 +38,17 @@ import Testing
3738
}
3839
}
3940

40-
@Test func 鍵が存在しない場合にデコードするとnilが入る() throws {
41+
@Test func 鍵が存在しない場合にデコードするとonLostKeyValue値が入る() throws {
4142
// Arrange
4243
let event = Event(
43-
id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!,
44+
id: UUID(),
4445
職業: "暗号専門家",
45-
個人情報: .init(
46-
氏名: "アリス",
47-
誕生日: Date(timeIntervalSince1970: 54),
48-
年齢: 777
49-
)
46+
氏名: "佐藤",
47+
LINEやってる: true,
48+
誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!,
49+
年齢: 24,
50+
身長: 168.3,
51+
体重: 32.5
5052
)
5153

5254
let encrypted = try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) {
@@ -59,23 +61,30 @@ import Testing
5961
// Assert
6062
#expect(
6163
decrypted
62-
== .init(
63-
id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!,
64+
== Event(
65+
id: event.id,
6466
職業: "暗号専門家",
65-
個人情報: nil
66-
))
67+
氏名: "Lost",
68+
LINEやってる: false,
69+
誕生日: Date(timeIntervalSince1970: 0),
70+
年齢: 1,
71+
身長: 1,
72+
体重: nil
73+
)
74+
)
6775
}
6876

6977
@Test func 異なる鍵でデコードするとエラーが投げられる() throws {
7078
// Arrange
7179
let event = Event(
72-
id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!,
80+
id: UUID(),
7381
職業: "暗号専門家",
74-
個人情報: .init(
75-
氏名: "アリス",
76-
誕生日: Date(timeIntervalSince1970: 54),
77-
年齢: 777
78-
)
82+
氏名: "佐藤",
83+
LINEやってる: true,
84+
誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!,
85+
年齢: 24,
86+
身長: 168.3,
87+
体重: 32.5
7988
)
8089

8190
let encrypted = try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) {

Tests/CryptoCodableTests/SymmetricKeyDataTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import Testing
55

66
@Suite struct SymmetricKeyDataTests {
77
struct Content: Hashable, Codable, Sendable {
8-
@OptionalCryptoField var value: String?
8+
@CryptoField var value: String?
99
}
1010

1111
@Test func Dataに一度変換したもので復号可能() throws {

0 commit comments

Comments
 (0)