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