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