From 56c2f8b9b71085e14b78169ce3dd55b6d08d40a8 Mon Sep 17 00:00:00 2001 From: Adam Essenmacher Date: Sat, 11 Apr 2026 04:30:02 -0400 Subject: [PATCH] Add Firestore vector value binding --- .../Firebase/CloudFirestore/ApiDefinition.cs | 18 +++ .../FirebaseRuntimeDriftCases.cs | 105 ++++++++++++++++++ .../runtime-drift-cases.json | 11 ++ 3 files changed, 134 insertions(+) diff --git a/source/Firebase/CloudFirestore/ApiDefinition.cs b/source/Firebase/CloudFirestore/ApiDefinition.cs index d2be7c45..23f4501c 100644 --- a/source/Firebase/CloudFirestore/ApiDefinition.cs +++ b/source/Firebase/CloudFirestore/ApiDefinition.cs @@ -337,6 +337,24 @@ interface FieldValue [Export ("fieldValueForIntegerIncrement:")] FieldValue FromIntegerIncrement (long l); + // +(FIRVectorValue * _Nonnull)vectorWithArray:(NSArray * _Nonnull)array; + [Static] + [Export ("vectorWithArray:")] + VectorValue VectorWithArray (NSNumber [] array); + } + + // @interface FIRVectorValue : NSObject + [DisableDefaultCtor] + [BaseType (typeof (NSObject), Name = "FIRVectorValue")] + interface VectorValue + { + // @property (atomic, readonly) NSArray * _Nonnull array; + [Export ("array")] + NSNumber [] Array { get; } + + // -(instancetype _Nonnull)initWithArray:(NSArray * _Nonnull)array; + [Export ("initWithArray:")] + NativeHandle Constructor (NSNumber [] array); } // void (^)(id _Nullable result, NSError *_Nullable error) diff --git a/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs b/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs index c721ea24..bfe707e3 100644 --- a/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs +++ b/tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs @@ -48,6 +48,12 @@ using ObjCRuntime; #endif +#if ENABLE_RUNTIME_DRIFT_CASE_CLOUDFIRESTORE_FIELDVALUE_VECTORWITHARRAY +using Firebase.CloudFirestore; +using Foundation; +using ObjCRuntime; +#endif + #if ENABLE_RUNTIME_DRIFT_CASE_CLOUDFUNCTIONS_USEFUNCTIONSEMULATORORIGIN using Firebase.CloudFunctions; using Foundation; @@ -871,6 +877,105 @@ void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEven } #endif +#if ENABLE_RUNTIME_DRIFT_CASE_CLOUDFIRESTORE_FIELDVALUE_VECTORWITHARRAY + static Task VerifyCloudFirestoreFieldValueVectorWithArrayAsync() + { + const string selector = "vectorWithArray:"; + + var signature = typeof(FieldValue).GetMethod( + nameof(FieldValue.VectorWithArray), + BindingFlags.Static | BindingFlags.Public, + binder: null, + types: new[] { typeof(NSNumber[]) }, + modifiers: null); + if (signature is null) + { + throw new InvalidOperationException( + $"Expected managed API '{nameof(FieldValue.VectorWithArray)}({typeof(NSNumber[]).FullName})' was not found."); + } + + if (signature.ReturnType != typeof(VectorValue)) + { + throw new InvalidOperationException( + $"Managed signature regression: expected '{nameof(FieldValue.VectorWithArray)}' to return '{typeof(VectorValue).FullName}', observed '{signature.ReturnType.FullName}'."); + } + + var vectorConstructor = typeof(VectorValue).GetConstructor( + BindingFlags.Instance | BindingFlags.Public, + binder: null, + types: new[] { typeof(NSNumber[]) }, + modifiers: null); + if (vectorConstructor is null) + { + throw new InvalidOperationException( + $"Expected managed API '{nameof(VectorValue)}({typeof(NSNumber[]).FullName})' was not found."); + } + + using var first = NSNumber.FromInt64(1); + using var second = NSNumber.FromInt64(2); + var values = new[] { first, second }; + NSException? marshaledException = null; + MarshalObjectiveCExceptionMode? marshaledExceptionMode = null; + var vectorArrayLength = 0; + + void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEventArgs args) + { + marshaledException ??= args.Exception; + marshaledExceptionMode ??= args.ExceptionMode; + } + + Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException; + try + { + try + { + using var fieldValue = FieldValue.VectorWithArray(values); + if (fieldValue is null) + { + throw new InvalidOperationException( + $"Selector '{selector}' returned null for a valid NSNumber array."); + } + + using var vectorValue = new VectorValue(values); + var vectorArray = vectorValue.Array; + vectorArrayLength = vectorArray.Length; + if (vectorArrayLength != values.Length) + { + throw new InvalidOperationException( + $"VectorValue.Array returned {vectorArrayLength} values, expected {values.Length}."); + } + } + catch (ObjCException ex) + { + throw new InvalidOperationException( + $"Selector '{selector}' should not throw after the missing binding is added, but observed {ex.GetType().FullName}. " + + $"Managed vector argument type: {values.GetType().FullName}. " + + $"Vector value type: {typeof(VectorValue).FullName}. " + + $"NSException.Name: {FormatDetail(marshaledException?.Name?.ToString())}. " + + $"NSException.Reason: {FormatDetail(marshaledException?.Reason)}. " + + $"Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.", + ex); + } + + if (marshaledException is not null) + { + throw new InvalidOperationException( + $"Selector '{selector}' completed, but Runtime.MarshalObjectiveCException captured unexpected NSException.Name '{marshaledException.Name}'. " + + $"Reason: {FormatDetail(marshaledException.Reason)}. Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}."); + } + + return Task.FromResult( + $"Selector '{selector}' returned a VectorValue without ObjC exception after the missing binding was added. " + + $"Managed vector argument type: {values.GetType().FullName}. " + + $"Return type: {signature.ReturnType.FullName}. Vector array length: {vectorArrayLength}."); + } + finally + { + Runtime.MarshalObjectiveCException -= OnMarshalObjectiveCException; + } + } +#endif + #if ENABLE_RUNTIME_DRIFT_CASE_CLOUDFUNCTIONS_USEFUNCTIONSEMULATORORIGIN static Task VerifyCloudFunctionsUseFunctionsEmulatorOriginAsync() { diff --git a/tests/E2E/Firebase.Foundation/runtime-drift-cases.json b/tests/E2E/Firebase.Foundation/runtime-drift-cases.json index 272b67c3..758db920 100644 --- a/tests/E2E/Firebase.Foundation/runtime-drift-cases.json +++ b/tests/E2E/Firebase.Foundation/runtime-drift-cases.json @@ -82,6 +82,17 @@ } ] }, + { + "id": "cloudfirestore-fieldvalue-vectorwitharray", + "method": "VerifyCloudFirestoreFieldValueVectorWithArrayAsync", + "bindingPackage": "AdamE.Firebase.iOS.CloudFirestore", + "packages": [ + { + "id": "AdamE.Firebase.iOS.CloudFirestore", + "version": "12.6.0" + } + ] + }, { "id": "cloudfunctions-usefunctionsemulatororigin", "method": "VerifyCloudFunctionsUseFunctionsEmulatorOriginAsync",