expo-modules-core 0.6.5 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +1 -1
  3. package/android/ExpoModulesCorePlugin.gradle +15 -0
  4. package/android/build.gradle +31 -15
  5. package/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java +5 -5
  6. package/android/src/main/java/expo/modules/adapters/react/services/UIManagerModuleWrapper.java +13 -0
  7. package/android/src/main/java/expo/modules/core/ViewManager.java +9 -0
  8. package/android/src/main/java/expo/modules/core/interfaces/JavaScriptContextProvider.java +4 -0
  9. package/android/src/main/java/expo/modules/core/interfaces/ReactActivityHandler.java +37 -1
  10. package/android/src/main/java/expo/modules/core/interfaces/ReactNativeHostHandler.java +30 -0
  11. package/android/src/main/java/expo/modules/core/interfaces/services/UIManager.java +2 -0
  12. package/android/src/main/java/expo/modules/kotlin/AppContext.kt +23 -5
  13. package/android/src/main/java/expo/modules/kotlin/DynamicExtenstions.kt +5 -3
  14. package/android/src/main/java/expo/modules/kotlin/KPromiseWrapper.kt +3 -8
  15. package/android/src/main/java/expo/modules/kotlin/KotlinInteropModuleRegistry.kt +24 -9
  16. package/android/src/main/java/expo/modules/kotlin/ModuleHolder.kt +12 -7
  17. package/android/src/main/java/expo/modules/kotlin/ModuleRegistry.kt +23 -1
  18. package/android/src/main/java/expo/modules/kotlin/Promise.kt +1 -1
  19. package/android/src/main/java/expo/modules/kotlin/callbacks/Callback.kt +5 -0
  20. package/android/src/main/java/expo/modules/kotlin/callbacks/ViewCallback.kt +39 -0
  21. package/android/src/main/java/expo/modules/kotlin/callbacks/ViewCallbackDelegate.kt +27 -0
  22. package/android/src/main/java/expo/modules/kotlin/defaultmodules/ErrorManagerModule.kt +25 -0
  23. package/android/src/main/java/expo/modules/kotlin/events/EventEmitter.kt +13 -0
  24. package/android/src/main/java/expo/modules/kotlin/events/KModuleEventEmitterWrapper.kt +102 -0
  25. package/android/src/main/java/expo/modules/kotlin/exception/CodedException.kt +93 -9
  26. package/android/src/main/java/expo/modules/kotlin/exception/ExceptionDecorator.kt +11 -0
  27. package/android/src/main/java/expo/modules/kotlin/{methods/AnyMethod.kt → functions/AnyFunction.kt} +18 -18
  28. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunction.kt +15 -0
  29. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionBuilder.kt +170 -0
  30. package/android/src/main/java/expo/modules/kotlin/functions/AsyncFunctionWithPromise.kt +15 -0
  31. package/android/src/main/java/expo/modules/kotlin/functions/AsyncSuspendFunction.kt +36 -0
  32. package/android/src/main/java/expo/modules/kotlin/modules/DefinitionMarker.kt +4 -0
  33. package/android/src/main/java/expo/modules/kotlin/modules/Module.kt +17 -2
  34. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionBuilder.kt +416 -43
  35. package/android/src/main/java/expo/modules/kotlin/modules/ModuleDefinitionData.kt +2 -2
  36. package/android/src/main/java/expo/modules/kotlin/records/FieldValidator.kt +139 -0
  37. package/android/src/main/java/expo/modules/kotlin/records/RecordTypeConverter.kt +71 -15
  38. package/android/src/main/java/expo/modules/kotlin/records/Required.kt +5 -0
  39. package/android/src/main/java/expo/modules/kotlin/records/ValidationBinder.kt +110 -0
  40. package/android/src/main/java/expo/modules/kotlin/records/Validators.kt +61 -0
  41. package/android/src/main/java/expo/modules/kotlin/types/ArrayTypeConverter.kt +11 -5
  42. package/android/src/main/java/expo/modules/kotlin/types/JSTypeConverter.kt +35 -0
  43. package/android/src/main/java/expo/modules/kotlin/types/JSTypeConverterHelper.kt +148 -0
  44. package/android/src/main/java/expo/modules/kotlin/types/ListTypeConverter.kt +10 -4
  45. package/android/src/main/java/expo/modules/kotlin/types/MapTypeConverter.kt +12 -6
  46. package/android/src/main/java/expo/modules/kotlin/types/PairTypeConverter.kt +29 -13
  47. package/android/src/main/java/expo/modules/kotlin/types/TypeConverter.kt +2 -1
  48. package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +9 -1
  49. package/android/src/main/java/expo/modules/kotlin/views/CallbacksDefinition.kt +3 -0
  50. package/android/src/main/java/expo/modules/kotlin/views/GroupViewManagerWrapper.kt +71 -0
  51. package/android/src/main/java/expo/modules/kotlin/views/SimpleViewManagerWrapper.kt +22 -0
  52. package/android/src/main/java/expo/modules/kotlin/views/ViewGroupDefinition.kt +18 -0
  53. package/android/src/main/java/expo/modules/kotlin/views/ViewGroupDefinitionBuilder.kt +114 -0
  54. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinition.kt +30 -2
  55. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerDefinitionBuilder.kt +81 -2
  56. package/android/src/main/java/expo/modules/kotlin/views/ViewManagerWrapperDelegate.kt +62 -2
  57. package/build/EventEmitter.d.ts +1 -0
  58. package/build/EventEmitter.d.ts.map +1 -0
  59. package/build/NativeModulesProxy.d.ts +1 -0
  60. package/build/NativeModulesProxy.d.ts.map +1 -0
  61. package/build/NativeModulesProxy.native.d.ts +1 -4
  62. package/build/NativeModulesProxy.native.d.ts.map +1 -0
  63. package/build/NativeModulesProxy.native.js +1 -14
  64. package/build/NativeModulesProxy.native.js.map +1 -1
  65. package/build/NativeModulesProxy.types.d.ts +1 -3
  66. package/build/NativeModulesProxy.types.d.ts.map +1 -0
  67. package/build/NativeModulesProxy.types.js.map +1 -1
  68. package/build/NativeViewManagerAdapter.d.ts +1 -0
  69. package/build/NativeViewManagerAdapter.d.ts.map +1 -0
  70. package/build/NativeViewManagerAdapter.native.d.ts +1 -0
  71. package/build/NativeViewManagerAdapter.native.d.ts.map +1 -0
  72. package/build/NativeViewManagerAdapter.native.js +9 -33
  73. package/build/NativeViewManagerAdapter.native.js.map +1 -1
  74. package/build/PermissionsHook.d.ts +1 -0
  75. package/build/PermissionsHook.d.ts.map +1 -0
  76. package/build/PermissionsInterface.d.ts +1 -0
  77. package/build/PermissionsInterface.d.ts.map +1 -0
  78. package/build/Platform.d.ts +1 -0
  79. package/build/Platform.d.ts.map +1 -0
  80. package/build/SyntheticPlatformEmitter.d.ts +1 -0
  81. package/build/SyntheticPlatformEmitter.d.ts.map +1 -0
  82. package/build/SyntheticPlatformEmitter.web.d.ts +1 -0
  83. package/build/SyntheticPlatformEmitter.web.d.ts.map +1 -0
  84. package/build/deprecate.d.ts +1 -0
  85. package/build/deprecate.d.ts.map +1 -0
  86. package/build/environment/browser.d.ts +1 -0
  87. package/build/environment/browser.d.ts.map +1 -0
  88. package/build/environment/browser.web.d.ts +1 -0
  89. package/build/environment/browser.web.d.ts.map +1 -0
  90. package/build/errors/CodedError.d.ts +1 -0
  91. package/build/errors/CodedError.d.ts.map +1 -0
  92. package/build/errors/UnavailabilityError.d.ts +1 -0
  93. package/build/errors/UnavailabilityError.d.ts.map +1 -0
  94. package/build/index.d.ts +3 -0
  95. package/build/index.d.ts.map +1 -0
  96. package/build/index.js +2 -0
  97. package/build/index.js.map +1 -1
  98. package/build/requireNativeModule.d.ts +16 -0
  99. package/build/requireNativeModule.d.ts.map +1 -0
  100. package/build/requireNativeModule.js +18 -0
  101. package/build/requireNativeModule.js.map +1 -0
  102. package/build/sweet/NativeErrorManager.d.ts +3 -0
  103. package/build/sweet/NativeErrorManager.d.ts.map +1 -0
  104. package/build/sweet/NativeErrorManager.js +3 -0
  105. package/build/sweet/NativeErrorManager.js.map +1 -0
  106. package/build/sweet/setUpErrorManager.fx.d.ts +2 -0
  107. package/build/sweet/setUpErrorManager.fx.d.ts.map +1 -0
  108. package/build/sweet/setUpErrorManager.fx.js +11 -0
  109. package/build/sweet/setUpErrorManager.fx.js.map +1 -0
  110. package/ios/AppDelegates/EXAppDelegatesLoader.m +4 -8
  111. package/ios/AppDelegates/ExpoAppDelegate.swift +22 -20
  112. package/ios/EXAppDefines.h +1 -0
  113. package/ios/EXAppDefines.m +6 -0
  114. package/ios/EXUtilities.h +2 -0
  115. package/ios/EXUtilities.m +12 -0
  116. package/ios/ExpoModulesCore.h +4 -0
  117. package/ios/ExpoModulesCore.podspec +4 -2
  118. package/ios/Interfaces/FileSystem/EXFileSystemInterface.h +1 -1
  119. package/ios/Interfaces/TaskManager/EXTaskServiceInterface.h +1 -0
  120. package/ios/JSI/{JSIConversions.h → EXJSIConversions.h} +5 -0
  121. package/ios/JSI/{JSIConversions.mm → EXJSIConversions.mm} +21 -1
  122. package/ios/JSI/{JSIInstaller.h → EXJSIInstaller.h} +10 -0
  123. package/ios/JSI/EXJSIInstaller.mm +17 -0
  124. package/ios/JSI/EXJSIUtils.h +19 -0
  125. package/ios/JSI/EXJSIUtils.mm +89 -0
  126. package/ios/JSI/EXJavaScriptObject.h +97 -0
  127. package/ios/JSI/EXJavaScriptObject.mm +121 -0
  128. package/ios/JSI/EXJavaScriptRuntime.h +73 -0
  129. package/ios/JSI/EXJavaScriptRuntime.mm +153 -0
  130. package/ios/JSI/EXJavaScriptValue.h +57 -0
  131. package/ios/JSI/EXJavaScriptValue.mm +166 -0
  132. package/ios/JSI/ExpoModulesHostObject.h +33 -0
  133. package/ios/JSI/ExpoModulesHostObject.mm +41 -0
  134. package/ios/JSI/JavaScriptRuntime.swift +32 -0
  135. package/ios/JSI/JavaScriptValue.swift +94 -0
  136. package/ios/ModuleRegistryAdapter/EXModuleRegistryAdapter.m +3 -23
  137. package/ios/NativeModulesProxy/EXNativeModulesProxy.h +2 -2
  138. package/ios/NativeModulesProxy/EXNativeModulesProxy.mm +101 -75
  139. package/ios/RCTComponentData+Privates.h +12 -0
  140. package/ios/ReactDelegates/EXReactCompatibleHelpers.h +18 -0
  141. package/ios/ReactDelegates/EXReactCompatibleHelpers.m +19 -0
  142. package/ios/ReactDelegates/ExpoReactDelegate.swift +3 -3
  143. package/ios/ReactDelegates/ExpoReactDelegateHandler.swift +4 -4
  144. package/ios/ReactDelegates/ModulePriorities.swift +1 -1
  145. package/ios/Swift/AppContext.swift +57 -4
  146. package/ios/Swift/Arguments/AnyArgumentType.swift +1 -1
  147. package/ios/Swift/Arguments/ArgumentType.swift +4 -0
  148. package/ios/Swift/Arguments/Convertibles.swift +13 -13
  149. package/ios/Swift/Arguments/Types/EnumArgumentType.swift +11 -17
  150. package/ios/Swift/Arguments/Types/PromiseArgumentType.swift +1 -1
  151. package/ios/Swift/Arguments/Types/RawArgumentType.swift +2 -2
  152. package/ios/Swift/Conversions.swift +51 -56
  153. package/ios/Swift/EventListener.swift +8 -10
  154. package/ios/Swift/Events/Callback.swift +66 -0
  155. package/ios/Swift/Events/Event.swift +43 -0
  156. package/ios/Swift/Exceptions/ChainableException.swift +51 -0
  157. package/ios/Swift/{CodedError.swift → Exceptions/CodedError.swift} +1 -12
  158. package/ios/Swift/Exceptions/Exception.swift +62 -0
  159. package/ios/Swift/Exceptions/ExceptionOrigin.swift +28 -0
  160. package/ios/Swift/Exceptions/GenericException.swift +20 -0
  161. package/ios/Swift/Exceptions/UnexpectedException.swift +16 -0
  162. package/ios/Swift/Functions/AnyFunction.swift +16 -1
  163. package/ios/Swift/Functions/AsyncFunctionComponent.swift +182 -0
  164. package/ios/Swift/Functions/ConcreteFunction.swift +52 -59
  165. package/ios/Swift/Functions/SyncFunctionComponent.swift +181 -0
  166. package/ios/Swift/JavaScriptUtils.swift +99 -0
  167. package/ios/Swift/ModuleHolder.swift +69 -18
  168. package/ios/Swift/ModuleRegistry.swift +4 -1
  169. package/ios/Swift/Modules/AnyModule.swift +0 -1
  170. package/ios/Swift/Modules/ModuleDefinition.swift +4 -13
  171. package/ios/Swift/Modules/ModuleDefinitionBuilder.swift +0 -1
  172. package/ios/Swift/Modules/ModuleDefinitionComponents.swift +54 -220
  173. package/ios/Swift/ModulesProvider.swift +3 -11
  174. package/ios/Swift/Objects/ObjectDefinition.swift +30 -0
  175. package/ios/Swift/Objects/ObjectDefinitionComponents.swift +257 -0
  176. package/ios/Swift/Promise.swift +8 -3
  177. package/ios/Swift/Records/AnyField.swift +7 -0
  178. package/ios/Swift/Records/Field.swift +24 -19
  179. package/ios/Swift/Records/FieldOption.swift +1 -1
  180. package/ios/Swift/Records/Record.swift +12 -4
  181. package/ios/Swift/SwiftInteropBridge.swift +53 -15
  182. package/ios/Swift/Views/AnyViewProp.swift +1 -1
  183. package/ios/Swift/Views/ComponentData.swift +96 -0
  184. package/ios/Swift/Views/ConcreteViewProp.swift +6 -8
  185. package/ios/Swift/Views/ExpoView.swift +8 -0
  186. package/ios/Swift/Views/ViewFactory.swift +1 -1
  187. package/ios/Swift/Views/ViewManagerDefinition.swift +23 -2
  188. package/ios/Swift/Views/ViewManagerDefinitionBuilder.swift +0 -1
  189. package/ios/Swift/Views/ViewManagerDefinitionComponents.swift +49 -0
  190. package/ios/Swift/Views/ViewModuleWrapper.swift +5 -2
  191. package/ios/Swift.h +5 -0
  192. package/ios/Tests/ArgumentTypeSpec.swift +5 -7
  193. package/ios/Tests/ConstantsSpec.swift +6 -7
  194. package/ios/Tests/ConvertiblesSpec.swift +35 -36
  195. package/ios/Tests/ExceptionsSpec.swift +111 -0
  196. package/ios/Tests/ExpoModulesSpec.swift +75 -0
  197. package/ios/Tests/FunctionSpec.swift +21 -25
  198. package/ios/Tests/FunctionWithConvertiblesSpec.swift +4 -5
  199. package/ios/Tests/JavaScriptObjectSpec.swift +97 -0
  200. package/ios/Tests/JavaScriptRuntimeSpec.swift +94 -0
  201. package/ios/Tests/Mocks/ModuleMocks.swift +1 -1
  202. package/ios/Tests/Mocks/ModulesProviderMock.swift +0 -1
  203. package/ios/Tests/ModuleEventListenersSpec.swift +16 -17
  204. package/ios/Tests/ModuleRegistrySpec.swift +2 -3
  205. package/ios/Tests/RecordSpec.swift +9 -20
  206. package/package.json +3 -3
  207. package/src/NativeModulesProxy.native.ts +2 -22
  208. package/src/NativeModulesProxy.types.ts +0 -8
  209. package/src/NativeViewManagerAdapter.native.tsx +12 -28
  210. package/src/index.ts +4 -0
  211. package/src/requireNativeModule.ts +29 -0
  212. package/src/sweet/NativeErrorManager.ts +2 -0
  213. package/src/sweet/setUpErrorManager.fx.ts +12 -0
  214. package/android/src/main/java/expo/modules/kotlin/events/KEventEmitterWrapper.kt +0 -26
  215. package/android/src/main/java/expo/modules/kotlin/methods/Method.kt +0 -14
  216. package/android/src/main/java/expo/modules/kotlin/methods/PromiseMethod.kt +0 -15
  217. package/ios/JSI/ExpoModulesProxySpec.h +0 -24
  218. package/ios/JSI/ExpoModulesProxySpec.mm +0 -135
  219. package/ios/JSI/JSIInstaller.mm +0 -22
