Mac: build GatewayProtocol target and typed presence handling
This commit is contained in:
@@ -31,27 +31,67 @@ const header = `// Generated by scripts/protocol-gen-swift.ts — do not edit by
|
||||
.map((c) => ` case ${camelCase(c)} = "${c}"`)
|
||||
.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) {
|
||||
return input
|
||||
.replace(/[^a-zA-Z0-9]+/g, " ")
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.split("_")
|
||||
.split(/\s+/)
|
||||
.map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1)))
|
||||
.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 {
|
||||
const t = schema.type;
|
||||
const isOptional = !required;
|
||||
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 === "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
|
||||
base = "String";
|
||||
} else if (schema.patternProperties) {
|
||||
base = "[String: AnyCodable]";
|
||||
} else if (t === "object") {
|
||||
@@ -71,15 +111,21 @@ function emitStruct(name: string, schema: JsonSchema): string {
|
||||
lines.push("}\n");
|
||||
return lines.join("\n");
|
||||
}
|
||||
const codingKeys: string[] = [];
|
||||
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));
|
||||
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" +
|
||||
Object.entries(props)
|
||||
.map(([key, prop]) => {
|
||||
const propName = key === "description" ? "desc" : key;
|
||||
const propName = safeName(key);
|
||||
const req = required.has(key);
|
||||
return ` ${propName}: ${swiftType(prop, true)}${req ? "" : "?"}`;
|
||||
})
|
||||
@@ -87,24 +133,20 @@ function emitStruct(name: string, schema: JsonSchema): string {
|
||||
"\n ) {\n" +
|
||||
Object.entries(props)
|
||||
.map(([key]) => {
|
||||
const propName = key === "description" ? "desc" : key;
|
||||
const propName = safeName(key);
|
||||
return ` self.${propName} = ${propName}`;
|
||||
})
|
||||
.join("\n") +
|
||||
"\n }\n" +
|
||||
" private enum CodingKeys: String, CodingKey {\n" +
|
||||
codingKeys.join("\n") +
|
||||
"\n }\n}");
|
||||
lines.push("");
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function emitGatewayFrame(): string {
|
||||
const cases = [
|
||||
"hello",
|
||||
"hello-ok",
|
||||
"hello-error",
|
||||
"req",
|
||||
"res",
|
||||
"event",
|
||||
];
|
||||
const cases = ["hello", "hello-ok", "hello-error", "req", "res", "event"];
|
||||
const associated: Record<string, string> = {
|
||||
hello: "Hello",
|
||||
"hello-ok": "HelloOk",
|
||||
@@ -113,7 +155,7 @@ function emitGatewayFrame(): string {
|
||||
res: "ResponseFrame",
|
||||
event: "EventFrame",
|
||||
};
|
||||
const caseLines = cases.map((c) => ` case ${camelCase(c)}(${associated[c]})`);
|
||||
const caseLines = cases.map((c) => ` case ${safeName(c)}(${associated[c]})`);
|
||||
const initLines = `
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
@@ -123,17 +165,17 @@ function emitGatewayFrame(): string {
|
||||
}
|
||||
switch type {
|
||||
case "hello":
|
||||
self = .hello(try decodePayload(Hello.self, from: raw))
|
||||
self = .hello(try Self.decodePayload(Hello.self, from: raw))
|
||||
case "hello-ok":
|
||||
self = .helloOk(try decodePayload(HelloOk.self, from: raw))
|
||||
self = .helloOk(try Self.decodePayload(HelloOk.self, from: raw))
|
||||
case "hello-error":
|
||||
self = .helloError(try decodePayload(HelloError.self, from: raw))
|
||||
self = .helloError(try Self.decodePayload(HelloError.self, from: raw))
|
||||
case "req":
|
||||
self = .req(try decodePayload(RequestFrame.self, from: raw))
|
||||
self = .req(try Self.decodePayload(RequestFrame.self, from: raw))
|
||||
case "res":
|
||||
self = .res(try decodePayload(ResponseFrame.self, from: raw))
|
||||
self = .res(try Self.decodePayload(ResponseFrame.self, from: raw))
|
||||
case "event":
|
||||
self = .event(try decodePayload(EventFrame.self, from: raw))
|
||||
self = .event(try Self.decodePayload(EventFrame.self, from: raw))
|
||||
default:
|
||||
self = .unknown(type: type, raw: raw)
|
||||
}
|
||||
@@ -155,7 +197,7 @@ function emitGatewayFrame(): string {
|
||||
`;
|
||||
|
||||
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 decoder = JSONDecoder()
|
||||
return try decoder.decode(T.self, from: data)
|
||||
@@ -178,6 +220,10 @@ async function generate() {
|
||||
[string, JsonSchema]
|
||||
>;
|
||||
|
||||
for (const [name, schema] of definitions) {
|
||||
schemaNameByObject.set(schema as object, name);
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
parts.push(header);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user