Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e486b45
feature: Adds URL-safe base64 codings
NeedleInAJayStack Nov 21, 2022
2ebffb6
feature: Adds SCRAM client
NeedleInAJayStack Nov 23, 2022
0b7e5ef
feature: Adds SCRAM final message validation
NeedleInAJayStack Nov 25, 2022
631e09f
test: More complete SCRAM testing
NeedleInAJayStack Nov 25, 2022
4ca0577
feature: Haystack Client with login
NeedleInAJayStack Nov 25, 2022
500816a
feature: Adds format selection support
NeedleInAJayStack Nov 27, 2022
6436899
fix: Fixes zinc parseList infinite loop
NeedleInAJayStack Nov 27, 2022
3a6ccea
fix: Empty grid and `ver` meta support
NeedleInAJayStack Nov 28, 2022
7ac736b
fix: Empty grid gets newline
NeedleInAJayStack Dec 4, 2022
6ef28bc
feature: Isolates sessions to prevent cookie storage
NeedleInAJayStack Dec 4, 2022
54fcad9
Feature: Adds client JSON support
NeedleInAJayStack Dec 4, 2022
203d41f
fix: Unifies URL session by ignoring cookies
NeedleInAJayStack Dec 5, 2022
77a8180
fix: Corrects zinc ref reading
NeedleInAJayStack Dec 5, 2022
c258818
feature: Adds defs, libs, ops, filetypes, read, & readAll
NeedleInAJayStack Dec 5, 2022
dadaf3d
refactor: Allow Grid post requests
NeedleInAJayStack Dec 5, 2022
0c8daa3
feature: Adds watch ops
NeedleInAJayStack Dec 5, 2022
5a6142f
fix: Adds Number.isInt
NeedleInAJayStack Dec 5, 2022
5080e5e
feature: Adds HisRead
NeedleInAJayStack Dec 5, 2022
b5d2ba8
feature: Adds hisWrite
NeedleInAJayStack Dec 5, 2022
13bb3ba
feature: Adds pointWrite
NeedleInAJayStack Dec 5, 2022
adde412
feature: Adds invokeAction
NeedleInAJayStack Dec 5, 2022
00130a8
feature: Adds eval
NeedleInAJayStack Dec 5, 2022
c090c3c
refactor: Splits up files
NeedleInAJayStack Dec 5, 2022
0371f3c
doc: Adds public API documentation
NeedleInAJayStack Dec 15, 2022
a04cb95
fix: Fixes client op method name
NeedleInAJayStack Dec 15, 2022
cc13dcb
feature: Adds Haxall integration tests
NeedleInAJayStack Dec 15, 2022
305c924
chore: Removes spm artifacts
NeedleInAJayStack Dec 15, 2022
4fbd1bd
chore: ignores spm artifacts
NeedleInAJayStack Dec 15, 2022
eecc797
chore: Adds merge request tests
NeedleInAJayStack Dec 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: test
on:
pull_request:
push: { branches: [ main ] }

jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: fwal/setup-swift@v1
- uses: actions/checkout@v2
- name: Run tests
run: swift test
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm/config/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
.swiftpm/

This file was deleted.

