krz/keycask

Password manager: Swift core library, CLI for macOS/Linux/Windows, iOS/macOS app. cli password-manager swift

Sources/KeycaskCore/EntryID.swift

45 lines · 1521 bytes

 1public struct EntryID: Hashable, Sendable, CustomStringConvertible {
 2    public static let alphabet: [Character] = Array("abcdefghijkmnpqrstuvwxyz23456789")
 3    public static let length = 8
 4
 5    public let rawValue: String
 6
 7    public init?(_ raw: String) {
 8        guard raw.count == Self.length else { return nil }
 9        let allowed = Set(Self.alphabet)
10        guard raw.allSatisfy({ allowed.contains($0) }) else { return nil }
11        rawValue = raw
12    }
13
14    public static func random() -> EntryID {
15        var rng = SystemRandomNumberGenerator()
16        return random(using: &rng)
17    }
18
19    public static func random(using rng: inout some RandomNumberGenerator) -> EntryID {
20        var chars: [Character] = []
21        chars.reserveCapacity(length)
22        for _ in 0..<length {
23            chars.append(alphabet[Int(rng.next(upperBound: UInt32(alphabet.count)))])
24        }
25        return EntryID(String(chars))!
26    }
27
28    public var description: String { rawValue }
29}
30
31extension EntryID: Codable {
32    public init(from decoder: any Decoder) throws {
33        let raw = try decoder.singleValueContainer().decode(String.self)
34        guard let id = EntryID(raw) else {
35            throw DecodingError.dataCorrupted(
36                .init(codingPath: decoder.codingPath, debugDescription: "invalid entry id \(raw)"))
37        }
38        self = id
39    }
40
41    public func encode(to encoder: any Encoder) throws {
42        var container = encoder.singleValueContainer()
43        try container.encode(rawValue)
44    }
45}