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