@@ -0,0 +1,139 @@
1
+ package expo.modules.kotlin.records
2
+
3
+ import expo.modules.kotlin.exception.ValidationException
4
+
5
+ interface FieldValidator<T> {
6
+ fun validate(value: T)
7
+ }
8
+
9
+ class NumericRangeValidator<T : Comparable<T>>(
10
+ private val from: T,
11
+ private val to: T,
12
+ private val fromInclusive: Boolean,
13
+ private val toInclusive: Boolean
14
+ ) : FieldValidator<T> {
15
+ override fun validate(value: T) {
16
+ if (
17
+ value < from ||
18
+ to < value ||
19
+ value == from && !fromInclusive ||
20
+ value == to && !toInclusive
21
+ ) {
22
+ throw ValidationException("Value should be in range $from ${if (fromInclusive) "<=" else "<"} 'value' ${if (toInclusive) "<=" else "<"} $to, got $value")
23
+ }
24
+ }
25
+ }
26
+
27
+ class IsNotEmptyCollectionValidator : FieldValidator<Collection<*>> {
28
+ override fun validate(value: Collection<*>) {
29
+ if (value.isEmpty()) {
30
+ throw ValidationException("Collection is empty")
31
+ }
32
+ }
33
+ }
34
+
35
+ class IsNotEmptyIntArrayValidator : FieldValidator<IntArray> {
36
+ override fun validate(value: IntArray) {
37
+ if (value.isEmpty()) {
38
+ throw ValidationException("Array is empty")
39
+ }
40
+ }
41
+ }
42
+
43
+ class IsNotEmptyFloatArrayValidator : FieldValidator<FloatArray> {
44
+ override fun validate(value: FloatArray) {
45
+ if (value.isEmpty()) {
46
+ throw ValidationException("Array is empty")
47
+ }
48
+ }
49
+ }
50
+
51
+ class IsNotEmptyDoubleArrayValidator : FieldValidator<DoubleArray> {
52
+ override fun validate(value: DoubleArray) {
53
+ if (value.isEmpty()) {
54
+ throw ValidationException("Array is empty")
55
+ }
56
+ }
57
+ }
58
+
59
+ class IsNotEmptyArrayValidator : FieldValidator<Array<*>> {
60
+ override fun validate(value: Array<*>) {
61
+ if (value.isEmpty()) {
62
+ throw ValidationException("Array is empty")
63
+ }
64
+ }
65
+ }
66
+
67
+ class CollectionSizeValidator(
68
+ private val min: Int,
69
+ private val max: Int
70
+ ) : FieldValidator<Collection<*>> {
71
+ override fun validate(value: Collection<*>) {
72
+ if (value.size < min || value.size > max) {
73
+ throw ValidationException("Number of elements in the collection should be between $min and $max, got ${value.size}")
74
+ }
75
+ }
76
+ }
77
+
78
+ class IntArraySizeValidator(
79
+ private val min: Int,
80
+ private val max: Int
81
+ ) : FieldValidator<IntArray> {
82
+ override fun validate(value: IntArray) {
83
+ if (value.size < min || value.size > max) {
84
+ throw ValidationException("Number of elements in the array should be between $min and $max, got ${value.size}")
85
+ }
86
+ }
87
+ }
88
+
89
+ class DoubleArraySizeValidator(
90
+ private val min: Int,
91
+ private val max: Int
92
+ ) : FieldValidator<DoubleArray> {
93
+ override fun validate(value: DoubleArray) {
94
+ if (value.size < min || value.size > max) {
95
+ throw ValidationException("Number of elements in the array should be between $min and $max, got ${value.size}")
96
+ }
97
+ }
98
+ }
99
+
100
+ class FloatArraySizeValidator(
101
+ private val min: Int,
102
+ private val max: Int
103
+ ) : FieldValidator<FloatArray> {
104
+ override fun validate(value: FloatArray) {
105
+ if (value.size < min || value.size > max) {
106
+ throw ValidationException("Number of elements in the array should be between $min and $max, got ${value.size}")
107
+ }
108
+ }
109
+ }
110
+
111
+ class ArraySizeValidator(
112
+ private val min: Int,
113
+ private val max: Int
114
+ ) : FieldValidator<Array<*>> {
115
+ override fun validate(value: Array<*>) {
116
+ if (value.size < min || value.size > max) {
117
+ throw ValidationException("Number of elements in the array should be between $min and $max, got ${value.size}")
118
+ }
119
+ }
120
+ }
121
+
122
+ class StringSizeValidator(
123
+ private val min: Int,
124
+ private val max: Int
125
+ ) : FieldValidator<String> {
126
+ override fun validate(value: String) {
127
+ if (value.length < min || value.length > max) {
128
+ throw ValidationException("Length of the string should be between $min and $max, got $value (${value.length} characters)")
129
+ }
130
+ }
131
+ }
132
+
133
+ class RegexValidator(private val regex: Regex) : FieldValidator<CharSequence> {
134
+ override fun validate(value: CharSequence) {
135
+ if (!regex.matches(value)) {
136
+ throw ValidationException("Provided string $value didn't match regex $regex")
137
+ }
138
+ }
139
+ }
@@ -3,46 +3,80 @@ package expo.modules.kotlin.records
3
3
  import com.facebook.react.bridge.Dynamic
