Skip to content

Commit ece9b49

Browse files
Merge pull request #17 from NeedleInAJayStack/feat/server-nav
Adds `nav` support
2 parents b40fc9b + 7db785a commit ece9b49

File tree

4 files changed

+59
-10
lines changed

4 files changed

+59
-10
lines changed

Package.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,5 +76,6 @@ let package = Package(
7676
.product(name: "VaporTesting", package: "vapor", condition: .when(traits: ["ServerVapor"])),
7777
]
7878
),
79-
]
79+
],
80+
swiftLanguageModes: [.v5, .version("6")]
8081
)

Sources/Haystack/Bool.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extension Bool: Val {
1313
}
1414
}
1515

16-
extension Bool: Comparable {
16+
extension Bool: @retroactive Comparable {
1717
public static func < (lhs: Bool, rhs: Bool) -> Bool {
1818
return !lhs && rhs // false is less than true
1919
}

Sources/HaystackServer/HaystackServer.swift

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public final class HaystackServer: API, Sendable {
77
let recordStore: RecordStore
88
let historyStore: HistoryStore
99
let watchStore: WatchStore
10+
let navPath: [String]
1011

1112
let onInvokeAction: @Sendable (Haystack.Ref, String, [String: any Haystack.Val]) async throws -> Haystack.Grid
1213
let onEval: @Sendable (String) async throws -> Haystack.Grid
@@ -15,6 +16,7 @@ public final class HaystackServer: API, Sendable {
1516
recordStore: RecordStore,
1617
historyStore: HistoryStore,
1718
watchStore: WatchStore,
19+
navPath: [String] = ["site", "equip", "point"],
1820
onInvokeAction: @escaping @Sendable (Haystack.Ref, String, [String: any Haystack.Val]) async throws -> Haystack.Grid = { _, _, _ in
1921
GridBuilder().toGrid()
2022
},
@@ -25,6 +27,7 @@ public final class HaystackServer: API, Sendable {
2527
self.recordStore = recordStore
2628
self.historyStore = historyStore
2729
self.watchStore = watchStore
30+
self.navPath = navPath
2831
self.onInvokeAction = onInvokeAction
2932
self.onEval = onEval
3033
}
@@ -106,9 +109,52 @@ public final class HaystackServer: API, Sendable {
106109
return gb.toGrid()
107110
}
108111

109-
public func nav(navId _: Haystack.Ref?) async throws -> Haystack.Grid {
110-
// TODO: Implement
111-
return GridBuilder().toGrid()
112+
public func nav(navId parentId: Haystack.Ref?) async throws -> Haystack.Grid {
113+
let gb = Haystack.GridBuilder()
114+
try gb.addCol(name: "navId")
115+
guard navPath.count > 0 else {
116+
return gb.toGrid()
117+
}
118+
guard let parentId = parentId else {
119+
// If no input, just return the first level of navigation
120+
for result in try await recordStore.read(filter: "\(navPath[0])", limit: nil) {
121+
var navResult = result
122+
navResult["navId"] = result["id"]
123+
try gb.addRow(navResult)
124+
}
125+
return gb.toGrid()
126+
}
127+
guard let parentDict = try await recordStore.read(ids: [parentId]).first else {
128+
throw ServerError.idNotFound(parentId)
129+
}
130+
// Find the first component of the navPath that matches a tag on the input dict
131+
var parentNavPathIndex: Int? = nil
132+
for index in 0 ..< navPath.count {
133+
let component = navPath[index]
134+
if parentDict.has(component) {
135+
parentNavPathIndex = index
136+
}
137+
}
138+
guard let parentNavPathIndex = parentNavPathIndex else {
139+
throw ServerError.navPathComponentNotFound(navPath)
140+
}
141+
guard parentNavPathIndex < navPath.count - 1 else {
142+
// Parent is a navPath leaf. No further navigation is possible, so return nothing.
143+
return gb.toGrid()
144+
}
145+
let parentNavComponent = navPath[parentNavPathIndex]
146+
let childNavComponent = navPath[parentNavPathIndex + 1]
147+
// Read children using child component and inferring parent ref tag
148+
let children = try await recordStore.read(
149+
filter: "\(childNavComponent) and \(parentNavComponent)Ref == \(parentId.toZinc())",
150+
limit: nil
151+
)
152+
for child in children {
153+
var navChild = child
154+
navChild["navId"] = child["id"]
155+
try gb.addRow(navChild)
156+
}
157+
return gb.toGrid()
112158
}
113159

114160
public func hisRead(id: Haystack.Ref, range: Haystack.HisReadRange) async throws -> Haystack.Grid {
@@ -199,5 +245,6 @@ public final class HaystackServer: API, Sendable {
199245

200246
public enum ServerError: Error {
201247
case idNotFound(Haystack.Ref)
248+
case navPathComponentNotFound([String])
202249
case watchNotFound(String)
203250
}

Tests/HaystackServerTests/HaystackServerTests.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,14 @@ struct HaystackServerTests {
198198
watchStore: InMemoryWatchStore()
199199
)
200200

201-
var grid = try await server.nav(navId: Ref("site"))
202-
// TODO: Implement nav
203-
// #expect(grid.compactMap { ($0["id"] as? Ref)?.val }.sorted() == ["equip"])
201+
var grid = try await server.nav(navId: nil)
202+
#expect(grid.compactMap { ($0["navId"] as? Ref)?.val }.sorted() == ["site"])
203+
204+
grid = try await server.nav(navId: Ref("site"))
205+
#expect(grid.compactMap { ($0["navId"] as? Ref)?.val }.sorted() == ["equip"])
204206

205207
grid = try await server.nav(navId: Ref("equip"))
206-
// TODO: Implement nav
207-
// #expect(grid.compactMap { ($0["id"] as? Ref)?.val }.sorted() == ["point"])
208+
#expect(grid.compactMap { ($0["navId"] as? Ref)?.val }.sorted() == ["point"])
208209
}
209210

210211
@Test func hisRead() async throws {

0 commit comments

Comments
 (0)