@seamapi/nextlove-sdk-generator 1.4.1 → 1.5.0

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 (41) 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/deep-extract-resource-object-schemas.js +10 -0
  5. package/lib/generate-php-sdk/utils/deep-extract-resource-object-schemas.js.map +1 -1
  6. package/lib/generate-php-sdk/utils/generate-resource-object-class.js +9 -7
  7. package/lib/generate-php-sdk/utils/generate-resource-object-class.js.map +1 -1
  8. package/lib/generate-swift-sdk/generate-swift-sdk.d.ts +1 -0
  9. package/lib/generate-swift-sdk/generate-swift-sdk.js +107 -0
  10. package/lib/generate-swift-sdk/generate-swift-sdk.js.map +1 -0
  11. package/lib/generate-swift-sdk/map-swift-type.d.ts +2 -0
  12. package/lib/generate-swift-sdk/map-swift-type.js +21 -0
  13. package/lib/generate-swift-sdk/map-swift-type.js.map +1 -0
  14. package/lib/generate-swift-sdk/route-group-class-file.d.ts +20 -0
  15. package/lib/generate-swift-sdk/route-group-class-file.js +68 -0
  16. package/lib/generate-swift-sdk/route-group-class-file.js.map +1 -0
  17. package/lib/generate-swift-sdk/templates/anyjson.swift.template.d.ts +2 -0
  18. package/lib/generate-swift-sdk/templates/anyjson.swift.template.js +168 -0
  19. package/lib/generate-swift-sdk/templates/anyjson.swift.template.js.map +1 -0
  20. package/lib/generate-swift-sdk/templates/package.swift.template.d.ts +2 -0
  21. package/lib/generate-swift-sdk/templates/package.swift.template.js +28 -0
  22. package/lib/generate-swift-sdk/templates/package.swift.template.js.map +1 -0
  23. package/lib/generate-swift-sdk/templates/readme.md.template.d.ts +2 -0
  24. package/lib/generate-swift-sdk/templates/readme.md.template.js +48 -0
  25. package/lib/generate-swift-sdk/templates/readme.md.template.js.map +1 -0
  26. package/lib/generate-swift-sdk/templates/seamclient.swift.template.d.ts +5 -0
  27. package/lib/generate-swift-sdk/templates/seamclient.swift.template.js +73 -0
  28. package/lib/generate-swift-sdk/templates/seamclient.swift.template.js.map +1 -0
  29. package/lib/types.d.ts +5 -0
  30. package/package.json +3 -1
  31. package/src/index.ts +1 -0
  32. package/src/lib/generate-php-sdk/utils/deep-extract-resource-object-schemas.ts +10 -0
  33. package/src/lib/generate-php-sdk/utils/generate-resource-object-class.ts +14 -7
  34. package/src/lib/generate-swift-sdk/generate-swift-sdk.ts +135 -0
  35. package/src/lib/generate-swift-sdk/map-swift-type.ts +27 -0
  36. package/src/lib/generate-swift-sdk/route-group-class-file.ts +89 -0
  37. package/src/lib/generate-swift-sdk/templates/anyjson.swift.template.ts +167 -0
  38. package/src/lib/generate-swift-sdk/templates/package.swift.template.ts +27 -0
  39. package/src/lib/generate-swift-sdk/templates/readme.md.template.ts +47 -0
  40. package/src/lib/generate-swift-sdk/templates/seamclient.swift.template.ts +87 -0
  41. package/src/lib/types.ts +9 -4
@@ -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/lib/types.d.ts CHANGED
@@ -39,20 +39,25 @@ export type PrimitiveSchema = {
39
39
  } | {
40
40
  type: "boolean";
41
41
  enum?: boolean[];
42
+ nullable?: boolean;
42
43
  } | {
43
44
  type: "integer";
44
45
  enum?: number[];
46
+ nullable?: boolean;
45
47
  } | {
46
48
  type: "number";
49
+ nullable?: boolean;
47
50
  };
