@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.
Files changed (36) hide show
  1. package/index.d.ts +1 -0
  2. package/index.js +1 -0
  3. package/index.js.map +1 -1
  4. package/lib/generate-php-sdk/utils/generate-seam-client.js +5 -3
  5. package/lib/generate-php-sdk/utils/generate-seam-client.js.map +1 -1
  6. package/lib/generate-swift-sdk/generate-swift-sdk.d.ts +1 -0
  7. package/lib/generate-swift-sdk/generate-swift-sdk.js +107 -0
  8. package/lib/generate-swift-sdk/generate-swift-sdk.js.map +1 -0
  9. package/lib/generate-swift-sdk/map-swift-type.d.ts +2 -0
  10. package/lib/generate-swift-sdk/map-swift-type.js +21 -0
  11. package/lib/generate-swift-sdk/map-swift-type.js.map +1 -0
  12. package/lib/generate-swift-sdk/route-group-class-file.d.ts +20 -0
  13. package/lib/generate-swift-sdk/route-group-class-file.js +68 -0
  14. package/lib/generate-swift-sdk/route-group-class-file.js.map +1 -0
  15. package/lib/generate-swift-sdk/templates/anyjson.swift.template.d.ts +2 -0
  16. package/lib/generate-swift-sdk/templates/anyjson.swift.template.js +168 -0
  17. package/lib/generate-swift-sdk/templates/anyjson.swift.template.js.map +1 -0
  18. package/lib/generate-swift-sdk/templates/package.swift.template.d.ts +2 -0
  19. package/lib/generate-swift-sdk/templates/package.swift.template.js +28 -0
  20. package/lib/generate-swift-sdk/templates/package.swift.template.js.map +1 -0
  21. package/lib/generate-swift-sdk/templates/readme.md.template.d.ts +2 -0
  22. package/lib/generate-swift-sdk/templates/readme.md.template.js +48 -0
  23. package/lib/generate-swift-sdk/templates/readme.md.template.js.map +1 -0
  24. package/lib/generate-swift-sdk/templates/seamclient.swift.template.d.ts +5 -0
  25. package/lib/generate-swift-sdk/templates/seamclient.swift.template.js +73 -0
  26. package/lib/generate-swift-sdk/templates/seamclient.swift.template.js.map +1 -0
  27. package/package.json +3 -1
  28. package/src/index.ts +1 -0
  29. package/src/lib/generate-php-sdk/utils/generate-seam-client.ts +5 -3
  30. package/src/lib/generate-swift-sdk/generate-swift-sdk.ts +135 -0
  31. package/src/lib/generate-swift-sdk/map-swift-type.ts +27 -0
  32. package/src/lib/generate-swift-sdk/route-group-class-file.ts +89 -0
  33. package/src/lib/generate-swift-sdk/templates/anyjson.swift.template.ts +167 -0
  34. package/src/lib/generate-swift-sdk/templates/package.swift.template.ts +27 -0
  35. package/src/lib/generate-swift-sdk/templates/readme.md.template.ts +47 -0
  36. 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
- $statusCode = $response->getStatusCode();
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 ($statusCode >= 400) {
77
+ if ($status_code >= 400) {
78
+ $error_message = $response->getReasonPhrase();
79
+
78
80
  throw new Exception(
79
- "HTTP Error: [" . $statusCode . "] " . $method . " " . $path
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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8DnB,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC;CACxD,CAAA;AACD,CAAC,CAAA"}
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,2 @@
1
+ import type { PropertySchema } from "lib/types.ts";
2
+ export declare const mapSwiftType: (property_schema: PropertySchema, fallback?: string) => string;
@@ -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,2 @@
1
+ declare const _default: () => string;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: () => string;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: () => string;
2
+ export default _default;
@@ -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,5 @@
1
+ type SeamClientTemplateOptions = {
2
+ namespaceToRouteGroupClassName: Map<string, string>;
3
+ };
4
+ declare const _default: ({ namespaceToRouteGroupClassName, }: SeamClientTemplateOptions) => string;
5
+ export default _default;
@@ -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.4.2",
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
- $statusCode = $response->getStatusCode();
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 ($statusCode >= 400) {
90
+ if ($status_code >= 400) {
91
+ $error_message = $response->getReasonPhrase();
92
+
91
93
  throw new Exception(
92
- "HTTP Error: [" . $statusCode . "] " . $method . " " . $path
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
+ }`