4
4
  import expo.modules.kotlin.allocators.ObjectConstructor
5
5
  import expo.modules.kotlin.allocators.ObjectConstructorFactory
6
+ import expo.modules.kotlin.exception.FieldCastException
7
+ import expo.modules.kotlin.exception.FieldRequiredException
8
+ import expo.modules.kotlin.exception.RecordCastException
9
+ import expo.modules.kotlin.exception.exceptionDecorator
10
+ import expo.modules.kotlin.recycle
6
11
  import expo.modules.kotlin.types.TypeConverter
7
12
  import expo.modules.kotlin.types.TypeConverterProvider
8
13
  import kotlin.reflect.KClass
14
+ import kotlin.reflect.KProperty1
9
15
  import kotlin.reflect.KType
16
+ import kotlin.reflect.full.createInstance
10
17
  import kotlin.reflect.full.findAnnotation
11
18
  import kotlin.reflect.full.memberProperties
12
19
  import kotlin.reflect.jvm.javaField
13
20
 
14
- // TODO(@lukmccall): create all converters during initialization
15
21
  class RecordTypeConverter<T : Record>(
16
22
  private val converterProvider: TypeConverterProvider,
17
23
  val type: KType,
18
24
  ) : TypeConverter<T>(type.isMarkedNullable) {
19
25
  private val objectConstructorFactory = ObjectConstructorFactory()
26
+ private val propertyDescriptors: Map<KProperty1<out Any, *>, PropertyDescriptor> =
27
+ (type.classifier as KClass<*>)
28
+ .memberProperties
29
+ .map { property ->
30
+ val fieldAnnotation = property.findAnnotation<Field>() ?: return@map null
31
+ val typeConverter = converterProvider.obtainTypeConverter(property.returnType)
32
+
33
+ return@map property to PropertyDescriptor(
34
+ typeConverter,
35
+ fieldAnnotation,
36
+ isRequired = property.findAnnotation<Required>() != null,
37
+ validators = getValidators(property)
38
+ )
39
+ }
40
+ .filterNotNull()
41
+ .toMap()
20
42
 
21
- override fun convertNonOptional(value: Dynamic): T {
43
+ override fun convertNonOptional(value: Dynamic): T = exceptionDecorator({ cause -> RecordCastException(type, cause) }) {
22
44
  val jsMap = value.asMap()
23
45
 
24
46
  val kClass = type.classifier as KClass<*>
25
47
  val instance = getObjectConstructor(kClass.java).construct()
26
48
 
27
- kClass
28
- .memberProperties
29
- .map { property ->
30
- val filedInformation = property.findAnnotation<Field>() ?: return@map
31
- val jsKey = filedInformation.key.takeUnless { it == "" } ?: property.name
49
+ propertyDescriptors
50
+ .forEach { (property, descriptor) ->
51
+ val jsKey = descriptor.fieldAnnotation.key.takeUnless { it.isBlank() } ?: property.name
32
52
 
33
53
  if (!jsMap.hasKey(jsKey)) {
34
- // TODO(@lukmccall): handle required keys
35
- return@map
54
+ if (descriptor.isRequired) {
55
+ throw FieldRequiredException(property)
56
+ }
57
+
58
+ return@forEach
36
59
  }
37
60
 
38
- val value = jsMap.getDynamic(jsKey)
39
- val javaField = property.javaField!!
61
+ jsMap.getDynamic(jsKey).recycle {
62
+ val javaField = property.javaField!!
40
63
 
41
- val elementConverter = converterProvider.obtainTypeConverter(property.returnType)
42
- val casted = elementConverter.convert(value)
64
+ val casted = exceptionDecorator({ cause -> FieldCastException(property.name, property.returnType, type, cause) }) {
65
+ descriptor.typeConverter.convert(this)
66
+ }
43
67
 
44
- javaField.isAccessible = true
45
- javaField.set(instance, casted)
68
+ if (casted != null) {
69
+ descriptor
70
+ .validators
71
+ .forEach { validator ->
72
+ @Suppress("UNCHECKED_CAST")
73
+ (validator as FieldValidator<Any>).validate(casted)
74
+ }
75
+ }
76
+
77
+ javaField.isAccessible = true
78
+ javaField.set(instance, casted)
79
+ }
46
80
  }
47
81
 
48
82
  @Suppress("UNCHECKED_CAST")
@@ -52,4 +86,26 @@ class RecordTypeConverter<T : Record>(
52
86
  private fun <T> getObjectConstructor(clazz: Class<T>): ObjectConstructor<T> {
53
87
  return objectConstructorFactory.get(clazz)
54
88
  }
89
+
90
+ private fun getValidators(property: KProperty1<out Any, *>): List<FieldValidator<*>> {
91
+ return property
92
+ .annotations
93
+ .map findValidators@{ annotation ->
94
+ val binderAnnotation = annotation.annotationClass.findAnnotation<BindUsing>()
95
+ ?: return@findValidators null
96
+ annotation to binderAnnotation
97
+ }
98
+ .filterNotNull()
99
+ .map { (annotation, binderAnnotation) ->
100
+ val binderInstance = binderAnnotation.binder.createInstance() as ValidationBinder
101
+ binderInstance.bind(annotation, property.returnType)
102
+ }
103
+ }
104
+
105
+ private data class PropertyDescriptor(
106
+ val typeConverter: TypeConverter<*>,
107
+ val fieldAnnotation: Field,
108
+ val isRequired: Boolean,
109
+ val validators: List<FieldValidator<*>>
110
+ )
55
111
  }
@@ -0,0 +1,5 @@
1
+ package expo.modules.kotlin.records
2
+
3
+ @Target(AnnotationTarget.PROPERTY)
4
+ @Retention(AnnotationRetention.RUNTIME)
5
+ annotation class Required
@@ -0,0 +1,110 @@
1
+ package expo.modules.kotlin.records
2
+
3
+ import kotlin.reflect.KClass
4
+ import kotlin.reflect.KType
5
+ import kotlin.reflect.full.createType
6
+ import kotlin.reflect.full.isSubclassOf
7
+
8
+ interface ValidationBinder {
9
+ fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*>
10
+ }
11
+
12
+ @Retention(AnnotationRetention.RUNTIME)
13
+ @Target(AnnotationTarget.ANNOTATION_CLASS)
14
+ annotation class BindUsing(val binder: KClass<*>)
15
+
16
+ internal class IntRangeBinder : ValidationBinder {
17
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
18
+ val rangeAnnotation = annotation as IntRange
19
+ return NumericRangeValidator(
20
+ rangeAnnotation.from,
21
+ rangeAnnotation.to,
22
+ rangeAnnotation.fromInclusive,
23
+ rangeAnnotation.toInclusive
24
+ )
25
+ }
26
+ }
27
+
28
+ internal class LongRangeBinder : ValidationBinder {
29
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
30
+ val rangeAnnotation = annotation as LongRange
31
+ return NumericRangeValidator(
32
+ rangeAnnotation.from,
33
+ rangeAnnotation.to,
34
+ rangeAnnotation.fromInclusive,
35
+ rangeAnnotation.toInclusive
36
+ )
37
+ }
38
+ }
39
+
40
+ internal class FloatRangeBinder : ValidationBinder {
41
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
42
+ val rangeAnnotation = annotation as FloatRange
43
+ return NumericRangeValidator(
44
+ rangeAnnotation.from,
45
+ rangeAnnotation.to,
46
+ rangeAnnotation.fromInclusive,
47
+ rangeAnnotation.toInclusive
48
+ )
49
+ }
50
+ }
51
+
52
+ internal class DoubleRangeBinder : ValidationBinder {
53
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
54
+ val rangeAnnotation = annotation as DoubleRange
55
+ return NumericRangeValidator(
56
+ rangeAnnotation.from,
57
+ rangeAnnotation.to,
58
+ rangeAnnotation.fromInclusive,
59
+ rangeAnnotation.toInclusive
60
+ )
61
+ }
62
+ }
63
+
64
+ internal class IsCollectionNotEmptyBinder : ValidationBinder {
65
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
66
+ assert(annotation is IsNotEmpty)
67
+
68
+ when (fieldType) {
69
+ IntArray::class.createType() -> return IsNotEmptyIntArrayValidator()
70
+ DoubleArray::class.createType() -> return IsNotEmptyDoubleArrayValidator()
71
+ FloatArray::class.createType() -> return IsNotEmptyFloatArrayValidator()
72
+ }
73
+
74
+ val kClass = fieldType.classifier as KClass<*>
75
+ if (kClass.isSubclassOf(Array::class) || kClass.java.isArray) {
76
+ return IsNotEmptyArrayValidator()
77
+ }
78
+
79
+ return IsNotEmptyCollectionValidator()
80
+ }
81
+ }
82
+
83
+ internal class SizeBinder : ValidationBinder {
84
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
85
+ val sizeAnnotation = annotation as Size
86
+
87
+ when (fieldType) {
88
+ IntArray::class.createType() -> return IntArraySizeValidator(sizeAnnotation.min, sizeAnnotation.max)
89
+ DoubleArray::class.createType() -> return DoubleArraySizeValidator(sizeAnnotation.min, sizeAnnotation.max)
90
+ FloatArray::class.createType() -> return FloatArraySizeValidator(sizeAnnotation.min, sizeAnnotation.max)
91
+ }
92
+
93
+ val kClass = fieldType.classifier as KClass<*>
94
+
95
+ if (kClass.isSubclassOf(String::class)) {
96
+ return StringSizeValidator(sizeAnnotation.min, sizeAnnotation.max)
97
+ } else if (kClass.isSubclassOf(Array::class) || kClass.java.isArray) {
98
+ return ArraySizeValidator(sizeAnnotation.min, sizeAnnotation.max)
99
+ }
100
+
101
+ return CollectionSizeValidator(sizeAnnotation.min, sizeAnnotation.max)
102
+ }
103
+ }
104
+
105
+ internal class RegexBinder : ValidationBinder {
106
+ override fun bind(annotation: Annotation, fieldType: KType): FieldValidator<*> {
107
+ val regexAnnotation = annotation as RegularExpression
108
+ return RegexValidator(regexAnnotation.regex.toRegex())
109
+ }
110
+ }
@@ -0,0 +1,61 @@
1
+ package expo.modules.kotlin.records
2
+
3
+ @Retention(AnnotationRetention.RUNTIME)
4
+ @Target(AnnotationTarget.PROPERTY, AnnotationTarget.TYPE, AnnotationTarget.VALUE_PARAMETER)
5
+ @BindUsing(IntRangeBinder::class)
6
+ annotation class IntRange(
7
+ val from: Int,
8
+ val to: Int,
9
+ val fromInclusive: Boolean = true,
10
+ val toInclusive: Boolean = true,
11
+ )
12
+
13
+ @Retention(AnnotationRetention.RUNTIME)
14
+ @Target(AnnotationTarget.PROPERTY)
15
+ @BindUsing(LongRangeBinder::class)
16
+ annotation class LongRange(
17
+ val from: Long,
18
+ val to: Long,
19
+ val fromInclusive: Boolean = true,
20
+ val toInclusive: Boolean = true
21
+ )
22
+
23
+ @Retention(AnnotationRetention.RUNTIME)
24
+ @Target(AnnotationTarget.PROPERTY)
25
+ @BindUsing(FloatRangeBinder::class)
26
+ annotation class FloatRange(
27
+ val from: Float,
28
+ val to: Float,
29
+ val fromInclusive: Boolean = true,
30
+ val toInclusive: Boolean = true
31
+ )
32
+
33
+ @Retention(AnnotationRetention.RUNTIME)
34
+ @Target(AnnotationTarget.PROPERTY)
35
+ @BindUsing(DoubleRangeBinder::class)
36
+ annotation class DoubleRange(
37
+ val from: Double,
38
+ val to: Double,
39
+ val fromInclusive: Boolean = true,
40
+ val toInclusive: Boolean = true
41
+ )
42
+
43
+ @Retention(AnnotationRetention.RUNTIME)
44
+ @Target(AnnotationTarget.PROPERTY)
45
+ @BindUsing(IsCollectionNotEmptyBinder::class)
46
+ annotation class IsNotEmpty
47
+
48
+ @Retention(AnnotationRetention.RUNTIME)
49
+ @Target(AnnotationTarget.PROPERTY)
50
+ @BindUsing(SizeBinder::class)
51
+ annotation class Size(
52
+ val min: Int = 0,
53
+ val max: Int = Int.MAX_VALUE
54
+ )
55
+
56
+ @Retention(AnnotationRetention.RUNTIME)
57
+ @Target(AnnotationTarget.PROPERTY)
58
+ @BindUsing(RegexBinder::class)
59
+ annotation class RegularExpression(
60
+ val regex: String
61
+ )
@@ -1,16 +1,18 @@
1
1
  package expo.modules.kotlin.types
