1 // Copyright 2022-present 650 Industries. All rights reserved.
2 
3 // FIXME: Calling module's functions needs solid refactoring to not reference the module holder.
4 // Instead, it should be possible to directly call the function instance from here. (added by @tsapeta)
5 
6 /**
7  Creates a block that is executed when the module's async function is called.
8  */
9 internal func createAsyncFunctionBlock(holder: ModuleHolder, name functionName: String) -> JSAsyncFunctionBlock {
10   let moduleName = holder.name
11   return { [weak holder, moduleName] args, resolve, reject in
12     guard let holder = holder else {
13       let exception = ModuleUnavailableException(moduleName)
14       reject(exception.code, exception.description, exception)
15       return
16     }
17     holder.call(function: functionName, args: args) { result, error in
18       if let error = error {
19         reject(error.code, error.description, error)
20       } else {
21         resolve(result)
22       }
23     }
24   }
25 }
26 
27 /**
28  Creates a block that is executed when the module's sync function is called.
29  */
30 internal func createSyncFunctionBlock(holder: ModuleHolder, name functionName: String) -> JSSyncFunctionBlock {
31   return { [weak holder] args in
32     guard let holder = holder else {
33       return nil
34     }
35     return holder.callSync(function: functionName, args: args)
36   }
37 }
38 
39 // MARK: - Arguments
40 
41 /**
42  Tries to cast given argument to the type that is wrapped by the argument type.
43  - Parameters:
44   - argument: A value to be cast. If it's a ``JavaScriptValue``, it's first unpacked to the raw value.
45   - argumentType: Something that implements ``AnyArgumentType`` and knows how to cast the argument.
46  - Returns: A new value converted according to the argument type.
47  - Throws: Rethrows various exceptions that could be thrown by the argument type wrappers.
48  */
49 internal func castArgument(_ argument: Any, toType argumentType: AnyArgumentType) throws -> Any {
50   // TODO: Accept JavaScriptValue and JavaScriptObject as argument types.
51   if let argument = argument as? JavaScriptValue {
52     return try argumentType.cast(argument.getRaw())
53   }
54   return try argumentType.cast(argument)
55 }
56 
57 /**
58  Same as ``castArgument(_:argumentType:)`` but for an array of arguments.
59  - Parameters:
60    - arguments: An array of arguments to be cast.
61    - argumentTypes: An array of argument types in the same order as the array of arguments.
62  - Returns: An array of arguments after casting. Its size is the same as the input arrays.
63  - Throws: ``InvalidArgsNumberException`` when the sizes of arrays passed as parameters are not equal.
64    Rethrows exceptions thrown by ``castArgument(_:argumentType:)``.
65  */
66 internal func castArguments(_ arguments: [Any], toTypes argumentTypes: [AnyArgumentType]) throws -> [Any] {
67   if arguments.count != argumentTypes.count {
68     throw InvalidArgsNumberException((received: arguments.count, expected: argumentTypes.count))
69   }
70   return try arguments.enumerated().map { index, argument in
71     let argumentType = argumentTypes[index]
72 
73     do {
74       return try castArgument(argument, toType: argumentType)
75     } catch {
76       throw ArgumentCastException((index: index, type: argumentType)).causedBy(error)
77     }
78   }
79 }
80 
81 internal class InvalidArgsNumberException: GenericException<(received: Int, expected: Int)> {
82   override var reason: String {
83     "Received \(param.received) arguments, but \(param.expected) was expected"
84   }
85 }
86 
87 internal class ArgumentCastException: GenericException<(index: Int, type: AnyArgumentType)> {
88   override var reason: String {
89     "Argument at index '\(param.index)' couldn't be cast to type \(param.type.description)"
90   }
91 }
92 
93 // MARK: - Exceptions
94 
95 private class ModuleUnavailableException: GenericException<String> {
96   override var reason: String {
97     "Module '\(param)' is no longer available"
98   }
99 }
100