diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index fc97b076fa4..c18ebf6ba4a 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -11,7 +11,7 @@ on: - cron: '0 7 * * *' jobs: - buildup_SpecsReleasing_repo: + buildup_SpecsTesting_repo: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' env: @@ -31,17 +31,17 @@ jobs: oss-bot-access.txt "$bot_token_secret" scripts/decrypt_gha_secret.sh scripts/gha-encrypted/bot-access.txt.gpg \ bot-access.txt "$bot_token_secret" - - name: Update SpecsReleasing repo setup + - name: Update SpecsTesting repo setup run: | ossbotaccess=`cat oss-bot-access.txt` BOT_TOKEN="${ossbotaccess}" test_version="${nightly_version}" sdk_version_config="${GITHUB_WORKSPACE}/scripts/create_spec_repo/RC_firebase_sdk.textproto" local_sdk_repo_dir="${local_sdk_repo_dir}" podspec_repo_branch="${podspec_repo_branch}" scripts/release_testing_setup.sh prerelease_testing - - name: Update SpecsReleasing repo + - name: Update SpecsTesting repo run: | botaccess=`cat bot-access.txt` cd scripts/create_spec_repo/ swift build - pod repo add --silent "${local_repo}" https://"$botaccess"@github.com/FirebasePrivate/SpecsReleasing.git - BOT_TOKEN="${botaccess}" .build/debug/spec-repo-builder --sdk-repo "${local_sdk_repo_dir}" --local-spec-repo-name "${local_repo}" --sdk-repo-name SpecsReleasing --pod-sources 'https://${BOT_TOKEN}@github.com/FirebasePrivate/SpecsReleasing' --pod-sources "https://github.com/firebase/SpecsDev.git" --pod-sources "https://github.com/firebase/SpecsStaging.git" --pod-sources "https://cdn.cocoapods.org/" + pod repo add --silent "${local_repo}" https://"$botaccess"@github.com/Firebase/SpecsTesting.git + BOT_TOKEN="${botaccess}" .build/debug/spec-repo-builder --sdk-repo "${local_sdk_repo_dir}" --local-spec-repo-name "${local_repo}" --sdk-repo-name SpecsTesting --github-account Firebase --pod-sources 'https://${BOT_TOKEN}@github.com/Firebase/SpecsTesting' --pod-sources "https://github.com/firebase/SpecsDev.git" --pod-sources "https://github.com/firebase/SpecsStaging.git" --pod-sources "https://cdn.cocoapods.org/" - name: Clean Artifacts if: ${{ always() }} run: | @@ -52,7 +52,7 @@ jobs: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -86,7 +86,7 @@ jobs: auth_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -120,7 +120,7 @@ jobs: crashlytics_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -163,7 +163,7 @@ jobs: database_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -201,7 +201,7 @@ jobs: dynamiclinks_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -245,7 +245,7 @@ jobs: firestore_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -282,7 +282,7 @@ jobs: functions_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -324,7 +324,7 @@ jobs: inappmessaging_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -364,7 +364,7 @@ jobs: messaging_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -404,7 +404,7 @@ jobs: remoteconfig_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -438,7 +438,7 @@ jobs: storage_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -476,7 +476,7 @@ jobs: performance_quickstart: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' - needs: buildup_SpecsReleasing_repo + needs: buildup_SpecsTesting_repo env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index ba5ef5af038..9059dc33e14 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -117,14 +117,17 @@ jobs: # watchos is disabled since pod lib lint cannot handle test Auth dep even if the dep is only # specified for the other three platforms. target: [ios, tvos, macos] + spec: [ + 'FirebaseStorage.podspec --test-specs=unit', # The integration tests need more set up. + 'FirebaseStorageSwift.podspec --skip-tests' # No current unit tests. + ] steps: - uses: actions/checkout@v2 - name: Setup Bundler run: scripts/setup_bundler.sh - name: Build and test run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseStorage.podspec --skip-tests --platforms=${{ matrix.target }} - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseStorageSwift.podspec --skip-tests --platforms=${{ matrix.target }} + scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.spec }} --platforms=${{ matrix.target }} storage-cron-only: # Don't run on private repo. @@ -134,8 +137,8 @@ jobs: matrix: target: [ios, tvos, macos] flags: [ - '--skip-tests --use-static-frameworks', - '--skip-tests --use-libraries' + '--use-static-frameworks --skip-tests ', + '--use-libraries --skip-tests ' ] needs: pod-lib-lint steps: @@ -145,4 +148,22 @@ jobs: - name: PodLibLint Storage Cron run: | scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseStorage.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseStorage.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} + + storageswift-cron-only: + # Don't run on private repo. + if: github.event_name == 'schedule' && github.repository == 'Firebase/firebase-ios-sdk' + runs-on: macos-latest + strategy: + matrix: + target: [ios, tvos, macos] + flags: [ + '--use-static-frameworks --skip-tests', # Swift pods do not support --use-libraries + ] + needs: pod-lib-lint + steps: + - uses: actions/checkout@v2 + - name: Setup Bundler + run: scripts/setup_bundler.sh + - name: PodLibLint Storage Cron + run: | + scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseStorageSwift.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index d3d53bbca5b..4ff61bb45aa 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -223,7 +223,6 @@ 2EC1C4D202A01A632339A161 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; 2F3740131CC8F8230351B91D /* byte_stream_cpp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 01D10113ECC5B446DB35E96D /* byte_stream_cpp_test.cc */; }; 2F6E23D7888FC82475C63010 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; }; - 2F7D76FF225B550F83B95A72 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 2F8FDF35BBB549A6F4D2118E /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; }; 2FA0BAE32D587DF2EA5EEB97 /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; 3040FD156E1B7C92B0F2A70C /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; @@ -294,6 +293,7 @@ 3DF1AB74036BD8AEF4430FA6 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; 3DFBA7413965F3E6F366E923 /* grpc_unary_call_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964942163E63900EB9CFB /* grpc_unary_call_test.cc */; }; 3E38E4B33855DD6CF7526225 /* bundle_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C2A94EE24E60543F62CC35 /* bundle_serializer_test.cc */; }; + 3EACE718CBD63057CCF7E553 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; 3F3C2DAD9F9326BF789B1C96 /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; 3F4B6300198FD78E7B19BC5A /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; 3F6C9F8A993CF4B0CD51E7F0 /* lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */; }; @@ -421,9 +421,6 @@ 547E9A4522F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; 547E9A4622F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; 547E9A4722F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; - 548180A5228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; - 548180A6228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; - 548180A7228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; @@ -721,7 +718,6 @@ 804B0C6CCE3933CF3948F249 /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; 8077722A6BB175D3108CDC55 /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; 80AB93C807F35539EEC510B2 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; - 8146D5979B2A0B63C79B7AC4 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 814724DE70EFC3DDF439CD78 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; 816E8E62DC163649BA96951C /* EncodableFieldValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769122B7E915007DDFA9 /* EncodableFieldValueTests.swift */; }; 81A6B241E63540900F205817 /* view_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */; }; @@ -946,6 +942,7 @@ B49311BDE5EB6DF811E03C1B /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; }; B4C675BE9030D5C7D19C4D19 /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; B513F723728E923DFF34F60F /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; }; + B54AB4EFBFD2B2FAEE8EF405 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; B576823475FBCA5EFA583F9C /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; B592DB7DB492B1C1D5E67D01 /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; B5AEF7E4EBC29653DEE856A2 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; @@ -984,6 +981,7 @@ BA0BB02821F1949783C8AA50 /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; BA3C0BA8082A6FB2546E47AC /* CodableTimestampTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B65C996438B84DBC7616640 /* CodableTimestampTests.swift */; }; + BA4AD5C3079938B78614FAAC /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; BA9A65BD6D993B2801A3C768 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; BACBBF4AF2F5455673AEAB35 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; @@ -1095,6 +1093,7 @@ D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6BBE42F21262CF400C6A53E /* grpc_stream_test.cc */; }; D6E0E54CD1640E726900828A /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; + D6E5485510AF9AD28F723163 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; D73BBA4AB42940AB187169E3 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; @@ -1182,7 +1181,6 @@ E884336B43BBD1194C17E3C4 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; E8BA7055EDB8B03CC99A528F /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; }; E9430D3EBDAE12E9016B708F /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; - E9B704651F9783B70F2D5E86 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; EA38690795FBAA182A9AA63E /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; }; EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; EAA1962BFBA0EBFBA53B343F /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; @@ -1239,6 +1237,7 @@ F7718C43D3A8FCCDB4BB0071 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; F7B1DF16A9DDFB664EA98EBB /* memory_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */; }; F800F48743D3CB31BA1EBAE7 /* random_access_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 014C60628830D95031574D15 /* random_access_queue_test.cc */; }; + F804B5D1872BEA04ED2AE2C4 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; F8126CD7308A4B8AEC0F30A8 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; F950A371FADCA2F0B73683E0 /* remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7EB299CF85034F09CFD6F3FD /* remote_document_cache_test.cc */; }; F9705E595FC3818F13F6375A /* to_string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B68B1E002213A764008977EF /* to_string_apple_test.mm */; }; @@ -1247,6 +1246,7 @@ FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; FA7837C5CDFB273DE447E447 /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; FA90FA91F7381E5C678EFA30 /* target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C37696557C81A6C2B7271A /* target_cache_test.cc */; }; + FAB7EC9625C3DC0BD81A71C3 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */; }; FABE084FA7DA6E216A41EE80 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; }; FAD97B82766AEC29B7B5A1B7 /* index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */; }; FAE5DA6ED3E1842DC21453EE /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; @@ -1401,7 +1401,6 @@ 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTGoogleTestTests.mm; sourceTree = ""; }; 5477CDE922EE71C8000FCC1E /* append_only_list_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = append_only_list_test.cc; sourceTree = ""; }; 547E9A4122F9EA7300A275E0 /* document_set_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = document_set_test.cc; sourceTree = ""; }; - 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTUserDataConverterTests.mm; sourceTree = ""; }; 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = comparison_test.cc; sourceTree = ""; }; 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = ""; }; 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBSpecTests.mm; sourceTree = ""; }; @@ -1523,6 +1522,7 @@ 620C1427763BA5D3CCFB5A1F /* BridgingHeader.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = BridgingHeader.h; sourceTree = ""; }; 62E103B28B48A81D682A0DE9 /* Pods_Firestore_Example_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64AA92CFA356A2360F3C5646 /* filesystem_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = filesystem_testing.h; sourceTree = ""; }; + 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; path = FSTUserDataReaderTests.mm; sourceTree = ""; }; 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_benchmark.cc; sourceTree = ""; }; @@ -2423,7 +2423,7 @@ B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */, 5492E047202154AA00B64F25 /* FSTAPIHelpers.h */, 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */, - 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */, + 666324E85D5F68788F781776 /* FSTUserDataReaderTests.mm */, ); path = API; sourceTree = ""; @@ -3450,7 +3450,7 @@ F3261CBFC169DB375A0D9492 /* FSTMockDatastore.mm in Sources */, A907244EE37BC32C8D82948E /* FSTSpecTests.mm in Sources */, 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A6228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + F804B5D1872BEA04ED2AE2C4 /* FSTUserDataReaderTests.mm in Sources */, 6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */, 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */, FF3405218188DFCE586FB26B /* app_testing.mm in Sources */, @@ -3642,7 +3642,7 @@ 31D8E3D925FA3F70AA20ACCE /* FSTMockDatastore.mm in Sources */, D5E9954FC1C5ABBC7A180B33 /* FSTSpecTests.mm in Sources */, D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A7228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + FAB7EC9625C3DC0BD81A71C3 /* FSTUserDataReaderTests.mm in Sources */, AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */, 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */, 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */, @@ -3846,7 +3846,7 @@ BFEAC4151D3AA8CE1F92CC2D /* FSTSpecTests.mm in Sources */, F2AB7EACA1B9B1A7046D3995 /* FSTSyncEngineTestDriver.mm in Sources */, D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */, - E9B704651F9783B70F2D5E86 /* FSTUserDataConverterTests.mm in Sources */, + B54AB4EFBFD2B2FAEE8EF405 /* FSTUserDataReaderTests.mm in Sources */, 3B1E27D951407FD237E64D07 /* FirestoreEncoderTests.swift in Sources */, 4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */, 276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */, @@ -4051,7 +4051,7 @@ F609600E9A88A4D44FD1FCEB /* FSTSpecTests.mm in Sources */, 42208EDA18C500BC271B6E95 /* FSTSyncEngineTestDriver.mm in Sources */, 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */, - 8146D5979B2A0B63C79B7AC4 /* FSTUserDataConverterTests.mm in Sources */, + BA4AD5C3079938B78614FAAC /* FSTUserDataReaderTests.mm in Sources */, 5E89B1A5A5430713C79C4854 /* FirestoreEncoderTests.swift in Sources */, 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */, EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */, @@ -4254,7 +4254,7 @@ 5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */, 5492E03520213FFC00B64F25 /* FSTSpecTests.mm in Sources */, 5492E03320213FFC00B64F25 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A5228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + 3EACE718CBD63057CCF7E553 /* FSTUserDataReaderTests.mm in Sources */, 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */, 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */, 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */, @@ -4477,7 +4477,7 @@ 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */, 5FC0157A03EF9820BCCCC4A3 /* FSTSyncEngineTestDriver.mm in Sources */, 5492E07F202154EC00B64F25 /* FSTTransactionTests.mm in Sources */, - 2F7D76FF225B550F83B95A72 /* FSTUserDataConverterTests.mm in Sources */, + D6E5485510AF9AD28F723163 /* FSTUserDataReaderTests.mm in Sources */, 6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */, 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */, 02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */, diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm index 3443e117865..16e205e75e5 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.mm +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm @@ -31,7 +31,7 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/view_snapshot.h" #include "Firestore/core/src/model/document.h" @@ -85,8 +85,8 @@ BOOL fromCache) { absl::optional doc; if (data) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - FieldValue parsed = [converter parsedQueryValue:data]; + FSTUserDataReader *reader = FSTTestUserDataReader(); + FieldValue parsed = [reader parsedQueryValue:data]; doc = Doc(path, version, parsed, hasMutations ? DocumentState::kLocalMutations : DocumentState::kSynced); @@ -115,13 +115,13 @@ NSDictionary *> *docsToAdd, BOOL hasPendingWrites, BOOL fromCache) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); + FSTUserDataReader *reader = FSTTestUserDataReader(); SnapshotMetadata metadata(hasPendingWrites, fromCache); DocumentSet oldDocuments(DocumentComparator::ByKey()); DocumentKeySet mutatedKeys; for (NSString *key in oldDocs) { - FieldValue doc = [converter parsedQueryValue:oldDocs[key]]; + FieldValue doc = [reader parsedQueryValue:oldDocs[key]]; std::string documentKey = util::StringFormat("%s/%s", path, key); oldDocuments = oldDocuments.insert( Doc(documentKey, 1, doc, @@ -134,7 +134,7 @@ DocumentSet newDocuments = oldDocuments; std::vector documentChanges; for (NSString *key in docsToAdd) { - FieldValue doc = [converter parsedQueryValue:docsToAdd[key]]; + FieldValue doc = [reader parsedQueryValue:docsToAdd[key]]; std::string documentKey = util::StringFormat("%s/%s", path, key); Document docToAdd = Doc(documentKey, 1, doc, diff --git a/Firestore/Example/Tests/API/FSTUserDataConverterTests.mm b/Firestore/Example/Tests/API/FSTUserDataReaderTests.mm similarity index 97% rename from Firestore/Example/Tests/API/FSTUserDataConverterTests.mm rename to Firestore/Example/Tests/API/FSTUserDataReaderTests.mm index e8d0496a6b2..56b05185c93 100644 --- a/Firestore/Example/Tests/API/FSTUserDataConverterTests.mm +++ b/Firestore/Example/Tests/API/FSTUserDataReaderTests.mm @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import #import @@ -47,10 +47,10 @@ using firebase::firestore::nanopb::MakeNSData; using firebase::firestore::testutil::Field; -@interface FSTUserDataConverterTests : XCTestCase +@interface FSTUserDataReaderTests : XCTestCase @end -@implementation FSTUserDataConverterTests +@implementation FSTUserDataReaderTests - (void)testConvertsIntegers { NSArray *values = @[ @@ -95,7 +95,7 @@ - (void)testConvertsBooleans { } - (void)testConvertsUnsignedCharToInteger { - // See comments in FSTUserDataConverter regarding handling of signed char. Essentially, signed + // See comments in FSTUserDataReader regarding handling of signed char. Essentially, signed // char has to be treated as boolean. Unsigned chars could conceivably be handled consistently // with signed chars but on arm64 these end up being stored as signed shorts. This forces us to // choose, and it's more useful to support shorts as Integers than it is to treat unsigned char as diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm index 2a000728b17..f8096492bf3 100644 --- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm +++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm @@ -23,7 +23,7 @@ #include #import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index c097538b0e2..891841a8037 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -31,7 +31,7 @@ #include #include -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import "Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" @@ -204,7 +204,7 @@ @implementation FSTSpecTests { BOOL _gcEnabled; size_t _maxConcurrentLimboResolutions; BOOL _networkEnabled; - FSTUserDataConverter *_converter; + FSTUserDataReader *_reader; std::shared_ptr user_executor_; } @@ -230,7 +230,7 @@ - (BOOL)shouldRunWithTags:(NSArray *)tags { } - (void)setUpForSpecWithConfig:(NSDictionary *)config { - _converter = FSTTestUserDataConverter(); + _reader = FSTTestUserDataReader(); std::unique_ptr user_executor = util::Executor::CreateSerial("user executor"); user_executor_ = absl::ShareUniquePtr(std::move(user_executor)); @@ -298,7 +298,7 @@ - (Query)parseQuery:(id)querySpec { for (NSArray *filter in filters) { std::string key = util::MakeString(filter[0]); std::string op = util::MakeString(filter[1]); - FieldValue value = [_converter parsedQueryValue:filter[2]]; + FieldValue value = [_reader parsedQueryValue:filter[2]]; query = query.AddingFilter(Filter(key, op, value)); } } @@ -332,7 +332,7 @@ - (DocumentViewChange)parseChange:(NSDictionary *)jsonDoc ofType:(DocumentViewCh : DocumentState::kSynced); XCTAssert([jsonDoc[@"key"] isKindOfClass:[NSString class]]); - FieldValue data = [_converter parsedQueryValue:jsonDoc[@"value"]]; + FieldValue data = [_reader parsedQueryValue:jsonDoc[@"value"]]; Document doc = Doc(util::MakeString((NSString *)jsonDoc[@"key"]), version.longLongValue, data, documentState); return DocumentViewChange{doc, type}; diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index ce6a58c5217..be9674824d8 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -24,7 +24,7 @@ @class FIRGeoPoint; @class FSTDocumentKeyReference; -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace model = firebase::firestore::model; @@ -93,7 +93,7 @@ NSData *FSTTestData(int bytes, ...); FIRGeoPoint *FSTTestGeoPoint(double latitude, double longitude); /** Creates a user data converter set up for a generic project. */ -FSTUserDataConverter *FSTTestUserDataConverter(); +FSTUserDataReader *FSTTestUserDataReader(); /** * Creates a new NSDateComponents from components. Note that year, month, and day are all diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index bd08184ea0f..bbd44d9072c 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -22,7 +22,7 @@ #include #include -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/user_data.h" #include "Firestore/core/src/model/delete_mutation.h" @@ -94,20 +94,20 @@ return comps; } -FSTUserDataConverter *FSTTestUserDataConverter() { - FSTUserDataConverter *converter = - [[FSTUserDataConverter alloc] initWithDatabaseID:DatabaseId("project") - preConverter:^id _Nullable(id _Nullable input) { - return input; - }]; - return converter; +FSTUserDataReader *FSTTestUserDataReader() { + FSTUserDataReader *reader = + [[FSTUserDataReader alloc] initWithDatabaseID:DatabaseId("project") + preConverter:^id _Nullable(id _Nullable input) { + return input; + }]; + return reader; } FieldValue FSTTestFieldValue(id _Nullable value) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); + FSTUserDataReader *reader = FSTTestUserDataReader(); // HACK: We use parsedQueryValue: since it accepts scalars as well as arrays / objects, and // our tests currently use FSTTestFieldValue() pretty generically so we don't know the intent. - return [converter parsedQueryValue:value]; + return [reader parsedQueryValue:value]; } ObjectValue FSTTestObjectValue(NSDictionary *data) { @@ -126,8 +126,8 @@ DocumentKey FSTTestDocKey(NSString *path) { } SetMutation FSTTestSetMutation(NSString *path, NSDictionary *values) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - ParsedSetData result = [converter parsedSetData:values]; + FSTUserDataReader *reader = FSTTestUserDataReader(); + ParsedSetData result = [reader parsedSetData:values]; return SetMutation(FSTTestDocKey(path), result.data(), Precondition::None(), result.field_transforms()); } @@ -144,8 +144,8 @@ PatchMutation FSTTestPatchMutation(NSString *path, } }]; - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - ParsedUpdateData parsed = [converter parsedUpdateData:mutableValues]; + FSTUserDataReader *reader = FSTTestUserDataReader(); + ParsedUpdateData parsed = [reader parsedUpdateData:mutableValues]; DocumentKey key = FSTTestDocKey(path); diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm index 5de4af5e1e3..6e2ac1db681 100644 --- a/Firestore/Source/API/FIRCollectionReference.mm +++ b/Firestore/Source/API/FIRCollectionReference.mm @@ -21,7 +21,7 @@ #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -115,7 +115,7 @@ - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)da - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)data completion: (nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = [self.firestore.dataConverter parsedSetData:data]; + ParsedSetData parsed = [self.firestore.dataReader parsedSetData:data]; DocumentReference docRef = self.reference.AddDocument(std::move(parsed), util::MakeCallback(completion)); return [[FIRDocumentReference alloc] initWithReference:std::move(docRef)]; diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index 24dfe73a3bf..81c08b74a1b 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -26,7 +26,7 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRFirestoreSource+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -157,17 +157,17 @@ - (void)setData:(NSDictionary *)documentData - (void)setData:(NSDictionary *)documentData merge:(BOOL)merge completion:(nullable void (^)(NSError *_Nullable error))completion { - auto dataConverter = self.firestore.dataConverter; - ParsedSetData parsed = merge ? [dataConverter parsedMergeData:documentData fieldMask:nil] - : [dataConverter parsedSetData:documentData]; + auto dataReader = self.firestore.dataReader; + ParsedSetData parsed = merge ? [dataReader parsedMergeData:documentData fieldMask:nil] + : [dataReader parsedSetData:documentData]; _documentReference.SetData(std::move(parsed), util::MakeCallback(completion)); } - (void)setData:(NSDictionary *)documentData mergeFields:(NSArray *)mergeFields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = [self.firestore.dataConverter parsedMergeData:documentData - fieldMask:mergeFields]; + ParsedSetData parsed = [self.firestore.dataReader parsedMergeData:documentData + fieldMask:mergeFields]; _documentReference.SetData(std::move(parsed), util::MakeCallback(completion)); } @@ -177,7 +177,7 @@ - (void)updateData:(NSDictionary *)fields { - (void)updateData:(NSDictionary *)fields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedUpdateData parsed = [self.firestore.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.firestore.dataReader parsedUpdateData:fields]; _documentReference.UpdateData(std::move(parsed), util::MakeCallback(completion)); } diff --git a/Firestore/Source/API/FIRDocumentSnapshot.mm b/Firestore/Source/API/FIRDocumentSnapshot.mm index cde83ab8511..022b09452df 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.mm +++ b/Firestore/Source/API/FIRDocumentSnapshot.mm @@ -72,6 +72,7 @@ * Converts a public FIRServerTimestampBehavior into its internal equivalent. */ ServerTimestampBehavior InternalServerTimestampBehavior(FIRServerTimestampBehavior behavior) { + // TODO(mutabledocuments): Remove since we only use FIRServerTimestampBehavior switch (behavior) { case FIRServerTimestampBehaviorNone: return ServerTimestampBehavior::kNone; @@ -207,6 +208,7 @@ - (FieldValueOptions)optionsForServerTimestampBehavior: return FieldValueOptions(InternalServerTimestampBehavior(serverTimestampBehavior)); } +// TODO(mutabledocuments): Replace with UserDataWriter - (id)convertedValue:(FieldValue)value options:(const FieldValueOptions &)options { switch (value.type()) { case FieldValue::Type::Null: diff --git a/Firestore/Source/API/FIRFirestore+Internal.h b/Firestore/Source/API/FIRFirestore+Internal.h index 018f75fb696..0e74f72ded4 100644 --- a/Firestore/Source/API/FIRFirestore+Internal.h +++ b/Firestore/Source/API/FIRFirestore+Internal.h @@ -25,7 +25,7 @@ @class FIRApp; @class FSTFirestoreClient; -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace firebase { namespace firestore { @@ -79,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign, readonly) std::shared_ptr wrapped; @property(nonatomic, assign, readonly) const model::DatabaseId &databaseID; -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm index 1b57d237c87..1ecada7558c 100644 --- a/Firestore/Source/API/FIRFirestore.mm +++ b/Firestore/Source/API/FIRFirestore.mm @@ -31,7 +31,7 @@ #import "Firestore/Source/API/FIRTransaction+Internal.h" #import "Firestore/Source/API/FIRWriteBatch+Internal.h" #import "Firestore/Source/API/FSTFirestoreComponent.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -89,7 +89,7 @@ @interface FIRFirestore () -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end @@ -162,8 +162,8 @@ - (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID } }; - _dataConverter = [[FSTUserDataConverter alloc] initWithDatabaseID:_firestore->database_id() - preConverter:block]; + _dataReader = [[FSTUserDataReader alloc] initWithDatabaseID:_firestore->database_id() + preConverter:block]; // Use the property setter so the default settings get plumbed into _firestoreClient. self.settings = [[FIRFirestoreSettings alloc] init]; } @@ -232,8 +232,7 @@ - (FIRQuery *)collectionGroupWithID:(NSString *)collectionID { } - (FIRWriteBatch *)batch { - return [FIRWriteBatch writeBatchWithDataConverter:self.dataConverter - writeBatch:_firestore->GetBatch()]; + return [FIRWriteBatch writeBatchWithDataReader:self.dataReader writeBatch:_firestore->GetBatch()]; } - (void)runTransactionWithBlock:(UserUpdateBlock)updateBlock diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index b06018c5ca0..a0e887888ce 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -32,7 +32,7 @@ #import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/query_core.h" #include "Firestore/core/src/api/query_listener_registration.h" @@ -474,11 +474,11 @@ - (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues { #pragma mark - Private Methods - (FieldValue)parsedQueryValue:(id)value { - return [self.firestore.dataConverter parsedQueryValue:value]; + return [self.firestore.dataReader parsedQueryValue:value]; } - (FieldValue)parsedQueryValue:(id)value allowArrays:(bool)allowArrays { - return [self.firestore.dataConverter parsedQueryValue:value allowArrays:allowArrays]; + return [self.firestore.dataReader parsedQueryValue:value allowArrays:allowArrays]; } - (QuerySnapshotListener)wrapQuerySnapshotBlock:(FIRQuerySnapshotBlock)block { diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index a145180989c..d7f85549479 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -24,7 +24,7 @@ #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRTransaction+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/transaction.h" #include "Firestore/core/src/core/user_data.h" @@ -89,8 +89,8 @@ - (FIRTransaction *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document merge:(BOOL)merge { [self validateReference:document]; - ParsedSetData parsed = merge ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil] - : [self.firestore.dataConverter parsedSetData:data]; + ParsedSetData parsed = merge ? [self.firestore.dataReader parsedMergeData:data fieldMask:nil] + : [self.firestore.dataReader parsedSetData:data]; _internalTransaction->Set(document.key, std::move(parsed)); return self; } @@ -99,7 +99,7 @@ - (FIRTransaction *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document mergeFields:(NSArray *)mergeFields { [self validateReference:document]; - ParsedSetData parsed = [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields]; + ParsedSetData parsed = [self.firestore.dataReader parsedMergeData:data fieldMask:mergeFields]; _internalTransaction->Set(document.key, std::move(parsed)); return self; } @@ -107,7 +107,7 @@ - (FIRTransaction *)setData:(NSDictionary *)data - (FIRTransaction *)updateData:(NSDictionary *)fields forDocument:(FIRDocumentReference *)document { [self validateReference:document]; - ParsedUpdateData parsed = [self.firestore.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.firestore.dataReader parsedUpdateData:fields]; _internalTransaction->Update(document.key, std::move(parsed)); return self; } diff --git a/Firestore/Source/API/FIRWriteBatch+Internal.h b/Firestore/Source/API/FIRWriteBatch+Internal.h index ffd297e7738..54329f53e1a 100644 --- a/Firestore/Source/API/FIRWriteBatch+Internal.h +++ b/Firestore/Source/API/FIRWriteBatch+Internal.h @@ -20,7 +20,7 @@ #include "Firestore/core/src/api/write_batch.h" -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace api = firebase::firestore::api; @@ -28,8 +28,8 @@ NS_ASSUME_NONNULL_BEGIN @interface FIRWriteBatch (Internal) -+ (instancetype)writeBatchWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch; ++ (instancetype)writeBatchWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch; @end diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm index 5499bba9e2a..afa02c49dec 100644 --- a/Firestore/Source/API/FIRWriteBatch.mm +++ b/Firestore/Source/API/FIRWriteBatch.mm @@ -17,7 +17,7 @@ #import "Firestore/Source/API/FIRWriteBatch+Internal.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/write_batch.h" #include "Firestore/core/src/core/user_data.h" @@ -35,19 +35,18 @@ @interface FIRWriteBatch () -- (instancetype)initWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch NS_DESIGNATED_INITIALIZER; -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end @implementation FIRWriteBatch (Internal) -+ (instancetype)writeBatchWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch { - return [[FIRWriteBatch alloc] initWithDataConverter:dataConverter - writeBatch:std::move(writeBatch)]; ++ (instancetype)writeBatchWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch { + return [[FIRWriteBatch alloc] initWithDataReader:dataReader writeBatch:std::move(writeBatch)]; } @end @@ -56,11 +55,11 @@ @implementation FIRWriteBatch { util::DelayedConstructor _writeBatch; } -- (instancetype)initWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch { +- (instancetype)initWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch { self = [super init]; if (self) { - _dataConverter = dataConverter; + _dataReader = dataReader; _writeBatch.Init(std::move(writeBatch)); } return self; @@ -74,8 +73,8 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document merge:(BOOL)merge { - ParsedSetData parsed = merge ? [self.dataConverter parsedMergeData:data fieldMask:nil] - : [self.dataConverter parsedSetData:data]; + ParsedSetData parsed = merge ? [self.dataReader parsedMergeData:data fieldMask:nil] + : [self.dataReader parsedSetData:data]; _writeBatch->SetData(document.internalReference, std::move(parsed)); return self; @@ -84,7 +83,7 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document mergeFields:(NSArray *)mergeFields { - ParsedSetData parsed = [self.dataConverter parsedMergeData:data fieldMask:mergeFields]; + ParsedSetData parsed = [self.dataReader parsedMergeData:data fieldMask:mergeFields]; _writeBatch->SetData(document.internalReference, std::move(parsed)); return self; @@ -92,7 +91,7 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)updateData:(NSDictionary *)fields forDocument:(FIRDocumentReference *)document { - ParsedUpdateData parsed = [self.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.dataReader parsedUpdateData:fields]; _writeBatch->UpdateData(document.internalReference, std::move(parsed)); return self; diff --git a/Firestore/Source/API/FSTUserDataConverter_legacy.h b/Firestore/Source/API/FSTUserDataConverter_legacy.h new file mode 100644 index 00000000000..a70a92e516d --- /dev/null +++ b/Firestore/Source/API/FSTUserDataConverter_legacy.h @@ -0,0 +1,155 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +#include + +#include "Firestore/core/src/core/core_fwd.h" +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/model_fwd.h" + +// TODO(mutabledoucments): This file is a mostly unmodified version of the +// legacy UserDataConverter. Comments have been added to make sure that the main +// Git diff is between the old UserDataConverter and UserDataReader. Once +// reviewed, this file can be removed. + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@class FIRTimestamp; + +namespace core = firebase::firestore::core; +namespace model = firebase::firestore::model; + +NS_ASSUME_NONNULL_BEGIN + +/** + * An interface that allows arbitrary pre-converting of user data. + * + * Returns the converted value (can return back the input to act as a no-op). + */ +typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); + +/** + * Helper for parsing raw user input (provided via the API) into internal model classes. + */ +@interface FSTUserDataConverter : NSObject + +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID + preConverter:(FSTPreConverterBlock)preConverter NS_DESIGNATED_INITIALIZER; + +/** Parse document data from a non-merge setData call.*/ +- (core::ParsedSetData)parsedSetData:(id)input; + +/** Parse document data from a setData call with `merge:YES`. */ +- (core::ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fieldMask; + +/** Parse update data from an updateData call. */ +- (core::ParsedUpdateData)parsedUpdateData:(id)input; + +/** Parse a "query value" (e.g. value in a where filter or a value in a cursor bound). */ +- (model::FieldValue)parsedQueryValue:(id)input; + +/** + * Parse a "query value" (e.g. value in a where filter or a value in a cursor bound). + * + * @param allowArrays Whether the query value is an array that may directly contain additional + * arrays (e.g.) the operand of an `in` query). + */ +- (model::FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FSTUserDataConverter_legacy.mm b/Firestore/Source/API/FSTUserDataConverter_legacy.mm new file mode 100644 index 00000000000..6bc849a7dcb --- /dev/null +++ b/Firestore/Source/API/FSTUserDataConverter_legacy.mm @@ -0,0 +1,876 @@ +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "Firestore/Source/API/FSTUserDataConverter_legacy.h" + +#include +#include +#include +#include +#include + +#import "FIRGeoPoint.h" +#import "FIRTimestamp.h" + +#import "Firestore/Source/API/FIRDocumentReference+Internal.h" +#import "Firestore/Source/API/FIRFieldPath+Internal.h" +#import "Firestore/Source/API/FIRFieldValue+Internal.h" +#import "Firestore/Source/API/FIRFirestore+Internal.h" +#import "Firestore/Source/API/FIRGeoPoint+Internal.h" +#import "Firestore/Source/API/FSTUserDataReader.h" +#import "Firestore/Source/API/converters.h" + +#include "Firestore/core/src/core/user_data.h" +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/field_mask.h" +#include "Firestore/core/src/model/field_path.h" +#include "Firestore/core/src/model/field_transform.h" +#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/precondition.h" +#include "Firestore/core/src/model/transform_operation.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/timestamp_internal.h" +#include "Firestore/core/src/util/exception.h" +#include "Firestore/core/src/util/hard_assert.h" +#include "Firestore/core/src/util/string_apple.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/types/optional.h" + +// TODO(mutabledoucments): This file is a mostly unmodified version of the +// legacy UserDataConverter. Comments have been added to make sure that the main +// Git diff is between the old UserDataConverter and UserDataReader. Once +// reviewed, this file can be removed. + +namespace util = firebase::firestore::util; +using firebase::Timestamp; +using firebase::TimestampInternal; +using firebase::firestore::GeoPoint; +using firebase::firestore::core::ParseAccumulator; +using firebase::firestore::core::ParseContext; +using firebase::firestore::core::ParsedSetData; +using firebase::firestore::core::ParsedUpdateData; +using firebase::firestore::core::UserDataSource; +using firebase::firestore::model::ArrayTransform; +using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::DocumentKey; +using firebase::firestore::model::FieldMask; +using firebase::firestore::model::FieldPath; +using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::FieldValue; +using firebase::firestore::model::NumericIncrementTransform; +using firebase::firestore::model::ObjectValue; +using firebase::firestore::model::Precondition; +using firebase::firestore::model::ServerTimestampTransform; +using firebase::firestore::model::TransformOperation; +using firebase::firestore::nanopb::MakeByteString; +using firebase::firestore::util::ThrowInvalidArgument; + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +NS_ASSUME_NONNULL_BEGIN + +#pragma mark - FSTUserDataConverter + +@interface FSTUserDataConverter () +@property(strong, nonatomic, readonly) FSTPreConverterBlock preConverter; +@end + +@implementation FSTUserDataConverter { + DatabaseId _databaseID; +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (instancetype)initWithDatabaseID:(DatabaseId)databaseID + preConverter:(FSTPreConverterBlock)preConverter { + self = [super init]; + if (self) { + _databaseID = std::move(databaseID); + _preConverter = preConverter; + } + return self; +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (ParsedSetData)parsedSetData:(id)input { + // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust + // Obj-C to verify the type for us. + if (![input isKindOfClass:[NSDictionary class]]) { + ThrowInvalidArgument("Data to be written must be an NSDictionary."); + } + + ParseAccumulator accumulator{UserDataSource::Set}; + absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); + + return std::move(accumulator).SetData(ObjectValue(std::move(*updateData))); +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fieldMask { + // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust + // Obj-C to verify the type for us. + if (![input isKindOfClass:[NSDictionary class]]) { + ThrowInvalidArgument("Data to be written must be an NSDictionary."); + } + + ParseAccumulator accumulator{UserDataSource::MergeSet}; + + absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); + + ObjectValue updateObject = ObjectValue(std::move(*updateData)); + + if (fieldMask) { + std::set validatedFieldPaths; + for (id fieldPath in fieldMask) { + FieldPath path; + + if ([fieldPath isKindOfClass:[NSString class]]) { + path = FieldPath::FromDotSeparatedString(util::MakeString(fieldPath)); + } else if ([fieldPath isKindOfClass:[FIRFieldPath class]]) { + path = static_cast(fieldPath).internalValue; + } else { + ThrowInvalidArgument("All elements in mergeFields: must be NSStrings or FIRFieldPaths."); + } + + // Verify that all elements specified in the field mask are part of the parsed context. + if (!accumulator.Contains(path)) { + ThrowInvalidArgument( + "Field '%s' is specified in your field mask but missing from your input data.", + path.CanonicalString()); + } + + validatedFieldPaths.insert(path); + } + + return std::move(accumulator) + .MergeData(updateObject, FieldMask{std::move(validatedFieldPaths)}); + + } else { + return std::move(accumulator).MergeData(updateObject); + } +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (ParsedUpdateData)parsedUpdateData:(id)input { + // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust + // Obj-C to verify the type for us. + if (![input isKindOfClass:[NSDictionary class]]) { + ThrowInvalidArgument("Data to be written must be an NSDictionary."); + } + + NSDictionary *dict = input; + + ParseAccumulator accumulator{UserDataSource::Update}; + __block ParseContext context = accumulator.RootContext(); + __block ObjectValue updateData = ObjectValue::Empty(); + + [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) { + FieldPath path; + + if ([key isKindOfClass:[NSString class]]) { + path = FieldPath::FromDotSeparatedString(util::MakeString(key)); + } else if ([key isKindOfClass:[FIRFieldPath class]]) { + path = ((FIRFieldPath *)key).internalValue; + } else { + ThrowInvalidArgument("Dictionary keys in updateData: must be NSStrings or FIRFieldPaths."); + } + + value = self.preConverter(value); + if ([value isKindOfClass:[FSTDeleteFieldValue class]]) { + // Add it to the field mask, but don't add anything to updateData. + context.AddToFieldMask(std::move(path)); + } else { + absl::optional parsedValue = [self parseData:value + context:context.ChildContext(path)]; + if (parsedValue) { + context.AddToFieldMask(path); + updateData = updateData.Set(path, *parsedValue); + } + } + }]; + + return std::move(accumulator).UpdateData(updateData); +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (FieldValue)parsedQueryValue:(id)input { + return [self parsedQueryValue:input allowArrays:false]; +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { + ParseAccumulator accumulator{allowArrays ? UserDataSource::ArrayArgument + : UserDataSource::Argument}; + + absl::optional parsed = [self parseData:input context:accumulator.RootContext()]; + HARD_ASSERT(parsed, "Parsed data should not be nil."); + HARD_ASSERT(accumulator.field_transforms().empty(), + "Field transforms should have been disallowed."); + return *parsed; +} + +/** + * Internal helper for parsing user data. + * + * @param input Data to be parsed. + * @param context A context object representing the current path being parsed, the source of the + * data being parsed, etc. + * + * @return The parsed value, or nil if the value was a FieldValue sentinel that should not be + * included in the resulting parsed data. + */ +- (absl::optional)parseData:(id)input context:(ParseContext &&)context { + input = self.preConverter(input); + if ([input isKindOfClass:[NSDictionary class]]) { + return [self parseDictionary:(NSDictionary *)input context:std::move(context)]; + + } else if ([input isKindOfClass:[FIRFieldValue class]]) { + // FieldValues usually parse into transforms (except FieldValue.delete()) in which case we + // do not want to include this field in our parsed data (as doing so will overwrite the field + // directly prior to the transform trying to transform it). So we don't call appendToFieldMask + // and we return nil as our parsing result. + [self parseSentinelFieldValue:(FIRFieldValue *)input context:std::move(context)]; + return absl::nullopt; + + } else { + // If context path is unset we are already inside an array and we don't support field mask paths + // more granular than the top-level array. + if (context.path()) { + context.AddToFieldMask(*context.path()); + } + + if ([input isKindOfClass:[NSArray class]]) { + // TODO(b/34871131): Include the path containing the array in the error message. + // In the case of IN queries, the parsed data is an array (representing the set of values to + // be included for the IN query) that may directly contain additional arrays (each + // representing an individual field value), so we disable this validation. + if (context.array_element() && context.data_source() != UserDataSource::ArrayArgument) { + ThrowInvalidArgument("Nested arrays are not supported"); + } + return [self parseArray:(NSArray *)input context:std::move(context)]; + } else { + return [self parseScalarValue:input context:std::move(context)]; + } + } +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (FieldValue)parseDictionary:(NSDictionary *)dict + context:(ParseContext &&)context { + if (dict.count == 0) { + const FieldPath *path = context.path(); + if (path && !path->empty()) { + context.AddToFieldMask(*path); + } + return ObjectValue::Empty().AsFieldValue(); + } else { + __block ObjectValue result = ObjectValue::Empty(); + + [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) { + absl::optional parsedValue = + [self parseData:value context:context.ChildContext(util::MakeString(key))]; + if (parsedValue) { + FieldPath path = FieldPath{util::MakeString(key)}; + result = result.Set(path, *parsedValue); + } + }]; + + return result; + } +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (FieldValue)parseArray:(NSArray *)array context:(ParseContext &&)context { + __block FieldValue::Array result; + result.reserve(array.count); + + [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *) { + absl::optional parsedEntry = [self parseData:entry + context:context.ChildContext(idx)]; + if (!parsedEntry) { + // Just include nulls in the array for fields being replaced with a sentinel. + parsedEntry = FieldValue::Null(); + } + result.push_back(*parsedEntry); + }]; + return FieldValue::FromArray(std::move(result)); +} + +/** + * "Parses" the provided FIRFieldValue, adding any necessary transforms to + * context.fieldTransforms. + */ +- (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContext &&)context { + // Sentinels are only supported with writes, and not within arrays. + if (!context.write()) { + ThrowInvalidArgument("%s can only be used with updateData() and setData()%s", + fieldValue.methodName, context.FieldDescription()); + } + if (!context.path()) { + ThrowInvalidArgument("%s is not currently supported inside arrays", fieldValue.methodName); + } + + if ([fieldValue isKindOfClass:[FSTDeleteFieldValue class]]) { + if (context.data_source() == UserDataSource::MergeSet) { + // No transform to add for a delete, but we need to add it to our fieldMask so it gets + // deleted. + context.AddToFieldMask(*context.path()); + + } else if (context.data_source() == UserDataSource::Update) { + HARD_ASSERT(context.path()->size() > 0, + "FieldValue.delete() at the top level should have already been handled."); + ThrowInvalidArgument("FieldValue.delete() can only appear at the top level of your " + "update data%s", + context.FieldDescription()); + } else { + // We shouldn't encounter delete sentinels for queries or non-merge setData calls. + ThrowInvalidArgument( + "FieldValue.delete() can only be used with updateData() and setData() with merge:true%s", + context.FieldDescription()); + } + + } else if ([fieldValue isKindOfClass:[FSTServerTimestampFieldValue class]]) { + context.AddToFieldTransforms(*context.path(), ServerTimestampTransform()); + + } else if ([fieldValue isKindOfClass:[FSTArrayUnionFieldValue class]]) { + std::vector parsedElements = + [self parseArrayTransformElements:((FSTArrayUnionFieldValue *)fieldValue).elements]; + ArrayTransform array_union(TransformOperation::Type::ArrayUnion, std::move(parsedElements)); + context.AddToFieldTransforms(*context.path(), std::move(array_union)); + + } else if ([fieldValue isKindOfClass:[FSTArrayRemoveFieldValue class]]) { + std::vector parsedElements = + [self parseArrayTransformElements:((FSTArrayRemoveFieldValue *)fieldValue).elements]; + ArrayTransform array_remove(TransformOperation::Type::ArrayRemove, std::move(parsedElements)); + context.AddToFieldTransforms(*context.path(), std::move(array_remove)); + + } else if ([fieldValue isKindOfClass:[FSTNumericIncrementFieldValue class]]) { + FSTNumericIncrementFieldValue *numericIncrementFieldValue = + (FSTNumericIncrementFieldValue *)fieldValue; + FieldValue operand = [self parsedQueryValue:numericIncrementFieldValue.operand]; + NumericIncrementTransform numeric_increment(std::move(operand)); + + context.AddToFieldTransforms(*context.path(), std::move(numeric_increment)); + + } else { + HARD_FAIL("Unknown FIRFieldValue type: %s", NSStringFromClass([fieldValue class])); + } +} + +/** + * Helper to parse a scalar value (i.e. not an NSDictionary, NSArray, or FIRFieldValue). + * + * Note that it handles all NSNumber values that are encodable as int64_t or doubles + * (depending on the underlying type of the NSNumber). Unsigned integer values are handled though + * any value outside what is representable by int64_t (a signed 64-bit value) will throw an + * exception. + * + * @return The parsed value. + */ +- (absl::optional)parseScalarValue:(nullable id)input context:(ParseContext &&)context { + if (!input || [input isMemberOfClass:[NSNull class]]) { + return FieldValue::Null(); + + } else if ([input isKindOfClass:[NSNumber class]]) { + // Recover the underlying type of the number, using the method described here: + // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber + const char *cType = [input objCType]; + + // Type Encoding values taken from + // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/ + // Articles/ocrtTypeEncodings.html + switch (cType[0]) { + case 'q': + return FieldValue::FromInteger([input longLongValue]); + + case 'i': // Falls through. + case 's': // Falls through. + case 'l': // Falls through. + case 'I': // Falls through. + case 'S': + // Coerce integer values that aren't long long. Allow unsigned integer types that are + // guaranteed small enough to skip a length check. + return FieldValue::FromInteger([input longLongValue]); + + case 'L': // Falls through. + case 'Q': + // Unsigned integers that could be too large. Note that the 'L' (long) case is handled here + // because when compiled for LP64, unsigned long is 64 bits and could overflow int64_t. + { + unsigned long long extended = [input unsignedLongLongValue]; + + if (extended > LLONG_MAX) { + ThrowInvalidArgument("NSNumber (%s) is too large%s", [input unsignedLongLongValue], + context.FieldDescription()); + + } else { + return FieldValue::FromInteger(static_cast(extended)); + } + } + + case 'f': + return FieldValue::FromDouble([input doubleValue]); + + case 'd': + // Double values are already the right type, so just reuse the existing boxed double. + // + // Note that NSNumber already performs NaN normalization to a single shared instance + // so there's no need to treat NaN specially here. + return FieldValue::FromDouble([input doubleValue]); + + case 'B': // Falls through. + case 'c': // Falls through. + case 'C': + // Boolean values are weird. + // + // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL) + // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that + // legitimate usage of signed chars is impossible, but this should be rare. + // + // Additionally, for consistency, map unsigned chars to bools in the same way. + return FieldValue::FromBoolean([input boolValue]); + + default: + // All documented codes should be handled above, so this shouldn't happen. + HARD_FAIL("Unknown NSNumber objCType %s on %s", cType, input); + } + + } else if ([input isKindOfClass:[NSString class]]) { + return FieldValue::FromString(util::MakeString(input)); + + } else if ([input isKindOfClass:[NSDate class]]) { + NSDate *inputDate = input; + return FieldValue::FromTimestamp(api::MakeTimestamp(inputDate)); + + } else if ([input isKindOfClass:[FIRTimestamp class]]) { + FIRTimestamp *inputTimestamp = input; + Timestamp timestamp = TimestampInternal::Truncate(api::MakeTimestamp(inputTimestamp)); + return FieldValue::FromTimestamp(timestamp); + + } else if ([input isKindOfClass:[FIRGeoPoint class]]) { + return FieldValue::FromGeoPoint(api::MakeGeoPoint(input)); + + } else if ([input isKindOfClass:[NSData class]]) { + NSData *inputData = input; + return FieldValue::FromBlob(MakeByteString(inputData)); + + } else if ([input isKindOfClass:[FSTDocumentKeyReference class]]) { + FSTDocumentKeyReference *reference = input; + if (reference.databaseID != _databaseID) { + const DatabaseId &other = reference.databaseID; + ThrowInvalidArgument( + "Document Reference is for database %s/%s but should be for database %s/%s%s", + other.project_id(), other.database_id(), _databaseID.project_id(), + _databaseID.database_id(), context.FieldDescription()); + } + return FieldValue::FromReference(_databaseID, reference.key); + + } else { + ThrowInvalidArgument("Unsupported type: %s%s", NSStringFromClass([input class]), + context.FieldDescription()); + } +} + +/* + * Copyright 2017 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +- (std::vector)parseArrayTransformElements:(NSArray *)elements { + ParseAccumulator accumulator{UserDataSource::Argument}; + + std::vector values; + for (NSUInteger i = 0; i < elements.count; i++) { + id element = elements[i]; + // Although array transforms are used with writes, the actual elements being unioned or removed + // are not considered writes since they cannot contain any FieldValue sentinels, etc. + ParseContext context = accumulator.RootContext(); + + absl::optional parsedElement = [self parseData:element + context:context.ChildContext(i)]; + HARD_ASSERT(parsedElement && accumulator.field_transforms().size() == 0, + "Failed to properly parse array transform element: %s", element); + values.push_back(*parsedElement); + } + return values; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataReader.h similarity index 97% rename from Firestore/Source/API/FSTUserDataConverter.h rename to Firestore/Source/API/FSTUserDataReader.h index 7c4ed0119eb..ea29db4fbd0 100644 --- a/Firestore/Source/API/FSTUserDataConverter.h +++ b/Firestore/Source/API/FSTUserDataReader.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); /** * Helper for parsing raw user input (provided via the API) into internal model classes. */ -@interface FSTUserDataConverter : NSObject +@interface FSTUserDataReader : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataReader.mm similarity index 64% rename from Firestore/Source/API/FSTUserDataConverter.mm rename to Firestore/Source/API/FSTUserDataReader.mm index 7aef3a04f72..c0726742ac9 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataReader.mm @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include #include @@ -30,8 +30,10 @@ #import "Firestore/Source/API/FIRFieldValue+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRGeoPoint+Internal.h" +#import "Firestore/Source/API/FSTUserDataConverter_legacy.h" #import "Firestore/Source/API/converters.h" +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/user_data.h" #include "Firestore/core/src/model/database_id.h" #include "Firestore/core/src/model/document_key.h" @@ -39,18 +41,25 @@ #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/model/field_transform.h" #include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/object_value.h" #include "Firestore/core/src/model/precondition.h" +#include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/transform_operation.h" #include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/nanopb/reader.h" +#include "Firestore/core/src/remote/serializer.h" #include "Firestore/core/src/timestamp_internal.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hard_assert.h" +#include "Firestore/core/src/util/read_context.h" #include "Firestore/core/src/util/string_apple.h" + #include "absl/memory/memory.h" #include "absl/strings/match.h" #include "absl/types/optional.h" namespace util = firebase::firestore::util; +namespace nanopb = firebase::firestore::nanopb; using firebase::Timestamp; using firebase::TimestampInternal; using firebase::firestore::GeoPoint; @@ -68,11 +77,22 @@ using firebase::firestore::model::FieldValue; using firebase::firestore::model::NumericIncrementTransform; using firebase::firestore::model::ObjectValue; +using firebase::firestore::model::MutableObjectValue; +using firebase::firestore::model::ResourcePath; using firebase::firestore::model::Precondition; using firebase::firestore::model::ServerTimestampTransform; using firebase::firestore::model::TransformOperation; -using firebase::firestore::nanopb::MakeByteString; +using firebase::firestore::remote::Serializer; using firebase::firestore::util::ThrowInvalidArgument; +using firebase::firestore::util::ReadContext; +using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::google_firestore_v1_MapValue; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_protobuf_NullValue_NULL_VALUE; +using firebase::firestore::google_firestore_v1_MapValue_FieldsEntry; +using firebase::firestore::google_type_LatLng; +using firebase::firestore::google_protobuf_Timestamp; +using nanopb::StringReader; NS_ASSUME_NONNULL_BEGIN @@ -102,16 +122,15 @@ - (instancetype)initWithKey:(DocumentKey)key databaseID:(DatabaseId)databaseID { @end -#pragma mark - Conversion helpers - -#pragma mark - FSTUserDataConverter +#pragma mark - FSTUserDataReader -@interface FSTUserDataConverter () +@interface FSTUserDataReader () @property(strong, nonatomic, readonly) FSTPreConverterBlock preConverter; @end -@implementation FSTUserDataConverter { +@implementation FSTUserDataReader { DatabaseId _databaseID; + std::unique_ptr _serializer; } - (instancetype)initWithDatabaseID:(DatabaseId)databaseID @@ -120,10 +139,29 @@ - (instancetype)initWithDatabaseID:(DatabaseId)databaseID if (self) { _databaseID = std::move(databaseID); _preConverter = preConverter; + _serializer.reset(new Serializer{_databaseID}); } return self; } +// TODO(mutabledocuments): Remove these methods once we remove FieldValue +- (FieldValue)wrapValue:(google_firestore_v1_Value)value { + ReadContext context; + return _serializer->DecodeFieldValue(&context, value); +} + +- (std::vector)wrapValues:(const std::vector &)values { + std::vector fieldValues; + for (const auto &value : values) { + fieldValues.emplace_back([self wrapValue:value]); + } + return fieldValues; +} + +- (ObjectValue)wrapObject:(google_firestore_v1_Value)value { + return ObjectValue{[self wrapValue:value]}; +} + - (ParsedSetData)parsedSetData:(id)input { // NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust // Obj-C to verify the type for us. @@ -132,10 +170,11 @@ - (ParsedSetData)parsedSetData:(id)input { } ParseAccumulator accumulator{UserDataSource::Set}; - absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + absl::optional updateData = [self parseData:input + context:accumulator.RootContext()]; HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); - return std::move(accumulator).SetData(ObjectValue(std::move(*updateData))); + return std::move(accumulator).SetData([self wrapObject:*updateData]); } - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fieldMask { @@ -147,10 +186,11 @@ - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fie ParseAccumulator accumulator{UserDataSource::MergeSet}; - absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + absl::optional updateData = [self parseData:input + context:accumulator.RootContext()]; HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); - ObjectValue updateObject = ObjectValue(std::move(*updateData)); + ObjectValue updateObject = [self wrapObject:*updateData]; if (fieldMask) { std::set validatedFieldPaths; @@ -194,7 +234,7 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { ParseAccumulator accumulator{UserDataSource::Update}; __block ParseContext context = accumulator.RootContext(); - __block ObjectValue updateData = ObjectValue::Empty(); + __block MutableObjectValue updateData; [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) { FieldPath path; @@ -212,16 +252,17 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { // Add it to the field mask, but don't add anything to updateData. context.AddToFieldMask(std::move(path)); } else { - absl::optional parsedValue = [self parseData:value - context:context.ChildContext(path)]; + absl::optional parsedValue = + [self parseData:value context:context.ChildContext(path)]; if (parsedValue) { context.AddToFieldMask(path); - updateData = updateData.Set(path, *parsedValue); + updateData.Set(path, *parsedValue); } } }]; - return std::move(accumulator).UpdateData(updateData); + google_firestore_v1_Value rootValue = updateData.Get(FieldPath::EmptyPath()).value(); + return std::move(accumulator).UpdateData([self wrapObject:rootValue]); } - (FieldValue)parsedQueryValue:(id)input { @@ -232,11 +273,12 @@ - (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { ParseAccumulator accumulator{allowArrays ? UserDataSource::ArrayArgument : UserDataSource::Argument}; - absl::optional parsed = [self parseData:input context:accumulator.RootContext()]; + absl::optional parsed = [self parseData:input + context:accumulator.RootContext()]; HARD_ASSERT(parsed, "Parsed data should not be nil."); HARD_ASSERT(accumulator.field_transforms().empty(), "Field transforms should have been disallowed."); - return *parsed; + return [self wrapValue:*parsed]; } /** @@ -249,7 +291,7 @@ - (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { * @return The parsed value, or nil if the value was a FieldValue sentinel that should not be * included in the resulting parsed data. */ -- (absl::optional)parseData:(id)input context:(ParseContext &&)context { +- (absl::optional)parseData:(id)input context:(ParseContext &&)context { input = self.preConverter(input); if ([input isKindOfClass:[NSDictionary class]]) { return [self parseDictionary:(NSDictionary *)input context:std::move(context)]; @@ -284,44 +326,62 @@ - (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { } } -- (FieldValue)parseDictionary:(NSDictionary *)dict - context:(ParseContext &&)context { +- (google_firestore_v1_Value)parseDictionary:(NSDictionary *)dict + context:(ParseContext &&)context { + __block google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_map_value_tag; + if (dict.count == 0) { const FieldPath *path = context.path(); if (path && !path->empty()) { context.AddToFieldMask(*path); } - return ObjectValue::Empty().AsFieldValue(); } else { - __block ObjectValue result = ObjectValue::Empty(); + // Compute the final size of the fields array, which contains an entry for + // all fields that are not FieldValue sentinels + __block pb_size_t count = 0; + [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) { + if (![value isKindOfClass:[FIRFieldValue class]]) { + ++count; + } + }]; + result.map_value.fields_count = count; + result.map_value.fields = nanopb::MakeArray(count); + + __block pb_size_t index = 0; [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) { - absl::optional parsedValue = + absl::optional parsedValue = [self parseData:value context:context.ChildContext(util::MakeString(key))]; if (parsedValue) { - FieldPath path = FieldPath{util::MakeString(key)}; - result = result.Set(path, *parsedValue); + result.map_value.fields[index].key = nanopb::MakeBytesArray(util::MakeString(key)); + result.map_value.fields[index].value = *parsedValue; + ++index; } }]; - - return result; } + + return result; } -- (FieldValue)parseArray:(NSArray *)array context:(ParseContext &&)context { - __block FieldValue::Array result; - result.reserve(array.count); +- (google_firestore_v1_Value)parseArray:(NSArray *)array context:(ParseContext &&)context { + __block google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_array_value_tag; + result.array_value.values_count = static_cast([array count]); + result.array_value.values = + nanopb::MakeArray(result.array_value.values_count); [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *) { - absl::optional parsedEntry = [self parseData:entry - context:context.ChildContext(idx)]; + absl::optional parsedEntry = + [self parseData:entry context:context.ChildContext(idx)]; if (!parsedEntry) { // Just include nulls in the array for fields being replaced with a sentinel. - parsedEntry = FieldValue::Null(); + parsedEntry = [self encodeNullValue]; } - result.push_back(*parsedEntry); + result.array_value.values[idx] = *parsedEntry; }]; - return FieldValue::FromArray(std::move(result)); + + return result; } /** @@ -361,22 +421,28 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex context.AddToFieldTransforms(*context.path(), ServerTimestampTransform()); } else if ([fieldValue isKindOfClass:[FSTArrayUnionFieldValue class]]) { - std::vector parsedElements = + std::vector parsedElements = [self parseArrayTransformElements:((FSTArrayUnionFieldValue *)fieldValue).elements]; - ArrayTransform array_union(TransformOperation::Type::ArrayUnion, std::move(parsedElements)); - context.AddToFieldTransforms(*context.path(), std::move(array_union)); + ArrayTransform arrayUnion(TransformOperation::Type::ArrayUnion, + [self wrapValues:std::move(parsedElements)]); + context.AddToFieldTransforms(*context.path(), std::move(arrayUnion)); } else if ([fieldValue isKindOfClass:[FSTArrayRemoveFieldValue class]]) { - std::vector parsedElements = + std::vector parsedElements = [self parseArrayTransformElements:((FSTArrayRemoveFieldValue *)fieldValue).elements]; - ArrayTransform array_remove(TransformOperation::Type::ArrayRemove, std::move(parsedElements)); - context.AddToFieldTransforms(*context.path(), std::move(array_remove)); + ArrayTransform arrayRemove(TransformOperation::Type::ArrayRemove, + [self wrapValues:std::move(parsedElements)]); + context.AddToFieldTransforms(*context.path(), std::move(arrayRemove)); } else if ([fieldValue isKindOfClass:[FSTNumericIncrementFieldValue class]]) { FSTNumericIncrementFieldValue *numericIncrementFieldValue = (FSTNumericIncrementFieldValue *)fieldValue; - FieldValue operand = [self parsedQueryValue:numericIncrementFieldValue.operand]; - NumericIncrementTransform numeric_increment(std::move(operand)); + + // TODO(mutabledocuments): Use `parseQueryValue`; + ParseAccumulator accumulator{UserDataSource::Argument}; + absl::optional operand = + [self parseData:numericIncrementFieldValue.operand context:accumulator.RootContext()]; + NumericIncrementTransform numeric_increment([self wrapValue:*operand]); context.AddToFieldTransforms(*context.path(), std::move(numeric_increment)); @@ -395,9 +461,9 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex * * @return The parsed value. */ -- (absl::optional)parseScalarValue:(nullable id)input context:(ParseContext &&)context { +- (google_firestore_v1_Value)parseScalarValue:(nullable id)input context:(ParseContext &&)context { if (!input || [input isMemberOfClass:[NSNull class]]) { - return FieldValue::Null(); + return [self encodeNullValue]; } else if ([input isKindOfClass:[NSNumber class]]) { // Recover the underlying type of the number, using the method described here: @@ -409,7 +475,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex // Articles/ocrtTypeEncodings.html switch (cType[0]) { case 'q': - return FieldValue::FromInteger([input longLongValue]); + return [self encodeInteger:[input longLongValue]]; case 'i': // Falls through. case 's': // Falls through. @@ -418,7 +484,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex case 'S': // Coerce integer values that aren't long long. Allow unsigned integer types that are // guaranteed small enough to skip a length check. - return FieldValue::FromInteger([input longLongValue]); + return [self encodeInteger:[input longLongValue]]; case 'L': // Falls through. case 'Q': @@ -432,19 +498,19 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex context.FieldDescription()); } else { - return FieldValue::FromInteger(static_cast(extended)); + return [self encodeInteger:static_cast(extended)]; } } case 'f': - return FieldValue::FromDouble([input doubleValue]); + return [self encodeDouble:[input doubleValue]]; case 'd': // Double values are already the right type, so just reuse the existing boxed double. // // Note that NSNumber already performs NaN normalization to a single shared instance // so there's no need to treat NaN specially here. - return FieldValue::FromDouble([input doubleValue]); + return [self encodeDouble:[input doubleValue]]; case 'B': // Falls through. case 'c': // Falls through. @@ -456,7 +522,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex // legitimate usage of signed chars is impossible, but this should be rare. // // Additionally, for consistency, map unsigned chars to bools in the same way. - return FieldValue::FromBoolean([input boolValue]); + return [self encodeBoolean:[input boolValue]]; default: // All documented codes should be handled above, so this shouldn't happen. @@ -464,23 +530,23 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex } } else if ([input isKindOfClass:[NSString class]]) { - return FieldValue::FromString(util::MakeString(input)); + std::string inputString = util::MakeString(input); + return [self encodeStringValue:inputString]; } else if ([input isKindOfClass:[NSDate class]]) { NSDate *inputDate = input; - return FieldValue::FromTimestamp(api::MakeTimestamp(inputDate)); + return [self encodeTimestampValue:api::MakeTimestamp(inputDate)]; } else if ([input isKindOfClass:[FIRTimestamp class]]) { FIRTimestamp *inputTimestamp = input; Timestamp timestamp = TimestampInternal::Truncate(api::MakeTimestamp(inputTimestamp)); - return FieldValue::FromTimestamp(timestamp); + return [self encodeTimestampValue:timestamp]; } else if ([input isKindOfClass:[FIRGeoPoint class]]) { - return FieldValue::FromGeoPoint(api::MakeGeoPoint(input)); - + return [self encodeGeoPoint:api::MakeGeoPoint(input)]; } else if ([input isKindOfClass:[NSData class]]) { NSData *inputData = input; - return FieldValue::FromBlob(MakeByteString(inputData)); + return [self encodeBlob:(nanopb::MakeByteString(inputData))]; } else if ([input isKindOfClass:[FSTDocumentKeyReference class]]) { FSTDocumentKeyReference *reference = input; @@ -491,7 +557,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex other.project_id(), other.database_id(), _databaseID.project_id(), _databaseID.database_id(), context.FieldDescription()); } - return FieldValue::FromReference(_databaseID, reference.key); + return [self encodeReference:_databaseID key:reference.key]; } else { ThrowInvalidArgument("Unsupported type: %s%s", NSStringFromClass([input class]), @@ -499,18 +565,92 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex } } -- (std::vector)parseArrayTransformElements:(NSArray *)elements { +- (google_firestore_v1_Value)encodeNullValue { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_null_value_tag; + result.null_value = google_protobuf_NullValue_NULL_VALUE; + return result; +} + +- (google_firestore_v1_Value)encodeBoolean:(bool)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_boolean_value_tag; + result.boolean_value = value; + return result; +} + +- (google_firestore_v1_Value)encodeInteger:(int64_t)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_integer_value_tag; + result.integer_value = value; + return result; +} + +- (google_firestore_v1_Value)encodeDouble:(double)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_double_value_tag; + result.double_value = value; + return result; +} + +- (google_firestore_v1_Value)encodeTimestampValue:(Timestamp)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_timestamp_value_tag; + result.timestamp_value.seconds = value.seconds(); + result.timestamp_value.nanos = value.nanoseconds(); + return result; +} + +- (google_firestore_v1_Value)encodeStringValue:(const std::string &)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_string_value_tag; + result.string_value = nanopb::MakeBytesArray(value); + return result; +} + +- (google_firestore_v1_Value)encodeBlob:(const nanopb::ByteString &)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_bytes_value_tag; + // Copy the blob so that pb_release can do the right thing. + result.bytes_value = nanopb::CopyBytesArray(value.get()); + return result; +} + +- (google_firestore_v1_Value)encodeReference:(const DatabaseId &)databaseId + key:(const DocumentKey &)key { + HARD_ASSERT(_databaseID == databaseId, "Database %s cannot encode reference from %s", + _databaseID.ToString(), databaseId.ToString()); + + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_reference_value_tag; + + std::string referenceName = ResourcePath({"projects", databaseId.project_id(), "databases", + databaseId.database_id(), "documents", key.ToString()}) + .CanonicalString(); + result.reference_value = nanopb::MakeBytesArray(referenceName); + return result; +} + +- (google_firestore_v1_Value)encodeGeoPoint:(const GeoPoint &)value { + google_firestore_v1_Value result{}; + result.which_value_type = google_firestore_v1_Value_geo_point_value_tag; + result.geo_point_value.latitude = value.latitude(); + result.geo_point_value.longitude = value.longitude(); + return result; +} + +- (std::vector)parseArrayTransformElements:(NSArray *)elements { ParseAccumulator accumulator{UserDataSource::Argument}; - std::vector values; + std::vector values; for (NSUInteger i = 0; i < elements.count; i++) { id element = elements[i]; // Although array transforms are used with writes, the actual elements being unioned or removed // are not considered writes since they cannot contain any FieldValue sentinels, etc. ParseContext context = accumulator.RootContext(); - absl::optional parsedElement = [self parseData:element - context:context.ChildContext(i)]; + absl::optional parsedElement = + [self parseData:element context:context.ChildContext(i)]; HARD_ASSERT(parsedElement && accumulator.field_transforms().size() == 0, "Failed to properly parse array transform element: %s", element); values.push_back(*parsedElement); diff --git a/Firestore/core/src/remote/serializer.cc b/Firestore/core/src/remote/serializer.cc index 7a77b8ce9b2..642969bb15e 100644 --- a/Firestore/core/src/remote/serializer.cc +++ b/Firestore/core/src/remote/serializer.cc @@ -173,6 +173,7 @@ pb_bytes_array_t* Serializer::EncodeDatabaseName() const { return EncodeString(DatabaseName(database_id_).CanonicalString()); } +// TODO(mutabledocuments): Remove this and related methods. google_firestore_v1_Value Serializer::EncodeFieldValue( const FieldValue& field_value) const { switch (field_value.type()) { diff --git a/scripts/build.sh b/scripts/build.sh index c5afbe2d790..fbf6fd9673e 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -496,13 +496,11 @@ case "$product-$platform-$method" in Storage-*-xcodebuild) pod_gen FirebaseStorage.podspec --platforms=ios - RunXcodebuild \ - -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ - -scheme "FirebaseStorage-Unit-unit" \ - "${ios_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test + + # Add GoogleService-Info.plist to generated Test Wrapper App. + ruby ./scripts/update_xcode_target.rb gen/FirebaseStorage/Pods/Pods.xcodeproj \ + AppHost-FirebaseStorage-Unit-Tests \ + ../../../FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist if check_secrets; then # Integration tests are only run on iOS to minimize flake failures. @@ -522,28 +520,16 @@ case "$product-$platform-$method" in build \ test fi - - pod_gen FirebaseStorage.podspec --platforms=macos --clean - RunXcodebuild \ - -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ - -scheme "FirebaseStorage-Unit-unit" \ - "${macos_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test - - pod_gen FirebaseStorage.podspec --platforms=tvos --clean - RunXcodebuild \ - -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ - -scheme "FirebaseStorage-Unit-unit" \ - "${tvos_flags[@]}" \ - "${xcb_flags[@]}" \ - build \ - test ;; StorageSwift-*-xcodebuild) pod_gen FirebaseStorageSwift.podspec --platforms=ios + + # Add GoogleService-Info.plist to generated Test Wrapper App. + ruby ./scripts/update_xcode_target.rb gen/FirebaseStorageSwift/Pods/Pods.xcodeproj \ + AppHost-FirebaseStorageSwift-Unit-Tests \ + ../../../FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist + if check_secrets; then # Integration tests are only run on iOS to minimize flake failures. RunXcodebuild \ diff --git a/scripts/code_coverage_report/code_coverage_file_list.json b/scripts/code_coverage_report/code_coverage_file_list.json index 9a2885c8997..5bc86f9a4e8 100644 --- a/scripts/code_coverage_report/code_coverage_file_list.json +++ b/scripts/code_coverage_report/code_coverage_file_list.json @@ -46,16 +46,6 @@ "FirebaseFirestore\\.podspec", "CMakeLists\\.txt", "cmake/.*", - "scripts/binary_to_array\\.py", - "scripts/build\\.sh", - "scripts/install_prereqs\\.sh", - "scripts/localize_podfile\\.swift", - "scripts/pod_lib_lint\\.rb", - "scripts/run_firestore_emulator\\.sh", - "scripts/setup_[^/]+", - "scripts/sync_project\\.rb", - "scripts/test_quickstart\\.sh", - "scripts/xcresult_logs\\.py", "\\.github/workflows/firestore\\.yml", "Gemfile" ] diff --git a/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift b/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift index b7ebe47fda1..499bf62d495 100644 --- a/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift +++ b/scripts/create_spec_repo/Sources/SpecRepoBuilder/main.swift @@ -23,7 +23,7 @@ extension Constants { static let skipLinesWithWords = ["unit_tests", "test_spec"] static let dependencyLineSeparators = CharacterSet(charactersIn: " ,/") static let podSources = [ - "https://${BOT_TOKEN}@github.com/FirebasePrivate/SpecsTesting", + "https://${BOT_TOKEN}@github.com/Firebase/SpecsTesting", "https://github.com/firebase/SpecsStaging.git", "https://cdn.cocoapods.org/", ] @@ -100,6 +100,8 @@ enum SpecRepoBuilderError: Error { case failedToPush(pods: [String]) // Error occurs when a podspec is not found in the repo. case podspecNotFound(_ podspec: String, from: String) + // Error occurs when a direotyr path cannot be determined. + case pathNotFound(_ path: String) } struct SpecRepoBuilder: ParsableCommand { @@ -258,10 +260,31 @@ struct SpecRepoBuilder: ParsableCommand { ) let fileManager = FileManager.default do { - let dirs = try fileManager.contentsOfDirectory(atPath: "\(repoPath)/\(sdkRepoName)") + let sdk_repo_path = "\(repoPath)/\(sdkRepoName)" + print("The repo path is \(sdk_repo_path)") + guard let repo_url = URL(string: sdk_repo_path) else { + print("Error: cannot find \(sdk_repo_path).") + Self + .exit(withError: SpecRepoBuilderError + .pathNotFound(sdk_repo_path)) + } + // Skip hidden files, e.g. /.git + let dirs = try fileManager.contentsOfDirectory( + at: repo_url, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) + print("Found following unhidden dirs: \(dirs)") for dir in dirs { - if dir != ".git" { - shell.run("cd \(sdkRepoName); git rm -r \(dir)") + guard let isDir = (try dir.resourceValues(forKeys: [.isDirectoryKey])).isDirectory else { + print("Error: cannot determine if \(dir.path) is a directory or not.") + Self + .exit(withError: SpecRepoBuilderError + .pathNotFound(dir.path)) + } + if isDir { + print("Removing \(dir.path)") + shell.run("cd \(sdkRepoName); git rm -r \(dir.path)") } } shell.run("cd \(sdkRepoName); git commit -m 'Empty repo'; git push") diff --git a/scripts/update_xcode_target.rb b/scripts/update_xcode_target.rb new file mode 100755 index 00000000000..362e4b5f6a6 --- /dev/null +++ b/scripts/update_xcode_target.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby + +# Copyright 2021 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script to add a file to an Xcode target. +# Adapted from https://github.com/firebase/quickstart-ios/blob/master/scripts/info_script.rb + +require 'xcodeproj' +project_path = ARGV[0] +target = ARGV[1] +file_name = ARGV[2] + +project = Xcodeproj::Project.open(project_path) + +# Add a file to the project in the main group +file = project.new_file(file_name) + +# Add the file to the all targets +project.targets.each do |t| + if t.to_s == target + t.add_file_references([file]) + end +end + +# Save project +project.save()