1ec62a260STomasz Sapeta // Copyright 2022-present 650 Industries. All rights reserved.
2ec62a260STomasz Sapeta 
3ec62a260STomasz Sapeta /**
4ec62a260STomasz Sapeta  Enum with available kinds of values. It's almost the same as a result of "typeof"
5ec62a260STomasz Sapeta  in JavaScript, however `null` has its own kind (typeof null == "object").
6ec62a260STomasz Sapeta  */
7ec62a260STomasz Sapeta public enum JavaScriptValueKind: String {
8ec62a260STomasz Sapeta   case undefined
9ec62a260STomasz Sapeta   case null
10ec62a260STomasz Sapeta   case bool
11ec62a260STomasz Sapeta   case number
12ec62a260STomasz Sapeta   case symbol
13ec62a260STomasz Sapeta   case string
14ec62a260STomasz Sapeta   case function
15ec62a260STomasz Sapeta   case object
16ec62a260STomasz Sapeta }
17ec62a260STomasz Sapeta 
18a2ddfabcSTomasz Sapeta /**
19a2ddfabcSTomasz Sapeta  A protocol that JavaScript values, objects and functions can conform to.
20a2ddfabcSTomasz Sapeta  */
21a2ddfabcSTomasz Sapeta protocol AnyJavaScriptValue {
22a2ddfabcSTomasz Sapeta   /**
23a2ddfabcSTomasz Sapeta    Tries to convert a raw JavaScript value to the conforming type.
24a2ddfabcSTomasz Sapeta    */
convertnull25a2ddfabcSTomasz Sapeta   static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self
26a2ddfabcSTomasz Sapeta }
27a2ddfabcSTomasz Sapeta 
28*45a388e1SŁukasz Kosmaty extension JavaScriptValue: AnyJavaScriptValue, AnyArgument {
2972193c75SŁukasz Kosmaty   public var kind: JavaScriptValueKind {
30ec62a260STomasz Sapeta     switch true {
31ec62a260STomasz Sapeta     case isUndefined():
32ec62a260STomasz Sapeta       return .undefined
33ec62a260STomasz Sapeta     case isNull():
34ec62a260STomasz Sapeta       return .null
35ec62a260STomasz Sapeta     case isBool():
36ec62a260STomasz Sapeta       return .bool
37ec62a260STomasz Sapeta     case isNumber():
38ec62a260STomasz Sapeta       return .number
39ec62a260STomasz Sapeta     case isSymbol():
40ec62a260STomasz Sapeta       return .symbol
41ec62a260STomasz Sapeta     case isString():
42ec62a260STomasz Sapeta       return .string
43ec62a260STomasz Sapeta     case isFunction():
44ec62a260STomasz Sapeta       return .function
45ec62a260STomasz Sapeta     default:
46ec62a260STomasz Sapeta       return .object
47ec62a260STomasz Sapeta     }
48ec62a260STomasz Sapeta   }
49ec62a260STomasz Sapeta 
50ec62a260STomasz Sapeta   func asBool() throws -> Bool {
51ec62a260STomasz Sapeta     if isBool() {
52ec62a260STomasz Sapeta       return getBool()
53ec62a260STomasz Sapeta     }
54ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Bool"))
55ec62a260STomasz Sapeta   }
56ec62a260STomasz Sapeta 
57ec62a260STomasz Sapeta   func asInt() throws -> Int {
58ec62a260STomasz Sapeta     if isNumber() {
59ec62a260STomasz Sapeta       return getInt()
60ec62a260STomasz Sapeta     }
61ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Int"))
62ec62a260STomasz Sapeta   }
63ec62a260STomasz Sapeta 
64ec62a260STomasz Sapeta   func asDouble() throws -> Double {
65ec62a260STomasz Sapeta     if isNumber() {
66ec62a260STomasz Sapeta       return getDouble()
67ec62a260STomasz Sapeta     }
68ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Double"))
69ec62a260STomasz Sapeta   }
70ec62a260STomasz Sapeta 
71ec62a260STomasz Sapeta   func asString() throws -> String {
72ec62a260STomasz Sapeta     if isString() {
73ec62a260STomasz Sapeta       return getString()
74ec62a260STomasz Sapeta     }
75ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "String"))
76ec62a260STomasz Sapeta   }
77ec62a260STomasz Sapeta 
78ec62a260STomasz Sapeta   func asArray() throws -> [JavaScriptValue?] {
79ec62a260STomasz Sapeta     if isObject() {
80ec62a260STomasz Sapeta       return getArray()
81ec62a260STomasz Sapeta     }
82ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Array"))
83ec62a260STomasz Sapeta   }
84ec62a260STomasz Sapeta 
8556c10913STomasz Sapeta   func asDict() throws -> [String: Any] {
86ec62a260STomasz Sapeta     if isObject() {
87ec62a260STomasz Sapeta       return getDictionary()
88ec62a260STomasz Sapeta     }
89ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Dict"))
90ec62a260STomasz Sapeta   }
91ec62a260STomasz Sapeta 
92ec62a260STomasz Sapeta   func asObject() throws -> JavaScriptObject {
93ec62a260STomasz Sapeta     if isObject() {
94ec62a260STomasz Sapeta       return getObject()
95ec62a260STomasz Sapeta     }
96ec62a260STomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Object"))
97ec62a260STomasz Sapeta   }
98002d516eSTomasz Sapeta 
99a2ddfabcSTomasz Sapeta   func asFunction() throws -> RawJavaScriptFunction {
100a2ddfabcSTomasz Sapeta     if isFunction() {
101a2ddfabcSTomasz Sapeta       return getFunction()
102a2ddfabcSTomasz Sapeta     }
103a2ddfabcSTomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "Function"))
104a2ddfabcSTomasz Sapeta   }
105a2ddfabcSTomasz Sapeta 
106002d516eSTomasz Sapeta   func asTypedArray() throws -> JavaScriptTypedArray {
107002d516eSTomasz Sapeta     if let typedArray = getTypedArray() {
108002d516eSTomasz Sapeta       return typedArray
109002d516eSTomasz Sapeta     }
110002d516eSTomasz Sapeta     throw JavaScriptValueConversionException((kind: kind, target: "TypedArray"))
111002d516eSTomasz Sapeta   }
112a2ddfabcSTomasz Sapeta 
113a2ddfabcSTomasz Sapeta   // MARK: - AnyJavaScriptValue
114a2ddfabcSTomasz Sapeta 
115a2ddfabcSTomasz Sapeta   internal static func convert(from value: JavaScriptValue, appContext: AppContext) throws -> Self {
116a2ddfabcSTomasz Sapeta     // It's already a `JavaScriptValue` so it should always pass through.
117a2ddfabcSTomasz Sapeta     if let value = value as? Self {
118a2ddfabcSTomasz Sapeta       return value
119a2ddfabcSTomasz Sapeta     }
120a2ddfabcSTomasz Sapeta     throw JavaScriptValueConversionException((kind: value.kind, target: String(describing: Self.self)))
121a2ddfabcSTomasz Sapeta   }
122ec62a260STomasz Sapeta }
123ec62a260STomasz Sapeta 
124ec62a260STomasz Sapeta internal final class JavaScriptValueConversionException: GenericException<(kind: JavaScriptValueKind, target: String)> {
125ec62a260STomasz Sapeta   override var reason: String {
126ec62a260STomasz Sapeta     "Cannot represent a value of kind '\(param.kind)' as \(param.target)"
127ec62a260STomasz Sapeta   }
128ec62a260STomasz Sapeta }
129