2
2
 
3
3
  import com.facebook.react.bridge.Dynamic
4
+ import expo.modules.kotlin.exception.CollectionElementCastException
5
+ import expo.modules.kotlin.exception.exceptionDecorator
4
6
  import expo.modules.kotlin.recycle
5
7
  import kotlin.reflect.KClass
6
8
  import kotlin.reflect.KType
7
9
 
8
10
  class ArrayTypeConverter(
9
11
  converterProvider: TypeConverterProvider,
10
- private val type: KType,
11
- ) : TypeConverter<Array<*>>(type.isMarkedNullable) {
12
+ private val arrayType: KType,
13
+ ) : TypeConverter<Array<*>>(arrayType.isMarkedNullable) {
12
14
  private val arrayElementConverter = converterProvider.obtainTypeConverter(
13
- requireNotNull(type.arguments.first().type) {
15
+ requireNotNull(arrayType.arguments.first().type) {
14
16
  "The array type should contain the type of the elements."
15
17
  }
16
18
  )
@@ -22,7 +24,11 @@ class ArrayTypeConverter(
22
24
  array[i] = jsArray
23
25
  .getDynamic(i)
24
26
  .recycle {
25
- arrayElementConverter.convert(this)
27
+ exceptionDecorator({ cause ->
28
+ CollectionElementCastException(arrayType, arrayType.arguments.first().type!!, type, cause)
29
+ }) {
30
+ arrayElementConverter.convert(this)
31
+ }
26
32
  }
27
33
  }
28
34
  return array
@@ -37,7 +43,7 @@ class ArrayTypeConverter(
37
43
  @Suppress("UNCHECKED_CAST")
38
44
  private fun createTypedArray(size: Int): Array<Any?> {
39
45
  return java.lang.reflect.Array.newInstance(
40
- (type.arguments.first().type!!.classifier as KClass<*>).java,
46
+ (arrayType.arguments.first().type!!.classifier as KClass<*>).java,
41
47
  size
42
48
  ) as Array<Any?>
43
49
  }
@@ -0,0 +1,35 @@
1
+ package expo.modules.kotlin.types
2
+
3
+ import android.os.Bundle
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.WritableArray
6
+ import com.facebook.react.bridge.WritableMap
7
+ import expo.modules.kotlin.records.Record
8
+
9
+ object JSTypeConverter {
10
+ interface ContainerProvider {
11
+ fun createMap(): WritableMap
12
+ fun createArray(): WritableArray
13
+ }
14
+
15
+ internal object DefaultContainerProvider : ContainerProvider {
16
+ override fun createMap(): WritableMap = Arguments.createMap()
17
+ override fun createArray(): WritableArray = Arguments.createArray()
18
+ }
19
+
20
+ fun convertToJSValue(value: Any?, containerProvider: ContainerProvider = DefaultContainerProvider): Any? {
21
+ return when (value) {
22
+ null, is Unit -> null
23
+ is Bundle -> value.toJSValue(containerProvider)
24
+ is Iterable<*> -> value.toJSValue(containerProvider)
25
+ is Array<*> -> value.toJSValue(containerProvider)
26
+ is IntArray -> value.toJSValue(containerProvider)
27
+ is FloatArray -> value.toJSValue(containerProvider)
28
+ is DoubleArray -> value.toJSValue(containerProvider)
29
+ is Map<*, *> -> value.toJSValue(containerProvider)
30
+ is Enum<*> -> value.toJSValue()
31
+ is Record -> value.toJSValue(containerProvider)
32
+ else -> value
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,148 @@
1
+ package expo.modules.kotlin.types
2
+
3
+ import android.os.Bundle
4
+ import com.facebook.react.bridge.ReadableArray
5
+ import com.facebook.react.bridge.ReadableMap
6
+ import com.facebook.react.bridge.WritableArray
7
+ import com.facebook.react.bridge.WritableMap
8
+ import expo.modules.kotlin.records.Field
9
+ import expo.modules.kotlin.records.Record
10
+ import kotlin.reflect.KProperty1
11
+ import kotlin.reflect.full.declaredMemberProperties
12
+ import kotlin.reflect.full.findAnnotation
13
+ import kotlin.reflect.full.memberProperties
14
+ import kotlin.reflect.full.primaryConstructor
15
+ import kotlin.reflect.jvm.isAccessible
16
+
17
+ fun Record.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableMap {
18
+ val result = containerProvider.createMap()
19
+
20
+ javaClass
21
+ .kotlin
22
+ .memberProperties.map { property ->
23
+ val fieldInformation = property.findAnnotation<Field>() ?: return@map
24
+ val jsKey = fieldInformation.key.takeUnless { it == "" } ?: property.name
25
+
26
+ property.isAccessible = true
27
+
28
+ val value = property.get(this)
29
+ val convertedValue = JSTypeConverter.convertToJSValue(value, containerProvider)
30
+ result.putGeneric(jsKey, convertedValue)
31
+ }
32
+
33
+ return result
34
+ }
35
+
36
+ fun Bundle.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableMap {
37
+ val result = containerProvider.createMap()
38
+
39
+ for (key in keySet()) {
40
+ val value = get(key)
41
+ val convertedValue = JSTypeConverter.convertToJSValue(value, containerProvider)
42
+ result.putGeneric(key, convertedValue)
43
+ }
44
+
45
+ return result
46
+ }
47
+
48
+ fun <K, V> Map<K, V>.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableMap {
49
+ val result = containerProvider.createMap()
50
+
51
+ for ((key, value) in entries) {
52
+ val convertedValue = JSTypeConverter.convertToJSValue(value, containerProvider)
53
+ result.putGeneric(key.toString(), convertedValue)
54
+ }
55
+
56
+ return result
57
+ }
58
+
59
+ fun <T> Iterable<T>.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableArray {
60
+ val result = containerProvider.createArray()
61
+
62
+ for (value in this) {
63
+ val convertedValue = JSTypeConverter.convertToJSValue(value, containerProvider)
64
+ result.putGeneric(convertedValue)
65
+ }
66
+
67
+ return result
68
+ }
69
+
70
+ fun <T> Array<T>.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableArray {
71
+ val result = containerProvider.createArray()
72
+
73
+ for (value in this) {
74
+ val convertedValue = JSTypeConverter.convertToJSValue(value, containerProvider)
75
+ result.putGeneric(convertedValue)
76
+ }
77
+
78
+ return result
79
+ }
80
+
81
+ fun IntArray.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableArray {
82
+ return containerProvider.createArray().also {
83
+ for (value in this) {
84
+ it.pushInt(value)
85
+ }
86
+ }
87
+ }
88
+
89
+ fun FloatArray.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableArray {
90
+ return containerProvider.createArray().also {
91
+ for (value in this) {
92
+ it.pushDouble(value.toDouble())
93
+ }
94
+ }
95
+ }
96
+
97
+ fun DoubleArray.toJSValue(containerProvider: JSTypeConverter.ContainerProvider): WritableArray {
98
+ return containerProvider.createArray().also {
99
+ for (value in this) {
100
+ it.pushDouble(value)
101
+ }
102
+ }
103
+ }
104
+
105
+ fun Enum<*>.toJSValue(): Any? {
106
+ val primaryConstructor = requireNotNull(this::class.primaryConstructor) {
107
+ "Cannot convert enum without the primary constructor to js value"
108
+ }
109
+
110
+ if (primaryConstructor.parameters.isEmpty()) {
111
+ return this.name
112
+ } else if (primaryConstructor.parameters.size == 1) {
113
+ val parameterName = primaryConstructor.parameters.first().name!!
114
+ @Suppress("UNCHECKED_CAST")
115
+ val parameterProperty = this::class.declaredMemberProperties
116
+ .find { it.name == parameterName } as KProperty1<Enum<*>, *>
117
+
118
+ return parameterProperty.get(this)
119
+ }
120
+
121
+ throw IllegalStateException("Enum '$javaClass' cannot be used as return type (incompatible with JS)")
122
+ }
123
+
124
+ internal fun WritableMap.putGeneric(key: String, value: Any?) {
125
+ when (value) {
126
+ null, is Unit -> putNull(key)
127
+ is ReadableArray -> putArray(key, value)
128
+ is ReadableMap -> putMap(key, value)
129
+ is String -> putString(key, value)
130
+ is Int -> putInt(key, value)
131
+ is Number -> putDouble(key, value.toDouble())
132
+ is Boolean -> putBoolean(key, value)
133
+ else -> throw IllegalArgumentException("Could not put '${value.javaClass}' to WritableMap")
134
+ }
135
+ }
136
+
137
+ internal fun WritableArray.putGeneric(value: Any?) {
138
+ when (value) {
139
+ null, is Unit -> pushNull()
140
+ is ReadableArray -> pushArray(value)
141
+ is ReadableMap -> pushMap(value)
142
+ is String -> pushString(value)
143
+ is Int -> pushInt(value)
144
+ is Number -> pushDouble(value.toDouble())
145
+ is Boolean -> pushBoolean(value)
146
+ else -> throw IllegalArgumentException("Could not put '${value.javaClass}' to WritableArray")
147
+ }
148
+ }