|
| 1 | +// Package: web3swift |
| 2 | +// Created by Alex Vlasov. |
| 3 | +// Copyright © 2022 Yaroslav Yashin. All rights reserved. |
| 4 | + |
| 5 | +/// Protocol to restrict supported types which can be passed into `JSONRPCRequest` to a node. |
| 6 | +/// |
| 7 | +/// You **must not** conform any type to that protocol. |
| 8 | +/// |
| 9 | +/// Due to internal logic and swift itself restrictions, there's lack of encoding generic types |
| 10 | +/// so current implementation of `JSONRPCParameter`s belongs on hardcoded supported types. |
| 11 | +/// |
| 12 | +/// Conformance of that protocol by a custom type will be silently failed to encode (e.g. there won't be in request). |
| 13 | +/// |
| 14 | +/// Please see `RPCParameter` documentation for more details. |
| 15 | +public protocol JSONRPCParameter: Encodable { } |
| 16 | + |
| 17 | +protocol JSONParameterElement: Encodable { } |
| 18 | + |
| 19 | +extension Int: JSONRPCParameter { } |
| 20 | + |
| 21 | +extension UInt: JSONRPCParameter { } |
| 22 | + |
| 23 | +// FIXME: Drop all non default types support |
| 24 | +extension UInt64: JSONRPCParameter { } |
| 25 | + |
| 26 | +extension Double: JSONRPCParameter { } |
| 27 | + |
| 28 | +extension String: JSONRPCParameter { } |
| 29 | + |
| 30 | +extension Bool: JSONRPCParameter { } |
| 31 | + |
| 32 | +extension Array: JSONRPCParameter where Element: JSONParameterElement { } |
| 33 | + |
| 34 | +extension TransactionParameters: JSONRPCParameter { } |
| 35 | + |
| 36 | +extension EventFilterParameters: JSONRPCParameter { } |
| 37 | + |
| 38 | +extension Double: JSONParameterElement { } |
| 39 | + |
| 40 | +extension Int: JSONParameterElement { } |
| 41 | + |
| 42 | +// FIXME: Drop all non default types support |
| 43 | +extension UInt64: JSONParameterElement { } |
| 44 | + |
| 45 | +extension String: JSONParameterElement { } |
| 46 | + |
| 47 | +/** |
| 48 | + Enum to compose request to the node params. |
| 49 | + |
| 50 | + In most cases request params are passed to Ethereum JSON RPC request as array of mixed type values, such as `[12,"this",true]`, |
| 51 | + thus this is not appropriate API design we have what we have. |
| 52 | + |
| 53 | + Meanwhile swift don't provide strict way to compose such array it gives some hacks to solve this task |
| 54 | + and one of them is using `RawRepresentable` protocol. |
| 55 | + |
| 56 | + Conforming this protocol gives designated type ability to represent itself in `String` representation in any way. |
| 57 | + |
| 58 | + So in our case we're using such to implement custom `encode` method to any used in node request params types. |
| 59 | + |
| 60 | + Latter is required to encode array of `RPCParameter` to not to `[RPCParameter.int(1)]`, but to `[1]`. |
| 61 | + |
| 62 | + Here's an example of using this enum in field. |
| 63 | + ```swift |
| 64 | + let jsonRPCParams: [JSONRPCParameter] = [ |
| 65 | + .init(rawValue: 12)!, |
| 66 | + .init(rawValue: "this")!, |
| 67 | + .init(rawValue: 12.2)!, |
| 68 | + .init(rawValue: [12.2, 12.4])! |
| 69 | + ] |
| 70 | + let encoded = try JSONEncoder().encode(jsonRPCParams) |
| 71 | + print(String(data: encoded, encoding: .utf8)!) |
| 72 | + //> [12,\"this\",12.2,[12.2,12.4]]` |
| 73 | + ``` |
| 74 | + */ |
| 75 | +public enum RPCParameter { |
| 76 | + case int(Int) |
| 77 | + case intArray([Int]) |
| 78 | + |
| 79 | + case uint(UInt64) |
| 80 | + case uintArray([UInt64]) |
| 81 | + |
| 82 | + case double(Double) |
| 83 | + case doubleArray([Double]) |
| 84 | + |
| 85 | + case string(String) |
| 86 | + case stringArray([String]) |
| 87 | + |
| 88 | + case bool(Bool) |
| 89 | + case transaction(TransactionParameters) |
| 90 | + case eventFilter(EventFilterParameters) |
| 91 | +} |
| 92 | + |
| 93 | + |
| 94 | +extension RPCParameter: RawRepresentable { |
| 95 | + /** |
| 96 | + This init required by `RawRepresentable` protocol, which is requred to encode mixed type values array in JSON. |
| 97 | + |
| 98 | + This protocol used to implement custom `encode` method for that enum, |
| 99 | + which is encodes array of self into array of self assotiated values. |
| 100 | + |
| 101 | + You're totally free to use explicit and more convenience member init as `RPCParameter.int(12)` in your code. |
| 102 | + */ |
| 103 | + public init?(rawValue: JSONRPCParameter) { |
| 104 | + /// force casting in this switch is safe because |
| 105 | + /// each `rawValue` forced to casts only in exact case which is runs based on `rawValues` type |
| 106 | + // swiftlint:disable force_cast |
| 107 | + switch type(of: rawValue) { |
| 108 | + case is Int.Type: self = .int(rawValue as! Int) |
| 109 | + case is [Int].Type: self = .intArray(rawValue as! [Int]) |
| 110 | + |
| 111 | + case is UInt64.Type: self = .uint(rawValue as! UInt64) |
| 112 | + case is [UInt64].Type: self = .uintArray(rawValue as! [UInt64]) |
| 113 | + |
| 114 | + case is String.Type: self = .string(rawValue as! String) |
| 115 | + case is [String].Type: self = .stringArray(rawValue as! [String]) |
| 116 | + |
| 117 | + case is Double.Type: self = .double(rawValue as! Double) |
| 118 | + case is [Double].Type: self = .doubleArray(rawValue as! [Double]) |
| 119 | + |
| 120 | + case is Bool.Type: self = .bool(rawValue as! Bool) |
| 121 | + case is TransactionParameters.Type: self = .transaction(rawValue as! TransactionParameters) |
| 122 | + case is EventFilterParameters.Type: self = .eventFilter(rawValue as! EventFilterParameters) |
| 123 | + default: return nil |
| 124 | + } |
| 125 | + // swiftlint:enable force_cast |
| 126 | + } |
| 127 | + |
| 128 | + /// Returning associated value of the enum case. |
| 129 | + public var rawValue: JSONRPCParameter { |
| 130 | + // cases can't be merged, coz it cause compiler error since it couldn't predict what exact type on exact case will be returned. |
| 131 | + switch self { |
| 132 | + case let .int(value): return value |
| 133 | + case let .intArray(value): return value |
| 134 | + |
| 135 | + case let .uint(value): return value |
| 136 | + case let .uintArray(value): return value |
| 137 | + |
| 138 | + case let .string(value): return value |
| 139 | + case let .stringArray(value): return value |
| 140 | + |
| 141 | + case let .double(value): return value |
| 142 | + case let .doubleArray(value): return value |
| 143 | + |
| 144 | + case let .bool(value): return value |
| 145 | + case let .transaction(value): return value |
| 146 | + case let .eventFilter(value): return value |
| 147 | + } |
| 148 | + } |
| 149 | +} |
| 150 | + |
| 151 | +extension RPCParameter: Encodable { |
| 152 | + /** |
| 153 | + This encoder encodes `RPCParameter` assotiated value ignoring self value |
| 154 | + |
| 155 | + This is required to encode mixed types array, like |
| 156 | + |
| 157 | + ```swift |
| 158 | + let someArray: [RPCParameter] = [ |
| 159 | + .init(rawValue: 12)!, |
| 160 | + .init(rawValue: "this")!, |
| 161 | + .init(rawValue: 12.2)!, |
| 162 | + .init(rawValue: [12.2, 12.4])! |
| 163 | + ] |
| 164 | + let encoded = try JSONEncoder().encode(someArray) |
| 165 | + print(String(data: encoded, encoding: .utf8)!) |
| 166 | + //> [12,\"this\",12.2,[12.2,12.4]]` |
| 167 | + ``` |
| 168 | + */ |
| 169 | + public func encode(to encoder: Encoder) throws { |
| 170 | + var enumContainer = encoder.singleValueContainer() |
| 171 | + /// force casting in this switch is safe because |
| 172 | + /// each `rawValue` forced to casts only in exact case which is runs based on `rawValue` type |
| 173 | + // swiftlint:disable force_cast |
| 174 | + switch type(of: self.rawValue) { |
| 175 | + case is Int.Type: try enumContainer.encode(rawValue as! Int) |
| 176 | + case is [Int].Type: try enumContainer.encode(rawValue as! [Int]) |
| 177 | + |
| 178 | + case is UInt64.Type: try enumContainer.encode(rawValue as! UInt64) |
| 179 | + case is [UInt64].Type: try enumContainer.encode(rawValue as! [UInt64]) |
| 180 | + |
| 181 | + case is String.Type: try enumContainer.encode(rawValue as! String) |
| 182 | + case is [String].Type: try enumContainer.encode(rawValue as! [String]) |
| 183 | + |
| 184 | + case is Double.Type: try enumContainer.encode(rawValue as! Double) |
| 185 | + case is [Double].Type: try enumContainer.encode(rawValue as! [Double]) |
| 186 | + |
| 187 | + case is Bool.Type: try enumContainer.encode(rawValue as! Bool) |
| 188 | + case is TransactionParameters.Type: try enumContainer.encode(rawValue as! TransactionParameters) |
| 189 | + case is EventFilterParameters.Type: try enumContainer.encode(rawValue as! EventFilterParameters) |
| 190 | + default: break /// can't be executed, coz possible `self.rawValue` types are strictly defined in it's inplementation.` |
| 191 | + } |
| 192 | + // swiftlint:enable force_cast |
| 193 | + } |
| 194 | +} |
0 commit comments