Skip to content

Commit 35b9e00

Browse files
Merge pull request #1 from NeedleInAJayStack/feature/client
Haystack client
2 parents 8cef725 + eecc797 commit 35b9e00

File tree

23 files changed

+1432
-36
lines changed

23 files changed

+1432
-36
lines changed

.github/workflows/test.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: test
2+
on:
3+
pull_request:
4+
push: { branches: [ main ] }
5+
6+
jobs:
7+
test:
8+
strategy:
9+
matrix:
10+
os: [ubuntu-latest, macos-latest]
11+
runs-on: ${{ matrix.os }}
12+
steps:
13+
- uses: fwal/setup-swift@v1
14+
- uses: actions/checkout@v2
15+
- name: Run tests
16+
run: swift test

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,5 @@
44
/*.xcodeproj
55
xcuserdata/
66
DerivedData/
7-
.swiftpm/config/registries.json
8-
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
97
.netrc
8+
.swiftpm/

.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

Lines changed: 0 additions & 8 deletions
This file was deleted.

Package.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,32 @@ let package = Package(
99
name: "Haystack",
1010
targets: ["Haystack"]
1111
),
12+
.library(
13+
name: "HaystackClient",
14+
targets: ["HaystackClient"]
15+
),
1216
],
1317
dependencies: [],
1418
targets: [
1519
.target(
1620
name: "Haystack",
1721
dependencies: []
1822
),
23+
.target(
24+
name: "HaystackClient",
25+
dependencies: ["Haystack"]
26+
),
1927
.testTarget(
2028
name: "HaystackTests",
2129
dependencies: ["Haystack"]
2230
),
31+
.testTarget(
32+
name: "HaystackClientTests",
33+
dependencies: ["HaystackClient"]
34+
),
35+
.testTarget(
36+
name: "HaystackClientIntegrationTests",
37+
dependencies: ["HaystackClient"]
38+
),
2339
]
2440
)

Sources/Haystack/Grid.swift

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import Foundation
55
/// unit of data exchange over the
66
/// [HTTP API](https://project-haystack.org/doc/docHaystack/HttpApi).
77
///
8+
/// To create a Grid, use a `GridBuilder`.
9+
///
810
/// [Docs](https://project-haystack.org/doc/docHaystack/Kinds#grid)
911
public struct Grid: Val {
1012
public static var valType: ValType { .Grid }
@@ -22,30 +24,39 @@ public struct Grid: Val {
2224
/// Converts to Zinc formatted string.
2325
/// See [Zinc Literals](https://project-haystack.org/doc/docHaystack/Zinc#literals)
2426
public func toZinc() -> String {
25-
var zinc = #"ver:"3.0""#
26-
if meta.elements.count > 0 {
27-
zinc += " \(meta.toZinc(withBraces: false))"
28-
}
29-
zinc += "\n"
27+
// Ensure `ver` is listed first in meta
28+
let ver = meta.elements["ver"] ?? "3.0"
29+
var zinc = "ver:\(ver.toZinc())"
3030

31-
let zincCols = cols.map { col in
32-
var colZinc = col.name
33-
if let colMeta = col.meta, colMeta.elements.count > 0 {
34-
colZinc += " \(colMeta.toZinc(withBraces: false))"
35-
}
36-
return colZinc
31+
var metaWithoutVer = meta.elements
32+
metaWithoutVer["ver"] = nil
33+
if metaWithoutVer.count > 0 {
34+
zinc += " \(Dict(metaWithoutVer).toZinc(withBraces: false))"
3735
}
38-
zinc += zincCols.joined(separator: ", ")
3936
zinc += "\n"
4037

41-
let zincRows = rows.map { row in
42-
let rowZincElements = cols.map { col in
43-
let element = row.elements[col.name] ?? null
44-
return element.toZinc()
38+
if cols.isEmpty {
39+
zinc += "empty\n"
40+
} else {
41+
let zincCols = cols.map { col in
42+
var colZinc = col.name
43+
if let colMeta = col.meta, colMeta.elements.count > 0 {
44+
colZinc += " \(colMeta.toZinc(withBraces: false))"
45+
}
46+
return colZinc
4547
}
46-
return rowZincElements.joined(separator: ", ")
48+
zinc += zincCols.joined(separator: ", ")
49+
zinc += "\n"
50+
51+
let zincRows = rows.map { row in
52+
let rowZincElements = cols.map { col in
53+
let element = row.elements[col.name] ?? null
54+
return element.toZinc()
55+
}
56+
return rowZincElements.joined(separator: ", ")
57+
}
58+
zinc += zincRows.joined(separator: "\n")
4759
}
48-
zinc += zincRows.joined(separator: "\n")
4960

5061
return zinc
5162
}
@@ -77,8 +88,14 @@ extension Grid {
7788
}
7889

7990
self.meta = try container.decode(Dict.self, forKey: .meta)
80-
self.cols = try container.decode([Col].self, forKey: .cols)
81-
self.rows = try container.decode([Dict].self, forKey: .rows)
91+
let cols = try container.decode([Col].self, forKey: .cols)
92+
if cols.map(\.name) == ["empty"] {
93+
self.cols = []
94+
self.rows = []
95+
} else {
96+
self.cols = cols
97+
self.rows = try container.decode([Dict].self, forKey: .rows)
98+
}
8299
} else {
83100
throw DecodingError.typeMismatch(
84101
Self.self,
@@ -96,8 +113,13 @@ extension Grid {
96113
var container = encoder.container(keyedBy: Self.CodingKeys)
97114
try container.encode(Self.kindValue, forKey: ._kind)
98115
try container.encode(meta, forKey: .meta)
99-
try container.encode(cols, forKey: .cols)
100-
try container.encode(rows, forKey: .rows)
116+
if cols.isEmpty {
117+
try container.encode([Col(name: "empty")], forKey: .cols)
118+
try container.encode([Dict](), forKey: .rows)
119+
} else {
120+
try container.encode(cols, forKey: .cols)
121+
try container.encode(rows, forKey: .rows)
122+
}
101123
}
102124
}
103125

Sources/Haystack/IO/ZincReader.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,10 @@ public class ZincReader {
127127
private func parseLiteral() throws -> any Val {
128128
var val = self.curVal
129129
if cur == .ref, peek == .str {
130-
guard let refVal = curVal as? String, let dis = peekVal as? String else {
130+
guard let refVal = curVal as? Ref, let dis = peekVal as? String else {
131131
throw ZincReaderError.invalidRef
132132
}
133-
val = try Ref(refVal, dis: dis)
133+
val = try Ref(refVal.val, dis: dis)
134134
try consume(.ref)
135135
}
136136
try consume()
@@ -139,6 +139,7 @@ public class ZincReader {
139139

140140
private func parseList() throws -> List {
141141
var elements = [any Val]()
142+
try consume(.lbracket)
142143
while cur != .rbracket, cur != .eof {
143144
try elements.append(parseVal())
144145
guard cur == .comma else {

Sources/Haystack/Number.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ public struct Number: Val {
5151
}
5252
return zinc
5353
}
54+
55+
public var isInt: Bool {
56+
return val == val.rounded()
57+
}
5458
}
5559

5660
// Number + Codable

Sources/Haystack/Utils/GridBuilder.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class GridBuilder {
88
var rows: [[String: any Val]]
99

1010
public init() {
11-
meta = [:]
11+
meta = ["ver":"3.0"] // We don't back-support old grid versions
1212
colNames = []
1313
colMeta = [:]
1414
rows = []
@@ -17,6 +17,15 @@ public class GridBuilder {
1717
/// Construct a grid from the assets of this instance
1818
/// - Returns: The resulting grid
1919
public func toGrid() -> Grid {
20+
// empty grid handler
21+
if colNames == ["empty"] {
22+
return Grid(
23+
meta: Dict(meta),
24+
cols: [],
25+
rows: []
26+
)
27+
}
28+
2029
let cols = colNames.map { colName in
2130
if let meta = colMeta[colName] {
2231
return Col(name: colName, meta: Dict(meta))
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import CryptoKit
2+
3+
@available(macOS 10.15, *)
4+
enum AuthHash: String {
5+
case SHA512 = "SHA-512"
6+
case SHA256 = "SHA-256"
7+
8+
var hash: any HashFunction.Type {
9+
switch self {
10+
case .SHA256:
11+
return CryptoKit.SHA256.self
12+
case .SHA512:
13+
return CryptoKit.SHA512.self
14+
}
15+
}
16+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
struct AuthMessage: CustomStringConvertible {
2+
let scheme: String
3+
let attributes: [String: String]
4+
5+
var description: String {
6+
// Unwrap is safe because attributes is immutable
7+
"\(scheme) \(attributes.keys.sorted().map { "\($0)=\(attributes[$0]!)" }.joined(separator: ", "))"
8+
}
9+
10+
static func from(_ string: String) throws -> Self {
11+
// Example input: "SCRAM hash=SHA-256, handshakeToken=aabbcc"
12+
let scheme: String
13+
let attributes: [String: String]
14+
// If space exists then parse attributes as well.
15+
if let spaceIndex = string.firstIndex(of: " ") {
16+
scheme = String(string[..<spaceIndex]).trimmingCharacters(in: .whitespaces)
17+
let attributesString = String(string[spaceIndex...]).trimmingCharacters(in: .whitespaces)
18+
attributes = extractNameValuePairs(from: attributesString)
19+
} else {
20+
scheme = string
21+
attributes = [:]
22+
}
23+
return Self(scheme: scheme, attributes: attributes)
24+
}
25+
}

0 commit comments

Comments
 (0)