diff --git a/README.md b/README.md index 06b1ec1..25309eb 100644 --- a/README.md +++ b/README.md @@ -20,12 +20,11 @@ ## サポート - macOS >= 13 -- 6.0 > Swift >= 5.10 +- Swift >= 6.0 ## 特徴 - AES-GCMでJSONプロパティを暗号化・復号 -- 暗号化対象のプロパティは`Sendable`、`Codable`、および`Hashable`に準拠 -- 暗号化対象プロパティは`Optional`型である必要がある +- 暗号化対象のプロパティは`Sendable`、`CryptoFieldable`に準拠している必要がある ## インストール @@ -67,12 +66,13 @@ import Foundation struct Event: Hashable, Codable, Sendable { var id: UUID - @CryptoField var 個人情報: Self.個人情報? - struct 個人情報: Hashable, Codable, Sendable { - var 氏名: String - var 誕生日: Date - var 年齢: Int - } + var 職業: String + @CryptoField var 氏名: String + @CryptoField var LINEやってる: Bool + @CryptoField var 誕生日: Date + @CryptoField var 年齢: Int + @CryptoField var 身長: Double + @CryptoField var 体重: Double? } ``` @@ -103,8 +103,8 @@ let event: Event = try CryptoConfigContainer.$key.withValue(key) { ``` ## 暗号化可能なプロパティの条件 -- プロパティの型は`Sendable`、`Codable`、`Hashable`に準拠している必要があります。 -- プロパティは`Optional`型である必要があります。 +- プロパティの型は`Sendable`と`CryptoFieldable`に準拠している必要があります。 +- Int, String, Double, Bool, Date, Optional型はデフォルトで準拠している ## 暗号鍵が設定されていない場合 暗号鍵が設定されていない場合、プロパティには`nil`が設定されますが、デコード自体は成功します。 diff --git a/Sources/CryptoCodable/CryptoConfigContainer.swift b/Sources/CryptoCodable/CryptoConfigContainer.swift index cef55be..a9a5428 100644 --- a/Sources/CryptoCodable/CryptoConfigContainer.swift +++ b/Sources/CryptoCodable/CryptoConfigContainer.swift @@ -3,7 +3,7 @@ import Foundation /// 暗号鍵等の設定を保持するための名前空間 public struct CryptoConfigContainer: Sendable { - /// OptionalCryptoFieldのエンコード/デコード時に使用する暗号鍵 + /// ``CryptoField``のエンコード/デコード時に使用する暗号鍵 /// /// 暗号化・復号時に事前にセットする必要がある /// @@ -25,12 +25,12 @@ public struct CryptoConfigContainer: Sendable { /// ``` @TaskLocal public static var key: SymmetricKey? - /// OptionalCryptoFieldのエンコード/デコード時に使用するJSONEncoder + /// CryptoFieldのエンコード/デコード時に使用するJSONEncoder /// /// カスタマイズしたい場合のみ設定が必要 @TaskLocal public static var encoder: JSONEncoder = .init() - /// OptionalCryptoFieldのエンコード/デコード時に使用するJSONDecoder + /// CryptoFieldのエンコード/デコード時に使用するJSONDecoder /// /// カスタマイズしたい場合のみ設定が必要 @TaskLocal public static var decoder: JSONDecoder = .init() diff --git a/Sources/CryptoCodable/OptionalCryptoField.swift b/Sources/CryptoCodable/CryptoField.swift similarity index 69% rename from Sources/CryptoCodable/OptionalCryptoField.swift rename to Sources/CryptoCodable/CryptoField.swift index 3a94d64..a63f695 100644 --- a/Sources/CryptoCodable/OptionalCryptoField.swift +++ b/Sources/CryptoCodable/CryptoField.swift @@ -5,20 +5,21 @@ import Foundation /// /// ## 使用方法 /// -/// 1. OptionalCryptoFieldプロパティラッパーを使ったCodableな型を定義する +/// 1. ``CryptoField``プロパティラッパーを使った``Codable``な型を定義する /// /// ```swift /// import CryptoCodable /// import Foundation /// /// struct Event: Hashable, Codable, Sendable { -/// var id: UUID -/// @OptionalCryptoField var 個人情報: Self.個人情報? -/// struct 個人情報: Hashable, Codable, Sendable { -/// var 氏名: String -/// var 誕生日: Date -/// var 年齢: Int -/// } +/// var id: UUID +/// var 職業: String +/// @CryptoField var 氏名: String +/// @CryptoField var LINEやってる: Bool +/// @CryptoField var 誕生日: Date +/// @CryptoField var 年齢: Int +/// @CryptoField var 身長: Double +/// @CryptoField var 体重: Double? /// } /// ``` /// @@ -48,20 +49,21 @@ import Foundation /// /// ## 暗号化可能なプロパティの条件 /// -/// - プロパティの型はSendable, Codable, Hashbleに準拠している -/// - プロパティはOptional型である +/// - プロパティの型は``Sendable``, ``CryptoFieldable``に準拠している +/// - Int, String, Double, Bool, Date, Optional型はデフォルトで``CryptoFieldable``準拠している /// /// ## 暗号鍵が設定されていない場合 /// -/// 暗号鍵が存在しない場合、プロパティにnilが設定され、デコード自体は成功します。 +/// 暗号鍵が存在しない場合、プロパティにonLostKeyValue値が設定され、デコード自体は成功します。 /// -/// - throws: `DecryptFailure` 暗号鍵が異なる場合 +/// - throws: ``DecryptFailure`` 暗号鍵が異なる場合 /// @propertyWrapper -public struct OptionalCryptoField: Codable, Sendable, Hashable where T: Sendable & Codable & Hashable { - public var wrappedValue: T? +public struct CryptoField: Codable, Sendable, Hashable +where T: Sendable & Codable & Hashable & CryptoFieldable { + public var wrappedValue: T - public init(wrappedValue: T?) { + public init(wrappedValue: T) { self.wrappedValue = wrappedValue } @@ -72,7 +74,7 @@ public struct OptionalCryptoField: Codable, Sendable, Hashable where T: Senda public init(from decoder: any Decoder) throws { guard let key = CryptoConfigContainer.key else { - wrappedValue = nil + wrappedValue = T.onLostKeyValue return } let container = try decoder.singleValueContainer() @@ -92,7 +94,7 @@ public struct OptionalCryptoField: Codable, Sendable, Hashable where T: Senda get throws { let json = try CryptoConfigContainer.encoder.encode(wrappedValue) guard let key = CryptoConfigContainer.key else { - fatalError("暗号鍵が設定されていません。OptionalCryptoFieldKey.keyに暗号鍵を設定してください。") + fatalError("暗号鍵が設定されていません。CryptoFieldKey.keyに暗号鍵を設定してください。") } let sealedBox = try AES.GCM.seal(json, using: key) guard let combined = sealedBox.combined else { @@ -108,7 +110,7 @@ public struct OptionalCryptoField: Codable, Sendable, Hashable where T: Senda /// 基本的に投げられることはないため、エラー処理は不要 public struct EncryptIllegalSizeNounceError: Error, Hashable, Codable, Sendable {} -/// デコード(復号)時にOptionalCryptoFieldプロパティが復号に失敗した際に投げられるエラー +/// デコード(復号)時にCryptoFieldプロパティが復号に失敗した際に投げられるエラー /// /// 暗号鍵が誤っている場合に投げられる public struct DecryptFailure: Error, Hashable, Codable, Sendable {} diff --git a/Sources/CryptoCodable/CryptoFieldable.swift b/Sources/CryptoCodable/CryptoFieldable.swift new file mode 100644 index 0000000..402e937 --- /dev/null +++ b/Sources/CryptoCodable/CryptoFieldable.swift @@ -0,0 +1,30 @@ +import Foundation + +/// 鍵が無い場合のデフォルト値を持つことができるプロトコル +public protocol CryptoFieldable: Hashable, Codable { + static var onLostKeyValue: Self { get } +} +extension Int: CryptoFieldable { + /// 鍵が紛失した場合のデフォルト値 + @TaskLocal public static var onLostKeyValue = 1 +} +extension String: CryptoFieldable { + /// 鍵が紛失した場合のデフォルト値 + @TaskLocal public static var onLostKeyValue = "Lost" +} +extension Double: CryptoFieldable { + /// 鍵が紛失した場合のデフォルト値 + @TaskLocal public static var onLostKeyValue = 1.0 +} +extension Bool: CryptoFieldable { + /// 鍵が紛失した場合のデフォルト値 + @TaskLocal public static var onLostKeyValue = false +} +extension Date: CryptoFieldable { + /// 鍵が紛失した場合のデフォルト値 + @TaskLocal public static var onLostKeyValue = Date(timeIntervalSince1970: 0) +} +extension Optional: CryptoFieldable where Wrapped: Hashable & Codable { + /// 鍵が紛失した場合のデフォルト値 + public static var onLostKeyValue: Self { nil } +} diff --git a/Sources/CryptoCodable/SymmetricKey+data.swift b/Sources/CryptoCodable/SymmetricKey+data.swift index 7605141..b8f718b 100644 --- a/Sources/CryptoCodable/SymmetricKey+data.swift +++ b/Sources/CryptoCodable/SymmetricKey+data.swift @@ -2,7 +2,7 @@ import Crypto import Foundation extension SymmetricKey { - /// `SymmetricKey`を`Data`に変換する + /// ``SymmetricKey``を``Data``に変換する /// /// この`Data`を保存することで後から再度デコード・復号することが可能 /// @@ -14,7 +14,7 @@ extension SymmetricKey { /// let data = key.data // このデータを永続化する /// ``` /// - /// dataは標準イニシャライザで`SymmetricKey`に戻すことができる + /// dataは標準イニシャライザで``SymmetricKey``に戻すことができる /// /// ```swift /// import Crypto diff --git a/Tests/CryptoCodableTests/OptionalCryptoFieldTests.swift b/Tests/CryptoCodableTests/CryptoFieldTests.swift similarity index 56% rename from Tests/CryptoCodableTests/OptionalCryptoFieldTests.swift rename to Tests/CryptoCodableTests/CryptoFieldTests.swift index 0148521..2464153 100644 --- a/Tests/CryptoCodableTests/OptionalCryptoFieldTests.swift +++ b/Tests/CryptoCodableTests/CryptoFieldTests.swift @@ -3,16 +3,16 @@ import CryptoCodable import Foundation import Testing -@Suite struct OptionalCryptoFieldTests { +@Suite struct CryptoFieldTests { struct Event: Hashable, Codable, Sendable { var id: UUID var 職業: String - @OptionalCryptoField var 個人情報: Self.個人情報? - struct 個人情報: Hashable, Codable, Sendable { - var 氏名: String - var 誕生日: Date - var 年齢: Int - } + @CryptoField var 氏名: String + @CryptoField var LINEやってる: Bool + @CryptoField var 誕生日: Date + @CryptoField var 年齢: Int + @CryptoField var 身長: Double + @CryptoField var 体重: Double? } @Test func 暗号化・復号できる() throws { @@ -20,11 +20,12 @@ import Testing let event = Event( id: UUID(), 職業: "暗号専門家", - 個人情報: .init( - 氏名: "アリス", - 誕生日: Date(timeIntervalSince1970: 54), - 年齢: 777 - ) + 氏名: "佐藤", + LINEやってる: true, + 誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!, + 年齢: 24, + 身長: 168.3, + 体重: 32.5 ) try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) { @@ -37,16 +38,17 @@ import Testing } } - @Test func 鍵が存在しない場合にデコードするとnilが入る() throws { + @Test func 鍵が存在しない場合にデコードするとonLostKeyValue値が入る() throws { // Arrange let event = Event( - id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!, + id: UUID(), 職業: "暗号専門家", - 個人情報: .init( - 氏名: "アリス", - 誕生日: Date(timeIntervalSince1970: 54), - 年齢: 777 - ) + 氏名: "佐藤", + LINEやってる: true, + 誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!, + 年齢: 24, + 身長: 168.3, + 体重: 32.5 ) let encrypted = try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) { @@ -59,23 +61,30 @@ import Testing // Assert #expect( decrypted - == .init( - id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!, + == Event( + id: event.id, 職業: "暗号専門家", - 個人情報: nil - )) + 氏名: "Lost", + LINEやってる: false, + 誕生日: Date(timeIntervalSince1970: 0), + 年齢: 1, + 身長: 1, + 体重: nil + ) + ) } @Test func 異なる鍵でデコードするとエラーが投げられる() throws { // Arrange let event = Event( - id: UUID(uuidString: "C09B74E3-1BEF-4F34-994C-FAE04390FBA8")!, + id: UUID(), 職業: "暗号専門家", - 個人情報: .init( - 氏名: "アリス", - 誕生日: Date(timeIntervalSince1970: 54), - 年齢: 777 - ) + 氏名: "佐藤", + LINEやってる: true, + 誕生日: ISO8601DateFormatter().date(from: "2001-06-01T00:00:00Z")!, + 年齢: 24, + 身長: 168.3, + 体重: 32.5 ) let encrypted = try CryptoConfigContainer.$key.withValue(.init(size: .bits256)) { diff --git a/Tests/CryptoCodableTests/SymmetricKeyDataTests.swift b/Tests/CryptoCodableTests/SymmetricKeyDataTests.swift index 389cb32..6115d7c 100644 --- a/Tests/CryptoCodableTests/SymmetricKeyDataTests.swift +++ b/Tests/CryptoCodableTests/SymmetricKeyDataTests.swift @@ -5,7 +5,7 @@ import Testing @Suite struct SymmetricKeyDataTests { struct Content: Hashable, Codable, Sendable { - @OptionalCryptoField var value: String? + @CryptoField var value: String? } @Test func Dataに一度変換したもので復号可能() throws {