16 changes: 16 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,32 @@ let package = Package(
name: "Haystack",
targets: ["Haystack"]
),
.library(
name: "HaystackClient",
targets: ["HaystackClient"]
),
],
dependencies: [],
targets: [
.target(
name: "Haystack",
dependencies: []
),
.target(
name: "HaystackClient",
dependencies: ["Haystack"]
),
.testTarget(
name: "HaystackTests",
dependencies: ["Haystack"]
),
.testTarget(
name: "HaystackClientTests",
dependencies: ["HaystackClient"]
),
.testTarget(
name: "HaystackClientIntegrationTests",
dependencies: ["HaystackClient"]
),
]
)
66 changes: 44 additions & 22 deletions Sources/Haystack/Grid.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import Foundation
/// unit of data exchange over the
/// [HTTP API](https://project-haystack.org/doc/docHaystack/HttpApi).
///
/// To create a Grid, use a `GridBuilder`.
///
/// [Docs](https://project-haystack.org/doc/docHaystack/Kinds#grid)
public struct Grid: Val {
public static var valType: ValType { .Grid }
Expand All @@ -22,30 +24,39 @@ public struct Grid: Val {
/// Converts to Zinc formatted string.
/// See [Zinc Literals](https://project-haystack.org/doc/docHaystack/Zinc#literals)
public func toZinc() -> String {
var zinc = #"ver:"3.0""#
if meta.elements.count > 0 {
zinc += " \(meta.toZinc(withBraces: false))"
}
zinc += "\n"
// Ensure `ver` is listed first in meta
let ver = meta.elements["ver"] ?? "3.0"
var zinc = "ver:\(ver.toZinc())"

let zincCols = cols.map { col in
var colZinc = col.name
if let colMeta = col.meta, colMeta.elements.count > 0 {
colZinc += " \(colMeta.toZinc(withBraces: false))"
}
return colZinc
var metaWithoutVer = meta.elements
metaWithoutVer["ver"] = nil
if metaWithoutVer.count > 0 {
zinc += " \(Dict(metaWithoutVer).toZinc(withBraces: false))"
}
zinc += zincCols.joined(separator: ", ")
zinc += "\n"

let zincRows = rows.map { row in
let rowZincElements = cols.map { col in
let element = row.elements[col.name] ?? null
return element.toZinc()
if cols.isEmpty {
zinc += "empty\n"
} else {
let zincCols = cols.map { col in
var colZinc = col.name
if let colMeta = col.meta, colMeta.elements.count > 0 {
colZinc += " \(colMeta.toZinc(withBraces: false))"
}
return colZinc
}
return rowZincElements.joined(separator: ", ")
zinc += zincCols.joined(separator: ", ")
zinc += "\n"

let zincRows = rows.map { row in
let rowZincElements = cols.map { col in
let element = row.elements[col.name] ?? null
return element.toZinc()
}
return rowZincElements.joined(separator: ", ")
}
zinc += zincRows.joined(separator: "\n")
}
zinc += zincRows.joined(separator: "\n")

return zinc
}
Expand Down Expand Up @@ -77,8 +88,14 @@ extension Grid {
}

self.meta = try container.decode(Dict.self, forKey: .meta)
self.cols = try container.decode([Col].self, forKey: .cols)
self.rows = try container.decode([Dict].self, forKey: .rows)
let cols = try container.decode([Col].self, forKey: .cols)
if cols.map(\.name) == ["empty"] {
self.cols = []
self.rows = []
} else {
self.cols = cols
self.rows = try container.decode([Dict].self, forKey: .rows)
}
} else {
throw DecodingError.typeMismatch(
Self.self,
Expand All @@ -96,8 +113,13 @@ extension Grid {
var container = encoder.container(keyedBy: Self.CodingKeys)
try container.encode(Self.kindValue, forKey: ._kind)
try container.encode(meta, forKey: .meta)
try container.encode(cols, forKey: .cols)
try container.encode(rows, forKey: .rows)
if cols.isEmpty {
try container.encode([Col(name: "empty")], forKey: .cols)
try container.encode([Dict](), forKey: .rows)
} else {
try container.encode(cols, forKey: .cols)
try container.encode(rows, forKey: .rows)
}
}
}

Expand Down
5 changes: 3 additions & 2 deletions Sources/Haystack/IO/ZincReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ public class ZincReader {
private func parseLiteral() throws -> any Val {
var val = self.curVal
if cur == .ref, peek == .str {
guard let refVal = curVal as? String, let dis = peekVal as? String else {
guard let refVal = curVal as? Ref, let dis = peekVal as? String else {
throw ZincReaderError.invalidRef
}
val = try Ref(refVal, dis: dis)
val = try Ref(refVal.val, dis: dis)
try consume(.ref)
}
try consume()
Expand All @@ -139,6 +139,7 @@ public class ZincReader {

private func parseList() throws -> List {
var elements = [any Val]()
try consume(.lbracket)
while cur != .rbracket, cur != .eof {
try elements.append(parseVal())
guard cur == .comma else {
Expand Down
4 changes: 4 additions & 0 deletions Sources/Haystack/Number.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public struct Number: Val {
}
return zinc
}

public var isInt: Bool {
return val == val.rounded()
}
}

// Number + Codable
Expand Down
11 changes: 10 additions & 1 deletion Sources/Haystack/Utils/GridBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class GridBuilder {
var rows: [[String: any Val]]

public init() {
meta = [:]
meta = ["ver":"3.0"] // We don't back-support old grid versions
colNames = []
colMeta = [:]
rows = []
Expand All @@ -17,6 +17,15 @@ public class GridBuilder {
/// Construct a grid from the assets of this instance
/// - Returns: The resulting grid
public func toGrid() -> Grid {
// empty grid handler
if colNames == ["empty"] {
return Grid(
meta: Dict(meta),
cols: [],
rows: []
)
}

let cols = colNames.map { colName in
if let meta = colMeta[colName] {
return Col(name: colName, meta: Dict(meta))
Expand Down
16 changes: 16 additions & 0 deletions Sources/HaystackClient/Authentication/AuthHash.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import CryptoKit

@available(macOS 10.15, *)
enum AuthHash: String {
case SHA512 = "SHA-512"
case SHA256 = "SHA-256"

var hash: any HashFunction.Type {
switch self {
case .SHA256:
return CryptoKit.SHA256.self
case .SHA512:
return CryptoKit.SHA512.self
}
}
}
25 changes: 25 additions & 0 deletions Sources/HaystackClient/Authentication/AuthMessage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
struct AuthMessage: CustomStringConvertible {
let scheme: String
let attributes: [String: String]

var description: String {
// Unwrap is safe because attributes is immutable
"\(scheme) \(attributes.keys.sorted().map { "\($0)=\(attributes[$0]!)" }.joined(separator: ", "))"
}

static func from(_ string: String) throws -> Self {
// Example input: "SCRAM hash=SHA-256, handshakeToken=aabbcc"
let scheme: String
let attributes: [String: String]
// If space exists then parse attributes as well.
if let spaceIndex = string.firstIndex(of: " ") {
scheme = String(string[..<spaceIndex]).trimmingCharacters(in: .whitespaces)
let attributesString = String(string[spaceIndex...]).trimmingCharacters(in: .whitespaces)
attributes = extractNameValuePairs(from: attributesString)
} else {
scheme = string
attributes = [:]
}
return Self(scheme: scheme, attributes: attributes)
}
}
4 changes: 4 additions & 0 deletions Sources/HaystackClient/Authentication/Authenticator.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@available(macOS 10.15, *)
protocol Authenticator {
func getAuthToken() async throws -> String
}
Loading