48
51
  export type ArraySchema = {
49
52
  type: "array";
50
53
  items: PropertySchema;
54
+ nullable?: boolean;
51
55
  };
52
56
  export type ObjSchema = {
53
57
  type: "object";
54
58
  properties: Record<string, PropertySchema>;
55
59
  required: string[];
60
+ nullable?: boolean;
56
61
  };
57
62
  export type RefSchema = {
58
63
  $ref: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamapi/nextlove-sdk-generator",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
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"
@@ -82,11 +82,15 @@ export function deepExtractResourceObjectSchemas({
82
82
  property_schema.type === "array" &&
83
83
  "items" in property_schema &&
84
84
  "properties" in property_schema.items
85
+ const is_nullable_set = "nullable" in property_schema
85
86
 
86
87
  if (is_object_schema) {
87
88
  property_schemas_with_type_references[property_name] = {
88
89
  type: "object",
89
90
  reference_object_type_name,
91
+ ...(is_nullable_set && {
92
+ nullable: property_schema.nullable,
93
+ }),
90
94
  }
91
95
 
92
96
  createResourceSchema({
@@ -98,6 +102,9 @@ export function deepExtractResourceObjectSchemas({
98
102
  property_schemas_with_type_references[property_name] = {
99
103
  type: "array",
100
104
  reference_object_type_name,
105
+ ...(is_nullable_set && {
106
+ nullable: property_schema.nullable,
107
+ }),
101
108
  }
102
109
 
103
110
  createResourceSchema({
@@ -127,6 +134,9 @@ export function deepExtractResourceObjectSchemas({
127
134
  } else {
128
135
  property_schemas_with_type_references[property_name] = {
129
136
  type: property_schema.type as any,
137
+ ...(is_nullable_set && {
138
+ nullable: property_schema.nullable,
139
+ }),
130
140
  }
131
141
  }
132
142
  }
@@ -26,11 +26,18 @@ export const generateResourceObjectClass = ({
26
26
  prop_schema.type === "array" &&
27
27
  "reference_object_type_name" in prop_schema
28
28
 
29
+ const is_property_required =
30
+ "nullable" in prop_schema
31
+ ? !prop_schema.nullable
32
+ : required_property_names.includes(prop_name)
33
+
29
34
  if (is_object_referencing_resource) {
30
35
  return `${prop_name}: ${
36
+ is_property_required ? "" : `isset($json->${prop_name}) ? `
37
+ }${
31
38
  prop_schema.reference_object_type_name
32
39
  }::from_json($json->${prop_name})${
33
- required_property_names.includes(prop_name) ? "," : " ?? null,"
40
+ is_property_required ? "," : " : null,"
34
41
  }`
35
42
  } else if (is_array_referencing_resource) {
36
43
  return `${prop_name}: array_map(
@@ -39,7 +46,7 @@ export const generateResourceObjectClass = ({
39
46
  ),`
40
47
  } else {
41
48
  return `${prop_name}: $json->${prop_name}${
42
- required_property_names.includes(prop_name) ? "," : " ?? null,"
49
+ is_property_required ? "," : " ?? null,"
43
50
  }`
44
51
  }
45
52
  }
@@ -74,13 +81,13 @@ export const generateResourceObjectClass = ({
74
81
  const php_type = is_object_referencing_resource
75
82
  ? prop_schema.reference_object_type_name
76
83
  : getPhpType("type" in prop_schema ? prop_schema.type : "mixed")
84
+ const is_property_required =
85
+ "nullable" in prop_schema
86
+ ? !prop_schema.nullable
87
+ : required_property_names.includes(prop_name)
77
88
 
78
89
  return `public ${php_type}${
79
- php_type === "mixed"
80
- ? ""
81
- : required_property_names.includes(prop_name)
82
- ? ""
83
- : " | null"
90
+ php_type === "mixed" ? "" : is_property_required ? "" : " | null"
84
91
  } $${prop_name},`
85
92
  })
86
93
  .join("\n ")}
@@ -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
+ `