mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Support Dict
This commit is contained in:
@@ -248,7 +248,10 @@ public final class ExpressionResolver {
|
||||
)
|
||||
}
|
||||
|
||||
private lazy var supportedFunctions: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] =
|
||||
private lazy var supportedFunctions: [
|
||||
AnyCalcExpression.Symbol: AnyCalcExpression
|
||||
.SymbolEvaluator
|
||||
] =
|
||||
_supportedFunctions + ValueFunctions.allCases.map { $0.getDeclaration(resolver: self) }.flat()
|
||||
}
|
||||
|
||||
@@ -259,6 +262,7 @@ private let _supportedFunctions: [AnyCalcExpression.Symbol: AnyCalcExpression.Sy
|
||||
+ DatetimeFunctions.allCases.map(\.declaration).flat()
|
||||
+ MathFunctions.allCases.map(\.declaration).flat()
|
||||
+ IntervalFunctions.allCases.map(\.declaration).flat()
|
||||
+ DictFunctions.allCases.map(\.declaration).flat()
|
||||
|
||||
extension ExpressionResolver: ConstantsProvider {
|
||||
func getValue(_ name: String) -> Any? {
|
||||
|
||||
@@ -7,6 +7,8 @@ enum CastFunctions: String, CaseIterable {
|
||||
case toString
|
||||
case toNumber
|
||||
case toInteger
|
||||
case toColor
|
||||
case toUrl
|
||||
|
||||
var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] {
|
||||
[.function(rawValue, arity: function.arity): function.symbolEvaluator]
|
||||
@@ -58,6 +60,10 @@ enum CastFunctions: String, CaseIterable {
|
||||
AnyCalcExpression.Error.toInteger($0.first)
|
||||
}
|
||||
)
|
||||
case .toColor:
|
||||
return FunctionUnary(impl: _stringToColor)
|
||||
case .toUrl:
|
||||
return FunctionUnary(impl: _stringToUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +142,20 @@ private func _doubleToInteger(value: Double) throws -> Int {
|
||||
return number
|
||||
}
|
||||
|
||||
private func _stringToColor(value: String) throws -> Color {
|
||||
guard let color = Color.color(withHexString: value) else {
|
||||
throw AnyCalcExpression.Error.toColor(value)
|
||||
}
|
||||
return color
|
||||
}
|
||||
|
||||
private func _stringToUrl(value: String) throws -> URL {
|
||||
guard let url = URL(string: value) else {
|
||||
throw AnyCalcExpression.Error.toUrl(value)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
extension Bool {
|
||||
fileprivate func toInteger() -> Int {
|
||||
switch self {
|
||||
@@ -206,6 +226,18 @@ extension AnyCalcExpression.Error {
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate static func toColor(_ value: String) -> AnyCalcExpression.Error {
|
||||
.message(
|
||||
"Failed to evaluate [toColor('\(value)')]. Unable to convert value to Color, expected format #AARRGGBB."
|
||||
)
|
||||
}
|
||||
|
||||
fileprivate static func toUrl(_ value: String) -> AnyCalcExpression.Error {
|
||||
.message(
|
||||
"Failed to evaluate [toUrl('\(value)')]. Unable to convert value to URL."
|
||||
)
|
||||
}
|
||||
|
||||
private static func formatValue(_ value: Any?) -> String {
|
||||
switch value {
|
||||
case let strValue as String:
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
import CommonCorePublic
|
||||
import Foundation
|
||||
|
||||
enum DictFunctions: String, CaseIterable {
|
||||
case getDictString
|
||||
case getDictNumber
|
||||
case getDictInteger
|
||||
case getDictBoolean
|
||||
case getDictColor
|
||||
case getDictUrl
|
||||
case getDictOptString
|
||||
case getDictOptNumber
|
||||
case getDictOptInteger
|
||||
case getDictOptBoolean
|
||||
case getDictOptColor
|
||||
case getDictOptUrl
|
||||
|
||||
var declaration: [AnyCalcExpression.Symbol: AnyCalcExpression.SymbolEvaluator] {
|
||||
[.function(rawValue, arity: function.arity): function.symbolEvaluator]
|
||||
}
|
||||
|
||||
var function: Function {
|
||||
switch self {
|
||||
case .getDictString:
|
||||
return FunctionVarBinary(impl: _getDictString)
|
||||
case .getDictNumber:
|
||||
return FunctionVarBinary(impl: _getDictNumber)
|
||||
case .getDictInteger:
|
||||
return FunctionVarBinary(impl: _getDictInteger)
|
||||
case .getDictBoolean:
|
||||
return FunctionVarBinary(impl: _getDictBoolean)
|
||||
case .getDictColor:
|
||||
return FunctionVarBinary(impl: _getDictColor)
|
||||
case .getDictUrl:
|
||||
return FunctionVarBinary(impl: _getDictUrl)
|
||||
case .getDictOptString:
|
||||
return FunctionVarTernary(impl: _getDictOptString)
|
||||
case .getDictOptNumber:
|
||||
return FunctionVarTernary(impl: _getDictOptNumber)
|
||||
case .getDictOptInteger:
|
||||
return FunctionVarTernary(impl: _getDictOptInteger)
|
||||
case .getDictOptBoolean:
|
||||
return FunctionVarTernary(impl: _getDictOptBoolean)
|
||||
case .getDictOptColor:
|
||||
return OverloadedFunction(
|
||||
functions: [
|
||||
FunctionVarTernary(impl: _getDictOptColorWithColorFallback),
|
||||
FunctionVarTernary(impl: _getDictOptColorWithStringFallback),
|
||||
]
|
||||
)
|
||||
case .getDictOptUrl:
|
||||
return OverloadedFunction(
|
||||
functions: [
|
||||
FunctionVarTernary(impl: _getDictOptUrlWithURLFallback),
|
||||
FunctionVarTernary(impl: _getDictOptUrlWithStringFallback),
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func _getDictString(dict: [String: AnyHashable], path: [String]) throws -> String {
|
||||
let expression = makeExpression("getDictString", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
guard let stringValue = result as? String else {
|
||||
throw Error.incorrectValueType(expression, "string", getActualType(result)).message
|
||||
}
|
||||
return stringValue
|
||||
}
|
||||
|
||||
private func _getDictNumber(dict: [String: AnyHashable], path: [String]) throws -> Double {
|
||||
let expression = makeExpression("getDictNumber", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
if result.isBool {
|
||||
throw Error.incorrectValueType(expression, "number", getActualType(result)).message
|
||||
}
|
||||
if let numberValue = result as? Double {
|
||||
return numberValue
|
||||
}
|
||||
if let intValue = result as? Int {
|
||||
return Double(intValue)
|
||||
}
|
||||
throw Error.incorrectValueType(expression, "number", getActualType(result)).message
|
||||
}
|
||||
|
||||
private func _getDictInteger(dict: [String: AnyHashable], path: [String]) throws -> Int {
|
||||
let expression = makeExpression("getDictInteger", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
if result.isBool {
|
||||
throw Error.incorrectValueType(expression, "integer", getActualType(result)).message
|
||||
}
|
||||
guard let intValue = result as? Int else {
|
||||
if let doubleValue = result as? Double {
|
||||
if doubleValue < Double(Int.min) || doubleValue > Double(Int.max) {
|
||||
throw Error.integerOverflow(expression).message
|
||||
}
|
||||
throw Error.cannotConvertToInteger(expression).message
|
||||
}
|
||||
throw Error.incorrectValueType(expression, "integer", getActualType(result)).message
|
||||
}
|
||||
return intValue
|
||||
}
|
||||
|
||||
private func _getDictBoolean(dict: [String: AnyHashable], path: [String]) throws -> Bool {
|
||||
let expression = makeExpression("getDictBoolean", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
guard result.isBool else {
|
||||
throw Error.incorrectValueType(expression, "boolean", getActualType(result)).message
|
||||
}
|
||||
guard let boolValue = result as? Bool else {
|
||||
throw Error.incorrectValueType(expression, "boolean", getActualType(result)).message
|
||||
}
|
||||
return boolValue
|
||||
}
|
||||
|
||||
private func _getDictColor(dict: [String: AnyHashable], path: [String]) throws -> Color {
|
||||
let expression = makeExpression("getDictColor", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
guard let stringValue = result as? String else {
|
||||
throw Error.incorrectValueType(expression, "color", getActualType(result)).message
|
||||
}
|
||||
guard let color = Color.color(withHexString: stringValue) else {
|
||||
throw Error.incorrectColorFormat(expression).message
|
||||
}
|
||||
return color
|
||||
}
|
||||
|
||||
private func _getDictUrl(dict: [String: AnyHashable], path: [String]) throws -> URL {
|
||||
let expression = makeExpression("getDictUrl", path)
|
||||
let result = try getProp(
|
||||
dict: dict,
|
||||
path: path,
|
||||
expression: expression
|
||||
)
|
||||
guard let stringValue = result as? String else {
|
||||
throw Error.incorrectValueType(expression, "url", getActualType(result)).message
|
||||
}
|
||||
guard let url = URL(string: stringValue) else {
|
||||
throw Error.incorrectValueType(expression, "url", getActualType(result)).message
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
private func _getDictOptString(
|
||||
fallback: String,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> String {
|
||||
guard let value = try? _getDictString(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptNumber(
|
||||
fallback: Double,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> Double {
|
||||
guard let value = try? _getDictNumber(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptInteger(
|
||||
fallback: Int,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> Int {
|
||||
guard let value = try? _getDictInteger(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptBoolean(
|
||||
fallback: Bool,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> Bool {
|
||||
guard let value = try? _getDictBoolean(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptColorWithColorFallback(
|
||||
fallback: Color,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> Color {
|
||||
guard let value = try? _getDictColor(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptColorWithStringFallback(
|
||||
fallback: String,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> Color {
|
||||
guard let value = try? _getDictColor(dict: dict, path: path) else {
|
||||
return Color.color(withHexString: fallback)!
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptUrlWithURLFallback(
|
||||
fallback: URL,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> URL {
|
||||
guard let value = try? _getDictUrl(dict: dict, path: path) else {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func _getDictOptUrlWithStringFallback(
|
||||
fallback: String,
|
||||
dict: [String: AnyHashable],
|
||||
path: [String]
|
||||
) -> URL {
|
||||
guard let value = try? _getDictUrl(dict: dict, path: path) else {
|
||||
return URL(string: fallback)!
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private func getProp(
|
||||
dict: [String: AnyHashable],
|
||||
path: [String],
|
||||
expression: String
|
||||
) throws -> AnyHashable {
|
||||
var dictionary: [String: AnyHashable]? = dict
|
||||
var i = 0
|
||||
while i < path.count - 1 {
|
||||
let key = path[i]
|
||||
if let current = dictionary {
|
||||
dictionary = current[key] as? [String: AnyHashable]
|
||||
} else {
|
||||
throw Error.missingProperty(expression, key).message
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
guard let key = path.last else {
|
||||
throw Error.wrongPath(expression).message
|
||||
}
|
||||
if let current = dictionary,
|
||||
let result = current[key] {
|
||||
return result
|
||||
} else {
|
||||
throw Error.missingProperty(expression, key).message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getActualType(_ value: AnyHashable) -> String {
|
||||
if value.isBool {
|
||||
return "boolean"
|
||||
}
|
||||
switch value {
|
||||
case is [String: AnyHashable]:
|
||||
return "dict"
|
||||
case is [Any]:
|
||||
return "array"
|
||||
case is String:
|
||||
return "string"
|
||||
case is Int, is Double:
|
||||
return "number"
|
||||
default:
|
||||
return "null"
|
||||
}
|
||||
}
|
||||
|
||||
private func makeExpression(_ function: String, _ path: [String]) -> String {
|
||||
"\(function)(<dict>, \(path.map { "'\($0)'" }.joined(separator: ", ")))"
|
||||
}
|
||||
|
||||
private enum Error {
|
||||
case missingProperty(String, String)
|
||||
case wrongPath(String)
|
||||
case incorrectValueType(String, String, String)
|
||||
case cannotConvertToInteger(String)
|
||||
case integerOverflow(String)
|
||||
case incorrectColorFormat(String)
|
||||
|
||||
private var description: String {
|
||||
switch self {
|
||||
case let .missingProperty(expression, missing):
|
||||
return "Failed to evaluate [\(expression)]. Missing property \"\(missing)\" in the dict."
|
||||
case let .wrongPath(expression):
|
||||
return "Failed to evaluate [\(expression)]. Path is wrong."
|
||||
case let .incorrectValueType(expression, expectedType, actualType):
|
||||
return "Failed to evaluate [\(expression)]. Incorrect value type: expected \"\(expectedType)\", got \"\(actualType)\"."
|
||||
case let .cannotConvertToInteger(expression):
|
||||
return "Failed to evaluate [\(expression)]. Cannot convert value to integer."
|
||||
case let .integerOverflow(expression):
|
||||
return "Failed to evaluate [\(expression)]. Integer overflow."
|
||||
case let .incorrectColorFormat(expression):
|
||||
return "Failed to evaluate [\(expression)]. Unable to convert value to Color, expected format #AARRGGBB."
|
||||
}
|
||||
}
|
||||
|
||||
var message: AnyCalcExpression.Error {
|
||||
AnyCalcExpression.Error.message(description)
|
||||
}
|
||||
}
|
||||
|
||||
extension AnyHashable {
|
||||
fileprivate var isBool: Bool {
|
||||
Bool(description) != nil && !(self is String)
|
||||
}
|
||||
}
|
||||
@@ -180,6 +180,63 @@ struct FunctionVarUnary<T1, R>: SimpleFunction {
|
||||
}
|
||||
}
|
||||
|
||||
struct FunctionVarBinary<T1, T2, R>: SimpleFunction {
|
||||
private let impl: (T1, [T2]) throws -> R
|
||||
|
||||
var signature: FunctionSignature {
|
||||
get throws {
|
||||
.init(
|
||||
arguments: [
|
||||
.init(type: try .from(type: T1.self)),
|
||||
.init(type: try .from(type: T2.self), vararg: true),
|
||||
],
|
||||
resultType: try .from(type: R.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init(impl: @escaping (T1, [T2]) throws -> R) {
|
||||
self.impl = impl
|
||||
}
|
||||
|
||||
func invoke(args: [Any]) throws -> Any {
|
||||
guard let v1 = args[0] as? T1 else {
|
||||
throw FunctionSignature.Error.arityMismatch.message
|
||||
}
|
||||
let v2 = args.dropFirst().map { $0 as! T2 }
|
||||
return try impl(v1, v2)
|
||||
}
|
||||
}
|
||||
|
||||
struct FunctionVarTernary<T1, T2, T3, R>: SimpleFunction {
|
||||
private let impl: (T1, T2, [T3]) throws -> R
|
||||
|
||||
var signature: FunctionSignature {
|
||||
get throws {
|
||||
.init(
|
||||
arguments: [
|
||||
.init(type: try .from(type: T1.self)),
|
||||
.init(type: try .from(type: T2.self)),
|
||||
.init(type: try .from(type: T3.self), vararg: true),
|
||||
],
|
||||
resultType: try .from(type: R.self)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init(impl: @escaping (T1, T2, [T3]) throws -> R) {
|
||||
self.impl = impl
|
||||
}
|
||||
|
||||
func invoke(args: [Any]) throws -> Any {
|
||||
guard let v1 = args[0] as? T1, let v2 = args[1] as? T2 else {
|
||||
throw FunctionSignature.Error.arityMismatch.message
|
||||
}
|
||||
let v3 = args.dropFirst(2).map { $0 as! T3 }
|
||||
return try impl(v1, v2, v3)
|
||||
}
|
||||
}
|
||||
|
||||
struct OverloadedFunction: Function {
|
||||
let functions: [SimpleFunction]
|
||||
let makeError: ([Any]) -> Error
|
||||
@@ -257,7 +314,7 @@ struct FunctionSignature: Decodable {
|
||||
case .url:
|
||||
return URL.self
|
||||
case .dict:
|
||||
return [String: Any].self
|
||||
return [String: AnyHashable].self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ public enum DivVariableValue: Equatable {
|
||||
case bool(Bool)
|
||||
case color(Color)
|
||||
case url(URL)
|
||||
case dict([String: AnyHashable])
|
||||
|
||||
@inlinable
|
||||
public func typedValue<T>() -> T? {
|
||||
@@ -29,6 +30,8 @@ public enum DivVariableValue: Equatable {
|
||||
return value as? T
|
||||
case let .url(value):
|
||||
return value as? T
|
||||
case let .dict(value):
|
||||
return value as? T
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,7 +83,7 @@ public final class DivVariablesStorage {
|
||||
let variable = storage.local[cardId]?[name] ?? storage.global[name]
|
||||
return variable?.typedValue()
|
||||
}
|
||||
|
||||
|
||||
public func set(
|
||||
cardId: DivCardID,
|
||||
variables: DivVariables
|
||||
@@ -254,6 +257,9 @@ extension Dictionary where Key == DivVariableName, Value == DivVariableValue {
|
||||
} else {
|
||||
newValue = nil
|
||||
}
|
||||
case .dict:
|
||||
newValue = nil
|
||||
DivKitLogger.warning("Unsupported variable type: dict")
|
||||
}
|
||||
|
||||
if newValue == oldValue {
|
||||
@@ -300,8 +306,15 @@ extension Collection where Element == DivVariable {
|
||||
let name = DivVariableName(rawValue: urlVariable.name)
|
||||
if variables.keys.contains(name) { return }
|
||||
variables[name] = .url(urlVariable.value)
|
||||
case .dictVariable:
|
||||
DivKitLogger.warning("Unsupported variable type: dict")
|
||||
case let .dictVariable(dictVariable):
|
||||
let name = DivVariableName(rawValue: dictVariable.name)
|
||||
if variables.keys.contains(name) { return }
|
||||
if let dictionary = NSDictionary(dictionary: dictVariable.value) as? [String: AnyHashable] {
|
||||
variables[name] = .dict(dictionary)
|
||||
} else {
|
||||
DivKitLogger.error("Incorrect value for dict variable \(name): \(dictVariable.value)")
|
||||
variables[name] = .dict([:])
|
||||
}
|
||||
}
|
||||
}
|
||||
return variables
|
||||
|
||||
@@ -61,6 +61,18 @@ final class ExpressionResolvingTests: XCTestCase {
|
||||
perform(on: testCases, type: type)
|
||||
}
|
||||
|
||||
func test_Functions_To_Color() throws {
|
||||
let testCases = try makeTestCases(for: "functions_to_color")
|
||||
let type: ExpressionType<String> = .stringBased(initializer: { $0 })
|
||||
perform(on: testCases, type: type)
|
||||
}
|
||||
|
||||
func test_Functions_To_Url() throws {
|
||||
let testCases = try makeTestCases(for: "functions_to_url")
|
||||
let type: ExpressionType<String> = .stringBased(initializer: { $0 })
|
||||
perform(on: testCases, type: type)
|
||||
}
|
||||
|
||||
func test_Functions_Datetime() throws {
|
||||
let testCases = try makeTestCases(for: "functions_datetime")
|
||||
let type: ExpressionType<String> = .singleItem
|
||||
@@ -73,6 +85,12 @@ final class ExpressionResolvingTests: XCTestCase {
|
||||
perform(on: testCases, type: type)
|
||||
}
|
||||
|
||||
func test_Functions_Dict() throws {
|
||||
let testCases = try makeTestCases(for: "functions_dict")
|
||||
let type: ExpressionType<String> = .stringBased(initializer: { $0 })
|
||||
perform(on: testCases, type: type)
|
||||
}
|
||||
|
||||
func test_Functions_Interval() throws {
|
||||
let testCases = try makeTestCases(for: "functions_interval")
|
||||
let type: ExpressionType<String> = .stringBased(initializer: { $0 })
|
||||
@@ -514,6 +532,9 @@ extension DivVariable: Decodable {
|
||||
case UrlVariable.type:
|
||||
let value = try container.decode(String.self, forKey: .value)
|
||||
self = .urlVariable(UrlVariable(name: name, value: URL(string: value)!))
|
||||
case DictVariable.type:
|
||||
let dict = try container.decode(Dict.self, forKey: .value)
|
||||
self = .dictVariable(DictVariable(name: name, value: dict.value))
|
||||
default:
|
||||
throw DecodingError.valueNotFound(
|
||||
String.self,
|
||||
@@ -529,3 +550,79 @@ extension DivVariable: Decodable {
|
||||
case type, name, value
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct JSONCodingKeys: CodingKey {
|
||||
var stringValue: String
|
||||
|
||||
init(stringValue: String) {
|
||||
self.stringValue = stringValue
|
||||
}
|
||||
|
||||
var intValue: Int?
|
||||
|
||||
init?(intValue: Int) {
|
||||
self.init(stringValue: "\(intValue)")
|
||||
self.intValue = intValue
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct Dict: Decodable {
|
||||
var value: [String: Any] = [:]
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
if let container = try? decoder.container(keyedBy: JSONCodingKeys.self) {
|
||||
self.value = decode(fromObject: container)
|
||||
}
|
||||
}
|
||||
|
||||
func decode(fromObject container: KeyedDecodingContainer<JSONCodingKeys>) -> [String: Any] {
|
||||
var result: [String: Any] = [:]
|
||||
|
||||
for key in container.allKeys {
|
||||
if let val = try? container.decode(Int.self, forKey: key) {
|
||||
result[key.stringValue] = val
|
||||
} else if let val = try? container.decode(Double.self, forKey: key) {
|
||||
result[key.stringValue] = val
|
||||
} else if let val = try? container.decode(String.self, forKey: key) {
|
||||
result[key.stringValue] = val
|
||||
} else if let val = try? container.decode(Bool.self, forKey: key) {
|
||||
result[key.stringValue] = val
|
||||
} else if let nestedContainer = try? container.nestedContainer(
|
||||
keyedBy: JSONCodingKeys.self,
|
||||
forKey: key
|
||||
) {
|
||||
result[key.stringValue] = decode(fromObject: nestedContainer)
|
||||
} else if var nestedArray = try? container.nestedUnkeyedContainer(forKey: key) {
|
||||
result[key.stringValue] = decode(fromArray: &nestedArray)
|
||||
} else if (try? container.decodeNil(forKey: key)) == true {
|
||||
result.updateValue(Any?(nil) as Any, forKey: key.stringValue)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func decode(fromArray container: inout UnkeyedDecodingContainer) -> [Any] {
|
||||
var result: [Any] = []
|
||||
|
||||
while !container.isAtEnd {
|
||||
if let value = try? container.decode(String.self) {
|
||||
result.append(value)
|
||||
} else if let value = try? container.decode(Int.self) {
|
||||
result.append(value)
|
||||
} else if let value = try? container.decode(Double.self) {
|
||||
result.append(value)
|
||||
} else if let value = try? container.decode(Bool.self) {
|
||||
result.append(value)
|
||||
} else if let nestedContainer = try? container.nestedContainer(keyedBy: JSONCodingKeys.self) {
|
||||
result.append(decode(fromObject: nestedContainer))
|
||||
} else if var nestedArray = try? container.nestedUnkeyedContainer() {
|
||||
result.append(decode(fromArray: &nestedArray))
|
||||
} else if (try? container.decodeNil()) == true {
|
||||
result.append(Any?(nil) as Any)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ private let functions: [String: Function] = {
|
||||
var result = [String: Function]()
|
||||
DatetimeFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
ColorFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
StringFunctions.allCases.forEach { result[$0.rawValue] = $0.function}
|
||||
CastFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
StringFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
CastFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
MathFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
DictFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
IntervalFunctions.allCases.forEach { result[$0.rawValue] = $0.function }
|
||||
ValueFunctions.allCases.forEach {
|
||||
result[$0.rawValue] = $0.getFunction(resolver: ExpressionResolver(variables: [:]))
|
||||
|
||||
@@ -202,7 +202,10 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/dict_variable",
|
||||
"platforms": []
|
||||
"platforms": [
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -215,7 +215,8 @@
|
||||
],
|
||||
"result_type": "color",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -230,7 +231,8 @@
|
||||
],
|
||||
"result_type": "url",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -416,7 +418,8 @@
|
||||
],
|
||||
"result_type": "string",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -436,7 +439,8 @@
|
||||
],
|
||||
"result_type": "number",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -456,7 +460,8 @@
|
||||
],
|
||||
"result_type": "integer",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -476,7 +481,8 @@
|
||||
],
|
||||
"result_type": "boolean",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -496,7 +502,8 @@
|
||||
],
|
||||
"result_type": "color",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -516,7 +523,8 @@
|
||||
],
|
||||
"result_type": "url",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -540,7 +548,8 @@
|
||||
],
|
||||
"result_type": "string",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -564,7 +573,8 @@
|
||||
],
|
||||
"result_type": "number",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -588,7 +598,8 @@
|
||||
],
|
||||
"result_type": "integer",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -612,7 +623,8 @@
|
||||
],
|
||||
"result_type": "boolean",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -636,7 +648,8 @@
|
||||
],
|
||||
"result_type": "color",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -660,7 +673,8 @@
|
||||
],
|
||||
"result_type": "color",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -684,7 +698,8 @@
|
||||
],
|
||||
"result_type": "url",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -708,7 +723,8 @@
|
||||
],
|
||||
"result_type": "url",
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -37,7 +37,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -57,7 +58,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -79,7 +81,33 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getDictString(dict, 'prop', 'prop2', 'prop3') => 'val'",
|
||||
"expression": "@{getDictString(dict, 'prop', 'prop2', 'prop3')}",
|
||||
"expected": {
|
||||
"type": "string",
|
||||
"value": "val"
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"type": "dict",
|
||||
"name": "dict",
|
||||
"value": {
|
||||
"prop": {
|
||||
"prop2": {
|
||||
"prop3": "val"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -101,7 +129,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -121,7 +150,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -143,7 +173,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -163,7 +194,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -183,7 +215,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -203,7 +236,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -223,7 +257,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -243,7 +278,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -263,7 +299,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -283,7 +320,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -303,7 +341,29 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "getDictNumber(dict, 'prop') => error (2)",
|
||||
"expression": "@{getDictNumber(dict, 'prop')}",
|
||||
"expected": {
|
||||
"type": "error",
|
||||
"value": "Failed to evaluate [getDictNumber(<dict>, 'prop')]. Incorrect value type: expected \"number\", got \"string\"."
|
||||
},
|
||||
"variables": [
|
||||
{
|
||||
"type": "dict",
|
||||
"name": "dict",
|
||||
"value": {
|
||||
"prop": "val"
|
||||
}
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -323,7 +383,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -343,7 +404,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -363,7 +425,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -383,7 +446,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -398,12 +462,13 @@
|
||||
"type": "dict",
|
||||
"name": "dict",
|
||||
"value": {
|
||||
"prop": 9223372036854775808
|
||||
"prop": 9223372036854775808000000000000000000000
|
||||
}
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -423,7 +488,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -443,7 +509,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -463,7 +530,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -483,7 +551,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -503,7 +572,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -523,7 +593,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -543,7 +614,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -563,7 +635,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -583,7 +656,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -603,7 +677,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -625,7 +700,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -645,7 +721,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -667,7 +744,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -687,7 +765,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -707,7 +786,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -727,7 +807,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -747,7 +828,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -767,7 +849,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -787,7 +870,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -807,7 +891,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -827,7 +912,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -847,7 +933,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -867,7 +954,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -882,12 +970,13 @@
|
||||
"type": "dict",
|
||||
"name": "dict",
|
||||
"value": {
|
||||
"prop": 9223372036854775808
|
||||
"prop": 922337203685477580800
|
||||
}
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -907,7 +996,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -927,7 +1017,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -947,7 +1038,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -967,7 +1059,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -987,7 +1080,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1007,7 +1101,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1027,7 +1122,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1047,7 +1143,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1067,7 +1164,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1087,7 +1185,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1107,7 +1206,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1127,7 +1227,8 @@
|
||||
}
|
||||
],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
},
|
||||
"variables": [],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -21,7 +22,8 @@
|
||||
},
|
||||
"variables": [],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
},
|
||||
"variables": [],
|
||||
"platforms": [
|
||||
"web"
|
||||
"web",
|
||||
"ios"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user