Mac: build GatewayProtocol target and typed presence handling

This commit is contained in:
Peter Steinberger
2025-12-09 15:35:06 +01:00
parent a7737912b0
commit 336c9d6caa
6 changed files with 262 additions and 101 deletions

View File

@@ -20,6 +20,13 @@ let package = Package(
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"), .package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"),
], ],
targets: [ targets: [
.target(
name: "ClawdisProtocol",
dependencies: [],
path: "Sources/ClawdisProtocol",
swiftSettings: [
.enableUpcomingFeature("StrictConcurrency"),
]),
.target( .target(
name: "ClawdisIPC", name: "ClawdisIPC",
dependencies: [], dependencies: [],
@@ -30,6 +37,7 @@ let package = Package(
name: "Clawdis", name: "Clawdis",
dependencies: [ dependencies: [
"ClawdisIPC", "ClawdisIPC",
"ClawdisProtocol",
.product(name: "AsyncXPCConnection", package: "AsyncXPCConnection"), .product(name: "AsyncXPCConnection", package: "AsyncXPCConnection"),
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"), .product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
.product(name: "Subprocess", package: "swift-subprocess"), .product(name: "Subprocess", package: "swift-subprocess"),
@@ -46,6 +54,7 @@ let package = Package(
name: "ClawdisCLI", name: "ClawdisCLI",
dependencies: [ dependencies: [
"ClawdisIPC", "ClawdisIPC",
"ClawdisProtocol",
.product(name: "AsyncXPCConnection", package: "AsyncXPCConnection"), .product(name: "AsyncXPCConnection", package: "AsyncXPCConnection"),
], ],
swiftSettings: [ swiftSettings: [

View File

@@ -120,7 +120,8 @@ private actor GatewayChannelActor {
} }
switch frame { switch frame {
case let .res(res): case let .res(res):
if let id = res.id, let waiter = pending.removeValue(forKey: id) { let id = res.id
if let waiter = pending.removeValue(forKey: id) {
waiter.resume(returning: .res(res)) waiter.resume(returning: .res(res))
} }
case let .event(evt): case let .event(evt):
@@ -182,12 +183,14 @@ private actor GatewayChannelActor {
throw NSError(domain: "Gateway", code: 2, userInfo: [NSLocalizedDescriptionKey: "unexpected frame"]) throw NSError(domain: "Gateway", code: 2, userInfo: [NSLocalizedDescriptionKey: "unexpected frame"])
} }
if res.ok == false { if res.ok == false {
let msg = (res.error?.message) ?? "gateway error" let msg = (res.error?["message"]?.value as? String) ?? "gateway error"
throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg]) throw NSError(domain: "Gateway", code: 3, userInfo: [NSLocalizedDescriptionKey: msg])
} }
if let payload = res.payload?.value { if let payload = res.payload?.value {
let payloadData = try JSONSerialization.data(withJSONObject: payload) if JSONSerialization.isValidJSONObject(payload) {
return payloadData let payloadData = try JSONSerialization.data(withJSONObject: payload)
return payloadData
}
} }
return Data() return Data()
} }

View File

@@ -97,8 +97,8 @@ final class InstancesStore: ObservableObject {
let frame = note.object as? GatewayFrame else { return } let frame = note.object as? GatewayFrame else { return }
switch frame { switch frame {
case let .helloOk(hello): case let .helloOk(hello):
let presence = hello.snapshot.presence if JSONSerialization.isValidJSONObject(hello.snapshot.presence),
if let data = try? JSONEncoder().encode(presence) { let data = try? JSONEncoder().encode(hello.snapshot.presence) {
Task { @MainActor [weak self] in self?.decodeAndApplyPresenceData(data) } Task { @MainActor [weak self] in self?.decodeAndApplyPresenceData(data) }
} }
default: default:

View File

@@ -2,12 +2,12 @@ import Foundation
/// Lightweight `Codable` wrapper that round-trips heterogeneous JSON payloads. /// Lightweight `Codable` wrapper that round-trips heterogeneous JSON payloads.
/// Marked `@unchecked Sendable` because it can hold reference types. /// Marked `@unchecked Sendable` because it can hold reference types.
struct AnyCodable: Codable, @unchecked Sendable { public struct AnyCodable: Codable, @unchecked Sendable {
let value: Any public let value: Any
init(_ value: Any) { self.value = value } public init(_ value: Any) { self.value = value }
init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
if let intVal = try? container.decode(Int.self) { self.value = intVal; return } if let intVal = try? container.decode(Int.self) { self.value = intVal; return }
if let doubleVal = try? container.decode(Double.self) { self.value = doubleVal; return } if let doubleVal = try? container.decode(Double.self) { self.value = doubleVal; return }
@@ -21,7 +21,7 @@ struct AnyCodable: Codable, @unchecked Sendable {
debugDescription: "Unsupported type") debugDescription: "Unsupported type")
} }
func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer() var container = encoder.singleValueContainer()
switch self.value { switch self.value {
case let intVal as Int: try container.encode(intVal) case let intVal as Int: try container.encode(intVal)

View File

@@ -12,76 +12,100 @@ public enum ErrorCode: String, Codable {
public struct Hello: Codable { public struct Hello: Codable {
public let type: String public let type: String
public let minProtocol: Int public let minprotocol: Int
public let maxProtocol: Int public let maxprotocol: Int
public let client: [String: AnyCodable] public let client: [String: AnyCodable]
public let caps: [String]? public let caps: [String]?
public let auth: [String: AnyCodable]? public let auth: [String: AnyCodable]?
public let locale: String? public let locale: String?
public let userAgent: String? public let useragent: String?
public init( public init(
type: String, type: String,
minProtocol: Int, minprotocol: Int,
maxProtocol: Int, maxprotocol: Int,
client: [String: AnyCodable], client: [String: AnyCodable],
caps: [String]?, caps: [String]?,
auth: [String: AnyCodable]?, auth: [String: AnyCodable]?,
locale: String?, locale: String?,
userAgent: String? useragent: String?
) { ) {
self.type = type self.type = type
self.minProtocol = minProtocol self.minprotocol = minprotocol
self.maxProtocol = maxProtocol self.maxprotocol = maxprotocol
self.client = client self.client = client
self.caps = caps self.caps = caps
self.auth = auth self.auth = auth
self.locale = locale self.locale = locale
self.userAgent = userAgent self.useragent = useragent
}
private enum CodingKeys: String, CodingKey {
case type
case minprotocol = "minProtocol"
case maxprotocol = "maxProtocol"
case client
case caps
case auth
case locale
case useragent = "userAgent"
} }
} }
public struct HelloOk: Codable { public struct HelloOk: Codable {
public let type: String public let type: String
public let protocol: Int public let _protocol: Int
public let server: [String: AnyCodable] public let server: [String: AnyCodable]
public let features: [String: AnyCodable] public let features: [String: AnyCodable]
public let snapshot: [String: AnyCodable] public let snapshot: Snapshot
public let policy: [String: AnyCodable] public let policy: [String: AnyCodable]
public init( public init(
type: String, type: String,
protocol: Int, _protocol: Int,
server: [String: AnyCodable], server: [String: AnyCodable],
features: [String: AnyCodable], features: [String: AnyCodable],
snapshot: [String: AnyCodable], snapshot: Snapshot,
policy: [String: AnyCodable] policy: [String: AnyCodable]
) { ) {
self.type = type self.type = type
self.protocol = protocol self._protocol = _protocol
self.server = server self.server = server
self.features = features self.features = features
self.snapshot = snapshot self.snapshot = snapshot
self.policy = policy self.policy = policy
} }
private enum CodingKeys: String, CodingKey {
case type
case _protocol = "protocol"
case server
case features
case snapshot
case policy
}
} }
public struct HelloError: Codable { public struct HelloError: Codable {
public let type: String public let type: String
public let reason: String public let reason: String
public let expectedProtocol: Int? public let expectedprotocol: Int?
public let minClient: String? public let minclient: String?
public init( public init(
type: String, type: String,
reason: String, reason: String,
expectedProtocol: Int?, expectedprotocol: Int?,
minClient: String? minclient: String?
) { ) {
self.type = type self.type = type
self.reason = reason self.reason = reason
self.expectedProtocol = expectedProtocol self.expectedprotocol = expectedprotocol
self.minClient = minClient self.minclient = minclient
}
private enum CodingKeys: String, CodingKey {
case type
case reason
case expectedprotocol = "expectedProtocol"
case minclient = "minClient"
} }
} }
@@ -102,6 +126,12 @@ public struct RequestFrame: Codable {
self.method = method self.method = method
self.params = params self.params = params
} }
private enum CodingKeys: String, CodingKey {
case type
case id
case method
case params
}
} }
public struct ResponseFrame: Codable { public struct ResponseFrame: Codable {
@@ -124,6 +154,13 @@ public struct ResponseFrame: Codable {
self.payload = payload self.payload = payload
self.error = error self.error = error
} }
private enum CodingKeys: String, CodingKey {
case type
case id
case ok
case payload
case error
}
} }
public struct EventFrame: Codable { public struct EventFrame: Codable {
@@ -131,20 +168,27 @@ public struct EventFrame: Codable {
public let event: String public let event: String
public let payload: AnyCodable? public let payload: AnyCodable?
public let seq: Int? public let seq: Int?
public let stateVersion: [String: AnyCodable]? public let stateversion: [String: AnyCodable]?
public init( public init(
type: String, type: String,
event: String, event: String,
payload: AnyCodable?, payload: AnyCodable?,
seq: Int?, seq: Int?,
stateVersion: [String: AnyCodable]? stateversion: [String: AnyCodable]?
) { ) {
self.type = type self.type = type
self.event = event self.event = event
self.payload = payload self.payload = payload
self.seq = seq self.seq = seq
self.stateVersion = stateVersion self.stateversion = stateversion
}
private enum CodingKeys: String, CodingKey {
case type
case event
case payload
case seq
case stateversion = "stateVersion"
} }
} }
@@ -153,35 +197,47 @@ public struct PresenceEntry: Codable {
public let ip: String? public let ip: String?
public let version: String? public let version: String?
public let mode: String? public let mode: String?
public let lastInputSeconds: Int? public let lastinputseconds: Int?
public let reason: String? public let reason: String?
public let tags: [String]? public let tags: [String]?
public let text: String? public let text: String?
public let ts: Int public let ts: Int
public let instanceId: String? public let instanceid: String?
public init( public init(
host: String?, host: String?,
ip: String?, ip: String?,
version: String?, version: String?,
mode: String?, mode: String?,
lastInputSeconds: Int?, lastinputseconds: Int?,
reason: String?, reason: String?,
tags: [String]?, tags: [String]?,
text: String?, text: String?,
ts: Int, ts: Int,
instanceId: String? instanceid: String?
) { ) {
self.host = host self.host = host
self.ip = ip self.ip = ip
self.version = version self.version = version
self.mode = mode self.mode = mode
self.lastInputSeconds = lastInputSeconds self.lastinputseconds = lastinputseconds
self.reason = reason self.reason = reason
self.tags = tags self.tags = tags
self.text = text self.text = text
self.ts = ts self.ts = ts
self.instanceId = instanceId self.instanceid = instanceid
}
private enum CodingKeys: String, CodingKey {
case host
case ip
case version
case mode
case lastinputseconds = "lastInputSeconds"
case reason
case tags
case text
case ts
case instanceid = "instanceId"
} }
} }
@@ -196,24 +252,34 @@ public struct StateVersion: Codable {
self.presence = presence self.presence = presence
self.health = health self.health = health
} }
private enum CodingKeys: String, CodingKey {
case presence
case health
}
} }
public struct Snapshot: Codable { public struct Snapshot: Codable {
public let presence: [[String: AnyCodable]] public let presence: [PresenceEntry]
public let health: AnyCodable public let health: AnyCodable
public let stateVersion: [String: AnyCodable] public let stateversion: StateVersion
public let uptimeMs: Int public let uptimems: Int
public init( public init(
presence: [[String: AnyCodable]], presence: [PresenceEntry],
health: AnyCodable, health: AnyCodable,
stateVersion: [String: AnyCodable], stateversion: StateVersion,
uptimeMs: Int uptimems: Int
) { ) {
self.presence = presence self.presence = presence
self.health = health self.health = health
self.stateVersion = stateVersion self.stateversion = stateversion
self.uptimeMs = uptimeMs self.uptimems = uptimems
}
private enum CodingKeys: String, CodingKey {
case presence
case health
case stateversion = "stateVersion"
case uptimems = "uptimeMs"
} }
} }
@@ -222,92 +288,122 @@ public struct ErrorShape: Codable {
public let message: String public let message: String
public let details: AnyCodable? public let details: AnyCodable?
public let retryable: Bool? public let retryable: Bool?
public let retryAfterMs: Int? public let retryafterms: Int?
public init( public init(
code: String, code: String,
message: String, message: String,
details: AnyCodable?, details: AnyCodable?,
retryable: Bool?, retryable: Bool?,
retryAfterMs: Int? retryafterms: Int?
) { ) {
self.code = code self.code = code
self.message = message self.message = message
self.details = details self.details = details
self.retryable = retryable self.retryable = retryable
self.retryAfterMs = retryAfterMs self.retryafterms = retryafterms
}
private enum CodingKeys: String, CodingKey {
case code
case message
case details
case retryable
case retryafterms = "retryAfterMs"
} }
} }
public struct AgentEvent: Codable { public struct AgentEvent: Codable {
public let runId: String public let runid: String
public let seq: Int public let seq: Int
public let stream: String public let stream: String
public let ts: Int public let ts: Int
public let data: [String: AnyCodable] public let data: [String: AnyCodable]
public init( public init(
runId: String, runid: String,
seq: Int, seq: Int,
stream: String, stream: String,
ts: Int, ts: Int,
data: [String: AnyCodable] data: [String: AnyCodable]
) { ) {
self.runId = runId self.runid = runid
self.seq = seq self.seq = seq
self.stream = stream self.stream = stream
self.ts = ts self.ts = ts
self.data = data self.data = data
} }
private enum CodingKeys: String, CodingKey {
case runid = "runId"
case seq
case stream
case ts
case data
}
} }
public struct SendParams: Codable { public struct SendParams: Codable {
public let to: String public let to: String
public let message: String public let message: String
public let mediaUrl: String? public let mediaurl: String?
public let provider: String? public let provider: String?
public let idempotencyKey: String public let idempotencykey: String
public init( public init(
to: String, to: String,
message: String, message: String,
mediaUrl: String?, mediaurl: String?,
provider: String?, provider: String?,
idempotencyKey: String idempotencykey: String
) { ) {
self.to = to self.to = to
self.message = message self.message = message
self.mediaUrl = mediaUrl self.mediaurl = mediaurl
self.provider = provider self.provider = provider
self.idempotencyKey = idempotencyKey self.idempotencykey = idempotencykey
}
private enum CodingKeys: String, CodingKey {
case to
case message
case mediaurl = "mediaUrl"
case provider
case idempotencykey = "idempotencyKey"
} }
} }
public struct AgentParams: Codable { public struct AgentParams: Codable {
public let message: String public let message: String
public let to: String? public let to: String?
public let sessionId: String? public let sessionid: String?
public let thinking: String? public let thinking: String?
public let deliver: Bool? public let deliver: Bool?
public let timeout: Int? public let timeout: Int?
public let idempotencyKey: String public let idempotencykey: String
public init( public init(
message: String, message: String,
to: String?, to: String?,
sessionId: String?, sessionid: String?,
thinking: String?, thinking: String?,
deliver: Bool?, deliver: Bool?,
timeout: Int?, timeout: Int?,
idempotencyKey: String idempotencykey: String
) { ) {
self.message = message self.message = message
self.to = to self.to = to
self.sessionId = sessionId self.sessionid = sessionid
self.thinking = thinking self.thinking = thinking
self.deliver = deliver self.deliver = deliver
self.timeout = timeout self.timeout = timeout
self.idempotencyKey = idempotencyKey self.idempotencykey = idempotencykey
}
private enum CodingKeys: String, CodingKey {
case message
case to
case sessionid = "sessionId"
case thinking
case deliver
case timeout
case idempotencykey = "idempotencyKey"
} }
} }
@@ -319,25 +415,32 @@ public struct TickEvent: Codable {
) { ) {
self.ts = ts self.ts = ts
} }
private enum CodingKeys: String, CodingKey {
case ts
}
} }
public struct ShutdownEvent: Codable { public struct ShutdownEvent: Codable {
public let reason: String public let reason: String
public let restartExpectedMs: Int? public let restartexpectedms: Int?
public init( public init(
reason: String, reason: String,
restartExpectedMs: Int? restartexpectedms: Int?
) { ) {
self.reason = reason self.reason = reason
self.restartExpectedMs = restartExpectedMs self.restartexpectedms = restartexpectedms
}
private enum CodingKeys: String, CodingKey {
case reason
case restartexpectedms = "restartExpectedMs"
} }
} }
public enum GatewayFrame: Codable { public enum GatewayFrame: Codable {
case hello(Hello) case hello(Hello)
case hello-ok(HelloOk) case helloOk(HelloOk)
case hello-error(HelloError) case helloError(HelloError)
case req(RequestFrame) case req(RequestFrame)
case res(ResponseFrame) case res(ResponseFrame)
case event(EventFrame) case event(EventFrame)
@@ -351,17 +454,17 @@ public enum GatewayFrame: Codable {
} }
switch type { switch type {
case "hello": case "hello":
self = .hello(try decodePayload(Hello.self, from: raw)) self = .hello(try Self.decodePayload(Hello.self, from: raw))
case "hello-ok": case "hello-ok":
self = .helloOk(try decodePayload(HelloOk.self, from: raw)) self = .helloOk(try Self.decodePayload(HelloOk.self, from: raw))
case "hello-error": case "hello-error":
self = .helloError(try decodePayload(HelloError.self, from: raw)) self = .helloError(try Self.decodePayload(HelloError.self, from: raw))
case "req": case "req":
self = .req(try decodePayload(RequestFrame.self, from: raw)) self = .req(try Self.decodePayload(RequestFrame.self, from: raw))
case "res": case "res":
self = .res(try decodePayload(ResponseFrame.self, from: raw)) self = .res(try Self.decodePayload(ResponseFrame.self, from: raw))
case "event": case "event":
self = .event(try decodePayload(EventFrame.self, from: raw)) self = .event(try Self.decodePayload(EventFrame.self, from: raw))
default: default:
self = .unknown(type: type, raw: raw) self = .unknown(type: type, raw: raw)
} }
@@ -382,7 +485,7 @@ public enum GatewayFrame: Codable {
} }
private func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T { private static func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T {
let data = try JSONSerialization.data(withJSONObject: raw) let data = try JSONSerialization.data(withJSONObject: raw)
let decoder = JSONDecoder() let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data) return try decoder.decode(T.self, from: data)

View File

@@ -31,27 +31,67 @@ const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by
.map((c) => ` case ${camelCase(c)} = "${c}"`) .map((c) => ` case ${camelCase(c)} = "${c}"`)
.join("\n")}\n}\n`; .join("\n")}\n}\n`;
const reserved = new Set([
"associatedtype",
"class",
"deinit",
"enum",
"extension",
"fileprivate",
"func",
"import",
"init",
"inout",
"internal",
"let",
"open",
"operator",
"private",
"precedencegroup",
"protocol",
"public",
"rethrows",
"static",
"struct",
"subscript",
"typealias",
"var",
]);
function camelCase(input: string) { function camelCase(input: string) {
return input return input
.replace(/[^a-zA-Z0-9]+/g, " ")
.trim()
.toLowerCase() .toLowerCase()
.split("_") .split(/\s+/)
.map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1))) .map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1)))
.join(""); .join("");
} }
function safeName(name: string) {
const cc = camelCase(name.replace(/-/g, "_"));
if (reserved.has(cc)) return `_${cc}`;
return cc;
}
// filled later once schemas are loaded
const schemaNameByObject = new Map<object, string>();
function swiftType(schema: JsonSchema, required: boolean): string { function swiftType(schema: JsonSchema, required: boolean): string {
const t = schema.type; const t = schema.type;
const isOptional = !required; const isOptional = !required;
let base: string; let base: string;
if (t === "string") base = "String"; const named = schemaNameByObject.get(schema as object);
if (named) {
base = named;
} else if (t === "string") base = "String";
else if (t === "integer") base = "Int"; else if (t === "integer") base = "Int";
else if (t === "number") base = "Double"; else if (t === "number") base = "Double";
else if (t === "boolean") base = "Bool"; else if (t === "boolean") base = "Bool";
else if (t === "array") { else if (t === "array") {
base = `[${swiftType(schema.items ?? { type: "Any" }, true)}]`; base = `[${swiftType(schema.items ?? { type: "Any" }, true)}]`;
} else if (schema.enum) { } else if (schema.enum) {
base = schema.enum.map((v) => `\"${v}\"`).join(" | "); base = "String";
base = "String"; // simplify enums to String; custom enums could be added if needed
} else if (schema.patternProperties) { } else if (schema.patternProperties) {
base = "[String: AnyCodable]"; base = "[String: AnyCodable]";
} else if (t === "object") { } else if (t === "object") {
@@ -71,15 +111,21 @@ function emitStruct(name: string, schema: JsonSchema): string {
lines.push("}\n"); lines.push("}\n");
return lines.join("\n"); return lines.join("\n");
} }
const codingKeys: string[] = [];
for (const [key, propSchema] of Object.entries(props)) { for (const [key, propSchema] of Object.entries(props)) {
const propName = key === "description" ? "desc" : key; const propName = safeName(key);
const propType = swiftType(propSchema, required.has(key)); const propType = swiftType(propSchema, required.has(key));
lines.push(` public let ${propName}: ${propType}`); lines.push(` public let ${propName}: ${propType}`);
if (propName !== key) {
codingKeys.push(` case ${propName} = "${key}"`);
} else {
codingKeys.push(` case ${propName}`);
}
} }
lines.push("\n public init(\n" + lines.push("\n public init(\n" +
Object.entries(props) Object.entries(props)
.map(([key, prop]) => { .map(([key, prop]) => {
const propName = key === "description" ? "desc" : key; const propName = safeName(key);
const req = required.has(key); const req = required.has(key);
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`; return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
}) })
@@ -87,24 +133,20 @@ function emitStruct(name: string, schema: JsonSchema): string {
"\n ) {\n" + "\n ) {\n" +
Object.entries(props) Object.entries(props)
.map(([key]) => { .map(([key]) => {
const propName = key === "description" ? "desc" : key; const propName = safeName(key);
return ` self.${propName} = ${propName}`; return ` self.${propName} = ${propName}`;
}) })
.join("\n") + .join("\n") +
"\n }\n" +
" private enum CodingKeys: String, CodingKey {\n" +
codingKeys.join("\n") +
"\n }\n}"); "\n }\n}");
lines.push(""); lines.push("");
return lines.join("\n"); return lines.join("\n");
} }
function emitGatewayFrame(): string { function emitGatewayFrame(): string {
const cases = [ const cases = ["hello", "hello-ok", "hello-error", "req", "res", "event"];
"hello",
"hello-ok",
"hello-error",
"req",
"res",
"event",
];
const associated: Record<string, string> = { const associated: Record<string, string> = {
hello: "Hello", hello: "Hello",
"hello-ok": "HelloOk", "hello-ok": "HelloOk",
@@ -113,7 +155,7 @@ function emitGatewayFrame(): string {
res: "ResponseFrame", res: "ResponseFrame",
event: "EventFrame", event: "EventFrame",
}; };
const caseLines = cases.map((c) => ` case ${camelCase(c)}(${associated[c]})`); const caseLines = cases.map((c) => ` case ${safeName(c)}(${associated[c]})`);
const initLines = ` const initLines = `
public init(from decoder: Decoder) throws { public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer() let container = try decoder.singleValueContainer()
@@ -123,17 +165,17 @@ function emitGatewayFrame(): string {
} }
switch type { switch type {
case "hello": case "hello":
self = .hello(try decodePayload(Hello.self, from: raw)) self = .hello(try Self.decodePayload(Hello.self, from: raw))
case "hello-ok": case "hello-ok":
self = .helloOk(try decodePayload(HelloOk.self, from: raw)) self = .helloOk(try Self.decodePayload(HelloOk.self, from: raw))
case "hello-error": case "hello-error":
self = .helloError(try decodePayload(HelloError.self, from: raw)) self = .helloError(try Self.decodePayload(HelloError.self, from: raw))
case "req": case "req":
self = .req(try decodePayload(RequestFrame.self, from: raw)) self = .req(try Self.decodePayload(RequestFrame.self, from: raw))
case "res": case "res":
self = .res(try decodePayload(ResponseFrame.self, from: raw)) self = .res(try Self.decodePayload(ResponseFrame.self, from: raw))
case "event": case "event":
self = .event(try decodePayload(EventFrame.self, from: raw)) self = .event(try Self.decodePayload(EventFrame.self, from: raw))
default: default:
self = .unknown(type: type, raw: raw) self = .unknown(type: type, raw: raw)
} }
@@ -155,7 +197,7 @@ function emitGatewayFrame(): string {
`; `;
const helper = ` const helper = `
private func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T { private static func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T {
let data = try JSONSerialization.data(withJSONObject: raw) let data = try JSONSerialization.data(withJSONObject: raw)
let decoder = JSONDecoder() let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data) return try decoder.decode(T.self, from: data)
@@ -178,6 +220,10 @@ async function generate() {
[string, JsonSchema] [string, JsonSchema]
>; >;
for (const [name, schema] of definitions) {
schemaNameByObject.set(schema as object, name);
}
const parts: string[] = []; const parts: string[] = [];
parts.push(header); parts.push(header);