Protocol: add TypeBox-driven Swift generator
This commit is contained in:
41
apps/macos/Sources/ClawdisProtocol/AnyCodable.swift
Normal file
41
apps/macos/Sources/ClawdisProtocol/AnyCodable.swift
Normal file
@@ -0,0 +1,41 @@
|
||||
import Foundation
|
||||
|
||||
/// Lightweight `Codable` wrapper that round-trips heterogeneous JSON payloads.
|
||||
/// Marked `@unchecked Sendable` because it can hold reference types.
|
||||
struct AnyCodable: Codable, @unchecked Sendable {
|
||||
let value: Any
|
||||
|
||||
init(_ value: Any) { self.value = value }
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
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 boolVal = try? container.decode(Bool.self) { self.value = boolVal; return }
|
||||
if let stringVal = try? container.decode(String.self) { self.value = stringVal; return }
|
||||
if container.decodeNil() { self.value = NSNull(); return }
|
||||
if let dict = try? container.decode([String: AnyCodable].self) { self.value = dict; return }
|
||||
if let array = try? container.decode([AnyCodable].self) { self.value = array; return }
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Unsupported type")
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self.value {
|
||||
case let intVal as Int: try container.encode(intVal)
|
||||
case let doubleVal as Double: try container.encode(doubleVal)
|
||||
case let boolVal as Bool: try container.encode(boolVal)
|
||||
case let stringVal as String: try container.encode(stringVal)
|
||||
case is NSNull: try container.encodeNil()
|
||||
case let dict as [String: AnyCodable]: try container.encode(dict)
|
||||
case let array as [AnyCodable]: try container.encode(array)
|
||||
default:
|
||||
let context = EncodingError.Context(
|
||||
codingPath: encoder.codingPath,
|
||||
debugDescription: "Unsupported type")
|
||||
throw EncodingError.invalidValue(self.value, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
387
apps/macos/Sources/ClawdisProtocol/GatewayModels.swift
Normal file
387
apps/macos/Sources/ClawdisProtocol/GatewayModels.swift
Normal file
@@ -0,0 +1,387 @@
|
||||
// Generated by scripts/protocol-gen-swift.ts — do not edit by hand
|
||||
import Foundation
|
||||
|
||||
public let GATEWAY_PROTOCOL_VERSION = 1
|
||||
|
||||
public enum ErrorCode: String, Codable {
|
||||
case notLinked = "NOT_LINKED"
|
||||
case agentTimeout = "AGENT_TIMEOUT"
|
||||
case invalidRequest = "INVALID_REQUEST"
|
||||
case unavailable = "UNAVAILABLE"
|
||||
}
|
||||
|
||||
public struct Hello: Codable {
|
||||
public let type: String
|
||||
public let minProtocol: Int
|
||||
public let maxProtocol: Int
|
||||
public let client: [String: AnyCodable]
|
||||
public let caps: [String]?
|
||||
public let auth: [String: AnyCodable]?
|
||||
public let locale: String?
|
||||
public let userAgent: String?
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
minProtocol: Int,
|
||||
maxProtocol: Int,
|
||||
client: [String: AnyCodable],
|
||||
caps: [String]?,
|
||||
auth: [String: AnyCodable]?,
|
||||
locale: String?,
|
||||
userAgent: String?
|
||||
) {
|
||||
self.type = type
|
||||
self.minProtocol = minProtocol
|
||||
self.maxProtocol = maxProtocol
|
||||
self.client = client
|
||||
self.caps = caps
|
||||
self.auth = auth
|
||||
self.locale = locale
|
||||
self.userAgent = userAgent
|
||||
}
|
||||
}
|
||||
|
||||
public struct HelloOk: Codable {
|
||||
public let type: String
|
||||
public let protocol: Int
|
||||
public let server: [String: AnyCodable]
|
||||
public let features: [String: AnyCodable]
|
||||
public let snapshot: [String: AnyCodable]
|
||||
public let policy: [String: AnyCodable]
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
protocol: Int,
|
||||
server: [String: AnyCodable],
|
||||
features: [String: AnyCodable],
|
||||
snapshot: [String: AnyCodable],
|
||||
policy: [String: AnyCodable]
|
||||
) {
|
||||
self.type = type
|
||||
self.protocol = protocol
|
||||
self.server = server
|
||||
self.features = features
|
||||
self.snapshot = snapshot
|
||||
self.policy = policy
|
||||
}
|
||||
}
|
||||
|
||||
public struct HelloError: Codable {
|
||||
public let type: String
|
||||
public let reason: String
|
||||
public let expectedProtocol: Int?
|
||||
public let minClient: String?
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
reason: String,
|
||||
expectedProtocol: Int?,
|
||||
minClient: String?
|
||||
) {
|
||||
self.type = type
|
||||
self.reason = reason
|
||||
self.expectedProtocol = expectedProtocol
|
||||
self.minClient = minClient
|
||||
}
|
||||
}
|
||||
|
||||
public struct RequestFrame: Codable {
|
||||
public let type: String
|
||||
public let id: String
|
||||
public let method: String
|
||||
public let params: AnyCodable?
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
id: String,
|
||||
method: String,
|
||||
params: AnyCodable?
|
||||
) {
|
||||
self.type = type
|
||||
self.id = id
|
||||
self.method = method
|
||||
self.params = params
|
||||
}
|
||||
}
|
||||
|
||||
public struct ResponseFrame: Codable {
|
||||
public let type: String
|
||||
public let id: String
|
||||
public let ok: Bool
|
||||
public let payload: AnyCodable?
|
||||
public let error: [String: AnyCodable]?
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
id: String,
|
||||
ok: Bool,
|
||||
payload: AnyCodable?,
|
||||
error: [String: AnyCodable]?
|
||||
) {
|
||||
self.type = type
|
||||
self.id = id
|
||||
self.ok = ok
|
||||
self.payload = payload
|
||||
self.error = error
|
||||
}
|
||||
}
|
||||
|
||||
public struct EventFrame: Codable {
|
||||
public let type: String
|
||||
public let event: String
|
||||
public let payload: AnyCodable?
|
||||
public let seq: Int?
|
||||
public let stateVersion: [String: AnyCodable]?
|
||||
|
||||
public init(
|
||||
type: String,
|
||||
event: String,
|
||||
payload: AnyCodable?,
|
||||
seq: Int?,
|
||||
stateVersion: [String: AnyCodable]?
|
||||
) {
|
||||
self.type = type
|
||||
self.event = event
|
||||
self.payload = payload
|
||||
self.seq = seq
|
||||
self.stateVersion = stateVersion
|
||||
}
|
||||
}
|
||||
|
||||
public struct PresenceEntry: Codable {
|
||||
public let host: String?
|
||||
public let ip: String?
|
||||
public let version: String?
|
||||
public let mode: String?
|
||||
public let lastInputSeconds: Int?
|
||||
public let reason: String?
|
||||
public let tags: [String]?
|
||||
public let text: String?
|
||||
public let ts: Int
|
||||
public let instanceId: String?
|
||||
|
||||
public init(
|
||||
host: String?,
|
||||
ip: String?,
|
||||
version: String?,
|
||||
mode: String?,
|
||||
lastInputSeconds: Int?,
|
||||
reason: String?,
|
||||
tags: [String]?,
|
||||
text: String?,
|
||||
ts: Int,
|
||||
instanceId: String?
|
||||
) {
|
||||
self.host = host
|
||||
self.ip = ip
|
||||
self.version = version
|
||||
self.mode = mode
|
||||
self.lastInputSeconds = lastInputSeconds
|
||||
self.reason = reason
|
||||
self.tags = tags
|
||||
self.text = text
|
||||
self.ts = ts
|
||||
self.instanceId = instanceId
|
||||
}
|
||||
}
|
||||
|
||||
public struct StateVersion: Codable {
|
||||
public let presence: Int
|
||||
public let health: Int
|
||||
|
||||
public init(
|
||||
presence: Int,
|
||||
health: Int
|
||||
) {
|
||||
self.presence = presence
|
||||
self.health = health
|
||||
}
|
||||
}
|
||||
|
||||
public struct Snapshot: Codable {
|
||||
public let presence: [[String: AnyCodable]]
|
||||
public let health: AnyCodable
|
||||
public let stateVersion: [String: AnyCodable]
|
||||
public let uptimeMs: Int
|
||||
|
||||
public init(
|
||||
presence: [[String: AnyCodable]],
|
||||
health: AnyCodable,
|
||||
stateVersion: [String: AnyCodable],
|
||||
uptimeMs: Int
|
||||
) {
|
||||
self.presence = presence
|
||||
self.health = health
|
||||
self.stateVersion = stateVersion
|
||||
self.uptimeMs = uptimeMs
|
||||
}
|
||||
}
|
||||
|
||||
public struct ErrorShape: Codable {
|
||||
public let code: String
|
||||
public let message: String
|
||||
public let details: AnyCodable?
|
||||
public let retryable: Bool?
|
||||
public let retryAfterMs: Int?
|
||||
|
||||
public init(
|
||||
code: String,
|
||||
message: String,
|
||||
details: AnyCodable?,
|
||||
retryable: Bool?,
|
||||
retryAfterMs: Int?
|
||||
) {
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
self.retryable = retryable
|
||||
self.retryAfterMs = retryAfterMs
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentEvent: Codable {
|
||||
public let runId: String
|
||||
public let seq: Int
|
||||
public let stream: String
|
||||
public let ts: Int
|
||||
public let data: [String: AnyCodable]
|
||||
|
||||
public init(
|
||||
runId: String,
|
||||
seq: Int,
|
||||
stream: String,
|
||||
ts: Int,
|
||||
data: [String: AnyCodable]
|
||||
) {
|
||||
self.runId = runId
|
||||
self.seq = seq
|
||||
self.stream = stream
|
||||
self.ts = ts
|
||||
self.data = data
|
||||
}
|
||||
}
|
||||
|
||||
public struct SendParams: Codable {
|
||||
public let to: String
|
||||
public let message: String
|
||||
public let mediaUrl: String?
|
||||
public let provider: String?
|
||||
public let idempotencyKey: String
|
||||
|
||||
public init(
|
||||
to: String,
|
||||
message: String,
|
||||
mediaUrl: String?,
|
||||
provider: String?,
|
||||
idempotencyKey: String
|
||||
) {
|
||||
self.to = to
|
||||
self.message = message
|
||||
self.mediaUrl = mediaUrl
|
||||
self.provider = provider
|
||||
self.idempotencyKey = idempotencyKey
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentParams: Codable {
|
||||
public let message: String
|
||||
public let to: String?
|
||||
public let sessionId: String?
|
||||
public let thinking: String?
|
||||
public let deliver: Bool?
|
||||
public let timeout: Int?
|
||||
public let idempotencyKey: String
|
||||
|
||||
public init(
|
||||
message: String,
|
||||
to: String?,
|
||||
sessionId: String?,
|
||||
thinking: String?,
|
||||
deliver: Bool?,
|
||||
timeout: Int?,
|
||||
idempotencyKey: String
|
||||
) {
|
||||
self.message = message
|
||||
self.to = to
|
||||
self.sessionId = sessionId
|
||||
self.thinking = thinking
|
||||
self.deliver = deliver
|
||||
self.timeout = timeout
|
||||
self.idempotencyKey = idempotencyKey
|
||||
}
|
||||
}
|
||||
|
||||
public struct TickEvent: Codable {
|
||||
public let ts: Int
|
||||
|
||||
public init(
|
||||
ts: Int
|
||||
) {
|
||||
self.ts = ts
|
||||
}
|
||||
}
|
||||
|
||||
public struct ShutdownEvent: Codable {
|
||||
public let reason: String
|
||||
public let restartExpectedMs: Int?
|
||||
|
||||
public init(
|
||||
reason: String,
|
||||
restartExpectedMs: Int?
|
||||
) {
|
||||
self.reason = reason
|
||||
self.restartExpectedMs = restartExpectedMs
|
||||
}
|
||||
}
|
||||
|
||||
public enum GatewayFrame: Codable {
|
||||
case hello(Hello)
|
||||
case hello-ok(HelloOk)
|
||||
case hello-error(HelloError)
|
||||
case req(RequestFrame)
|
||||
case res(ResponseFrame)
|
||||
case event(EventFrame)
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let raw = try container.decode([String: AnyCodable].self)
|
||||
guard let type = raw["type"]?.value as? String else {
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "missing type")
|
||||
}
|
||||
switch type {
|
||||
case "hello":
|
||||
self = .hello(try decodePayload(Hello.self, from: raw))
|
||||
case "hello-ok":
|
||||
self = .helloOk(try decodePayload(HelloOk.self, from: raw))
|
||||
case "hello-error":
|
||||
self = .helloError(try decodePayload(HelloError.self, from: raw))
|
||||
case "req":
|
||||
self = .req(try decodePayload(RequestFrame.self, from: raw))
|
||||
case "res":
|
||||
self = .res(try decodePayload(ResponseFrame.self, from: raw))
|
||||
case "event":
|
||||
self = .event(try decodePayload(EventFrame.self, from: raw))
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "unknown type (type)")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
switch self {
|
||||
case .hello(let v): try v.encode(to: encoder)
|
||||
case .helloOk(let v): try v.encode(to: encoder)
|
||||
case .helloError(let v): try v.encode(to: encoder)
|
||||
case .req(let v): try v.encode(to: encoder)
|
||||
case .res(let v): try v.encode(to: encoder)
|
||||
case .event(let v): try v.encode(to: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T {
|
||||
let data = try JSONSerialization.data(withJSONObject: raw)
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,901 +0,0 @@
|
||||
// This file was generated from JSON Schema using quicktype, do not modify it directly.
|
||||
// To parse the JSON, add this file to your project and do:
|
||||
//
|
||||
// let clawdisGateway = try ClawdisGateway(json)
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - ClawdisGateway
|
||||
|
||||
/// Handshake, request/response, and event frames for the Gateway WebSocket.
|
||||
struct ClawdisGateway: Codable {
|
||||
let auth: Auth?
|
||||
let caps: [String]?
|
||||
let client: Client?
|
||||
let locale: String?
|
||||
let maxProtocol, minProtocol: Int?
|
||||
let type: TypeEnum
|
||||
let userAgent: String?
|
||||
let features: Features?
|
||||
let policy: Policy?
|
||||
let clawdisGatewayProtocol: Int?
|
||||
let server: Server?
|
||||
let snapshot: Snapshot?
|
||||
let expectedProtocol: Int?
|
||||
let minClient, reason, id, method: String?
|
||||
let params: JSONAny?
|
||||
let error: Error?
|
||||
let ok: Bool?
|
||||
let payload: JSONAny?
|
||||
let event: String?
|
||||
let seq: Int?
|
||||
let stateVersion: ClawdisGatewayStateVersion?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case auth, caps, client, locale, maxProtocol, minProtocol, type, userAgent, features, policy
|
||||
case clawdisGatewayProtocol = "protocol"
|
||||
case server, snapshot, expectedProtocol, minClient, reason, id, method, params, error, ok, payload, event, seq,
|
||||
stateVersion
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ClawdisGateway convenience initializers and mutators
|
||||
|
||||
extension ClawdisGateway {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(ClawdisGateway.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
auth: Auth?? = nil,
|
||||
caps: [String]?? = nil,
|
||||
client: Client?? = nil,
|
||||
locale: String?? = nil,
|
||||
maxProtocol: Int?? = nil,
|
||||
minProtocol: Int?? = nil,
|
||||
type: TypeEnum? = nil,
|
||||
userAgent: String?? = nil,
|
||||
features: Features?? = nil,
|
||||
policy: Policy?? = nil,
|
||||
clawdisGatewayProtocol: Int?? = nil,
|
||||
server: Server?? = nil,
|
||||
snapshot: Snapshot?? = nil,
|
||||
expectedProtocol: Int?? = nil,
|
||||
minClient: String?? = nil,
|
||||
reason: String?? = nil,
|
||||
id: String?? = nil,
|
||||
method: String?? = nil,
|
||||
params: JSONAny?? = nil,
|
||||
error: Error?? = nil,
|
||||
ok: Bool?? = nil,
|
||||
payload: JSONAny?? = nil,
|
||||
event: String?? = nil,
|
||||
seq: Int?? = nil,
|
||||
stateVersion: ClawdisGatewayStateVersion?? = nil) -> ClawdisGateway
|
||||
{
|
||||
ClawdisGateway(
|
||||
auth: auth ?? self.auth,
|
||||
caps: caps ?? self.caps,
|
||||
client: client ?? self.client,
|
||||
locale: locale ?? self.locale,
|
||||
maxProtocol: maxProtocol ?? self.maxProtocol,
|
||||
minProtocol: minProtocol ?? self.minProtocol,
|
||||
type: type ?? self.type,
|
||||
userAgent: userAgent ?? self.userAgent,
|
||||
features: features ?? self.features,
|
||||
policy: policy ?? self.policy,
|
||||
clawdisGatewayProtocol: clawdisGatewayProtocol ?? self.clawdisGatewayProtocol,
|
||||
server: server ?? self.server,
|
||||
snapshot: snapshot ?? self.snapshot,
|
||||
expectedProtocol: expectedProtocol ?? self.expectedProtocol,
|
||||
minClient: minClient ?? self.minClient,
|
||||
reason: reason ?? self.reason,
|
||||
id: id ?? self.id,
|
||||
method: method ?? self.method,
|
||||
params: params ?? self.params,
|
||||
error: error ?? self.error,
|
||||
ok: ok ?? self.ok,
|
||||
payload: payload ?? self.payload,
|
||||
event: event ?? self.event,
|
||||
seq: seq ?? self.seq,
|
||||
stateVersion: stateVersion ?? self.stateVersion)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Auth
|
||||
|
||||
struct Auth: Codable {
|
||||
let token: String?
|
||||
}
|
||||
|
||||
// MARK: Auth convenience initializers and mutators
|
||||
|
||||
extension Auth {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Auth.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
token: String?? = nil) -> Auth
|
||||
{
|
||||
Auth(
|
||||
token: token ?? self.token)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Client
|
||||
|
||||
struct Client: Codable {
|
||||
let instanceID: String?
|
||||
let mode, name, platform, version: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case instanceID = "instanceId"
|
||||
case mode, name, platform, version
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Client convenience initializers and mutators
|
||||
|
||||
extension Client {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Client.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
instanceID: String?? = nil,
|
||||
mode: String? = nil,
|
||||
name: String? = nil,
|
||||
platform: String? = nil,
|
||||
version: String? = nil) -> Client
|
||||
{
|
||||
Client(
|
||||
instanceID: instanceID ?? self.instanceID,
|
||||
mode: mode ?? self.mode,
|
||||
name: name ?? self.name,
|
||||
platform: platform ?? self.platform,
|
||||
version: version ?? self.version)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Error
|
||||
|
||||
struct Error: Codable {
|
||||
let code: String
|
||||
let details: JSONAny?
|
||||
let message: String
|
||||
let retryable: Bool?
|
||||
let retryAfterMS: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case code, details, message, retryable
|
||||
case retryAfterMS = "retryAfterMs"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Error convenience initializers and mutators
|
||||
|
||||
extension Error {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Error.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
code: String? = nil,
|
||||
details: JSONAny?? = nil,
|
||||
message: String? = nil,
|
||||
retryable: Bool?? = nil,
|
||||
retryAfterMS: Int?? = nil) -> Error
|
||||
{
|
||||
Error(
|
||||
code: code ?? self.code,
|
||||
details: details ?? self.details,
|
||||
message: message ?? self.message,
|
||||
retryable: retryable ?? self.retryable,
|
||||
retryAfterMS: retryAfterMS ?? self.retryAfterMS)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Features
|
||||
|
||||
struct Features: Codable {
|
||||
let events, methods: [String]
|
||||
}
|
||||
|
||||
// MARK: Features convenience initializers and mutators
|
||||
|
||||
extension Features {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Features.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
events: [String]? = nil,
|
||||
methods: [String]? = nil) -> Features
|
||||
{
|
||||
Features(
|
||||
events: events ?? self.events,
|
||||
methods: methods ?? self.methods)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Policy
|
||||
|
||||
struct Policy: Codable {
|
||||
let maxBufferedBytes, maxPayload, tickIntervalMS: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case maxBufferedBytes, maxPayload
|
||||
case tickIntervalMS = "tickIntervalMs"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Policy convenience initializers and mutators
|
||||
|
||||
extension Policy {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Policy.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
maxBufferedBytes: Int? = nil,
|
||||
maxPayload: Int? = nil,
|
||||
tickIntervalMS: Int? = nil) -> Policy
|
||||
{
|
||||
Policy(
|
||||
maxBufferedBytes: maxBufferedBytes ?? self.maxBufferedBytes,
|
||||
maxPayload: maxPayload ?? self.maxPayload,
|
||||
tickIntervalMS: tickIntervalMS ?? self.tickIntervalMS)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Server
|
||||
|
||||
struct Server: Codable {
|
||||
let commit: String?
|
||||
let connID: String
|
||||
let host: String?
|
||||
let version: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case commit
|
||||
case connID = "connId"
|
||||
case host, version
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Server convenience initializers and mutators
|
||||
|
||||
extension Server {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Server.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
commit: String?? = nil,
|
||||
connID: String? = nil,
|
||||
host: String?? = nil,
|
||||
version: String? = nil) -> Server
|
||||
{
|
||||
Server(
|
||||
commit: commit ?? self.commit,
|
||||
connID: connID ?? self.connID,
|
||||
host: host ?? self.host,
|
||||
version: version ?? self.version)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Snapshot
|
||||
|
||||
struct Snapshot: Codable {
|
||||
let health: JSONAny
|
||||
let presence: [Presence]
|
||||
let stateVersion: SnapshotStateVersion
|
||||
let uptimeMS: Int
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case health, presence, stateVersion
|
||||
case uptimeMS = "uptimeMs"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Snapshot convenience initializers and mutators
|
||||
|
||||
extension Snapshot {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Snapshot.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
health: JSONAny? = nil,
|
||||
presence: [Presence]? = nil,
|
||||
stateVersion: SnapshotStateVersion? = nil,
|
||||
uptimeMS: Int? = nil) -> Snapshot
|
||||
{
|
||||
Snapshot(
|
||||
health: health ?? self.health,
|
||||
presence: presence ?? self.presence,
|
||||
stateVersion: stateVersion ?? self.stateVersion,
|
||||
uptimeMS: uptimeMS ?? self.uptimeMS)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Presence
|
||||
|
||||
struct Presence: Codable {
|
||||
let host, instanceID, ip: String?
|
||||
let lastInputSeconds: Int?
|
||||
let mode, reason: String?
|
||||
let tags: [String]?
|
||||
let text: String?
|
||||
let ts: Int
|
||||
let version: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case host
|
||||
case instanceID = "instanceId"
|
||||
case ip, lastInputSeconds, mode, reason, tags, text, ts, version
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Presence convenience initializers and mutators
|
||||
|
||||
extension Presence {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(Presence.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
host: String?? = nil,
|
||||
instanceID: String?? = nil,
|
||||
ip: String?? = nil,
|
||||
lastInputSeconds: Int?? = nil,
|
||||
mode: String?? = nil,
|
||||
reason: String?? = nil,
|
||||
tags: [String]?? = nil,
|
||||
text: String?? = nil,
|
||||
ts: Int? = nil,
|
||||
version: String?? = nil) -> Presence
|
||||
{
|
||||
Presence(
|
||||
host: host ?? self.host,
|
||||
instanceID: instanceID ?? self.instanceID,
|
||||
ip: ip ?? self.ip,
|
||||
lastInputSeconds: lastInputSeconds ?? self.lastInputSeconds,
|
||||
mode: mode ?? self.mode,
|
||||
reason: reason ?? self.reason,
|
||||
tags: tags ?? self.tags,
|
||||
text: text ?? self.text,
|
||||
ts: ts ?? self.ts,
|
||||
version: version ?? self.version)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SnapshotStateVersion
|
||||
|
||||
struct SnapshotStateVersion: Codable {
|
||||
let health, presence: Int
|
||||
}
|
||||
|
||||
// MARK: SnapshotStateVersion convenience initializers and mutators
|
||||
|
||||
extension SnapshotStateVersion {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(SnapshotStateVersion.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
health: Int? = nil,
|
||||
presence: Int? = nil) -> SnapshotStateVersion
|
||||
{
|
||||
SnapshotStateVersion(
|
||||
health: health ?? self.health,
|
||||
presence: presence ?? self.presence)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ClawdisGatewayStateVersion
|
||||
|
||||
struct ClawdisGatewayStateVersion: Codable {
|
||||
let health, presence: Int
|
||||
}
|
||||
|
||||
// MARK: ClawdisGatewayStateVersion convenience initializers and mutators
|
||||
|
||||
extension ClawdisGatewayStateVersion {
|
||||
init(data: Data) throws {
|
||||
self = try newJSONDecoder().decode(ClawdisGatewayStateVersion.self, from: data)
|
||||
}
|
||||
|
||||
init(_ json: String, using encoding: String.Encoding = .utf8) throws {
|
||||
guard let data = json.data(using: encoding) else {
|
||||
throw NSError(domain: "JSONDecoding", code: 0, userInfo: nil)
|
||||
}
|
||||
try self.init(data: data)
|
||||
}
|
||||
|
||||
init(fromURL url: URL) throws {
|
||||
try self.init(data: Data(contentsOf: url))
|
||||
}
|
||||
|
||||
func with(
|
||||
health: Int? = nil,
|
||||
presence: Int? = nil) -> ClawdisGatewayStateVersion
|
||||
{
|
||||
ClawdisGatewayStateVersion(
|
||||
health: health ?? self.health,
|
||||
presence: presence ?? self.presence)
|
||||
}
|
||||
|
||||
func jsonData() throws -> Data {
|
||||
try newJSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
func jsonString(encoding: String.Encoding = .utf8) throws -> String? {
|
||||
try String(data: self.jsonData(), encoding: encoding)
|
||||
}
|
||||
}
|
||||
|
||||
enum TypeEnum: String, Codable {
|
||||
case event
|
||||
case hello
|
||||
case helloError = "hello-error"
|
||||
case helloOk = "hello-ok"
|
||||
case req
|
||||
case res
|
||||
}
|
||||
|
||||
// MARK: - Helper functions for creating encoders and decoders
|
||||
|
||||
func newJSONDecoder() -> JSONDecoder {
|
||||
let decoder = JSONDecoder()
|
||||
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
|
||||
decoder.dateDecodingStrategy = .iso8601
|
||||
}
|
||||
return decoder
|
||||
}
|
||||
|
||||
func newJSONEncoder() -> JSONEncoder {
|
||||
let encoder = JSONEncoder()
|
||||
if #available(iOS 10.0, OSX 10.12, tvOS 10.0, watchOS 3.0, *) {
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
}
|
||||
return encoder
|
||||
}
|
||||
|
||||
// MARK: - Encode/decode helpers
|
||||
|
||||
class JSONNull: Codable, Hashable {
|
||||
static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
|
||||
true
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(0)
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if !container.decodeNil() {
|
||||
throw DecodingError.typeMismatch(
|
||||
JSONNull.self,
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Wrong type for JSONNull"))
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encodeNil()
|
||||
}
|
||||
}
|
||||
|
||||
class JSONCodingKey: CodingKey {
|
||||
let key: String
|
||||
|
||||
required init?(intValue: Int) {
|
||||
nil
|
||||
}
|
||||
|
||||
required init?(stringValue: String) {
|
||||
self.key = stringValue
|
||||
}
|
||||
|
||||
var intValue: Int? {
|
||||
nil
|
||||
}
|
||||
|
||||
var stringValue: String {
|
||||
self.key
|
||||
}
|
||||
}
|
||||
|
||||
class JSONAny: Codable {
|
||||
let value: Any
|
||||
|
||||
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
|
||||
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
|
||||
return DecodingError.typeMismatch(JSONAny.self, context)
|
||||
}
|
||||
|
||||
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
|
||||
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
|
||||
return EncodingError.invalidValue(value, context)
|
||||
}
|
||||
|
||||
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
|
||||
if let value = try? container.decode(Bool.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Int64.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Double.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(String.self) {
|
||||
return value
|
||||
}
|
||||
if container.decodeNil() {
|
||||
return JSONNull()
|
||||
}
|
||||
throw self.decodingError(forCodingPath: container.codingPath)
|
||||
}
|
||||
|
||||
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
|
||||
if let value = try? container.decode(Bool.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Int64.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Double.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(String.self) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decodeNil() {
|
||||
if value {
|
||||
return JSONNull()
|
||||
}
|
||||
}
|
||||
if var container = try? container.nestedUnkeyedContainer() {
|
||||
return try self.decodeArray(from: &container)
|
||||
}
|
||||
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
|
||||
return try self.decodeDictionary(from: &container)
|
||||
}
|
||||
throw self.decodingError(forCodingPath: container.codingPath)
|
||||
}
|
||||
|
||||
static func decode(
|
||||
from container: inout KeyedDecodingContainer<JSONCodingKey>,
|
||||
forKey key: JSONCodingKey) throws -> Any
|
||||
{
|
||||
if let value = try? container.decode(Bool.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Int64.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(Double.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decode(String.self, forKey: key) {
|
||||
return value
|
||||
}
|
||||
if let value = try? container.decodeNil(forKey: key) {
|
||||
if value {
|
||||
return JSONNull()
|
||||
}
|
||||
}
|
||||
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
|
||||
return try self.decodeArray(from: &container)
|
||||
}
|
||||
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
|
||||
return try self.decodeDictionary(from: &container)
|
||||
}
|
||||
throw self.decodingError(forCodingPath: container.codingPath)
|
||||
}
|
||||
|
||||
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
|
||||
var arr: [Any] = []
|
||||
while !container.isAtEnd {
|
||||
let value = try decode(from: &container)
|
||||
arr.append(value)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
|
||||
var dict = [String: Any]()
|
||||
for key in container.allKeys {
|
||||
let value = try decode(from: &container, forKey: key)
|
||||
dict[key.stringValue] = value
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
|
||||
for value in array {
|
||||
if let value = value as? Bool {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? Int64 {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? Double {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? String {
|
||||
try container.encode(value)
|
||||
} else if value is JSONNull {
|
||||
try container.encodeNil()
|
||||
} else if let value = value as? [Any] {
|
||||
var container = container.nestedUnkeyedContainer()
|
||||
try self.encode(to: &container, array: value)
|
||||
} else if let value = value as? [String: Any] {
|
||||
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
|
||||
try self.encode(to: &container, dictionary: value)
|
||||
} else {
|
||||
throw self.encodingError(forValue: value, codingPath: container.codingPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
|
||||
for (key, value) in dictionary {
|
||||
let key = JSONCodingKey(stringValue: key)!
|
||||
if let value = value as? Bool {
|
||||
try container.encode(value, forKey: key)
|
||||
} else if let value = value as? Int64 {
|
||||
try container.encode(value, forKey: key)
|
||||
} else if let value = value as? Double {
|
||||
try container.encode(value, forKey: key)
|
||||
} else if let value = value as? String {
|
||||
try container.encode(value, forKey: key)
|
||||
} else if value is JSONNull {
|
||||
try container.encodeNil(forKey: key)
|
||||
} else if let value = value as? [Any] {
|
||||
var container = container.nestedUnkeyedContainer(forKey: key)
|
||||
try self.encode(to: &container, array: value)
|
||||
} else if let value = value as? [String: Any] {
|
||||
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
|
||||
try self.encode(to: &container, dictionary: value)
|
||||
} else {
|
||||
throw self.encodingError(forValue: value, codingPath: container.codingPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
|
||||
if let value = value as? Bool {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? Int64 {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? Double {
|
||||
try container.encode(value)
|
||||
} else if let value = value as? String {
|
||||
try container.encode(value)
|
||||
} else if value is JSONNull {
|
||||
try container.encodeNil()
|
||||
} else {
|
||||
throw self.encodingError(forValue: value, codingPath: container.codingPath)
|
||||
}
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
if var arrayContainer = try? decoder.unkeyedContainer() {
|
||||
self.value = try JSONAny.decodeArray(from: &arrayContainer)
|
||||
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
|
||||
self.value = try JSONAny.decodeDictionary(from: &container)
|
||||
} else {
|
||||
let container = try decoder.singleValueContainer()
|
||||
self.value = try JSONAny.decode(from: container)
|
||||
}
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
if let arr = self.value as? [Any] {
|
||||
var container = encoder.unkeyedContainer()
|
||||
try JSONAny.encode(to: &container, array: arr)
|
||||
} else if let dict = self.value as? [String: Any] {
|
||||
var container = encoder.container(keyedBy: JSONCodingKey.self)
|
||||
try JSONAny.encode(to: &container, dictionary: dict)
|
||||
} else {
|
||||
var container = encoder.singleValueContainer()
|
||||
try JSONAny.encode(to: &container, value: self.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,8 @@
|
||||
"test": "vitest",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"protocol:gen": "tsx scripts/protocol-gen.ts",
|
||||
"protocol:check": "pnpm protocol:gen && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdisProtocol/Protocol.swift",
|
||||
"protocol:gen:swift": "tsx scripts/protocol-gen-swift.ts",
|
||||
"protocol:check": "pnpm protocol:gen && pnpm protocol:gen:swift && git diff --exit-code -- dist/protocol.schema.json apps/macos/Sources/ClawdisProtocol/GatewayModels.swift",
|
||||
"webchat:bundle": "rolldown -c apps/macos/Sources/Clawdis/Resources/WebChat/rolldown.config.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
|
||||
200
scripts/protocol-gen-swift.ts
Normal file
200
scripts/protocol-gen-swift.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
ErrorCodes,
|
||||
PROTOCOL_VERSION,
|
||||
ProtocolSchemas,
|
||||
} from "../src/gateway/protocol/schema.js";
|
||||
|
||||
type JsonSchema = {
|
||||
type?: string | string[];
|
||||
properties?: Record<string, JsonSchema>;
|
||||
required?: string[];
|
||||
items?: JsonSchema;
|
||||
enum?: string[];
|
||||
patternProperties?: Record<string, JsonSchema>;
|
||||
};
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
const outPath = path.join(
|
||||
repoRoot,
|
||||
"apps",
|
||||
"macos",
|
||||
"Sources",
|
||||
"ClawdisProtocol",
|
||||
"GatewayModels.swift",
|
||||
);
|
||||
|
||||
const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by hand\nimport Foundation\n\npublic let GATEWAY_PROTOCOL_VERSION = ${PROTOCOL_VERSION}\n\npublic enum ErrorCode: String, Codable {\n${Object.values(ErrorCodes)
|
||||
.map((c) => ` case ${camelCase(c)} = "${c}"`)
|
||||
.join("\n")}\n}\n`;
|
||||
|
||||
function camelCase(input: string) {
|
||||
return input
|
||||
.toLowerCase()
|
||||
.split("_")
|
||||
.map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1)))
|
||||
.join("");
|
||||
}
|
||||
|
||||
function swiftType(schema: JsonSchema, required: boolean): string {
|
||||
const t = schema.type;
|
||||
const isOptional = !required;
|
||||
let base: string;
|
||||
if (t === "string") base = "String";
|
||||
else if (t === "integer") base = "Int";
|
||||
else if (t === "number") base = "Double";
|
||||
else if (t === "boolean") base = "Bool";
|
||||
else if (t === "array") {
|
||||
base = `[${swiftType(schema.items ?? { type: "Any" }, true)}]`;
|
||||
} else if (schema.enum) {
|
||||
base = schema.enum.map((v) => `\"${v}\"`).join(" | ");
|
||||
base = "String"; // simplify enums to String; custom enums could be added if needed
|
||||
} else if (schema.patternProperties) {
|
||||
base = "[String: AnyCodable]";
|
||||
} else if (t === "object") {
|
||||
base = "[String: AnyCodable]";
|
||||
} else {
|
||||
base = "AnyCodable";
|
||||
}
|
||||
return isOptional ? `${base}?` : base;
|
||||
}
|
||||
|
||||
function emitStruct(name: string, schema: JsonSchema): string {
|
||||
const props = schema.properties ?? {};
|
||||
const required = new Set(schema.required ?? []);
|
||||
const lines: string[] = [];
|
||||
lines.push(`public struct ${name}: Codable {`);
|
||||
if (Object.keys(props).length === 0) {
|
||||
lines.push("}\n");
|
||||
return lines.join("\n");
|
||||
}
|
||||
for (const [key, propSchema] of Object.entries(props)) {
|
||||
const propName = key === "description" ? "desc" : key;
|
||||
const propType = swiftType(propSchema, required.has(key));
|
||||
lines.push(` public let ${propName}: ${propType}`);
|
||||
}
|
||||
lines.push("\n public init(\n" +
|
||||
Object.entries(props)
|
||||
.map(([key, prop]) => {
|
||||
const propName = key === "description" ? "desc" : key;
|
||||
const req = required.has(key);
|
||||
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
|
||||
})
|
||||
.join(",\n") +
|
||||
"\n ) {\n" +
|
||||
Object.entries(props)
|
||||
.map(([key]) => {
|
||||
const propName = key === "description" ? "desc" : key;
|
||||
return ` self.${propName} = ${propName}`;
|
||||
})
|
||||
.join("\n") +
|
||||
"\n }\n}");
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function emitGatewayFrame(): string {
|
||||
const cases = [
|
||||
"hello",
|
||||
"hello-ok",
|
||||
"hello-error",
|
||||
"req",
|
||||
"res",
|
||||
"event",
|
||||
];
|
||||
const associated: Record<string, string> = {
|
||||
hello: "Hello",
|
||||
"hello-ok": "HelloOk",
|
||||
"hello-error": "HelloError",
|
||||
req: "RequestFrame",
|
||||
res: "ResponseFrame",
|
||||
event: "EventFrame",
|
||||
};
|
||||
const caseLines = cases.map((c) => ` case ${camelCase(c)}(${associated[c]})`);
|
||||
const initLines = `
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let raw = try container.decode([String: AnyCodable].self)
|
||||
guard let type = raw["type"]?.value as? String else {
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "missing type")
|
||||
}
|
||||
switch type {
|
||||
case "hello":
|
||||
self = .hello(try decodePayload(Hello.self, from: raw))
|
||||
case "hello-ok":
|
||||
self = .helloOk(try decodePayload(HelloOk.self, from: raw))
|
||||
case "hello-error":
|
||||
self = .helloError(try decodePayload(HelloError.self, from: raw))
|
||||
case "req":
|
||||
self = .req(try decodePayload(RequestFrame.self, from: raw))
|
||||
case "res":
|
||||
self = .res(try decodePayload(ResponseFrame.self, from: raw))
|
||||
case "event":
|
||||
self = .event(try decodePayload(EventFrame.self, from: raw))
|
||||
default:
|
||||
throw DecodingError.dataCorruptedError(in: container, debugDescription: "unknown type \(type)")
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
switch self {
|
||||
case .hello(let v): try v.encode(to: encoder)
|
||||
case .helloOk(let v): try v.encode(to: encoder)
|
||||
case .helloError(let v): try v.encode(to: encoder)
|
||||
case .req(let v): try v.encode(to: encoder)
|
||||
case .res(let v): try v.encode(to: encoder)
|
||||
case .event(let v): try v.encode(to: encoder)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const helper = `
|
||||
private func decodePayload<T: Decodable>(_ type: T.Type, from raw: [String: AnyCodable]) throws -> T {
|
||||
let data = try JSONSerialization.data(withJSONObject: raw)
|
||||
let decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
}
|
||||
`;
|
||||
|
||||
return [
|
||||
"public enum GatewayFrame: Codable {",
|
||||
...caseLines,
|
||||
initLines,
|
||||
helper,
|
||||
"}",
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
const definitions = Object.entries(ProtocolSchemas) as Array<
|
||||
[string, JsonSchema]
|
||||
>;
|
||||
|
||||
const parts: string[] = [];
|
||||
parts.push(header);
|
||||
|
||||
// Value structs
|
||||
for (const [name, schema] of definitions) {
|
||||
if (name === "GatewayFrame") continue;
|
||||
if (schema.type === "object") {
|
||||
parts.push(emitStruct(name, schema));
|
||||
}
|
||||
}
|
||||
|
||||
// Frame enum must come after payload structs
|
||||
parts.push(emitGatewayFrame());
|
||||
|
||||
const content = parts.join("\n");
|
||||
await fs.mkdir(path.dirname(outPath), { recursive: true });
|
||||
await fs.writeFile(outPath, content);
|
||||
console.log(`wrote ${outPath}`);
|
||||
}
|
||||
|
||||
generate().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user