Sources/KeycaskCore/Vault.swift
65 lines · 2081 bytes
1import Foundation
2
3public struct Vault: Codable, Equatable, Sendable {
4 public var entries: [Entry]
5
6 public init(entries: [Entry] = []) {
7 self.entries = entries
8 }
9
10 public func entry(id: EntryID) -> Entry? {
11 entries.first { $0.id == id }
12 }
13
14 public mutating func add(_ entry: Entry) throws {
15 guard self.entry(id: entry.id) == nil else { throw KeycaskError.duplicateID(entry.id) }
16 var normalized = entry
17 normalized.tags = Entry.normalize(tags: entry.tags)
18 entries.append(normalized)
19 }
20
21 public mutating func remove(id: EntryID) throws {
22 guard let index = entries.firstIndex(where: { $0.id == id }) else {
23 throw KeycaskError.notFound(id.rawValue)
24 }
25 entries.remove(at: index)
26 }
27
28 public mutating func update(
29 id: EntryID, now: Date = .now, _ change: (inout Entry) -> Void
30 ) throws {
31 guard let index = entries.firstIndex(where: { $0.id == id }) else {
32 throw KeycaskError.notFound(id.rawValue)
33 }
34 change(&entries[index])
35 entries[index].tags = Entry.normalize(tags: entries[index].tags)
36 entries[index].updated = Entry.truncateToSeconds(now)
37 }
38
39 public func resolve(_ ref: String) throws -> Entry {
40 if let id = EntryID(ref), let hit = entry(id: id) {
41 return hit
42 }
43 let byName = entries.filter { $0.name == ref }
44 switch byName.count {
45 case 0: throw KeycaskError.notFound(ref)
46 case 1: return byName[0]
47 default: throw KeycaskError.ambiguous(name: ref, candidates: byName)
48 }
49 }
50
51 public func filter(tag: String) -> [Entry] {
52 sortedEntries.filter { $0.hasTag(tag) }
53 }
54
55 public func search(_ query: String) -> [Entry] {
56 sortedEntries.filter { $0.matches(query) }
57 }
58
59 public var sortedEntries: [Entry] {
60 entries.sorted { a, b in
61 let (la, lb) = (a.name.lowercased(), b.name.lowercased())
62 return la == lb ? a.id.rawValue < b.id.rawValue : la < lb
63 }
64 }
65}