@seamapi/nextlove-sdk-generator 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/generate-php-sdk/utils/generate-seam-client.js +5 -3
- package/lib/generate-php-sdk/utils/generate-seam-client.js.map +1 -1
- package/lib/generate-swift-sdk/generate-swift-sdk.d.ts +1 -0
- package/lib/generate-swift-sdk/generate-swift-sdk.js +107 -0
- package/lib/generate-swift-sdk/generate-swift-sdk.js.map +1 -0
- package/lib/generate-swift-sdk/map-swift-type.d.ts +2 -0
- package/lib/generate-swift-sdk/map-swift-type.js +21 -0
- package/lib/generate-swift-sdk/map-swift-type.js.map +1 -0
- package/lib/generate-swift-sdk/route-group-class-file.d.ts +20 -0
- package/lib/generate-swift-sdk/route-group-class-file.js +68 -0
- package/lib/generate-swift-sdk/route-group-class-file.js.map +1 -0
- package/lib/generate-swift-sdk/templates/anyjson.swift.template.d.ts +2 -0
- package/lib/generate-swift-sdk/templates/anyjson.swift.template.js +168 -0
- package/lib/generate-swift-sdk/templates/anyjson.swift.template.js.map +1 -0
- package/lib/generate-swift-sdk/templates/package.swift.template.d.ts +2 -0
- package/lib/generate-swift-sdk/templates/package.swift.template.js +28 -0
- package/lib/generate-swift-sdk/templates/package.swift.template.js.map +1 -0
- package/lib/generate-swift-sdk/templates/readme.md.template.d.ts +2 -0
- package/lib/generate-swift-sdk/templates/readme.md.template.js +48 -0
- package/lib/generate-swift-sdk/templates/readme.md.template.js.map +1 -0
- package/lib/generate-swift-sdk/templates/seamclient.swift.template.d.ts +5 -0
- package/lib/generate-swift-sdk/templates/seamclient.swift.template.js +73 -0
- package/lib/generate-swift-sdk/templates/seamclient.swift.template.js.map +1 -0
- package/package.json +3 -1
- package/src/index.ts +1 -0
- package/src/lib/generate-php-sdk/utils/generate-seam-client.ts +5 -3
- package/src/lib/generate-swift-sdk/generate-swift-sdk.ts +135 -0
- package/src/lib/generate-swift-sdk/map-swift-type.ts +27 -0
- package/src/lib/generate-swift-sdk/route-group-class-file.ts +89 -0
- package/src/lib/generate-swift-sdk/templates/anyjson.swift.template.ts +167 -0
- package/src/lib/generate-swift-sdk/templates/package.swift.template.ts +27 -0
- package/src/lib/generate-swift-sdk/templates/readme.md.template.ts +47 -0
- package/src/lib/generate-swift-sdk/templates/seamclient.swift.template.ts +87 -0
package/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export * from "./lib/generate-csharp-sdk/generate-csharp-sdk.js";
|
|
|
4
4
|
export * from "./lib/generate-python-sdk/index.js";
|
|
5
5
|
export * from "./lib/generate-ruby-sdk/index.js";
|
|
6
6
|
export * from "./lib/generate-php-sdk/index.js";
|
|
7
|
+
export * from "./lib/generate-swift-sdk/generate-swift-sdk.js";
|
package/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export * from "./lib/generate-csharp-sdk/generate-csharp-sdk.js";
|
|
|
4
4
|
export * from "./lib/generate-python-sdk/index.js";
|
|
5
5
|
export * from "./lib/generate-ruby-sdk/index.js";
|
|
6
6
|
export * from "./lib/generate-php-sdk/index.js";
|
|
7
|
+
export * from "./lib/generate-swift-sdk/generate-swift-sdk.js";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kDAAkD,CAAA;AAChE,cAAc,oCAAoC,CAAA;AAClD,cAAc,kCAAkC,CAAA;AAChD,cAAc,iCAAiC,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA;AAC9B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,kDAAkD,CAAA;AAChE,cAAc,oCAAoC,CAAA;AAClD,cAAc,kCAAkC,CAAA;AAChD,cAAc,iCAAiC,CAAA;AAC/C,cAAc,gDAAgD,CAAA"}
|
|
@@ -53,7 +53,7 @@ class SeamClient
|
|
|
53
53
|
|
|
54
54
|
// TODO handle request errors
|
|
55
55
|
$response = $this->client->request($method, $path, $options);
|
|
56
|
-
$
|
|
56
|
+
$status_code = $response->getStatusCode();
|
|
57
57
|
|
|
58
58
|
$res_json = null;
|
|
59
59
|
try {
|
|
@@ -74,9 +74,11 @@ class SeamClient
|
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
if ($
|
|
77
|
+
if ($status_code >= 400) {
|
|
78
|
+
$error_message = $response->getReasonPhrase();
|
|
79
|
+
|
|
78
80
|
throw new Exception(
|
|
79
|
-
"HTTP Error: [" . $
|
|
81
|
+
"HTTP Error: " . $error_message . " [" . $status_code . "] " . $method . " " . $path
|
|
80
82
|
);
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-seam-client.js","sourceRoot":"","sources":["../../../src/lib/generate-php-sdk/utils/generate-seam-client.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EACjC,gBAAgB,EAChB,qBAAqB,GACd,EAAE,EAAE;IACX,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,MAAM,CACrD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAC1B,CAAA;IAED,OAAO;;;;EAIP,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;IAOrE,uBAAuB;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,WAAW,CAAC,CAAC,SAAS,GAAG,CAAC;SAC5D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;MAoBX,uBAAuB;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,WAAW,gBAAgB,CAAC;SACxE,IAAI,CAAC,QAAQ,CAAC
|
|
1
|
+
{"version":3,"file":"generate-seam-client.js","sourceRoot":"","sources":["../../../src/lib/generate-php-sdk/utils/generate-seam-client.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EACjC,gBAAgB,EAChB,qBAAqB,GACd,EAAE,EAAE;IACX,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,MAAM,CACrD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAC1B,CAAA;IAED,OAAO;;;;EAIP,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;IAOrE,uBAAuB;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,WAAW,CAAC,CAAC,SAAS,GAAG,CAAC;SAC5D,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;MAoBX,uBAAuB;SACtB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,SAAS,UAAU,CAAC,CAAC,WAAW,gBAAgB,CAAC;SACxE,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgEnB,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;CACxD,CAAA;AACD,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const generateSwiftSDK: () => Promise<Record<string, string>>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import readmeMdTemplate from "./templates/readme.md.template.js";
|
|
3
|
+
import packageSwiftTemplate from "./templates/package.swift.template.js";
|
|
4
|
+
import seamClientSwiftTemplate from "./templates/seamclient.swift.template.js";
|
|
5
|
+
import anyJsonSwiftTemplate from "./templates/anyjson.swift.template.js";
|
|
6
|
+
import { pascalCase } from "change-case";
|
|
7
|
+
import { flattenObjSchema, getParameterAndResponseSchema } from "../../lib/index.js";
|
|
8
|
+
import { mapSwiftType } from "./map-swift-type.js";
|
|
9
|
+
import { RouteGroupClassfile } from "./route-group-class-file.js";
|
|
10
|
+
export const generateSwiftSDK = async () => {
|
|
11
|
+
const openapi = await axios
|
|
12
|
+
.get("https://connect.getseam.com/openapi.json")
|
|
13
|
+
.then((res) => res.data);
|
|
14
|
+
const fs = {};
|
|
15
|
+
fs["README.md"] = readmeMdTemplate();
|
|
16
|
+
fs["Package.swift"] = packageSwiftTemplate();
|
|
17
|
+
const resources = Object.entries(openapi.components.schemas).map(([schema_name, schema]) => [schema_name, flattenObjSchema(schema)]);
|
|
18
|
+
fs["Sources/SeamAPI/AnyJSON.swift"] = anyJsonSwiftTemplate();
|
|
19
|
+
// Create a struct for each declared resource
|
|
20
|
+
fs["Sources/SeamAPI/Schemas.swift"] = [
|
|
21
|
+
"import Foundation",
|
|
22
|
+
...resources
|
|
23
|
+
.map(([snake_name, schema]) => {
|
|
24
|
+
const props = Object.entries(schema.properties)
|
|
25
|
+
.map(([name, property_schema]) => {
|
|
26
|
+
const is_optional = !schema.required?.includes(name) ||
|
|
27
|
+
property_schema.nullable;
|
|
28
|
+
return `let ${name}: ${mapSwiftType(property_schema, "AnyJSON")}${is_optional ? "?" : ""}`;
|
|
29
|
+
})
|
|
30
|
+
.join("\n");
|
|
31
|
+
return `struct ${pascalCase(snake_name)}: Decodable {\n${props}\n}`;
|
|
32
|
+
})
|
|
33
|
+
.filter((x) => x),
|
|
34
|
+
].join("\n");
|
|
35
|
+
// Construct route groups by namespace
|
|
36
|
+
const namespaceToRouteGroupClassName = new Map();
|
|
37
|
+
const routes = Object.entries(openapi.paths).map(([path, v]) => ({
|
|
38
|
+
path,
|
|
39
|
+
...v,
|
|
40
|
+
}));
|
|
41
|
+
const class_map = {};
|
|
42
|
+
const namespaces = [];
|
|
43
|
+
for (const route of routes) {
|
|
44
|
+
if (!route.post)
|
|
45
|
+
continue;
|
|
46
|
+
if (!route.post["x-fern-sdk-group-name"])
|
|
47
|
+
continue;
|
|
48
|
+
const group_names = [...route.post["x-fern-sdk-group-name"]];
|
|
49
|
+
const namespace = group_names.join("_");
|
|
50
|
+
group_names.reverse();
|
|
51
|
+
const class_name = pascalCase(group_names.join("_")) + "Routes";
|
|
52
|
+
if (!class_map[class_name]) {
|
|
53
|
+
namespaces.push(route.post["x-fern-sdk-group-name"]);
|
|
54
|
+
class_map[class_name] = new RouteGroupClassfile(class_name, namespace);
|
|
55
|
+
namespaceToRouteGroupClassName.set(namespace, class_name);
|
|
56
|
+
}
|
|
57
|
+
const cls = class_map[class_name];
|
|
58
|
+
if (!cls) {
|
|
59
|
+
console.warn(`No class for "${route.path}", skipping`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const { parameter_schema, response_obj_type, response_arr_type } = getParameterAndResponseSchema(route);
|
|
63
|
+
if (!response_obj_type && !response_arr_type) {
|
|
64
|
+
console.warn(`No response object/array ref for "${route.path}", skipping`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!parameter_schema) {
|
|
68
|
+
console.warn(`No parameter schema for "${route.path}", skipping`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
cls.addMethod({
|
|
72
|
+
method_name: route.post["x-fern-sdk-method-name"],
|
|
73
|
+
path: route.path,
|
|
74
|
+
bodyParameters: Object.entries(parameter_schema.properties)
|
|
75
|
+
.filter(([_, param_val]) => param_val && typeof param_val === "object" && "type" in param_val)
|
|
76
|
+
.map(([param_name, param_schema]) => {
|
|
77
|
+
const name = param_name;
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
swiftType: mapSwiftType(param_schema),
|
|
81
|
+
isRequired: parameter_schema.required?.includes(name) ?? false,
|
|
82
|
+
};
|
|
83
|
+
}),
|
|
84
|
+
return_path: [route.post["x-fern-sdk-return-value"]],
|
|
85
|
+
return_resource: response_obj_type
|
|
86
|
+
? pascalCase(response_obj_type)
|
|
87
|
+
: `[${pascalCase(response_arr_type)}]`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const route_groups = Object.values(class_map).filter((r) => !r.isEmpty);
|
|
91
|
+
// Write all route groups
|
|
92
|
+
for (const route_group of route_groups) {
|
|
93
|
+
fs[`Sources/SeamAPI/Routes/${pascalCase(route_group.namespace)}.swift`] =
|
|
94
|
+
route_group.serializeToClass();
|
|
95
|
+
}
|
|
96
|
+
// Remove route groups that were filtered out because they were empty
|
|
97
|
+
for (const namespace of namespaceToRouteGroupClassName.keys()) {
|
|
98
|
+
if (!route_groups.find((r) => r.namespace === namespace)) {
|
|
99
|
+
namespaceToRouteGroupClassName.delete(namespace);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
fs["Sources/SeamAPI/SeamClient.swift"] = seamClientSwiftTemplate({
|
|
103
|
+
namespaceToRouteGroupClassName,
|
|
104
|
+
});
|
|
105
|
+
return fs;
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=generate-swift-sdk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-swift-sdk.js","sourceRoot":"","sources":["../../src/lib/generate-swift-sdk/generate-swift-sdk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const mapSwiftType = (property_schema, fallback = "String") => {
|
|
2
|
+
if (typeof property_schema !== "object") {
|
|
3
|
+
return "Any";
|
|
4
|
+
}
|
|
5
|
+
if ("type" in property_schema && property_schema.type === "string") {
|
|
6
|
+
// todo: handle date-time
|
|
7
|
+
return "String";
|
|
8
|
+
}
|
|
9
|
+
if ("type" in property_schema && property_schema.type === "integer")
|
|
10
|
+
return "Int";
|
|
11
|
+
if ("type" in property_schema && property_schema.type === "boolean")
|
|
12
|
+
return "Bool";
|
|
13
|
+
if ("type" in property_schema && property_schema.type === "number")
|
|
14
|
+
return "Float";
|
|
15
|
+
if ("type" in property_schema && property_schema.type === "array")
|
|
16
|
+
return "[String]"; // TODO, make more specific
|
|
17
|
+
if ("type" in property_schema && property_schema.type === "object")
|
|
18
|
+
return `[String: ${fallback}]`; // TODO, make more specific
|
|
19
|
+
return fallback;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=map-swift-type.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"map-swift-type.js","sourceRoot":"","sources":["../../src/lib/generate-swift-sdk/map-swift-type.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAG,CAC1B,eAA+B,EAC/B,QAAQ,GAAG,QAAQ,EACnB,EAAE;IACF,IAAI,OAAO,eAAe,KAAK,QAAQ,EAAE;QACvC,OAAO,KAAK,CAAA;KACb;IAED,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,QAAQ,EAAE;QAClE,yBAAyB;QACzB,OAAO,QAAQ,CAAA;KAChB;IACD,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,SAAS;QACjE,OAAO,KAAK,CAAA;IACd,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,SAAS;QACjE,OAAO,MAAM,CAAA;IACf,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,QAAQ;QAChE,OAAO,OAAO,CAAA;IAChB,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,OAAO;QAC/D,OAAO,UAAU,CAAA,CAAC,2BAA2B;IAC/C,IAAI,MAAM,IAAI,eAAe,IAAI,eAAe,CAAC,IAAI,KAAK,QAAQ;QAChE,OAAO,YAAY,QAAQ,GAAG,CAAA,CAAC,2BAA2B;IAE5D,OAAO,QAAQ,CAAA;AACjB,CAAC,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type RouteGroupClassfileMethod = {
|
|
2
|
+
method_name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
bodyParameters: {
|
|
5
|
+
name: string;
|
|
6
|
+
swiftType: string;
|
|
7
|
+
isRequired: boolean;
|
|
8
|
+
}[];
|
|
9
|
+
return_path: string[];
|
|
10
|
+
return_resource: string;
|
|
11
|
+
};
|
|
12
|
+
export declare class RouteGroupClassfile {
|
|
13
|
+
private name;
|
|
14
|
+
namespace?: string | undefined;
|
|
15
|
+
private methods;
|
|
16
|
+
constructor(name: string, namespace?: string | undefined);
|
|
17
|
+
addMethod(method: RouteGroupClassfileMethod): void;
|
|
18
|
+
get isEmpty(): boolean;
|
|
19
|
+
serializeToClass(): string;
|
|
20
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class RouteGroupClassfile {
|
|
2
|
+
constructor(name, namespace) {
|
|
3
|
+
this.name = name;
|
|
4
|
+
this.namespace = namespace;
|
|
5
|
+
this.methods = [];
|
|
6
|
+
}
|
|
7
|
+
addMethod(method) {
|
|
8
|
+
this.methods.push(method);
|
|
9
|
+
}
|
|
10
|
+
get isEmpty() {
|
|
11
|
+
return this.methods.length === 0;
|
|
12
|
+
}
|
|
13
|
+
serializeToClass() {
|
|
14
|
+
return `import Foundation
|
|
15
|
+
#if canImport(FoundationNetworking)
|
|
16
|
+
import FoundationNetworking
|
|
17
|
+
#endif
|
|
18
|
+
|
|
19
|
+
class ${this.name} {
|
|
20
|
+
private let client: SeamClient
|
|
21
|
+
init(client: SeamClient) {
|
|
22
|
+
self.client = client
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
${this.methods
|
|
26
|
+
.map(({ method_name, bodyParameters, path, return_path, return_resource }) => ` func ${method_name}(${bodyParameters
|
|
27
|
+
.map(({ name, swiftType, isRequired }) => isRequired ? `${name}: ${swiftType}` : `${name}: ${swiftType}? = nil`)
|
|
28
|
+
.join(", ")}) async -> Result<${return_resource}, SeamClientError> {
|
|
29
|
+
${bodyParameters.length > 0 ? "var" : "let"} jsonBody: [String: Any] = [:]
|
|
30
|
+
|
|
31
|
+
${bodyParameters
|
|
32
|
+
.map(({ name, isRequired }) => isRequired
|
|
33
|
+
? `jsonBody["${name}"] = ${name}`
|
|
34
|
+
: ` if let ${name} = ${name} {
|
|
35
|
+
jsonBody["${name}"] = ${name}
|
|
36
|
+
}`)
|
|
37
|
+
.join("\n\n")}
|
|
38
|
+
|
|
39
|
+
let encodedJsonBody: Data?
|
|
40
|
+
do {
|
|
41
|
+
encodedJsonBody = try JSONSerialization.data(withJSONObject: jsonBody, options: [])
|
|
42
|
+
} catch let error {
|
|
43
|
+
return .failure(.unknown(error: error))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
struct Response: Decodable {
|
|
47
|
+
let ${return_path[0]}: ${return_resource}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let response = await client.request(
|
|
51
|
+
method: "POST",
|
|
52
|
+
path: "${path}",
|
|
53
|
+
responseSchema: Response.self,
|
|
54
|
+
jsonBody: encodedJsonBody
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
switch response {
|
|
58
|
+
case .success(let response):
|
|
59
|
+
return .success(response.${return_path[0]})
|
|
60
|
+
case .failure(let error):
|
|
61
|
+
return .failure(error)
|
|
62
|
+
}
|
|
63
|
+
}`)
|
|
64
|
+
.join("\n\n")}
|
|
65
|
+
}`.trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=route-group-class-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-group-class-file.js","sourceRoot":"","sources":["../../src/lib/generate-swift-sdk/route-group-class-file.ts"],"names":[],"mappings":"AAYA,MAAM,OAAO,mBAAmB;IAE9B,YACU,IAAY,EACb,SAAkB;QADjB,SAAI,GAAJ,IAAI,CAAQ;QACb,cAAS,GAAT,SAAS,CAAS;QAHnB,YAAO,GAAgC,EAAE,CAAA;IAI9C,CAAC;IAEJ,SAAS,CAAC,MAAiC;QACzC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3B,CAAC;IAED,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAA;IAClC,CAAC;IAED,gBAAgB;QACd,OAAO;;;;;YAKC,IAAI,CAAC,IAAI;;;;;;EAMnB,IAAI,CAAC,OAAO;aACX,GAAG,CACF,CAAC,EAAE,WAAW,EAAE,cAAc,EAAE,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,EAAE,EAAE,CACtE,UAAU,WAAW,IAAI,cAAc;aACpC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,EAAE,CACvC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,SAAS,SAAS,CACtE;aACA,IAAI,CAAC,IAAI,CAAC,qBAAqB,eAAe;MACjD,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK;;EAE7C,cAAc;aACb,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAC5B,UAAU;YACR,CAAC,CAAC,aAAa,IAAI,QAAQ,IAAI,EAAE;YACjC,CAAC,CAAC,cAAc,IAAI,MAAM,IAAI;kBAClB,IAAI,QAAQ,IAAI;MAC5B,CACH;aACA,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;YAUH,WAAW,CAAC,CAAC,CAAC,KAAK,eAAe;;;;;eAK/B,IAAI;;;;;;;iCAOc,WAAW,CAAC,CAAC,CAAC;;;;IAI3C,CACD;aACA,IAAI,CAAC,MAAM,CAAC;EACb,CAAC,IAAI,EAAE,CAAA;IACP,CAAC;CACF"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export default () => `// Thanks to https://adamrackis.dev/blog/swift-codable-any
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
func decode(fromObject container: KeyedDecodingContainer<AnyJSONCodingKeys>) -> [String: Any] {
|
|
5
|
+
var result: [String: Any] = [:]
|
|
6
|
+
|
|
7
|
+
for key in container.allKeys {
|
|
8
|
+
if let val = try? container.decode(Int.self, forKey: key) {
|
|
9
|
+
result[key.stringValue] = val
|
|
10
|
+
} else if let val = try? container.decode(Double.self, forKey: key) {
|
|
11
|
+
result[key.stringValue] = val
|
|
12
|
+
} else if let val = try? container.decode(String.self, forKey: key) {
|
|
13
|
+
result[key.stringValue] = val
|
|
14
|
+
} else if let val = try? container.decode(Bool.self, forKey: key) {
|
|
15
|
+
result[key.stringValue] = val
|
|
16
|
+
} else if let nestedContainer = try? container.nestedContainer(
|
|
17
|
+
keyedBy: AnyJSONCodingKeys.self, forKey: key)
|
|
18
|
+
{
|
|
19
|
+
result[key.stringValue] = decode(fromObject: nestedContainer)
|
|
20
|
+
} else if var nestedArray = try? container.nestedUnkeyedContainer(forKey: key) {
|
|
21
|
+
result[key.stringValue] = decode(fromArray: &nestedArray)
|
|
22
|
+
} else if (try? container.decodeNil(forKey: key)) == true {
|
|
23
|
+
result.updateValue(Any?(nil) as Any, forKey: key.stringValue)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func decode(fromArray container: inout UnkeyedDecodingContainer) -> [Any] {
|
|
31
|
+
var result: [Any] = []
|
|
32
|
+
|
|
33
|
+
while !container.isAtEnd {
|
|
34
|
+
if let value = try? container.decode(String.self) {
|
|
35
|
+
result.append(value)
|
|
36
|
+
} else if let value = try? container.decode(Int.self) {
|
|
37
|
+
result.append(value)
|
|
38
|
+
} else if let value = try? container.decode(Double.self) {
|
|
39
|
+
result.append(value)
|
|
40
|
+
} else if let value = try? container.decode(Bool.self) {
|
|
41
|
+
result.append(value)
|
|
42
|
+
} else if let nestedContainer = try? container.nestedContainer(keyedBy: AnyJSONCodingKeys.self) {
|
|
43
|
+
result.append(decode(fromObject: nestedContainer))
|
|
44
|
+
} else if var nestedArray = try? container.nestedUnkeyedContainer() {
|
|
45
|
+
result.append(decode(fromArray: &nestedArray))
|
|
46
|
+
} else if (try? container.decodeNil()) == true {
|
|
47
|
+
result.append(Any?(nil) as Any)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func encodeValue(
|
|
55
|
+
fromObjectContainer container: inout KeyedEncodingContainer<AnyJSONCodingKeys>, map: [String: Any]
|
|
56
|
+
) throws {
|
|
57
|
+
for k in map.keys {
|
|
58
|
+
let value = map[k]
|
|
59
|
+
let encodingKey = AnyJSONCodingKeys(stringValue: k)
|
|
60
|
+
|
|
61
|
+
if let value = value as? String {
|
|
62
|
+
try container.encode(value, forKey: encodingKey)
|
|
63
|
+
} else if let value = value as? Int {
|
|
64
|
+
try container.encode(value, forKey: encodingKey)
|
|
65
|
+
} else if let value = value as? Double {
|
|
66
|
+
try container.encode(value, forKey: encodingKey)
|
|
67
|
+
} else if let value = value as? Bool {
|
|
68
|
+
try container.encode(value, forKey: encodingKey)
|
|
69
|
+
} else if let value = value as? [String: Any] {
|
|
70
|
+
var keyedContainer = container.nestedContainer(
|
|
71
|
+
keyedBy: AnyJSONCodingKeys.self, forKey: encodingKey)
|
|
72
|
+
try encodeValue(fromObjectContainer: &keyedContainer, map: value)
|
|
73
|
+
} else if let value = value as? [Any] {
|
|
74
|
+
var unkeyedContainer = container.nestedUnkeyedContainer(forKey: encodingKey)
|
|
75
|
+
try encodeValue(fromArrayContainer: &unkeyedContainer, arr: value)
|
|
76
|
+
} else {
|
|
77
|
+
try container.encodeNil(forKey: encodingKey)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func encodeValue(fromArrayContainer container: inout UnkeyedEncodingContainer, arr: [Any]) throws {
|
|
83
|
+
for value in arr {
|
|
84
|
+
if let value = value as? String {
|
|
85
|
+
try container.encode(value)
|
|
86
|
+
} else if let value = value as? Int {
|
|
87
|
+
try container.encode(value)
|
|
88
|
+
} else if let value = value as? Double {
|
|
89
|
+
try container.encode(value)
|
|
90
|
+
} else if let value = value as? Bool {
|
|
91
|
+
try container.encode(value)
|
|
92
|
+
} else if let value = value as? [String: Any] {
|
|
93
|
+
var keyedContainer = container.nestedContainer(keyedBy: AnyJSONCodingKeys.self)
|
|
94
|
+
try encodeValue(fromObjectContainer: &keyedContainer, map: value)
|
|
95
|
+
} else if let value = value as? [Any] {
|
|
96
|
+
var unkeyedContainer = container.nestedUnkeyedContainer()
|
|
97
|
+
try encodeValue(fromArrayContainer: &unkeyedContainer, arr: value)
|
|
98
|
+
} else {
|
|
99
|
+
try container.encodeNil()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
struct AnyJSONCodingKeys: CodingKey {
|
|
107
|
+
var stringValue: String
|
|
108
|
+
|
|
109
|
+
init(stringValue: String) {
|
|
110
|
+
self.stringValue = stringValue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var intValue: Int?
|
|
114
|
+
|
|
115
|
+
init?(intValue: Int) {
|
|
116
|
+
self.init(stringValue: "\(intValue)")
|
|
117
|
+
self.intValue = intValue
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
struct AnyJSON: Codable {
|
|
122
|
+
var value: Any?
|
|
123
|
+
|
|
124
|
+
init(value: Any?) {
|
|
125
|
+
self.value = value
|
|
126
|
+
}
|
|
127
|
+
init(from decoder: Decoder) throws {
|
|
128
|
+
if let container = try? decoder.container(keyedBy: AnyJSONCodingKeys.self) {
|
|
129
|
+
self.value = decode(fromObject: container)
|
|
130
|
+
} else if var array = try? decoder.unkeyedContainer() {
|
|
131
|
+
self.value = decode(fromArray: &array)
|
|
132
|
+
} else if let value = try? decoder.singleValueContainer() {
|
|
133
|
+
if value.decodeNil() {
|
|
134
|
+
self.value = nil
|
|
135
|
+
} else {
|
|
136
|
+
if let result = try? value.decode(Int.self) { self.value = result }
|
|
137
|
+
if let result = try? value.decode(Double.self) { self.value = result }
|
|
138
|
+
if let result = try? value.decode(String.self) { self.value = result }
|
|
139
|
+
if let result = try? value.decode(Bool.self) { self.value = result }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func encode(to encoder: Encoder) throws {
|
|
145
|
+
if let map = self.value as? [String: Any] {
|
|
146
|
+
var container = encoder.container(keyedBy: AnyJSONCodingKeys.self)
|
|
147
|
+
try encodeValue(fromObjectContainer: &container, map: map)
|
|
148
|
+
} else if let arr = self.value as? [Any] {
|
|
149
|
+
var container = encoder.unkeyedContainer()
|
|
150
|
+
try encodeValue(fromArrayContainer: &container, arr: arr)
|
|
151
|
+
} else {
|
|
152
|
+
var container = encoder.singleValueContainer()
|
|
153
|
+
|
|
154
|
+
if let value = self.value as? String {
|
|
155
|
+
try! container.encode(value)
|
|
156
|
+
} else if let value = self.value as? Int {
|
|
157
|
+
try! container.encode(value)
|
|
158
|
+
} else if let value = self.value as? Double {
|
|
159
|
+
try! container.encode(value)
|
|
160
|
+
} else if let value = self.value as? Bool {
|
|
161
|
+
try! container.encode(value)
|
|
162
|
+
} else {
|
|
163
|
+
try! container.encodeNil()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}`;
|
|
168
|
+
//# sourceMappingURL=anyjson.swift.template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anyjson.swift.template.js","sourceRoot":"","sources":["../../../src/lib/generate-swift-sdk/templates/anyjson.swift.template.ts"],"names":[],"mappings":"AAAA,eAAe,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsKnB,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default () => `// swift-tools-version: 5.7
|
|
2
|
+
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
3
|
+
|
|
4
|
+
import PackageDescription
|
|
5
|
+
|
|
6
|
+
let package = Package(
|
|
7
|
+
name: "SeamAPI",
|
|
8
|
+
platforms: [
|
|
9
|
+
.macOS(.v12)
|
|
10
|
+
],
|
|
11
|
+
products: [
|
|
12
|
+
// Products define the executables and libraries a package produces, making them visible to other packages.
|
|
13
|
+
.library(
|
|
14
|
+
name: "SeamAPI",
|
|
15
|
+
targets: ["SeamAPI"]),
|
|
16
|
+
],
|
|
17
|
+
targets: [
|
|
18
|
+
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
|
19
|
+
// Targets can depend on other targets in this package and products from dependencies.
|
|
20
|
+
.target(
|
|
21
|
+
name: "SeamAPI"),
|
|
22
|
+
.testTarget(
|
|
23
|
+
name: "SeamAPITests",
|
|
24
|
+
dependencies: ["SeamAPI"]),
|
|
25
|
+
]
|
|
26
|
+
)
|
|
27
|
+
`;
|
|
28
|
+
//# sourceMappingURL=package.swift.template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.swift.template.js","sourceRoot":"","sources":["../../../src/lib/generate-swift-sdk/templates/package.swift.template.ts"],"names":[],"mappings":"AAAA,eAAe,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BpB,CAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export default () => `# Seam API Swift SDK
|
|
2
|
+
|
|
3
|
+
Official interface to the [Seam Connect API].
|
|
4
|
+
|
|
5
|
+
[Seam Connect API]: https://docs.seam.co/
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
This SDK provides convenient access to the [Seam Connect API] for Swift applications.
|
|
10
|
+
|
|
11
|
+
[Seam API endpoints]: https://docs.seam.co/latest/api-endpoints/overview
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Swift Package Manager
|
|
16
|
+
|
|
17
|
+
Add the following dependency to your \`Package.swift\` file:
|
|
18
|
+
|
|
19
|
+
\`\`\`swift
|
|
20
|
+
dependencies: [
|
|
21
|
+
.package(url: "https://github.com/seamapi/swift", .upToNextMajor(from: "0.0.1"))
|
|
22
|
+
]
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
_Refer to the [Seam Connect API documentation][Seam Connect API]._
|
|
29
|
+
|
|
30
|
+
### Requirements
|
|
31
|
+
|
|
32
|
+
- An API Key generated via the [Seam Dashboard].
|
|
33
|
+
|
|
34
|
+
[Seam Dashboard]: https://dashboard.getseam.com
|
|
35
|
+
|
|
36
|
+
### Example
|
|
37
|
+
|
|
38
|
+
\`\`\`swift
|
|
39
|
+
import SeamAPI
|
|
40
|
+
|
|
41
|
+
let seam = SeamClient(apiKey: "YOUR_API_KEY")
|
|
42
|
+
|
|
43
|
+
// List devices
|
|
44
|
+
let devices = try await seam.devices.list()
|
|
45
|
+
print(devices)
|
|
46
|
+
\`\`\`
|
|
47
|
+
`;
|
|
48
|
+
//# sourceMappingURL=readme.md.template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readme.md.template.js","sourceRoot":"","sources":["../../../src/lib/generate-swift-sdk/templates/readme.md.template.ts"],"names":[],"mappings":"AAAA,eAAe,GAAG,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CpB,CAAA"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { camelCase } from "change-case";
|
|
2
|
+
export default ({ namespaceToRouteGroupClassName, }) => `import Foundation
|
|
3
|
+
#if canImport(FoundationNetworking)
|
|
4
|
+
import FoundationNetworking
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
enum SeamClientError: Error {
|
|
8
|
+
case couldNotMakeRequest(request: URLRequest, error: Error)
|
|
9
|
+
case non2xxResponseData(response: URLResponse, errorType: String?, errorMessage: String?, requestID: String?)
|
|
10
|
+
case responseJSONDecodingError(error: DecodingError)
|
|
11
|
+
case unknown(error: Error)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class SeamClient {
|
|
15
|
+
var apiKey: String
|
|
16
|
+
var baseURL: URL
|
|
17
|
+
let decoder = JSONDecoder()
|
|
18
|
+
let urlSession = URLSession.init(configuration: .default)
|
|
19
|
+
|
|
20
|
+
${Array.from(namespaceToRouteGroupClassName.entries())
|
|
21
|
+
.map(([namespace, route_group_class_name]) => `var ${camelCase(namespace)}: ${route_group_class_name}! = nil`)
|
|
22
|
+
.join("\n ")}
|
|
23
|
+
|
|
24
|
+
init(apiKey: String, baseURL: URL = URL(string: "https://connect.getseam.com")!) {
|
|
25
|
+
self.apiKey = apiKey
|
|
26
|
+
self.baseURL = baseURL
|
|
27
|
+
|
|
28
|
+
${Array.from(namespaceToRouteGroupClassName.entries())
|
|
29
|
+
.map(([namespace, route_group_class_name]) => `self.${camelCase(namespace)} = ${route_group_class_name}(client: self)`)
|
|
30
|
+
.join("\n ")}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func request<ResponseSchema: Decodable>(method: String, path: String, responseSchema: ResponseSchema.Type, jsonBody: Data? = nil) async -> Result<ResponseSchema, SeamClientError> {
|
|
34
|
+
let url = self.baseURL.appendingPathComponent(path)
|
|
35
|
+
var request = URLRequest(url: url)
|
|
36
|
+
request.httpMethod = method
|
|
37
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
38
|
+
request.setValue("Bearer \\(apiKey)", forHTTPHeaderField: "Authorization")
|
|
39
|
+
|
|
40
|
+
if let jsonBody = jsonBody {
|
|
41
|
+
request.httpBody = jsonBody
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let requestResult: (Data, URLResponse)
|
|
45
|
+
do {
|
|
46
|
+
requestResult = try await self.urlSession.data(for: request)
|
|
47
|
+
} catch let error {
|
|
48
|
+
return .failure(.couldNotMakeRequest(request: request, error: error))
|
|
49
|
+
}
|
|
50
|
+
let (data, response) = requestResult
|
|
51
|
+
|
|
52
|
+
guard let httpResponse = response as? HTTPURLResponse,
|
|
53
|
+
(200...299).contains(httpResponse.statusCode) else {
|
|
54
|
+
// todo: better error
|
|
55
|
+
return .failure(.non2xxResponseData(
|
|
56
|
+
response: response,
|
|
57
|
+
errorType: nil,
|
|
58
|
+
errorMessage: nil,
|
|
59
|
+
requestID: nil
|
|
60
|
+
))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
do {
|
|
64
|
+
let decoded = try decoder.decode(ResponseSchema.self, from: data)
|
|
65
|
+
return .success(decoded)
|
|
66
|
+
} catch let error as DecodingError {
|
|
67
|
+
return .failure(.responseJSONDecodingError(error: error))
|
|
68
|
+
} catch let error {
|
|
69
|
+
return .failure(.unknown(error: error))
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}`;
|
|
73
|
+
//# sourceMappingURL=seamclient.swift.template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seamclient.swift.template.js","sourceRoot":"","sources":["../../../src/lib/generate-swift-sdk/templates/seamclient.swift.template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAMvC,eAAe,CAAC,EACd,8BAA8B,GACJ,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;IAkB7B,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,OAAO,EAAE,CAAC;KACnD,GAAG,CACF,CAAC,CAAC,SAAS,EAAE,sBAAsB,CAAC,EAAE,EAAE,CACtC,OAAO,SAAS,CAAC,SAAS,CAAC,KAAK,sBAAsB,SAAS,CAClE;KACA,IAAI,CAAC,MAAM,CAAC;;;;;;MAMX,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,OAAO,EAAE,CAAC;KACnD,GAAG,CACF,CAAC,CAAC,SAAS,EAAE,sBAAsB,CAAC,EAAE,EAAE,CACtC,QAAQ,SAAS,CACf,SAAS,CACV,MAAM,sBAAsB,gBAAgB,CAChD;KACA,IAAI,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0CnB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamapi/nextlove-sdk-generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "Utilities for building NextLove SDK Generators",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"generate:ruby-sdk": "tsx ./scripts/generate-ruby-sdk.ts && prettier -w ./output/ruby",
|
|
59
59
|
"generate:csharp-sdk": "tsx ./scripts/generate-csharp-sdk.ts && dotnet csharpier ./output/csharp",
|
|
60
60
|
"generate:php-sdk": "tsx ./scripts/generate-php-sdk.ts && prettier -w ./output/php",
|
|
61
|
+
"generate:swift-sdk": "tsx ./scripts/generate-swift-sdk.ts && swiftformat .",
|
|
61
62
|
"test:csharp-sdk": "dotnet test ./output/csharp"
|
|
62
63
|
},
|
|
63
64
|
"engines": {
|
|
@@ -69,6 +70,7 @@
|
|
|
69
70
|
"axios": "^1.5.0",
|
|
70
71
|
"change-case": "^4.1.2",
|
|
71
72
|
"lodash": "^4.17.21",
|
|
73
|
+
"tsx": "^4.7.0",
|
|
72
74
|
"yargs": "^17.7.2"
|
|
73
75
|
},
|
|
74
76
|
"devDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -4,3 +4,4 @@ export * from "./lib/generate-csharp-sdk/generate-csharp-sdk.js"
|
|
|
4
4
|
export * from "./lib/generate-python-sdk/index.js"
|
|
5
5
|
export * from "./lib/generate-ruby-sdk/index.js"
|
|
6
6
|
export * from "./lib/generate-php-sdk/index.js"
|
|
7
|
+
export * from "./lib/generate-swift-sdk/generate-swift-sdk.js"
|
|
@@ -66,7 +66,7 @@ class SeamClient
|
|
|
66
66
|
|
|
67
67
|
// TODO handle request errors
|
|
68
68
|
$response = $this->client->request($method, $path, $options);
|
|
69
|
-
$
|
|
69
|
+
$status_code = $response->getStatusCode();
|
|
70
70
|
|
|
71
71
|
$res_json = null;
|
|
72
72
|
try {
|
|
@@ -87,9 +87,11 @@ class SeamClient
|
|
|
87
87
|
);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
if ($
|
|
90
|
+
if ($status_code >= 400) {
|
|
91
|
+
$error_message = $response->getReasonPhrase();
|
|
92
|
+
|
|
91
93
|
throw new Exception(
|
|
92
|
-
"HTTP Error: [" . $
|
|
94
|
+
"HTTP Error: " . $error_message . " [" . $status_code . "] " . $method . " " . $path
|
|
93
95
|
);
|
|
94
96
|
}
|
|
95
97
|
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import axios from "axios"
|
|
2
|
+
import type { ObjSchema, OpenAPISchema, Route } from "lib/types.js"
|
|
3
|
+
import readmeMdTemplate from "./templates/readme.md.template.js"
|
|
4
|
+
import packageSwiftTemplate from "./templates/package.swift.template.js"
|
|
5
|
+
import seamClientSwiftTemplate from "./templates/seamclient.swift.template.js"
|
|
6
|
+
import anyJsonSwiftTemplate from "./templates/anyjson.swift.template.js"
|
|
7
|
+
import { pascalCase } from "change-case"
|
|
8
|
+
import { flattenObjSchema, getParameterAndResponseSchema } from "lib/index.js"
|
|
9
|
+
import { mapSwiftType } from "./map-swift-type.js"
|
|
10
|
+
import { RouteGroupClassfile } from "./route-group-class-file.js"
|
|
11
|
+
|
|
12
|
+
export const generateSwiftSDK = async () => {
|
|
13
|
+
const openapi: OpenAPISchema = await axios
|
|
14
|
+
.get("https://connect.getseam.com/openapi.json")
|
|
15
|
+
.then((res) => res.data)
|
|
16
|
+
|
|
17
|
+
const fs: Record<string, string> = {}
|
|
18
|
+
|
|
19
|
+
fs["README.md"] = readmeMdTemplate()
|
|
20
|
+
fs["Package.swift"] = packageSwiftTemplate()
|
|
21
|
+
|
|
22
|
+
const resources = Object.entries(openapi.components.schemas).map(
|
|
23
|
+
([schema_name, schema]) =>
|
|
24
|
+
[schema_name, flattenObjSchema(schema as any)] as [string, ObjSchema]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
fs["Sources/SeamAPI/AnyJSON.swift"] = anyJsonSwiftTemplate()
|
|
28
|
+
|
|
29
|
+
// Create a struct for each declared resource
|
|
30
|
+
fs["Sources/SeamAPI/Schemas.swift"] = [
|
|
31
|
+
"import Foundation",
|
|
32
|
+
...resources
|
|
33
|
+
.map(([snake_name, schema]) => {
|
|
34
|
+
const props = Object.entries(schema.properties)
|
|
35
|
+
.map(([name, property_schema]) => {
|
|
36
|
+
const is_optional =
|
|
37
|
+
!schema.required?.includes(name) ||
|
|
38
|
+
(property_schema as any).nullable
|
|
39
|
+
|
|
40
|
+
return `let ${name}: ${mapSwiftType(property_schema, "AnyJSON")}${
|
|
41
|
+
is_optional ? "?" : ""
|
|
42
|
+
}`
|
|
43
|
+
})
|
|
44
|
+
.join("\n")
|
|
45
|
+
|
|
46
|
+
return `struct ${pascalCase(snake_name)}: Decodable {\n${props}\n}`
|
|
47
|
+
})
|
|
48
|
+
.filter((x) => x),
|
|
49
|
+
].join("\n")
|
|
50
|
+
|
|
51
|
+
// Construct route groups by namespace
|
|
52
|
+
const namespaceToRouteGroupClassName = new Map<string, string>()
|
|
53
|
+
|
|
54
|
+
const routes: Route[] = Object.entries(openapi.paths).map(([path, v]) => ({
|
|
55
|
+
path,
|
|
56
|
+
...v,
|
|
57
|
+
}))
|
|
58
|
+
const class_map: Record<string, RouteGroupClassfile> = {}
|
|
59
|
+
const namespaces: string[][] = []
|
|
60
|
+
for (const route of routes) {
|
|
61
|
+
if (!route.post) continue
|
|
62
|
+
if (!route.post["x-fern-sdk-group-name"]) continue
|
|
63
|
+
const group_names = [...route.post["x-fern-sdk-group-name"]]
|
|
64
|
+
const namespace = group_names.join("_")
|
|
65
|
+
group_names.reverse()
|
|
66
|
+
const class_name = pascalCase(group_names.join("_")) + "Routes"
|
|
67
|
+
if (!class_map[class_name]) {
|
|
68
|
+
namespaces.push(route.post["x-fern-sdk-group-name"])
|
|
69
|
+
class_map[class_name] = new RouteGroupClassfile(class_name, namespace)
|
|
70
|
+
namespaceToRouteGroupClassName.set(namespace, class_name)
|
|
71
|
+
}
|
|
72
|
+
const cls = class_map[class_name]
|
|
73
|
+
|
|
74
|
+
if (!cls) {
|
|
75
|
+
console.warn(`No class for "${route.path}", skipping`)
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const { parameter_schema, response_obj_type, response_arr_type } =
|
|
80
|
+
getParameterAndResponseSchema(route)
|
|
81
|
+
|
|
82
|
+
if (!response_obj_type && !response_arr_type) {
|
|
83
|
+
console.warn(`No response object/array ref for "${route.path}", skipping`)
|
|
84
|
+
continue
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!parameter_schema) {
|
|
88
|
+
console.warn(`No parameter schema for "${route.path}", skipping`)
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
cls.addMethod({
|
|
93
|
+
method_name: route.post["x-fern-sdk-method-name"],
|
|
94
|
+
path: route.path,
|
|
95
|
+
bodyParameters: Object.entries(parameter_schema.properties)
|
|
96
|
+
.filter(
|
|
97
|
+
([_, param_val]) =>
|
|
98
|
+
param_val && typeof param_val === "object" && "type" in param_val
|
|
99
|
+
)
|
|
100
|
+
.map(([param_name, param_schema]) => {
|
|
101
|
+
const name = param_name
|
|
102
|
+
return {
|
|
103
|
+
name,
|
|
104
|
+
swiftType: mapSwiftType(param_schema),
|
|
105
|
+
isRequired: parameter_schema.required?.includes(name) ?? false,
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
return_path: [route.post["x-fern-sdk-return-value"]],
|
|
109
|
+
return_resource: response_obj_type
|
|
110
|
+
? pascalCase(response_obj_type)
|
|
111
|
+
: `[${pascalCase(response_arr_type)}]`,
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const route_groups = Object.values(class_map).filter((r) => !r.isEmpty)
|
|
116
|
+
|
|
117
|
+
// Write all route groups
|
|
118
|
+
for (const route_group of route_groups) {
|
|
119
|
+
fs[`Sources/SeamAPI/Routes/${pascalCase(route_group.namespace!)}.swift`] =
|
|
120
|
+
route_group.serializeToClass()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Remove route groups that were filtered out because they were empty
|
|
124
|
+
for (const namespace of namespaceToRouteGroupClassName.keys()) {
|
|
125
|
+
if (!route_groups.find((r) => r.namespace === namespace)) {
|
|
126
|
+
namespaceToRouteGroupClassName.delete(namespace)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs["Sources/SeamAPI/SeamClient.swift"] = seamClientSwiftTemplate({
|
|
131
|
+
namespaceToRouteGroupClassName,
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
return fs
|
|
135
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PropertySchema } from "lib/types.ts"
|
|
2
|
+
|
|
3
|
+
export const mapSwiftType = (
|
|
4
|
+
property_schema: PropertySchema,
|
|
5
|
+
fallback = "String"
|
|
6
|
+
) => {
|
|
7
|
+
if (typeof property_schema !== "object") {
|
|
8
|
+
return "Any"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if ("type" in property_schema && property_schema.type === "string") {
|
|
12
|
+
// todo: handle date-time
|
|
13
|
+
return "String"
|
|
14
|
+
}
|
|
15
|
+
if ("type" in property_schema && property_schema.type === "integer")
|
|
16
|
+
return "Int"
|
|
17
|
+
if ("type" in property_schema && property_schema.type === "boolean")
|
|
18
|
+
return "Bool"
|
|
19
|
+
if ("type" in property_schema && property_schema.type === "number")
|
|
20
|
+
return "Float"
|
|
21
|
+
if ("type" in property_schema && property_schema.type === "array")
|
|
22
|
+
return "[String]" // TODO, make more specific
|
|
23
|
+
if ("type" in property_schema && property_schema.type === "object")
|
|
24
|
+
return `[String: ${fallback}]` // TODO, make more specific
|
|
25
|
+
|
|
26
|
+
return fallback
|
|
27
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export type RouteGroupClassfileMethod = {
|
|
2
|
+
method_name: string
|
|
3
|
+
path: string
|
|
4
|
+
bodyParameters: {
|
|
5
|
+
name: string
|
|
6
|
+
swiftType: string
|
|
7
|
+
isRequired: boolean
|
|
8
|
+
}[]
|
|
9
|
+
return_path: string[]
|
|
10
|
+
return_resource: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class RouteGroupClassfile {
|
|
14
|
+
private methods: RouteGroupClassfileMethod[] = []
|
|
15
|
+
constructor(
|
|
16
|
+
private name: string,
|
|
17
|
+
public namespace?: string
|
|
18
|
+
) {}
|
|
19
|
+
|
|
20
|
+
addMethod(method: RouteGroupClassfileMethod) {
|
|
21
|
+
this.methods.push(method)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get isEmpty() {
|
|
25
|
+
return this.methods.length === 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
serializeToClass() {
|
|
29
|
+
return `import Foundation
|
|
30
|
+
#if canImport(FoundationNetworking)
|
|
31
|
+
import FoundationNetworking
|
|
32
|
+
#endif
|
|
33
|
+
|
|
34
|
+
class ${this.name} {
|
|
35
|
+
private let client: SeamClient
|
|
36
|
+
init(client: SeamClient) {
|
|
37
|
+
self.client = client
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${this.methods
|
|
41
|
+
.map(
|
|
42
|
+
({ method_name, bodyParameters, path, return_path, return_resource }) =>
|
|
43
|
+
` func ${method_name}(${bodyParameters
|
|
44
|
+
.map(({ name, swiftType, isRequired }) =>
|
|
45
|
+
isRequired ? `${name}: ${swiftType}` : `${name}: ${swiftType}? = nil`
|
|
46
|
+
)
|
|
47
|
+
.join(", ")}) async -> Result<${return_resource}, SeamClientError> {
|
|
48
|
+
${bodyParameters.length > 0 ? "var" : "let"} jsonBody: [String: Any] = [:]
|
|
49
|
+
|
|
50
|
+
${bodyParameters
|
|
51
|
+
.map(({ name, isRequired }) =>
|
|
52
|
+
isRequired
|
|
53
|
+
? `jsonBody["${name}"] = ${name}`
|
|
54
|
+
: ` if let ${name} = ${name} {
|
|
55
|
+
jsonBody["${name}"] = ${name}
|
|
56
|
+
}`
|
|
57
|
+
)
|
|
58
|
+
.join("\n\n")}
|
|
59
|
+
|
|
60
|
+
let encodedJsonBody: Data?
|
|
61
|
+
do {
|
|
62
|
+
encodedJsonBody = try JSONSerialization.data(withJSONObject: jsonBody, options: [])
|
|
63
|
+
} catch let error {
|
|
64
|
+
return .failure(.unknown(error: error))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
struct Response: Decodable {
|
|
68
|
+
let ${return_path[0]}: ${return_resource}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let response = await client.request(
|
|
72
|
+
method: "POST",
|
|
73
|
+
path: "${path}",
|
|
74
|
+
responseSchema: Response.self,
|
|
75
|
+
jsonBody: encodedJsonBody
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
switch response {
|
|
79
|
+
case .success(let response):
|
|
80
|
+
return .success(response.${return_path[0]})
|
|
81
|
+
case .failure(let error):
|
|
82
|
+
return .failure(error)
|
|
83
|
+
}
|
|
84
|
+
}`
|
|
85
|
+
)
|
|
86
|
+
.join("\n\n")}
|
|
87
|
+
}`.trim()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export default () => `// Thanks to https://adamrackis.dev/blog/swift-codable-any
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
func decode(fromObject container: KeyedDecodingContainer<AnyJSONCodingKeys>) -> [String: Any] {
|
|
5
|
+
var result: [String: Any] = [:]
|
|
6
|
+
|
|
7
|
+
for key in container.allKeys {
|
|
8
|
+
if let val = try? container.decode(Int.self, forKey: key) {
|
|
9
|
+
result[key.stringValue] = val
|
|
10
|
+
} else if let val = try? container.decode(Double.self, forKey: key) {
|
|
11
|
+
result[key.stringValue] = val
|
|
12
|
+
} else if let val = try? container.decode(String.self, forKey: key) {
|
|
13
|
+
result[key.stringValue] = val
|
|
14
|
+
} else if let val = try? container.decode(Bool.self, forKey: key) {
|
|
15
|
+
result[key.stringValue] = val
|
|
16
|
+
} else if let nestedContainer = try? container.nestedContainer(
|
|
17
|
+
keyedBy: AnyJSONCodingKeys.self, forKey: key)
|
|
18
|
+
{
|
|
19
|
+
result[key.stringValue] = decode(fromObject: nestedContainer)
|
|
20
|
+
} else if var nestedArray = try? container.nestedUnkeyedContainer(forKey: key) {
|
|
21
|
+
result[key.stringValue] = decode(fromArray: &nestedArray)
|
|
22
|
+
} else if (try? container.decodeNil(forKey: key)) == true {
|
|
23
|
+
result.updateValue(Any?(nil) as Any, forKey: key.stringValue)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
func decode(fromArray container: inout UnkeyedDecodingContainer) -> [Any] {
|
|
31
|
+
var result: [Any] = []
|
|
32
|
+
|
|
33
|
+
while !container.isAtEnd {
|
|
34
|
+
if let value = try? container.decode(String.self) {
|
|
35
|
+
result.append(value)
|
|
36
|
+
} else if let value = try? container.decode(Int.self) {
|
|
37
|
+
result.append(value)
|
|
38
|
+
} else if let value = try? container.decode(Double.self) {
|
|
39
|
+
result.append(value)
|
|
40
|
+
} else if let value = try? container.decode(Bool.self) {
|
|
41
|
+
result.append(value)
|
|
42
|
+
} else if let nestedContainer = try? container.nestedContainer(keyedBy: AnyJSONCodingKeys.self) {
|
|
43
|
+
result.append(decode(fromObject: nestedContainer))
|
|
44
|
+
} else if var nestedArray = try? container.nestedUnkeyedContainer() {
|
|
45
|
+
result.append(decode(fromArray: &nestedArray))
|
|
46
|
+
} else if (try? container.decodeNil()) == true {
|
|
47
|
+
result.append(Any?(nil) as Any)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
func encodeValue(
|
|
55
|
+
fromObjectContainer container: inout KeyedEncodingContainer<AnyJSONCodingKeys>, map: [String: Any]
|
|
56
|
+
) throws {
|
|
57
|
+
for k in map.keys {
|
|
58
|
+
let value = map[k]
|
|
59
|
+
let encodingKey = AnyJSONCodingKeys(stringValue: k)
|
|
60
|
+
|
|
61
|
+
if let value = value as? String {
|
|
62
|
+
try container.encode(value, forKey: encodingKey)
|
|
63
|
+
} else if let value = value as? Int {
|
|
64
|
+
try container.encode(value, forKey: encodingKey)
|
|
65
|
+
} else if let value = value as? Double {
|
|
66
|
+
try container.encode(value, forKey: encodingKey)
|
|
67
|
+
} else if let value = value as? Bool {
|
|
68
|
+
try container.encode(value, forKey: encodingKey)
|
|
69
|
+
} else if let value = value as? [String: Any] {
|
|
70
|
+
var keyedContainer = container.nestedContainer(
|
|
71
|
+
keyedBy: AnyJSONCodingKeys.self, forKey: encodingKey)
|
|
72
|
+
try encodeValue(fromObjectContainer: &keyedContainer, map: value)
|
|
73
|
+
} else if let value = value as? [Any] {
|
|
74
|
+
var unkeyedContainer = container.nestedUnkeyedContainer(forKey: encodingKey)
|
|
75
|
+
try encodeValue(fromArrayContainer: &unkeyedContainer, arr: value)
|
|
76
|
+
} else {
|
|
77
|
+
try container.encodeNil(forKey: encodingKey)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func encodeValue(fromArrayContainer container: inout UnkeyedEncodingContainer, arr: [Any]) throws {
|
|
83
|
+
for value in arr {
|
|
84
|
+
if let value = value as? String {
|
|
85
|
+
try container.encode(value)
|
|
86
|
+
} else if let value = value as? Int {
|
|
87
|
+
try container.encode(value)
|
|
88
|
+
} else if let value = value as? Double {
|
|
89
|
+
try container.encode(value)
|
|
90
|
+
} else if let value = value as? Bool {
|
|
91
|
+
try container.encode(value)
|
|
92
|
+
} else if let value = value as? [String: Any] {
|
|
93
|
+
var keyedContainer = container.nestedContainer(keyedBy: AnyJSONCodingKeys.self)
|
|
94
|
+
try encodeValue(fromObjectContainer: &keyedContainer, map: value)
|
|
95
|
+
} else if let value = value as? [Any] {
|
|
96
|
+
var unkeyedContainer = container.nestedUnkeyedContainer()
|
|
97
|
+
try encodeValue(fromArrayContainer: &unkeyedContainer, arr: value)
|
|
98
|
+
} else {
|
|
99
|
+
try container.encodeNil()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
struct AnyJSONCodingKeys: CodingKey {
|
|
107
|
+
var stringValue: String
|
|
108
|
+
|
|
109
|
+
init(stringValue: String) {
|
|
110
|
+
self.stringValue = stringValue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var intValue: Int?
|
|
114
|
+
|
|
115
|
+
init?(intValue: Int) {
|
|
116
|
+
self.init(stringValue: "\(intValue)")
|
|
117
|
+
self.intValue = intValue
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
struct AnyJSON: Codable {
|
|
122
|
+
var value: Any?
|
|
123
|
+
|
|
124
|
+
init(value: Any?) {
|
|
125
|
+
self.value = value
|
|
126
|
+
}
|
|
127
|
+
init(from decoder: Decoder) throws {
|
|
128
|
+
if let container = try? decoder.container(keyedBy: AnyJSONCodingKeys.self) {
|
|
129
|
+
self.value = decode(fromObject: container)
|
|
130
|
+
} else if var array = try? decoder.unkeyedContainer() {
|
|
131
|
+
self.value = decode(fromArray: &array)
|
|
132
|
+
} else if let value = try? decoder.singleValueContainer() {
|
|
133
|
+
if value.decodeNil() {
|
|
134
|
+
self.value = nil
|
|
135
|
+
} else {
|
|
136
|
+
if let result = try? value.decode(Int.self) { self.value = result }
|
|
137
|
+
if let result = try? value.decode(Double.self) { self.value = result }
|
|
138
|
+
if let result = try? value.decode(String.self) { self.value = result }
|
|
139
|
+
if let result = try? value.decode(Bool.self) { self.value = result }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
func encode(to encoder: Encoder) throws {
|
|
145
|
+
if let map = self.value as? [String: Any] {
|
|
146
|
+
var container = encoder.container(keyedBy: AnyJSONCodingKeys.self)
|
|
147
|
+
try encodeValue(fromObjectContainer: &container, map: map)
|
|
148
|
+
} else if let arr = self.value as? [Any] {
|
|
149
|
+
var container = encoder.unkeyedContainer()
|
|
150
|
+
try encodeValue(fromArrayContainer: &container, arr: arr)
|
|
151
|
+
} else {
|
|
152
|
+
var container = encoder.singleValueContainer()
|
|
153
|
+
|
|
154
|
+
if let value = self.value as? String {
|
|
155
|
+
try! container.encode(value)
|
|
156
|
+
} else if let value = self.value as? Int {
|
|
157
|
+
try! container.encode(value)
|
|
158
|
+
} else if let value = self.value as? Double {
|
|
159
|
+
try! container.encode(value)
|
|
160
|
+
} else if let value = self.value as? Bool {
|
|
161
|
+
try! container.encode(value)
|
|
162
|
+
} else {
|
|
163
|
+
try! container.encodeNil()
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default () => `// swift-tools-version: 5.7
|
|
2
|
+
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
3
|
+
|
|
4
|
+
import PackageDescription
|
|
5
|
+
|
|
6
|
+
let package = Package(
|
|
7
|
+
name: "SeamAPI",
|
|
8
|
+
platforms: [
|
|
9
|
+
.macOS(.v12)
|
|
10
|
+
],
|
|
11
|
+
products: [
|
|
12
|
+
// Products define the executables and libraries a package produces, making them visible to other packages.
|
|
13
|
+
.library(
|
|
14
|
+
name: "SeamAPI",
|
|
15
|
+
targets: ["SeamAPI"]),
|
|
16
|
+
],
|
|
17
|
+
targets: [
|
|
18
|
+
// Targets are the basic building blocks of a package, defining a module or a test suite.
|
|
19
|
+
// Targets can depend on other targets in this package and products from dependencies.
|
|
20
|
+
.target(
|
|
21
|
+
name: "SeamAPI"),
|
|
22
|
+
.testTarget(
|
|
23
|
+
name: "SeamAPITests",
|
|
24
|
+
dependencies: ["SeamAPI"]),
|
|
25
|
+
]
|
|
26
|
+
)
|
|
27
|
+
`
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export default () => `# Seam API Swift SDK
|
|
2
|
+
|
|
3
|
+
Official interface to the [Seam Connect API].
|
|
4
|
+
|
|
5
|
+
[Seam Connect API]: https://docs.seam.co/
|
|
6
|
+
|
|
7
|
+
## Description
|
|
8
|
+
|
|
9
|
+
This SDK provides convenient access to the [Seam Connect API] for Swift applications.
|
|
10
|
+
|
|
11
|
+
[Seam API endpoints]: https://docs.seam.co/latest/api-endpoints/overview
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### Swift Package Manager
|
|
16
|
+
|
|
17
|
+
Add the following dependency to your \`Package.swift\` file:
|
|
18
|
+
|
|
19
|
+
\`\`\`swift
|
|
20
|
+
dependencies: [
|
|
21
|
+
.package(url: "https://github.com/seamapi/swift", .upToNextMajor(from: "0.0.1"))
|
|
22
|
+
]
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
_Refer to the [Seam Connect API documentation][Seam Connect API]._
|
|
29
|
+
|
|
30
|
+
### Requirements
|
|
31
|
+
|
|
32
|
+
- An API Key generated via the [Seam Dashboard].
|
|
33
|
+
|
|
34
|
+
[Seam Dashboard]: https://dashboard.getseam.com
|
|
35
|
+
|
|
36
|
+
### Example
|
|
37
|
+
|
|
38
|
+
\`\`\`swift
|
|
39
|
+
import SeamAPI
|
|
40
|
+
|
|
41
|
+
let seam = SeamClient(apiKey: "YOUR_API_KEY")
|
|
42
|
+
|
|
43
|
+
// List devices
|
|
44
|
+
let devices = try await seam.devices.list()
|
|
45
|
+
print(devices)
|
|
46
|
+
\`\`\`
|
|
47
|
+
`
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { camelCase } from "change-case"
|
|
2
|
+
|
|
3
|
+
type SeamClientTemplateOptions = {
|
|
4
|
+
namespaceToRouteGroupClassName: Map<string, string>
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export default ({
|
|
8
|
+
namespaceToRouteGroupClassName,
|
|
9
|
+
}: SeamClientTemplateOptions) => `import Foundation
|
|
10
|
+
#if canImport(FoundationNetworking)
|
|
11
|
+
import FoundationNetworking
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
enum SeamClientError: Error {
|
|
15
|
+
case couldNotMakeRequest(request: URLRequest, error: Error)
|
|
16
|
+
case non2xxResponseData(response: URLResponse, errorType: String?, errorMessage: String?, requestID: String?)
|
|
17
|
+
case responseJSONDecodingError(error: DecodingError)
|
|
18
|
+
case unknown(error: Error)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class SeamClient {
|
|
22
|
+
var apiKey: String
|
|
23
|
+
var baseURL: URL
|
|
24
|
+
let decoder = JSONDecoder()
|
|
25
|
+
let urlSession = URLSession.init(configuration: .default)
|
|
26
|
+
|
|
27
|
+
${Array.from(namespaceToRouteGroupClassName.entries())
|
|
28
|
+
.map(
|
|
29
|
+
([namespace, route_group_class_name]) =>
|
|
30
|
+
`var ${camelCase(namespace)}: ${route_group_class_name}! = nil`
|
|
31
|
+
)
|
|
32
|
+
.join("\n ")}
|
|
33
|
+
|
|
34
|
+
init(apiKey: String, baseURL: URL = URL(string: "https://connect.getseam.com")!) {
|
|
35
|
+
self.apiKey = apiKey
|
|
36
|
+
self.baseURL = baseURL
|
|
37
|
+
|
|
38
|
+
${Array.from(namespaceToRouteGroupClassName.entries())
|
|
39
|
+
.map(
|
|
40
|
+
([namespace, route_group_class_name]) =>
|
|
41
|
+
`self.${camelCase(
|
|
42
|
+
namespace
|
|
43
|
+
)} = ${route_group_class_name}(client: self)`
|
|
44
|
+
)
|
|
45
|
+
.join("\n ")}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func request<ResponseSchema: Decodable>(method: String, path: String, responseSchema: ResponseSchema.Type, jsonBody: Data? = nil) async -> Result<ResponseSchema, SeamClientError> {
|
|
49
|
+
let url = self.baseURL.appendingPathComponent(path)
|
|
50
|
+
var request = URLRequest(url: url)
|
|
51
|
+
request.httpMethod = method
|
|
52
|
+
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
53
|
+
request.setValue("Bearer \\(apiKey)", forHTTPHeaderField: "Authorization")
|
|
54
|
+
|
|
55
|
+
if let jsonBody = jsonBody {
|
|
56
|
+
request.httpBody = jsonBody
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let requestResult: (Data, URLResponse)
|
|
60
|
+
do {
|
|
61
|
+
requestResult = try await self.urlSession.data(for: request)
|
|
62
|
+
} catch let error {
|
|
63
|
+
return .failure(.couldNotMakeRequest(request: request, error: error))
|
|
64
|
+
}
|
|
65
|
+
let (data, response) = requestResult
|
|
66
|
+
|
|
67
|
+
guard let httpResponse = response as? HTTPURLResponse,
|
|
68
|
+
(200...299).contains(httpResponse.statusCode) else {
|
|
69
|
+
// todo: better error
|
|
70
|
+
return .failure(.non2xxResponseData(
|
|
71
|
+
response: response,
|
|
72
|
+
errorType: nil,
|
|
73
|
+
errorMessage: nil,
|
|
74
|
+
requestID: nil
|
|
75
|
+
))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
do {
|
|
79
|
+
let decoded = try decoder.decode(ResponseSchema.self, from: data)
|
|
80
|
+
return .success(decoded)
|
|
81
|
+
} catch let error as DecodingError {
|
|
82
|
+
return .failure(.responseJSONDecodingError(error: error))
|
|
83
|
+
} catch let error {
|
|
84
|
+
return .failure(.unknown(error: error))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}`
|