diff --git a/.github/workflows/include-check-cache.yml b/.github/workflows/include-check-cache.yml index 98cb33a9ed..e22d5a98d2 100644 --- a/.github/workflows/include-check-cache.yml +++ b/.github/workflows/include-check-cache.yml @@ -35,8 +35,6 @@ on: value: ${{ jobs.android.outputs.packages-android-cache-hit }} android-test-base-apk-cache-hit: value: ${{ jobs.android-test-base.outputs.android-test-base-apk-cache-hit }} - android-test-sync-apk-cache-hit: - value: ${{ jobs.android-test-sync.outputs.android-test-sync-apk-cache-hit }} packages-macos-x64-cache-hit: value: ${{ jobs.macos-x64.outputs.packages-macos-x64-cache-hit }} packages-macos-arm64-cache-hit: @@ -295,36 +293,6 @@ jobs: path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk retention-days: 1 - # - # Android Sync Test APK - # - android-test-sync: - runs-on: ubuntu-latest - needs: checksum - name: Android Sync Test APK - outputs: - android-test-sync-apk-cache-hit: ${{ steps.android-test-sync-apk.outputs.cache-hit }} - - steps: - - name: Check Android Sync Test APK - id: android-test-sync-apk - uses: actions/cache@v4 - with: - key: android-sync-test-apk-key-${{ needs.checksum.outputs.packages-sha }} - path: | - ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - - - name: Save Android Sync Test APK - uses: actions/upload-artifact@v4 - if: always() && !cancelled() && steps.android-test-sync-apk.outputs.cache-hit == 'true' - with: - name: android-sync-test-apk-${{ needs.checksum.outputs.version-label }} - retention-days: 1 - path: | - ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - # # MacOS arm64 # diff --git a/.github/workflows/include-integration-tests.yml b/.github/workflows/include-integration-tests.yml index 2d3349ec8e..8149ce46ef 100644 --- a/.github/workflows/include-integration-tests.yml +++ b/.github/workflows/include-integration-tests.yml @@ -7,11 +7,6 @@ on: required: true type: string -env: - REALM_DISABLE_ANALYTICS: true - REALM_PRINT_ANALYTICS: true - REALM_FAIL_ON_ANALYTICS_ERRORS: true - jobs: # TODO: The Monkey seems to crash the app all the time, but with failures that are not coming from the app. Figure out why. diff --git a/.github/workflows/include-static-analysis.yml b/.github/workflows/include-static-analysis.yml index 60ec663580..6101bbd7cc 100644 --- a/.github/workflows/include-static-analysis.yml +++ b/.github/workflows/include-static-analysis.yml @@ -44,7 +44,6 @@ jobs: rsync -a --delete --ignore-errors examples/kmm-sample/shared/build/reports/ktlint/ /tmp/ktlint/example/ || true rsync -a --delete --ignore-errors packages/cinterop/build/reports/ktlint/ /tmp/ktlint/cinterop/ || true rsync -a --delete --ignore-errors packages/library-base/build/reports/ktlint/ /tmp/ktlint/library-base/ || true - rsync -a --delete --ignore-errors packages/library-sync/build/reports/ktlint/ /tmp/ktlint/library-sync/ || true rsync -a --delete --ignore-errors packages/plugin-compiler/build/reports/ktlint/ /tmp/ktlint/plugin-compiler/ || true rsync -a --delete --ignore-errors packages/gradle-plugin/build/reports/ktlint/ /tmp/ktlint/plugin-gradle/ || true rsync -a --delete --ignore-errors benchmarks/build/reports/ktlint/ /tmp/ktlint/benchmarks/ || true @@ -95,7 +94,6 @@ jobs: rsync -a --delete --ignore-errors examples/kmm-sample/shared/build/reports/detekt/ /tmp/detekt/example/ || true rsync -a --delete --ignore-errors packages/cinterop/build/reports/detekt/ /tmp/detekt/cinterop/ || true rsync -a --delete --ignore-errors packages/library-base/build/reports/detekt/ /tmp/detekt/library-base/ || true - rsync -a --delete --ignore-errors packages/library-sync/build/reports/detekt/ /tmp/detekt/library-sync/ || true rsync -a --delete --ignore-errors packages/plugin-compiler/build/reports/detekt/ /tmp/detekt/plugin-compiler/ || true rsync -a --delete --ignore-errors packages/gradle-plugin/build/reports/detekt/ /tmp/detekt/plugin-gradle/ || true rsync -a --delete --ignore-errors benchmarks/build/reports/detekt/ /tmp/detekt/benchmarks/ || true diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bf138c4591..a4c9ef5d13 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -14,8 +14,6 @@ concurrency: cancel-in-progress: true env: - REALM_DISABLE_ANALYTICS: true - REALM_PRINT_ANALYTICS: true CMAKE_C_COMPILER: /usr/local/bin/ccache-clang CMAKE_CXX_COMPILER: /usr/local/bin/ccache-clang++ # Workflow environment variables are not available in Job if statements: https://github.com/actions/runner/issues/1661 @@ -147,7 +145,6 @@ jobs: mkdir realmLinuxBuild cd realmLinuxBuild cmake -DCMAKE_BUILD_TYPE=Release \ - -DREALM_ENABLE_SYNC=1 \ -DREALM_NO_TESTS=1 \ -DREALM_BUILD_LIB_ONLY=true \ -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ @@ -220,7 +217,6 @@ jobs: ..\..\src\jvm ` -DCMAKE_GENERATOR_PLATFORM=x64 ` -DCMAKE_BUILD_TYPE=Release ` - -DREALM_ENABLE_SYNC=ON ` -DREALM_NO_TESTS=1 ` -DCMAKE_CXX_VISIBILITY_PRESET=hidden ` -DVCPKG_TARGET_TRIPLET=x64-windows-static @@ -537,8 +533,7 @@ jobs: if: | always() && !cancelled() && (needs.check-cache.outputs.packages-android-cache-hit != 'true' || - needs.check-cache.outputs.android-test-base-apk-cache-hit != 'true' || - needs.check-cache.outputs.android-test-sync-apk-cache-hit != 'true') + needs.check-cache.outputs.android-test-base-apk-cache-hit != 'true') steps: - name: Remove unnecessary files @@ -627,10 +622,6 @@ jobs: working-directory: packages run: ./gradlew :test-base:assembleAndroidTest -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false - - name: Build Android Sync Test Apk - working-directory: packages - run: ./gradlew :test-sync:packageDebug :test-sync:assembleAndroidTest -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false - - name: Build packages working-directory: packages run: ./gradlew publishCIPackages -Prealm.kotlin.targets=android -Prealm.kotlin.buildRealmCore=false -Prealm.kotlin.mainHost=false @@ -647,15 +638,6 @@ jobs: path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk key: android-base-test-apk-key-${{ needs.check-cache.outputs.packages-sha }} - # Must match naming found in include-check-cache.yml - - name: Store build cache for Android Sync Test APK - uses: actions/cache@v4 - with: - key: android-sync-test-apk-key-${{ needs.check-cache.outputs.packages-sha }} - path: | - ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - # Must match naming found in include-check-cache.yml # Must match naming found in include-check-cache.yml - name: Upload artifacts @@ -672,16 +654,6 @@ jobs: path: ./packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk retention-days: 1 - - name: Upload Android Sync Test APK - uses: actions/upload-artifact@v4 - with: - name: android-sync-test-apk-${{ needs.check-cache.outputs.version-label }} - retention-days: 1 - path: | - ./packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - ./packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - - # TODO: ccache is not being used by this build for some reason build-macos-x64-packages: runs-on: macos-14 # x64 builds fine on an arm64 node @@ -973,25 +945,15 @@ jobs: path: ./packages/build/m2-buildrepo/**/* retention-days: 1 - - # TODO Split into base and sync tests - # TODO If we hook up to Device Farm we can use ubuntu runners instead - # TODO Compare speed between emulator and Device Farm - # TODO We should be able to move this into a reusable work-flow - - # TODO Compare speed between emulator and Device Farm - # TODO We should be able to move this into a reusable work-flow test-android-packages-emulator: timeout-minutes: 60 strategy: fail-fast: false matrix: - type: [base, sync] + type: [base] include: - type: base test-title: Unit Test Results - Android Base (Emulator) - - type: sync - test-title: Unit Test Results - Android Sync (Emulator) runs-on: macos-12 # android emulator does not run on arm64 - https://github.com/ReactiveCircus/android-emulator-runner/issues/350 needs: [check-cache, build-android-packages, build-jvm-packages, build-kotlin-metadata-package] @@ -1007,27 +969,6 @@ jobs: with: submodules: "recursive" - # checkout BAAS CLI repo - - name: Checkout BAAS repo - if: matrix.type == 'sync' - run: | - echo ${{ secrets.BAAS_CLI }} | gh auth login --with-token - gh repo clone 10gen/baasaas - - # Start BAAS instance in the background - # We save the container id to poll against and get the hostname info later - - name: Start Baas instance in the background - id: baas_cli_start - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - # Adding a dummy tag (foo=bar) to avoid the following issue on macos-runner - # curl: option --data: error encountered when reading a file - OUTPUT=$(bash cli.sh start | jq -r '.id') - echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - - name: Setup Java 11 uses: actions/setup-java@v4 with: @@ -1057,17 +998,6 @@ jobs: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - # We poll the previously started BAAS container to get the hostname of the container to use with Device Farm tests - - name: Fetching the BAAS CLI hostname - id: baas_cli_poll - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh poll ${{ steps.baas_cli_start.outputs.baas_container_id }} | jq -r '.httpUrl') - echo "baas_container_hostname=$OUTPUT" >> $GITHUB_OUTPUT - # TODO This action does not support using `\` to split multiline scripts. - name: Run Integration Tests env: @@ -1086,7 +1016,7 @@ jobs: script: | adb logcat -c adb logcat > logcat.txt & - cd packages && ./gradlew :test-${{ matrix.type }}:connectedCheck -PsyncUsePlatformNetworking=true -PsyncTestUrl=${{ steps.baas_cli_poll.outputs.baas_container_hostname }} -PincludeSdkModules=false --info --no-daemon + cd packages && ./gradlew :test-${{ matrix.type }}:connectedCheck -PincludeSdkModules=false --info --no-daemon - name: Archive LogCat data uses: actions/upload-artifact@v4 @@ -1107,161 +1037,24 @@ jobs: list-tests: failed fail-on-error: true - - name: Stopping the BAAS container - if: always() && matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - if [ -n "${{ steps.baas_cli_start.outputs.baas_container_id }}" ]; then - bash cli.sh stop ${{ steps.baas_cli_start.outputs.baas_container_id }} - fi - - # Disable device farm test for Base, because running two in parallel seems to interfer somehow - test-android-packages-device-farm: - name: AWS Device Farm - timeout-minutes: 60 - runs-on: ubuntu-latest - needs: [ check-cache, build-android-packages, build-jvm-packages ] - if: | - false && - always() && - !cancelled() && - !contains(needs.*.result, 'failure') && - !contains(needs.*.result, 'cancelled') - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Restore Android Test APK - uses: actions/download-artifact@v4 - with: - name: android-base-test-apk-${{ needs.check-cache.outputs.version-label }} - path: ./packages/test-base/build/outputs/apk/androidTest/debug - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_DEVICEFARM_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_DEVICEFARM_SECRET_ACCESS_KEY }} - aws-region: us-west-2 - - - name: Run the tests - uses: ./.github/actions/run-android-device-farm-test - id: run_android_tests - with: - apk-path: ${{ github.workspace }}/packages/test-base/build/outputs/apk/androidTest/debug/test-base-debug-androidTest.apk - app-id: io.realm.testapp - project-arn: ${{ secrets.DEVICEFARM_PROJECT_ARN }} - device-pool-arn: ${{ secrets.DEVICEFARM_POOL_ARN }} - - - test-android-packages-device-farm-sync: - name: AWS Device Farm Sync Tests - timeout-minutes: 60 - runs-on: ubuntu-latest - needs: [ check-cache, build-android-packages, build-jvm-packages ] - if: | - always() && - !cancelled() && - !contains(needs.*.result, 'failure') && - !contains(needs.*.result, 'cancelled') && - endsWith(needs.check-cache.outputs.version-label, '-SNAPSHOT') && - (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/releases' || github.ref == 'refs/heads/release/k2') - steps: - - name: Checkout code - uses: actions/checkout@v4 - - # checkout BAAS CLI repo - - name: Checkout BAAS repo - run: | - echo ${{ secrets.BAAS_CLI }} | gh auth login --with-token - gh repo clone 10gen/baasaas - - # Start BAAS instance in the background - # We save the container id to poll against and get the hostname info later - - name: Start Baas instance in the background - id: baas_cli_start - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh start | jq -r '.id') - echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - - - name: Restore Android Sync Test APK - uses: actions/download-artifact@v4 - with: - name: android-sync-test-apk-${{ needs.check-cache.outputs.version-label }} - path: ./packages/test-sync/build/outputs/apk/ - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.AWS_DEVICEFARM_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_DEVICEFARM_SECRET_ACCESS_KEY }} - aws-region: us-west-2 - - # We poll the previously started BAAS container to get the hostname of the container to use with Device Farm tests - - name: Fetching the BAAS CLI hostname - id: baas_cli_poll - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh poll ${{ steps.baas_cli_start.outputs.baas_container_id }} | jq -r '.httpUrl') - echo "baas_container_hostname=$OUTPUT" >> $GITHUB_OUTPUT - - - name: Run the Sync tests - uses: ./.github/actions/run-android-device-farm-test - id: run_android_tests - with: - apk-path: ${{ github.workspace }}/packages/test-sync/build/outputs/apk/androidTest/debug/test-sync-debug-androidTest.apk - apk-auxiliary-path: ${{ github.workspace }}/packages/test-sync/build/outputs/apk/debug/test-sync-debug.apk - baas_url: ${{ steps.baas_cli_poll.outputs.baas_container_hostname }} - app-id: io.realm.sync.testapp.test - project-arn: ${{ secrets.DEVICEFARM_PROJECT_ARN }} - device-pool-arn: ${{ secrets.DEVICEFARM_POOL_ARN }} - - - name: Stopping the BAAS container - if: always() - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - if [ -n "${{ steps.baas_cli_start.outputs.baas_container_id }}" ]; then - bash cli.sh stop ${{ steps.baas_cli_start.outputs.baas_container_id }} - fi - test-macos-packages: timeout-minutes: 60 strategy: fail-fast: false matrix: os: [macos-12, macos-14] - type: [base, sync] + type: [base] include: - os: macos-12 type: base os-id: macos package-prefix: macos-x64 test-title: Unit Test Results - MacOS x64 Base - - os: macos-12 - type: sync - os-id: macos - package-prefix: macos-x64 - test-title: Unit Test Results - MacOS x64 Sync - os: macos-14 type: base os-id: macos package-prefix: macos-arm64 test-title: Unit Test Results - MacOS arm64 Base - - os: macos-14 - type: sync - os-id: macos - package-prefix: macos-arm64 - test-title: Unit Test Results - MacOS arm64 Sync runs-on: ${{ matrix.os }} # TODO Unclear why MacOS needs the metadata package when the Android Tests do not @@ -1277,27 +1070,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # checkout BAAS CLI repo - - name: Checkout BAAS repo - if: matrix.type == 'sync' - run: | - echo ${{ secrets.BAAS_CLI }} | gh auth login --with-token - gh repo clone 10gen/baasaas - - # Start BAAS instance in the background - # We save the container id to poll against and get the hostname info later - - name: Start Baas instance in the background - id: baas_cli_start - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - # Adding a dummy tag (foo=bar) to avoid the following issue on macos-runner - # curl: option --data: error encountered when reading a file - OUTPUT=$(bash cli.sh start | jq -r '.id') - echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - - name: Setup Java 11 uses: actions/setup-java@v4 with: @@ -1321,23 +1093,10 @@ jobs: name: packages-metadata-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - - # We poll the previously started BAAS container to get the hostname of the container to use with Device Farm tests - - name: Fetching the BAAS CLI hostname - id: baas_cli_poll - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh poll ${{ steps.baas_cli_start.outputs.baas_container_id }} | jq -r '.httpUrl') - echo "baas_container_hostname=$OUTPUT" >> $GITHUB_OUTPUT - - name: Run tests working-directory: packages run: > ./gradlew :test-${{ matrix.type }}:macosTest - -PsyncTestUrl=${{ steps.baas_cli_poll.outputs.baas_container_hostname }} -PincludeSdkModules=false --info --no-daemon @@ -1352,45 +1111,24 @@ jobs: list-tests: failed fail-on-error: true - - name: Stopping the BAAS container - if: always() && matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - if [ -n "${{ steps.baas_cli_start.outputs.baas_container_id }}" ]; then - bash cli.sh stop ${{ steps.baas_cli_start.outputs.baas_container_id }} - fi - - test-ios-packages: timeout-minutes: 60 strategy: fail-fast: false matrix: os: [macos-12, macos-14] - type: [base, sync] + type: [base] include: - os: macos-12 type: base package-prefix: x64 test-title: Unit Test Results - iOS x64 Base test-task: iosTest - - os: macos-12 - type: sync - package-prefix: x64 - test-title: Unit Test Results - iOS x64 Sync - test-task: iosTest - os: macos-14 type: base package-prefix: arm64 test-title: Unit Test Results - iOS arm64 Base test-task: iosTest - - os: macos-14 - type: sync - package-prefix: arm64 - test-title: Unit Test Results - iOS arm64 Sync - test-task: iosTest runs-on: ${{ matrix.os }} # TODO Unclear why MacOS needs the metadata package when the Android Tests do not @@ -1406,27 +1144,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # checkout BAAS CLI repo - - name: Checkout BAAS repo - if: matrix.type == 'sync' - run: | - echo ${{ secrets.BAAS_CLI }} | gh auth login --with-token - gh repo clone 10gen/baasaas - - # Start BAAS instance in the background - # We save the container id to poll against and get the hostname info later - - name: Start Baas instance in the background - id: baas_cli_start - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - # Adding a dummy tag (foo=bar) to avoid the following issue on macos-runner - # curl: option --data: error encountered when reading a file - OUTPUT=$(bash cli.sh start | jq -r '.id') - echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - - name: Setup Java 11 uses: actions/setup-java@v4 with: @@ -1451,23 +1168,11 @@ jobs: path: ./packages/build/m2-buildrepo - # We poll the previously started BAAS container to get the hostname of the container to use with Device Farm tests - - name: Fetching the BAAS CLI hostname - id: baas_cli_poll - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh poll ${{ steps.baas_cli_start.outputs.baas_container_id }} | jq -r '.httpUrl') - echo "baas_container_hostname=$OUTPUT" >> $GITHUB_OUTPUT - # App names are limited to 32 characters, so the appNamePrefix should not exceed 22 characters. - name: Run tests working-directory: packages run: > ./gradlew :test-${{ matrix.type }}:${{ matrix.test-task }} - -PsyncTestUrl=${{ steps.baas_cli_poll.outputs.baas_container_hostname }} -PincludeSdkModules=false --info --no-daemon @@ -1482,23 +1187,13 @@ jobs: list-tests: failed fail-on-error: true - - name: Stopping the BAAS container - if: always() && matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - if [ -n "${{ steps.baas_cli_start.outputs.baas_container_id }}" ]; then - bash cli.sh stop ${{ steps.baas_cli_start.outputs.baas_container_id }} - fi - test-jvm-packages: timeout-minutes: 60 strategy: fail-fast: false matrix: os: [macos-12, macos-14, ubuntu-latest, windows-latest] - type: [base, sync] + type: [base] include: - os: macos-12 os-id: mac @@ -1516,25 +1211,6 @@ jobs: os-id: win type: base test-title: Unit Test Results - Base JVM Windows - - os: macos-12 - os-id: mac - type: sync - test-title: Unit Test Results - Sync JVM MacOS x64 - - os: macos-14 - os-id: mac - type: sync - test-title: Unit Test Results - Sync JVM MacOS arm64 - - os: ubuntu-latest - os-id: ubu - type: sync - test-title: Unit Test Results - Sync JVM Linux - exclude: - # Do not run Windows Sync Tests, because the bash script for - # starting the BAAS container doesn not work on Windows. - - os: windows-latest - #os-id: win - type: sync - #test-title: Unit Test Results - Sync JVM Windows runs-on: ${{ matrix.os }} needs: [check-cache, build-jvm-packages, build-kotlin-metadata-package] @@ -1548,27 +1224,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - # checkout BAAS CLI repo - - name: Checkout BAAS repo - if: matrix.type == 'sync' - run: | - echo ${{ secrets.BAAS_CLI }} | gh auth login --with-token - gh repo clone 10gen/baasaas - - # Start BAAS instance in the background - # We save the container id to poll against and get the hostname info later - - name: Start Baas instance in the background - id: baas_cli_start - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - # Adding a dummy tag (foo=bar) to avoid the following issue on macos-runner - # curl: option --data: error encountered when reading a file - OUTPUT=$(bash cli.sh start | jq -r '.id') - echo "baas_container_id=$OUTPUT" >> $GITHUB_OUTPUT - - name: Setup Java 11 uses: actions/setup-java@v4 with: @@ -1592,24 +1247,11 @@ jobs: name: packages-jvm-${{ needs.check-cache.outputs.version-label }} path: ./packages/build/m2-buildrepo - # We poll the previously started BAAS container to get the hostname of the container to use with Device Farm tests - - name: Fetching the BAAS CLI hostname - id: baas_cli_poll - if: matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - OUTPUT=$(bash cli.sh poll ${{ steps.baas_cli_start.outputs.baas_container_id }} | jq -r '.httpUrl') - echo "baas_container_hostname=$OUTPUT" >> $GITHUB_OUTPUT - # App names are limited to 32 characters, so the appNamePrefix should not exceed 22 characters. - name: Run tests working-directory: packages run: > ./gradlew :test-${{ matrix.type }}:jvmTest - -PsyncUsePlatformNetworking=true - -PsyncTestUrl=${{ steps.baas_cli_poll.outputs.baas_container_hostname }} -PincludeSdkModules=false --info --no-daemon @@ -1624,16 +1266,6 @@ jobs: list-tests: failed fail-on-error: true - - name: Stopping the BAAS container - if: always() && matrix.type == 'sync' - working-directory: baasaas - env: - APIKEY: ${{ secrets.BAAS_CLI_API_KEY }} - run: | - if [ -n "${{ steps.baas_cli_start.outputs.baas_container_id }}" ]; then - bash cli.sh stop ${{ steps.baas_cli_start.outputs.baas_container_id }} - fi - package-all-artifacts: runs-on: ubuntu-latest needs: [check-cache, build-jvm-packages, build-android-packages, build-macos-x64-packages, build-macos-arm64-packages, build-ios-x64-packages, build-ios-arm64-packages, build-kotlin-metadata-package] @@ -1719,8 +1351,6 @@ jobs: test-macos-packages, test-ios-packages, test-android-packages-emulator, - # test-android-packages-device-farm, - test-android-packages-device-farm-sync, package-all-artifacts ] if: | @@ -1782,8 +1412,6 @@ jobs: test-macos-packages, test-ios-packages, test-android-packages-emulator, - # test-android-packages-device-farm, - test-android-packages-device-farm-sync, package-all-artifacts ] if: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f4386c27..8ff1cac353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ + ## 3.0.0-SNAPSHOT (YYYY-MM-DD) ### Breaking Changes -* None. +* [Sync] Atlas Device Sync related functionality has been removed from the project. ### Enhancements * None. @@ -33,6 +34,36 @@ ### Breaking Changes * None. +### Enhancements +* None. + +### Fixed +* Via https://github.com/realm/realm-kotlin/pull/1826. Fix compiler crash caused by a change in Kotlin 2.0.20. (Issue [#1825](https://github.com/realm/realm-kotlin/issues/1825)). Thanks @KitsuneAlex. + +### Compatibility +* File format: Generates Realms with file format v24 (reads and upgrades file format v10 or later). +* Realm Studio 15.0.0 or above is required to open Realms created by this version. +* This release is compatible with the following Kotlin releases: + * Kotlin 2.0.20 and above. Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. + * Ktor 2.1.2 and above. + * Coroutines 1.7.0 and above. + * AtomicFu 0.18.3 and above. + * The new memory model only. See https://github.com/realm/realm-kotlin#kotlin-memory-model-and-coroutine-compatibility +* Minimum Kbson 0.4.0. +* Minimum Gradle version: 7.2. +* Minimum Android Gradle Plugin version: 7.1.3. +* Minimum Android SDK: 16. +* Minimum R8: 8.0.34. + +### Internal +* None. + + +## 2.2.0 (2024-09-13) + +### Breaking Changes +* None. + ### Enhancements * Support Android 15 page size 16 KB. (Issue [#1787](https://github.com/realm/realm-kotlin/issues/1787) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1105)). * Reduce the size of the local transaction log produced by creating objects, improving the performance of insertion-heavy transactions (Core issue [realm/realm-core#7734](https://github.com/realm/realm-core/pull/7734)). @@ -49,7 +80,6 @@ * [Sync] Client reset cycle detection now checks if the previous recovery attempt was made by the same core version, and if not attempts recovery again (Core issue [realm/realm-core#7944](https://github.com/realm/realm-core/pull/7944)). ### Fixed -* Via https://github.com/realm/realm-kotlin/pull/1826. Fix compiler crash caused by a change in Kotlin 2.0.20. (Issue [#1825](https://github.com/realm/realm-kotlin/issues/1825)). Thanks @KitsuneAlex. * Comparing a numeric property with an argument list containing a string would throw. (Core issue [realm/realm-core#7714](https://github.com/realm/realm-core/issues/7714), since v2.0.0). * After compacting, a file upgrade would be triggered. This could cause loss of data if schema mode is SoftResetFile (Core issue [realm/realm-core#7747](https://github.com/realm/realm-core/issues/7747), since v1.15.0). * Encrypted files on Windows had a maximum size of 2GB even on x64 due to internal usage of `off_t`, which is a 32-bit type on 64-bit Windows (Core issue [realm/realm-core#7698](https://github.com/realm/realm-core/pull/7698)). @@ -93,36 +123,6 @@ * Updated to Sync protocol version 14 to support server intiated bootstraps and role change updates without a client reset. (Core issue [realm/realm-core#7440](https://github.com/realm/realm-core/pull/7440)). -## 2.1.1-SNAPSHOT (YYYY-MM-DD) - -### Breaking Changes -* None. - -### Enhancements -* None. - -### Fixed -* None. - -### Compatibility -* File format: Generates Realms with file format v24 (reads and upgrades file format v10 or later). -* Realm Studio 15.0.0 or above is required to open Realms created by this version. -* This release is compatible with the following Kotlin releases: - * Kotlin 2.0.0 and above. Support for experimental K2-compilation with `kotlin.experimental.tryK2=true`. - * Ktor 2.1.2 and above. - * Coroutines 1.7.0 and above. - * AtomicFu 0.18.3 and above. - * The new memory model only. See https://github.com/realm/realm-kotlin#kotlin-memory-model-and-coroutine-compatibility -* Minimum Kbson 0.4.0. -* Minimum Gradle version: 7.2. -* Minimum Android Gradle Plugin version: 7.1.3. -* Minimum Android SDK: 16. -* Minimum R8: 8.0.34. - -### Internal -* None. - - ## 2.1.0 (2024-07-12) ### Breaking Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0cd3db7f53..385832da54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,10 +50,6 @@ platforms with: cd packages ./gradlew :test-base:jvmTest :test-base:connectedAndroidTest :test-base:macosTest :test-base:iosTest -# Note that running the test-sync suite requires running a local server -# (see `tools/sync_test_server/start_local_server.sh` and `tools/sync_test_server/stop_local_server.sh`) - -./gradlew :test-sync:jvmTest :test-sync:connectedAndroidTest :test-sync:macosTest :test-sync:iosTest ``` You can also the test across all modules on the various platforms with ```sh @@ -230,9 +226,9 @@ All platform differentiated implementations are kept in `platform`-packages with Inside the various `packages/test-X/` modules there are 3 locations the files can be placed in: -* `packages/test-/src/commonTest` -* `package/test-/src/androidAndroidTest` -* `package/test-/src/nativeDarwinTest` (macOS) +* `packages/test-base/src/commonTest` +* `package/test-base/src/androidAndroidTest` +* `package/test-base/src/nativeDarwinTest` (macOS) Ideally all shared tests should be in `commonTest` with specific platform tests in `androidAndroidTest`/`nativeDarwinTest`. However IntelliJ does [not yet allow you to run common tests on Android from within the IDE](https://youtrack.jetbrains.com/issue/KT-46452), so we are using the following work-around: diff --git a/README.md b/README.md index 83478678f9..b450bfa5ff 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ This repository holds the source code for the Kotlin SDK for Realm, which runs o * **Intuitive to Developers:** Realm’s object-oriented data model is simple to learn, doesn’t need an ORM, and lets you write less code. * **Built for Mobile:** Realm is fully-featured, lightweight, and efficiently uses memory, disk space, and battery life. * **Designed for Offline Use:** Realm’s local database persists data on-disk, so apps work as well offline as they do online. -* **[Atlas Device Sync](https://www.mongodb.com/docs/atlas/app-services/sync/)**: Makes it simple to keep data in sync across users, devices, and your backend in real-time. Get started for free with [a template application](https://github.com/mongodb/template-app-kotlin-todo) and [create the cloud backend](http://mongodb.com/realm/register?utm_medium=github_atlas_CTA&utm_source=realm_kotlin_github). # General Availability @@ -37,7 +36,7 @@ If you are migrating from [Realm Java](https://github.com/realm/realm-java), ple ## Installation -Installation differs slightly depending on the type of project and whether or not you are using Device Sync. See the details in the documentation: +Installation differs slightly depending on the type of project. See the details in the documentation: * [Android](https://www.mongodb.com/docs/atlas/device-sdks/sdk/kotlin/install/#std-label-kotlin-install-android) * [Kotlin Multiplatform](https://www.mongodb.com/docs/atlas/device-sdks/sdk/kotlin/install/#std-label-kotlin-install-kotlin-multiplatform) diff --git a/benchmarks/androidApp/build.gradle.kts b/benchmarks/androidApp/build.gradle.kts index 2d3a387568..b3356b5986 100644 --- a/benchmarks/androidApp/build.gradle.kts +++ b/benchmarks/androidApp/build.gradle.kts @@ -47,7 +47,7 @@ android { } dependencies { - androidTestImplementation("io.realm.kotlin:library-sync:${Realm.version}") + androidTestImplementation("io.realm.kotlin:library-base:${Realm.version}") androidTestImplementation("androidx.test:runner:${Versions.androidxTest}") androidTestImplementation("androidx.test.ext:junit:${Versions.androidxJunit}") androidTestImplementation("junit:junit:${Versions.junit}") diff --git a/benchmarks/jvmApp/build.gradle.kts b/benchmarks/jvmApp/build.gradle.kts index 3244a5ee05..3d36f17cf8 100644 --- a/benchmarks/jvmApp/build.gradle.kts +++ b/benchmarks/jvmApp/build.gradle.kts @@ -6,7 +6,7 @@ apply(plugin = "kotlin") dependencies { jmh(project(":shared")) - jmh("io.realm.kotlin:library-sync:${Realm.version}") + jmh("io.realm.kotlin:library-base:${Realm.version}") jmh("org.openjdk.jmh:jmh-core:${Versions.jmh}") jmh("org.openjdk.jmh:jmh-generator-annprocess:${Versions.jmh}") } diff --git a/benchmarks/shared/build.gradle.kts b/benchmarks/shared/build.gradle.kts index 6fda4e66dd..fe13d1dfd3 100644 --- a/benchmarks/shared/build.gradle.kts +++ b/benchmarks/shared/build.gradle.kts @@ -28,7 +28,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("io.realm.kotlin:library-sync:${Realm.version}") + implementation("io.realm.kotlin:library-base:${Realm.version}") } } val androidMain by getting diff --git a/examples/realm-java-compatibility/app/build.gradle b/examples/realm-java-compatibility/app/build.gradle index da72b4e451..c06f0e3c23 100644 --- a/examples/realm-java-compatibility/app/build.gradle +++ b/examples/realm-java-compatibility/app/build.gradle @@ -51,10 +51,6 @@ android { } } -realm { - syncEnabled = true -} - dependencies { implementation 'androidx.core:core-ktx:1.7.0' @@ -62,7 +58,7 @@ dependencies { implementation 'com.google.android.material:material:1.6.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' - implementation "io.realm.kotlin:library-sync:${Realm.version}" + implementation "io.realm.kotlin:library-base:${Realm.version}" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/packages/build.gradle.kts b/packages/build.gradle.kts index 3af7043487..f7c43c95d5 100644 --- a/packages/build.gradle.kts +++ b/packages/build.gradle.kts @@ -102,15 +102,12 @@ tasks.register("publishCIPackages") { ":cinterop:publishIosSimulatorArm64PublicationToTestRepository", ":library-base:publishIosArm64PublicationToTestRepository", ":library-base:publishIosSimulatorArm64PublicationToTestRepository", - ":library-sync:publishIosArm64PublicationToTestRepository", - ":library-sync:publishIosSimulatorArm64PublicationToTestRepository", ) } "iosX64" -> { dependsOn( ":cinterop:publishIosX64PublicationToTestRepository", ":library-base:publishIosX64PublicationToTestRepository", - ":library-sync:publishIosX64PublicationToTestRepository", ) } "jvm" -> { @@ -118,21 +115,18 @@ tasks.register("publishCIPackages") { ":jni-swig-stub:publishAllPublicationsToTestRepository", ":cinterop:publishJvmPublicationToTestRepository", ":library-base:publishJvmPublicationToTestRepository", - ":library-sync:publishJvmPublicationToTestRepository", ) } "macosX64" -> { dependsOn( ":cinterop:publishMacosX64PublicationToTestRepository", ":library-base:publishMacosX64PublicationToTestRepository", - ":library-sync:publishMacosX64PublicationToTestRepository", ) } "macosArm64" -> { dependsOn( ":cinterop:publishMacosArm64PublicationToTestRepository", ":library-base:publishMacosArm64PublicationToTestRepository", - ":library-sync:publishMacosArm64PublicationToTestRepository", ) } "android" -> { @@ -140,14 +134,12 @@ tasks.register("publishCIPackages") { ":jni-swig-stub:publishAllPublicationsToTestRepository", ":cinterop:publishAndroidReleasePublicationToTestRepository", ":library-base:publishAndroidReleasePublicationToTestRepository", - ":library-sync:publishAndroidReleasePublicationToTestRepository", ) } "metadata" -> { dependsOn( ":cinterop:publishKotlinMultiplatformPublicationToTestRepository", ":library-base:publishKotlinMultiplatformPublicationToTestRepository", - ":library-sync:publishKotlinMultiplatformPublicationToTestRepository", ) } "compilerPlugin" -> { diff --git a/packages/cinterop/build.gradle.kts b/packages/cinterop/build.gradle.kts index 32a61dd7ca..3a01d95007 100644 --- a/packages/cinterop/build.gradle.kts +++ b/packages/cinterop/build.gradle.kts @@ -83,7 +83,6 @@ val nativeLibraryIncludesMacosUniversalRelease = includeBinaries( "Release/librealm.a", "parser/Release/librealm-parser.a", "object-store/Release/librealm-object-store.a", - "sync/Release/librealm-sync.a" ).map { "$absoluteCorePath/build-macos_universal/src/realm/$it" } ) val nativeLibraryIncludesMacosUniversalDebug = includeBinaries( @@ -92,7 +91,6 @@ val nativeLibraryIncludesMacosUniversalDebug = includeBinaries( "Debug/librealm-dbg.a", "parser/Debug/librealm-parser-dbg.a", "object-store/Debug/librealm-object-store-dbg.a", - "sync/Debug/librealm-sync-dbg.a" ).map { "$absoluteCorePath/build-macos_universal-dbg/src/realm/$it" } ) val releaseLibs = listOf( @@ -100,14 +98,12 @@ val releaseLibs = listOf( "librealm.a", "librealm-parser.a", "librealm-object-store.a", - "librealm-sync.a" ) val debugLibs = listOf( "librealm-ffi-static-dbg.a", "librealm-dbg.a", "librealm-parser-dbg.a", "librealm-object-store-dbg.a", - "librealm-sync-dbg.a" ) val nativeLibraryIncludesIosArm64Debug = includeBinaries(debugLibs.map { "$absoluteCorePath/build-capi_ios_Arm64-dbg/lib/$it" }) @@ -490,7 +486,6 @@ fun getSharedCMakeFlags(buildType: BuildType, ccache: Boolean = true): Array - ErrorCategory.of(nativeValue) - } - assertEquals(ErrorCategory.values().size, CategoryFlags.CATEGORY_ORDER.size) - } - - @Test - fun authProvider() { - checkEnum(realm_auth_provider_e::class) { nativeValue -> - AuthProvider.of(nativeValue) - } - } - - @Test - fun clientErrorCode() { - checkEnum(realm_errno_e::class) { nativeValue -> - ErrorCode.of(nativeValue) - } - } - - @Test - fun coreUserState() { - checkEnum(realm_user_state_e::class) { nativeValue -> - CoreUserState.of(nativeValue) - } - } - - @Test - fun metadataMode() { - checkEnum(realm_sync_client_metadata_mode_e::class) { nativeValue -> - MetadataMode.of(nativeValue) - } - } - - @Test - fun syncConnectionErrorCode() { - checkEnum(realm_sync_errno_connection_e::class) { nativeValue -> - SyncConnectionErrorCode.of(nativeValue) - } - } - - @Test - fun syncSessionErrorCode() { - checkEnum(realm_sync_errno_session_e::class) { nativeValue -> - SyncSessionErrorCode.of(nativeValue) - } - } - - @Test - fun websocketErrorCode() { - checkEnum(realm_web_socket_errno_e::class) { nativeValue -> - WebsocketErrorCode.of(nativeValue) - } - } - - @Test - fun syncSessionResyncMode() { - checkEnum(realm_sync_session_resync_mode_e::class) { nativeValue -> - SyncSessionResyncMode.of(nativeValue) - } - } - - @Test - fun syncSessionState() { - checkEnum(realm_sync_session_state_e::class) { nativeValue -> - CoreSyncSessionState.of(nativeValue) - } - } - - @Test - fun syncSessionConnectionState() { - checkEnum(realm_sync_connection_state_e::class) { nativeValue -> - CoreConnectionState.of(nativeValue) - } - } - - @Test - fun syncSubscriptionSetState() { - checkEnum(realm_flx_sync_subscription_set_state_e::class) { nativeValue -> - CoreSubscriptionSetState.of(nativeValue) - } - } - - @Test - fun websocketResultCode() { - checkEnum(realm_sync_socket_callback_result_e::class) { nativeValue -> - WebsocketCallbackResult.of(nativeValue) - } - } - - private inline fun checkEnum( - enumClass: KClass, - mapNativeValue: (Int) -> T? - ) { - // Fetch all native values - val coreNativeValues: IntArray = enumClass.java.fields - .map { it.getInt(null) } - .toIntArray() - - // Find all enums mapping to those values - val mappedKotlinEnums: Set = coreNativeValues - .map { - mapNativeValue(it) ?: fail("${enumClass.simpleName}: unmapped native value $it") - } - .toSet() - - // Validate we have a different enum defined for each core native value. - // Note, we cannot check that the name is mapped correctly, but the chance - // of that happening should be really low and we will catch all the common - // cases of adding, removing and renaming enums. - assertEquals(coreNativeValues.size, mappedKotlinEnums.size) - } -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt index fbef8d286e..e568fe7087 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/Callback.kt @@ -16,49 +16,16 @@ package io.realm.kotlin.internal.interop -import io.realm.kotlin.internal.interop.sync.AppError -import io.realm.kotlin.internal.interop.sync.CoreConnectionState -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.internal.interop.sync.SyncError - // TODO Could be replace by lambda. See realm_app_config_new networkTransportFactory for example. interface Callback { fun onChange(change: T) } -// Callback from asynchronous sync methods. Use AppCallback for void callbacks and -// AppCallback for callbacks with native pointers to core objects. -interface AppCallback { - fun onSuccess(result: T) - fun onError(error: AppError) -} - -fun interface SyncErrorCallback { - fun onSyncError(pointer: RealmSyncSessionPointer, error: SyncError) -} - -// Interface exposed towards `library-sync` -interface SyncSessionTransferCompletionCallback { - fun invoke(error: CoreError?) -} - interface LogCallback { // Passes core log levels as shorts to avoid unnecessary jumping between the SDK and JNI fun log(logLevel: Short, categoryValue: String, message: String?) } -interface SyncBeforeClientResetHandler { - fun onBeforeReset(realmBefore: FrozenRealmPointer) -} - -interface SyncAfterClientResetHandler { - fun onAfterReset( - realmBefore: FrozenRealmPointer, - realmAfter: LiveRealmPointer, - didRecover: Boolean - ) -} - fun interface CompactOnLaunchCallback { fun invoke(totalBytes: Long, usedBytes: Long): Boolean } @@ -71,28 +38,12 @@ fun interface MigrationCallback { ) } -fun interface SubscriptionSetCallback { - fun onChange(state: CoreSubscriptionSetState) -} - // The underlying Core implementation can also pass in Realm pointer, but since it is not // useful during construction, we omit it from this callback as it is only used as a signal. fun interface DataInitializationCallback { fun invoke() } -fun interface AsyncOpenCallback { - fun invoke(error: Throwable?) -} - -fun interface ProgressCallback { - fun onChange(progressEstimate: Double) -} - -fun interface ConnectionStateChangeCallback { - fun onChange(oldState: CoreConnectionState, newState: CoreConnectionState) -} - interface SyncThreadObserver { // Should return the name of the Java Sync thread. fun threadName(): String diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ClassInfo.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ClassInfo.kt index c2598aba25..9f27258a1b 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ClassInfo.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ClassInfo.kt @@ -29,7 +29,6 @@ data class ClassInfo( ) { val isEmbedded = flags and ClassFlags.RLM_CLASS_EMBEDDED != 0 - val isAsymmetric = flags and ClassFlags.RLM_CLASS_ASYMMETRIC != 0 companion object { // Convenience wrapper to ease maintaining compiler plugin @@ -38,11 +37,9 @@ data class ClassInfo( primaryKey: String?, numProperties: Long, isEmbedded: Boolean = false, - isAsymmetric: Boolean = false ): ClassInfo { val flags: Int = when { isEmbedded -> ClassFlags.RLM_CLASS_EMBEDDED - isAsymmetric -> ClassFlags.RLM_CLASS_ASYMMETRIC else -> ClassFlags.RLM_CLASS_NORMAL } return ClassInfo(name, primaryKey ?: SCHEMA_NO_VALUE, numProperties, 0, flags = flags) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreError.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreError.kt index 3d7334fcd7..829073c4f9 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreError.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreError.kt @@ -34,17 +34,9 @@ data class CategoryFlags(val categoryFlags: Int) { * associated either. */ val CATEGORY_ORDER: List = listOf( - ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR, - ErrorCategory.RLM_ERR_CAT_WEBSOCKET_ERROR, - ErrorCategory.RLM_ERR_CAT_SYNC_ERROR, - ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR, - ErrorCategory.RLM_ERR_CAT_JSON_ERROR, - ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR, ErrorCategory.RLM_ERR_CAT_SYSTEM_ERROR, ErrorCategory.RLM_ERR_CAT_FILE_ACCESS, - ErrorCategory.RLM_ERR_CAT_HTTP_ERROR, ErrorCategory.RLM_ERR_CAT_INVALID_ARG, - ErrorCategory.RLM_ERR_CAT_APP_ERROR, ErrorCategory.RLM_ERR_CAT_LOGIC, ErrorCategory.RLM_ERR_CAT_RUNTIME, ) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreErrorConverter.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreErrorConverter.kt index 0a60a8aa7b..60605a4453 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreErrorConverter.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/CoreErrorConverter.kt @@ -38,9 +38,7 @@ object CoreErrorConverter { return userError ?: when { ErrorCode.RLM_ERR_INDEX_OUT_OF_BOUNDS == errorCode -> IndexOutOfBoundsException(message) - ErrorCategory.RLM_ERR_CAT_INVALID_ARG in categories && ErrorCategory.RLM_ERR_CAT_SYNC_ERROR !in categories -> { - // Some sync errors flagged as both logical and illegal. In our case, we consider those - // IllegalState, so discard them them here and let them fall through to the bottom case + ErrorCategory.RLM_ERR_CAT_INVALID_ARG in categories -> { IllegalArgumentException(message) } ErrorCategory.RLM_ERR_CAT_LOGIC in categories || ErrorCategory.RLM_ERR_CAT_RUNTIME in categories -> diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt index 1cb48dacf7..2f1687eaea 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt @@ -25,15 +25,7 @@ expect enum class ErrorCategory : CodeDescription { RLM_ERR_CAT_RUNTIME, RLM_ERR_CAT_INVALID_ARG, RLM_ERR_CAT_FILE_ACCESS, - RLM_ERR_CAT_SYSTEM_ERROR, - RLM_ERR_CAT_APP_ERROR, - RLM_ERR_CAT_CLIENT_ERROR, - RLM_ERR_CAT_JSON_ERROR, - RLM_ERR_CAT_SERVICE_ERROR, - RLM_ERR_CAT_HTTP_ERROR, - RLM_ERR_CAT_CUSTOM_ERROR, - RLM_ERR_CAT_WEBSOCKET_ERROR, - RLM_ERR_CAT_SYNC_ERROR; + RLM_ERR_CAT_SYSTEM_ERROR; override val nativeValue: Int override val description: String? diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt index 36f25fa852..37d2af5a52 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt @@ -31,7 +31,6 @@ expect enum class SchemaMode { expect object ClassFlags { val RLM_CLASS_NORMAL: Int val RLM_CLASS_EMBEDDED: Int - val RLM_CLASS_ASYMMETRIC: Int } expect enum class PropertyType { diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 2595d08e98..d908df1144 100644 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -18,22 +18,7 @@ package io.realm.kotlin.internal.interop -import io.realm.kotlin.internal.interop.sync.ApiKeyWrapper -import io.realm.kotlin.internal.interop.sync.AuthProvider -import io.realm.kotlin.internal.interop.sync.CoreConnectionState -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState -import io.realm.kotlin.internal.interop.sync.CoreUserState -import io.realm.kotlin.internal.interop.sync.MetadataMode -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.ProgressDirection -import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode -import io.realm.kotlin.internal.interop.sync.SyncUserIdentity -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.interop.sync.WebsocketCallbackResult -import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode import kotlinx.coroutines.CoroutineDispatcher -import org.mongodb.kbson.ObjectId import kotlin.jvm.JvmInline import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -96,87 +81,6 @@ typealias RealmChangesPointer = NativePointer typealias RealmSchedulerPointer = NativePointer typealias RealmKeyPathArrayPointer = NativePointer -// Sync types -// Pure marker interfaces corresponding to the C-API realm_x_t struct types -interface RealmAsyncOpenTaskT : CapiT -interface RealmAppT : CapiT -interface RealmAppConfigT : CapiT -interface RealmSyncConfigT : CapiT -interface RealmSyncClientConfigT : CapiT -interface RealmCredentialsT : CapiT -interface RealmUserT : CapiT -interface RealmNetworkTransportT : CapiT -interface RealmSyncSessionT : CapiT -interface RealmSubscriptionT : CapiT -interface RealmSyncSocketObserverPointerT : CapiT -interface RealmSyncSocketCallbackPointerT : CapiT - -interface RealmBaseSubscriptionSet : CapiT -interface RealmSyncSocket : CapiT -interface RealmSubscriptionSetT : RealmBaseSubscriptionSet -interface RealmMutableSubscriptionSetT : RealmBaseSubscriptionSet -interface RealmSyncSocketT : RealmSyncSocket - -// Public type aliases binding to internal verbose type safe type definitions. This should allow us -// to easily change implementation details later on. -typealias RealmAsyncOpenTaskPointer = NativePointer -typealias RealmAppPointer = NativePointer -typealias RealmAppConfigurationPointer = NativePointer -typealias RealmSyncConfigurationPointer = NativePointer -typealias RealmSyncClientConfigurationPointer = NativePointer -typealias RealmCredentialsPointer = NativePointer -typealias RealmUserPointer = NativePointer -typealias RealmNetworkTransportPointer = NativePointer -typealias RealmSyncSessionPointer = NativePointer -typealias RealmSubscriptionPointer = NativePointer -typealias RealmBaseSubscriptionSetPointer = NativePointer -typealias RealmSubscriptionSetPointer = NativePointer -typealias RealmMutableSubscriptionSetPointer = NativePointer -typealias RealmSyncSocketPointer = NativePointer -typealias RealmSyncSocketObserverPointer = NativePointer -typealias RealmSyncSocketCallbackPointer = NativePointer -typealias RealmWebsocketHandlerCallbackPointer = NativePointer -typealias RealmWebsocketProviderPointer = NativePointer -/** - * Class for grouping and normalizing values we want to send as part of - * logging in Sync Users. - */ -@Suppress("LongParameterList") -class SyncConnectionParams( - sdkVersion: String, - bundleId: String, - platformVersion: String, - device: String, - deviceVersion: String, - framework: Runtime, - frameworkVersion: String -) { - val sdkName = "Kotlin" - val bundleId: String - val sdkVersion: String - val platformVersion: String - val device: String - val deviceVersion: String - val framework: String - val frameworkVersion: String - - enum class Runtime(public val description: String) { - JVM("JVM"), - ANDROID("Android"), - NATIVE("Native") - } - - init { - this.sdkVersion = sdkVersion - this.bundleId = bundleId - this.platformVersion = platformVersion - this.device = device - this.deviceVersion = deviceVersion - this.framework = framework.description - this.frameworkVersion = frameworkVersion - } -} - @Suppress("FunctionNaming", "LongParameterList") expect object RealmInterop { fun realm_value_get(value: RealmValue): Any? @@ -227,11 +131,6 @@ expect object RealmInterop { // dispatcher. The realm itself must also be opened on the same thread fun realm_open(config: RealmConfigurationPointer, scheduler: RealmSchedulerPointer): Pair - // Opening a Realm asynchronously. Only supported for synchronized realms. - fun realm_open_synchronized(config: RealmConfigurationPointer): RealmAsyncOpenTaskPointer - fun realm_async_open_task_start(task: RealmAsyncOpenTaskPointer, callback: AsyncOpenCallback) - fun realm_async_open_task_cancel(task: RealmAsyncOpenTaskPointer) - fun realm_add_realm_changed_callback(realm: LiveRealmPointer, block: () -> Unit): RealmCallbackTokenPointer fun realm_add_schema_changed_callback(realm: LiveRealmPointer, block: (RealmSchemaPointer) -> Unit): RealmCallbackTokenPointer @@ -516,102 +415,6 @@ expect object RealmInterop { builder: DictionaryChangeSetBuilder ) - // App - fun realm_app_get( - appConfig: RealmAppConfigurationPointer, - basePath: String, - ): RealmAppPointer - fun realm_app_get_current_user(app: RealmAppPointer): RealmUserPointer? - fun realm_app_get_all_users(app: RealmAppPointer): List - fun realm_app_log_in_with_credentials(app: RealmAppPointer, credentials: RealmCredentialsPointer, callback: AppCallback) - fun realm_app_log_out(app: RealmAppPointer, user: RealmUserPointer, callback: AppCallback) - fun realm_app_remove_user(app: RealmAppPointer, user: RealmUserPointer, callback: AppCallback) - fun realm_app_delete_user(app: RealmAppPointer, user: RealmUserPointer, callback: AppCallback) - fun realm_app_link_credentials(app: RealmAppPointer, user: RealmUserPointer, credentials: RealmCredentialsPointer, callback: AppCallback) - fun realm_app_switch_user(app: RealmAppPointer, user: RealmUserPointer) - fun realm_clear_cached_apps() - fun realm_app_sync_client_get_default_file_path_for_realm( - syncConfig: RealmSyncConfigurationPointer, - overriddenName: String? - ): String - fun realm_app_user_apikey_provider_client_create_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - callback: AppCallback - ) - - fun realm_app_user_apikey_provider_client_delete_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback, - ) - - fun realm_app_user_apikey_provider_client_disable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback, - ) - - fun realm_app_user_apikey_provider_client_enable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback, - ) - - fun realm_app_user_apikey_provider_client_fetch_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback, - ) - - fun realm_app_user_apikey_provider_client_fetch_apikeys( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback>, - ) - - fun realm_app_get_base_url( - app: RealmAppPointer, - ): String - - fun realm_app_update_base_url( - app: RealmAppPointer, - baseUrl: String?, - callback: AppCallback, - ) - - // User - fun realm_user_get_all_identities(user: RealmUserPointer): List - fun realm_user_get_identity(user: RealmUserPointer): String - fun realm_user_get_access_token(user: RealmUserPointer): String - fun realm_user_get_refresh_token(user: RealmUserPointer): String - fun realm_user_get_device_id(user: RealmUserPointer): String - fun realm_user_is_logged_in(user: RealmUserPointer): Boolean - fun realm_user_log_out(user: RealmUserPointer) - fun realm_user_get_state(user: RealmUserPointer): CoreUserState - fun realm_user_get_profile(user: RealmUserPointer): String - fun realm_user_get_custom_data(user: RealmUserPointer): String? - fun realm_user_refresh_custom_data(app: RealmAppPointer, user: RealmUserPointer, callback: AppCallback) - - // Sync client config - fun realm_app_config_get_sync_client_config(configPointer: RealmAppConfigurationPointer): RealmSyncClientConfigurationPointer - fun realm_sync_client_config_set_default_binding_thread_observer( - syncClientConfig: RealmSyncClientConfigurationPointer, - appId: String - ) - - fun realm_app_config_set_base_file_path( - appConfig: RealmAppConfigurationPointer, - basePath: String - ) - - fun realm_sync_client_config_set_multiplex_sessions(syncClientConfig: RealmSyncClientConfigurationPointer, enabled: Boolean) - fun realm_set_log_callback(callback: LogCallback) fun realm_set_log_level(level: CoreLogLevel) @@ -621,251 +424,4 @@ expect object RealmInterop { fun realm_get_log_level_category(category: String): CoreLogLevel fun realm_get_category_names(): List - - fun realm_app_config_set_metadata_mode( - appConfig: RealmAppConfigurationPointer, - metadataMode: MetadataMode - ) - - fun realm_app_config_set_metadata_encryption_key( - appConfig: RealmAppConfigurationPointer, - encryptionKey: ByteArray - ) - fun realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - bindingInfo: String - ) - fun realm_sync_client_config_set_user_agent_application_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - applicationInfo: String - ) - - fun realm_sync_client_config_set_connect_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) - fun realm_sync_client_config_set_connection_linger_time(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) - fun realm_sync_client_config_set_ping_keepalive_period(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) - fun realm_sync_client_config_set_pong_keepalive_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) - fun realm_sync_client_config_set_fast_reconnect_limit(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) - - fun realm_sync_config_new( - user: RealmUserPointer, - partition: String - ): RealmSyncConfigurationPointer - // Flexible Sync - fun realm_flx_sync_config_new(user: RealmUserPointer): RealmSyncConfigurationPointer - fun realm_sync_config_set_error_handler( - syncConfig: RealmSyncConfigurationPointer, - errorHandler: SyncErrorCallback - ) - fun realm_sync_config_set_resync_mode( - syncConfig: RealmSyncConfigurationPointer, - resyncMode: SyncSessionResyncMode - ) - fun realm_sync_config_set_before_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - beforeHandler: SyncBeforeClientResetHandler - ) - fun realm_sync_config_set_after_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - afterHandler: SyncAfterClientResetHandler - ) - fun realm_sync_immediately_run_file_actions(app: RealmAppPointer, syncPath: String): Boolean - - // SyncSession - fun realm_sync_session_get(realm: RealmPointer): RealmSyncSessionPointer - fun realm_sync_session_wait_for_download_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) - fun realm_sync_session_wait_for_upload_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) - fun realm_sync_session_state(syncSession: RealmSyncSessionPointer): CoreSyncSessionState - fun realm_sync_connection_state(syncSession: RealmSyncSessionPointer): CoreConnectionState - fun realm_sync_session_pause(syncSession: RealmSyncSessionPointer) - fun realm_sync_session_resume(syncSession: RealmSyncSessionPointer) - fun realm_sync_session_handle_error_for_testing( - syncSession: RealmSyncSessionPointer, - error: ErrorCode, - errorMessage: String, - isFatal: Boolean - ) - - fun realm_sync_session_register_progress_notifier( - syncSession: RealmSyncSessionPointer /* = io.realm.kotlin.internal.interop.NativePointer */, - direction: ProgressDirection, - isStreaming: Boolean, - callback: ProgressCallback, - ): RealmNotificationTokenPointer - - fun realm_sync_session_register_connection_state_change_callback( - syncSession: RealmSyncSessionPointer, - callback: ConnectionStateChangeCallback, - ): RealmNotificationTokenPointer - - // AppConfig - fun realm_network_transport_new(networkTransport: NetworkTransport): RealmNetworkTransportPointer - fun realm_app_config_new( - appId: String, - networkTransport: RealmNetworkTransportPointer, - baseUrl: String? = null, - connectionParams: SyncConnectionParams - ): RealmAppConfigurationPointer - fun realm_app_config_set_base_url(appConfig: RealmAppConfigurationPointer, baseUrl: String) - - // Credentials - fun realm_app_credentials_new_anonymous(reuseExisting: Boolean): RealmCredentialsPointer - fun realm_app_credentials_new_email_password(username: String, password: String): RealmCredentialsPointer - fun realm_app_credentials_new_api_key(key: String): RealmCredentialsPointer - fun realm_app_credentials_new_apple(idToken: String): RealmCredentialsPointer - fun realm_app_credentials_new_facebook(accessToken: String): RealmCredentialsPointer - fun realm_app_credentials_new_google_id_token(idToken: String): RealmCredentialsPointer - fun realm_app_credentials_new_google_auth_code(authCode: String): RealmCredentialsPointer - fun realm_app_credentials_new_jwt(jwtToken: String): RealmCredentialsPointer - fun realm_app_credentials_new_custom_function(serializedEjsonPayload: String): RealmCredentialsPointer - fun realm_auth_credentials_get_provider(credentials: RealmCredentialsPointer): AuthProvider - fun realm_app_credentials_serialize_as_json(credentials: RealmCredentialsPointer): String - - // Email Password Authentication - fun realm_app_email_password_provider_client_register_email( - app: RealmAppPointer, - email: String, - password: String, - callback: AppCallback - ) - fun realm_app_email_password_provider_client_confirm_user( - app: RealmAppPointer, - token: String, - tokenId: String, - callback: AppCallback - ) - fun realm_app_email_password_provider_client_resend_confirmation_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) - fun realm_app_email_password_provider_client_retry_custom_confirmation( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) - fun realm_app_email_password_provider_client_send_reset_password_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) - fun realm_app_email_password_provider_client_reset_password( - app: RealmAppPointer, - token: String, - tokenId: String, - newPassword: String, - callback: AppCallback - ) - fun realm_app_call_reset_password_function( - app: RealmAppPointer, - email: String, - newPassword: String, - serializedEjsonPayload: String, - callback: AppCallback - ) - - fun realm_app_call_function( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - serviceName: String? = null, - serializedEjsonArgs: String, // as ejson - callback: AppCallback - ) - - // Sync Client - fun realm_app_sync_client_reconnect(app: RealmAppPointer) - fun realm_app_sync_client_has_sessions(app: RealmAppPointer): Boolean - fun realm_app_sync_client_wait_for_sessions_to_terminate(app: RealmAppPointer) - - // Sync config - fun realm_config_set_sync_config( - realmConfiguration: RealmConfigurationPointer, - syncConfiguration: RealmSyncConfigurationPointer - ) - - // Flexible Sync Subscription - fun realm_sync_subscription_id(subscription: RealmSubscriptionPointer): ObjectId - fun realm_sync_subscription_name(subscription: RealmSubscriptionPointer): String? - fun realm_sync_subscription_object_class_name(subscription: RealmSubscriptionPointer): String - fun realm_sync_subscription_query_string(subscription: RealmSubscriptionPointer): String - fun realm_sync_subscription_created_at(subscription: RealmSubscriptionPointer): Timestamp - fun realm_sync_subscription_updated_at(subscription: RealmSubscriptionPointer): Timestamp - - // Flexible Sync Subscription Set - fun realm_sync_get_latest_subscriptionset(realm: RealmPointer): RealmSubscriptionSetPointer - fun realm_sync_on_subscriptionset_state_change_async( - subscriptionSet: RealmSubscriptionSetPointer, - destinationState: CoreSubscriptionSetState, - callback: SubscriptionSetCallback - ) - fun realm_sync_subscriptionset_version(subscriptionSet: RealmBaseSubscriptionSetPointer): Long - fun realm_sync_subscriptionset_state(subscriptionSet: RealmBaseSubscriptionSetPointer): CoreSubscriptionSetState - fun realm_sync_subscriptionset_error_str(subscriptionSet: RealmBaseSubscriptionSetPointer): String? - fun realm_sync_subscriptionset_size(subscriptionSet: RealmBaseSubscriptionSetPointer): Long - fun realm_sync_subscription_at( - subscriptionSet: RealmBaseSubscriptionSetPointer, - index: Long - ): RealmSubscriptionPointer - fun realm_sync_find_subscription_by_name( - subscriptionSet: RealmBaseSubscriptionSetPointer, - name: String - ): RealmSubscriptionPointer? - fun realm_sync_find_subscription_by_query( - subscriptionSet: RealmBaseSubscriptionSetPointer, - query: RealmQueryPointer - ): RealmSubscriptionPointer? - fun realm_sync_subscriptionset_refresh(subscriptionSet: RealmSubscriptionSetPointer): Boolean - fun realm_sync_make_subscriptionset_mutable( - subscriptionSet: RealmSubscriptionSetPointer - ): RealmMutableSubscriptionSetPointer - - // Flexible Sync Mutable Subscription Set - fun realm_sync_subscriptionset_clear( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): Boolean - // Returns a Pair of (, ) - fun realm_sync_subscriptionset_insert_or_assign( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer, - name: String? - ): Pair - fun realm_sync_subscriptionset_erase_by_name( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - name: String - ): Boolean - fun realm_sync_subscriptionset_erase_by_query( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer - ): Boolean - fun realm_sync_subscriptionset_erase_by_id( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - sub: RealmSubscriptionPointer - ): Boolean - fun realm_sync_subscriptionset_commit( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): RealmSubscriptionSetPointer - - fun realm_sync_set_websocket_transport( - syncClientConfig: RealmSyncClientConfigurationPointer, - webSocketTransport: WebSocketTransport - ) - - fun realm_sync_socket_callback_complete(nativePointer: RealmWebsocketHandlerCallbackPointer, cancelled: Boolean = false, status: WebsocketCallbackResult = WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS, reason: String = "") - - fun realm_sync_socket_websocket_connected(nativePointer: RealmWebsocketProviderPointer, protocol: String) - - fun realm_sync_socket_websocket_error(nativePointer: RealmWebsocketProviderPointer) - - fun realm_sync_socket_websocket_message( - nativePointer: RealmWebsocketProviderPointer, - data: ByteArray - ): Boolean - - fun realm_sync_socket_websocket_closed(nativePointer: RealmWebsocketProviderPointer, wasClean: Boolean, errorCode: WebsocketErrorCode, reason: String = "") } diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ApiKeyWrapper.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ApiKeyWrapper.kt deleted file mode 100644 index 2ccb0799ad..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ApiKeyWrapper.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.internal.interop.sync - -import org.mongodb.kbson.ObjectId - -public data class ApiKeyWrapper internal constructor( - public val id: ObjectId, - public val value: String?, - public val name: String, - public val disabled: Boolean -) { - - // Used by JNI - internal constructor( - id: ByteArray, - value: String?, - name: String, - disabled: Boolean - ) : this(ObjectId(id), value, name, disabled) -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AppError.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AppError.kt deleted file mode 100644 index d458d3eb87..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AppError.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.CodeDescription -import io.realm.kotlin.internal.interop.ErrorCategory -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.UnknownCodeDescription -import kotlin.jvm.JvmStatic - -/** - * Wrapper for C-API `realm_app_error`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L2638 - */ -data class AppError internal constructor( - val categoryFlags: Int, - val code: CodeDescription, - val httpStatusCode: Int, // If the category is HTTP, this is equal to errorCode - val message: String?, - val linkToServerLog: String? -) { - companion object { - @JvmStatic - fun newInstance( - categoryFlags: Int, - errorCode: Int, - httpStatusCode: Int, - message: String?, - linkToServerLog: String? - ): AppError { - val code = ErrorCode.of(errorCode) ?: UnknownCodeDescription(errorCode) - - return AppError( - categoryFlags, - code, - httpStatusCode, - message, - linkToServerLog - ) - } - } - - /** - * This method allows to check whether a error categories value contains a category or not. - * - * Core defines app categories as flag based values. - * - * Any App category is also a [ErrorCategory.RLM_ERR_CAT_RUNTIME] and [ErrorCategory.RLM_ERR_CAT_APP_ERROR]. - */ - operator fun contains(flag: ErrorCategory): Boolean = - this.categoryFlags and flag.nativeValue != 0 -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt deleted file mode 100644 index 7f96431b4d..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper for C-API `realm_auth_provider`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L2615 - */ -expect enum class AuthProvider { - RLM_AUTH_PROVIDER_ANONYMOUS, - RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE, - RLM_AUTH_PROVIDER_FACEBOOK, - RLM_AUTH_PROVIDER_GOOGLE, - RLM_AUTH_PROVIDER_APPLE, - RLM_AUTH_PROVIDER_CUSTOM, - RLM_AUTH_PROVIDER_EMAIL_PASSWORD, - RLM_AUTH_PROVIDER_FUNCTION, - RLM_AUTH_PROVIDER_API_KEY, -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt deleted file mode 100644 index ed6b052d9f..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.RealmValue - -expect class CoreCompensatingWriteInfo { - val reason: String - val objectName: String - val primaryKey: RealmValue -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt deleted file mode 100644 index 5d7c871ca5..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -expect enum class CoreConnectionState { - RLM_SYNC_CONNECTION_STATE_DISCONNECTED, - RLM_SYNC_CONNECTION_STATE_CONNECTING, - RLM_SYNC_CONNECTION_STATE_CONNECTED; -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt deleted file mode 100644 index 6544f6653e..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper around C-API `realm_flx_sync_subscription_set_state` - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L3356 - */ -expect enum class CoreSubscriptionSetState { - RLM_SYNC_SUBSCRIPTION_UNCOMMITTED, - RLM_SYNC_SUBSCRIPTION_PENDING, - RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING, - RLM_SYNC_SUBSCRIPTION_COMPLETE, - RLM_SYNC_SUBSCRIPTION_ERROR, - RLM_SYNC_SUBSCRIPTION_SUPERSEDED, - RLM_SYNC_SUBSCRIPTION_AWAITING_MARK; -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt deleted file mode 100644 index 7ce49223ba..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper around C-API `realm_sync_session_state` - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L3177 - */ -expect enum class CoreSyncSessionState { - RLM_SYNC_SESSION_STATE_DYING, - RLM_SYNC_SESSION_STATE_ACTIVE, - RLM_SYNC_SESSION_STATE_INACTIVE, - RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN, - RLM_SYNC_SESSION_STATE_PAUSED; -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt deleted file mode 100644 index e362966bd9..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper for C-API `realm_user_state`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L2513 - */ -expect enum class CoreUserState { - RLM_USER_STATE_LOGGED_OUT, - RLM_USER_STATE_LOGGED_IN, - RLM_USER_STATE_REMOVED -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt deleted file mode 100644 index 4e27cef7d0..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper for C-API `realm_sync_client_metadata_mode`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L3155 - */ -expect enum class MetadataMode { - RLM_SYNC_CLIENT_METADATA_MODE_DISABLED, - RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT, - RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/NetworkTransport.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/NetworkTransport.kt deleted file mode 100644 index 5e25135dd3..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/NetworkTransport.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -interface NetworkTransport { - - companion object { - const val GET = "get" - const val POST = "post" - const val PATCH = "patch" - const val PUT = "put" - const val DELETE = "delete" - } - - val authorizationHeaderName: String? - val customHeaders: Map - - fun sendRequest( - method: String, - url: String, - headers: Map, - body: String, - callback: ResponseCallback - ) - - /** - * Close any native resources associated with running a NetworkClient. - * E.g. in Ktor, the HttpClient should be closed - */ - fun close() -} - -fun interface ResponseCallback { - fun response(response: Response) -} - -data class Response( - val httpResponseCode: Int, - val customResponseCode: Int, - val headers: Map, - val body: String -) { - // Returns the HTTP headers in a JNI friendly way where it is being serialized to a - // String array consisting of pairs of { key , value } pairs. - fun getJNIFriendlyHeaders(): Array { - val jniHeaders = arrayOfNulls(headers.size * 2) - var i = 0 - for ((key, value) in headers) { - jniHeaders[i] = key - jniHeaders[i + 1] = value - i += 2 - } - return jniHeaders - } -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt deleted file mode 100644 index d8fae36986..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -expect enum class ProgressDirection { - RLM_SYNC_PROGRESS_DIRECTION_UPLOAD, - RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD, -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt deleted file mode 100644 index 5a2483d04d..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.CodeDescription - -/** - * Wrapper for C-API `realm_sync_errno_connection`. - * See https://github.com/realm/realm-core/blob/master/src/realm/error_codes.h#L209 - */ -expect enum class SyncConnectionErrorCode : CodeDescription { - RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED, - RLM_SYNC_ERR_CONNECTION_OTHER_ERROR, - RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE, - RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX, - RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED, - RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION, - RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT, - RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT, - RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION, - RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER, - RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION, - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX, - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE, - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC, - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS; - - override val nativeValue: Int - override val description: String? -} - -/** - * Wrapper for C-API `realm_sync_errno_session`. - * See https://github.com/realm/realm-core/blob/master/src/realm/error_codes.h#L228 - */ -expect enum class SyncSessionErrorCode : CodeDescription { - RLM_SYNC_ERR_SESSION_SESSION_CLOSED, - RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR, - RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED, - RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION, - RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH, - RLM_SYNC_ERR_SESSION_NO_SUCH_REALM, - RLM_SYNC_ERR_SESSION_PERMISSION_DENIED, - RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT, - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT, - RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION, - RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION, - RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES, - RLM_SYNC_ERR_SESSION_BAD_CHANGESET, - RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED, - RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE, - RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT, - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE, - RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED, - RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED, - RLM_SYNC_ERR_SESSION_USER_BLACKLISTED, - RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD, - RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED, - RLM_SYNC_ERR_SESSION_USER_MISMATCH, - RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS, - RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE, - RLM_SYNC_ERR_SESSION_BAD_QUERY, - RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS, - RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED, - RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED, - RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED, - RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE, - RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX, - RLM_SYNC_ERR_SESSION_BAD_PROGRESS, - RLM_SYNC_ERR_SESSION_REVERT_TO_PBS, - RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION, - RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED; - - override val nativeValue: Int - override val description: String? -} - -/** - * Wrapper for C-API `realm_web_socket_errno`. - * See https://github.com/realm/realm-core/blob/master/src/realm/error_codes.h#L266 - */ -expect enum class WebsocketErrorCode : CodeDescription { - RLM_ERR_WEBSOCKET_OK, - RLM_ERR_WEBSOCKET_GOINGAWAY, - RLM_ERR_WEBSOCKET_PROTOCOLERROR, - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA, - RLM_ERR_WEBSOCKET_RESERVED, - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED, - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE, - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA, - RLM_ERR_WEBSOCKET_POLICYVIOLATION, - RLM_ERR_WEBSOCKET_MESSAGETOOBIG, - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION, - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR, - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED, - RLM_ERR_WEBSOCKET_UNAUTHORIZED, - RLM_ERR_WEBSOCKET_FORBIDDEN, - RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY, - RLM_ERR_WEBSOCKET_RESOLVE_FAILED, - RLM_ERR_WEBSOCKET_CONNECTION_FAILED, - RLM_ERR_WEBSOCKET_READ_ERROR, - RLM_ERR_WEBSOCKET_WRITE_ERROR, - RLM_ERR_WEBSOCKET_RETRY_ERROR, - RLM_ERR_WEBSOCKET_FATAL_ERROR; - - override val nativeValue: Int - override val description: String? -} - -/** - * Wrapper for C-API `realm_sync_socket_callback_result` - * See https://github.com/realm/realm-core/blob/master/src/realm/error_codes.h#L298 - */ -expect enum class WebsocketCallbackResult : CodeDescription { - RLM_ERR_SYNC_SOCKET_SUCCESS, - RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED, - RLM_ERR_SYNC_SOCKET_RUNTIME, - RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY, - RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED, - RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED, - RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED, - RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT; - - override val nativeValue: Int - override val description: String? -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt deleted file mode 100644 index f1d27b8b48..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncError.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.CoreError - -/** - * Wrapper for C-API `realm_sync_error`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L3321 - */ -data class SyncError constructor( - val errorCode: CoreError, - val originalFilePath: String?, - val recoveryFilePath: String?, - val isFatal: Boolean, - val isUnrecognizedByClient: Boolean, - val isClientResetRequested: Boolean, - val compensatingWrites: Array, - val userError: Throwable?, -) { - // Constructs an SyncError out from a simple code. There are some situations (SyncSessionTransferCompletionCallback) - // where we receive an error code rather than a full SyncErrorCode, wrapping the code - // simplifies the error handling logic. - constructor( - error: CoreError, - ) : this( - errorCode = error, - originalFilePath = null, - recoveryFilePath = null, - isFatal = false, - isUnrecognizedByClient = false, - isClientResetRequested = false, - compensatingWrites = emptyArray(), - userError = null, - ) - - // Constructor used by JNI so we avoid creating too many objects on the JNI side. - constructor( - categoryFlags: Int, - value: Int, - message: String, - originalFilePath: String?, - recoveryFilePath: String?, - isFatal: Boolean, - isUnrecognizedByClient: Boolean, - isClientResetRequested: Boolean, - compensatingWrites: Array, - userError: Throwable?, - ) : this( - CoreError(categoryFlags, value, message), - originalFilePath, - recoveryFilePath, - isFatal, - isUnrecognizedByClient, - isClientResetRequested, - compensatingWrites, - userError, - ) -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt deleted file mode 100644 index 7eeb9804dd..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper for C-API `realm_sync_session_resync_mode`. - * See https://github.com/realm/realm-core/blob/master/src/realm.h#L3166 - */ -expect enum class SyncSessionResyncMode { - RLM_SYNC_SESSION_RESYNC_MODE_MANUAL, - RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL, - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER, - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD; -} diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncUserIdentity.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncUserIdentity.kt deleted file mode 100644 index 3fbca71c76..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/SyncUserIdentity.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -/** - * Wrapper for `realm_user_identity`. - * @see https://github.com/realm/realm-core/blob/master/src/realm.h - */ -data class SyncUserIdentity(val id: String, val provider: AuthProvider) diff --git a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/WebSocketTransport.kt b/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/WebSocketTransport.kt deleted file mode 100644 index 136a535c05..0000000000 --- a/packages/cinterop/src/commonMain/kotlin/io/realm/kotlin/internal/interop/sync/WebSocketTransport.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmWebsocketHandlerCallbackPointer -import io.realm.kotlin.internal.interop.RealmWebsocketProviderPointer -import kotlinx.coroutines.Job - -/** - * Interface to be implemented by the websocket provider. This helps un-bundle the implementation - * from Core to leverage the platform capabilities (Proxy, firewall, vpn etc.). - */ -interface WebSocketTransport { - /** - * Submit a handler function to be executed by the event loop. - */ - fun post(handlerCallback: RealmWebsocketHandlerCallbackPointer) - - /** - * Create and register a new timer whose handler function will be posted - * to the event loop when the provided delay expires. - * @return [CancellableTimer] to be called if the timer is to be cancelled before the delay. - */ - fun createTimer( - delayInMilliseconds: Long, - handlerCallback: RealmWebsocketHandlerCallbackPointer, - ): CancellableTimer - - /** - * Create a new websocket pointed to the server indicated by endpoint and - * connect to the server. Any events that occur during the execution of the - * websocket will call directly to the handlers provided by the observer (new messages, error, close events) - * - * @return [WebSocketClient] instance to be used by Core to send data, and signal a close session. - */ - @Suppress("LongParameterList") - fun connect( - observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - numProtocols: Long, - supportedSyncProtocols: String - ): WebSocketClient - - /** - * Writes to the previously created websocket in [connect] the binary data. The provided [handlerCallback] needs - * to run in the event loop after a successful write or in case of an error. - */ - fun write( - webSocketClient: WebSocketClient, - data: ByteArray, - length: Long, - handlerCallback: RealmWebsocketHandlerCallbackPointer - ) - - /** - * This helper function run the provided function pointer. It needs to be called within the same event loop context (thread) - * as the rest of the other functions. - */ - fun runCallback( - handlerCallback: RealmWebsocketHandlerCallbackPointer, - cancelled: Boolean = false, - status: WebsocketCallbackResult = WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS, - reason: String = "" - ) { - RealmInterop.realm_sync_socket_callback_complete( - handlerCallback, cancelled, status, reason - ) - } - - /** - * Core signal the transport, all websockets previously created with [connect] would have been closed at this point - * this is useful to do any resource cleanup like shutting down the engine or closing coroutine dispatcher. - */ - fun close() -} - -/** - * Cancel a previously scheduled timer created via [WebSocketTransport.createTimer]. - */ -class CancellableTimer( - private val job: Job, - private val cancelCallback: () -> Unit -) { - fun cancel() { - job.cancel() - cancelCallback() - } -} - -/** - * Define an interface to interact with the websocket created via [WebSocketTransport.connect]. - * This will be called from Core. - */ -interface WebSocketClient { - /** - * Send a binary Frame to the remote peer. - */ - fun send(message: ByteArray, handlerCallback: RealmWebsocketHandlerCallbackPointer) - - /** - * Close the websocket. - */ - fun close() -} - -/** - * Defines an abstraction of the underlying Http engine used to create the websocket. - * This abstraction is needed in order to deterministically create and shutdown the engine at the transport level. - * All websocket within the same App share the same transport and by definition the same engine. - */ -interface WebsocketEngine { - fun shutdown() - fun getInstance(): T -} - -/** - * Wrapper around Core callback pointer (observer). This will delegate calls for all incoming messages from the remote peer. - */ -class WebSocketObserver(private val webSocketObserverPointer: RealmWebsocketProviderPointer) { - /** - * Communicate the negotiated Sync protocol. - */ - fun onConnected(protocol: String) { - RealmInterop.realm_sync_socket_websocket_connected(webSocketObserverPointer, protocol) - } - - /** - * Notify an error. - */ - fun onError() { - RealmInterop.realm_sync_socket_websocket_error(webSocketObserverPointer) - } - - /** - * Forward received message to Core. - */ - fun onNewMessage(data: ByteArray): Boolean { - return RealmInterop.realm_sync_socket_websocket_message(webSocketObserverPointer, data) - } - - /** - * Notify closure message. - */ - fun onClose(wasClean: Boolean, errorCode: WebsocketErrorCode, reason: String) { - RealmInterop.realm_sync_socket_websocket_closed( - webSocketObserverPointer, wasClean, errorCode, reason - ) - } -} diff --git a/packages/cinterop/src/jvm/jni/java_class_global_def.hpp b/packages/cinterop/src/jvm/jni/java_class_global_def.hpp index 2c202ca1f6..cd721f9c15 100644 --- a/packages/cinterop/src/jvm/jni/java_class_global_def.hpp +++ b/packages/cinterop/src/jvm/jni/java_class_global_def.hpp @@ -48,30 +48,10 @@ class JavaClassGlobalDef { , m_java_lang_string(env, "java/lang/String", false) , m_kotlin_jvm_functions_function0(env, "kotlin/jvm/functions/Function0", false) , m_kotlin_jvm_functions_function1(env, "kotlin/jvm/functions/Function1", false) - , m_io_realm_kotlin_internal_interop_sync_network_transport(env, "io/realm/kotlin/internal/interop/sync/NetworkTransport", false) - , m_io_realm_kotlin_internal_interop_sync_response(env, "io/realm/kotlin/internal/interop/sync/Response", false) , m_io_realm_kotlin_internal_interop_long_pointer_wrapper(env, "io/realm/kotlin/internal/interop/LongPointerWrapper", false) - , m_io_realm_kotlin_internal_interop_sync_sync_error(env, "io/realm/kotlin/internal/interop/sync/SyncError", false) - , m_io_realm_kotlin_internal_interop_sync_core_compensating_write_info(env, "io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo", false) - , m_io_realm_kotlin_internal_interop_sync_app_error(env, "io/realm/kotlin/internal/interop/sync/AppError", false) , m_io_realm_kotlin_internal_interop_log_callback(env, "io/realm/kotlin/internal/interop/LogCallback", false) - , m_io_realm_kotlin_internal_interop_sync_error_callback(env, "io/realm/kotlin/internal/interop/SyncErrorCallback", false) - , m_io_realm_kotlin_internal_interop_sync_jvm_sync_session_transfer_completion_callback(env, "io/realm/kotlin/internal/interop/sync/JVMSyncSessionTransferCompletionCallback", false) - , m_io_realm_kotlin_internal_interop_sync_response_callback_impl(env, "io/realm/kotlin/internal/interop/sync/ResponseCallbackImpl", false) - , m_io_realm_kotlin_internal_interop_subscription_set_callback(env, "io/realm/kotlin/internal/interop/SubscriptionSetCallback", false) - , m_io_realm_kotlin_internal_interop_sync_before_client_reset_handler(env, "io/realm/kotlin/internal/interop/SyncBeforeClientResetHandler", false) - , m_io_realm_kotlin_internal_interop_sync_after_client_reset_handler(env, "io/realm/kotlin/internal/interop/SyncAfterClientResetHandler", false) , m_io_realm_kotlin_internal_interop_core_error_converter(env, "io/realm/kotlin/internal/interop/CoreErrorConverter", false) - , m_io_realm_kotlin_internal_interop_sync_async_open_callback(env, "io/realm/kotlin/internal/interop/AsyncOpenCallback", false) - , m_io_realm_kotlin_internal_interop_progress_callback(env, "io/realm/kotlin/internal/interop/ProgressCallback", false) - , m_io_realm_kotlin_internal_interop_app_callback(env, "io/realm/kotlin/internal/interop/AppCallback", false) - , m_io_realm_kotlin_internal_interop_connection_state_change_callback(env, "io/realm/kotlin/internal/interop/ConnectionStateChangeCallback", false) - , m_io_realm_kotlin_internal_interop_sync_thread_observer(env, "io/realm/kotlin/internal/interop/SyncThreadObserver", false) - , m_io_realm_kotlin_internal_interop_sync_websocket_transport(env, "io/realm/kotlin/internal/interop/sync/WebSocketTransport", false) - , m_io_realm_kotlin_internal_interop_sync_websocket_client(env, "io/realm/kotlin/internal/interop/sync/WebSocketClient", false) , m_io_realm_kotlin_internal_interop_notification_callback(env, "io/realm/kotlin/internal/interop/NotificationCallback", false) - , m_io_realm_kotlin_internal_interop_sync_connection_state(env, "io/realm/kotlin/internal/interop/sync/CoreConnectionState", false) - { } @@ -80,29 +60,10 @@ class JavaClassGlobalDef { jni_util::JavaClass m_java_lang_string; jni_util::JavaClass m_kotlin_jvm_functions_function0; jni_util::JavaClass m_kotlin_jvm_functions_function1; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_network_transport; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_response; jni_util::JavaClass m_io_realm_kotlin_internal_interop_long_pointer_wrapper; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_sync_error; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_core_compensating_write_info; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_app_error; jni_util::JavaClass m_io_realm_kotlin_internal_interop_log_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_error_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_jvm_sync_session_transfer_completion_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_response_callback_impl; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_subscription_set_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_before_client_reset_handler; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_after_client_reset_handler; jni_util::JavaClass m_io_realm_kotlin_internal_interop_core_error_converter; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_async_open_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_progress_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_app_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_connection_state_change_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_thread_observer; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_websocket_transport; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_websocket_client; jni_util::JavaClass m_io_realm_kotlin_internal_interop_notification_callback; - jni_util::JavaClass m_io_realm_kotlin_internal_interop_sync_connection_state; inline static std::unique_ptr& instance() { @@ -143,103 +104,20 @@ class JavaClassGlobalDef { return instance()->m_java_lang_string; } - inline static const jni_util::JavaClass& network_transport_class() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_network_transport; - } - - inline static const jni_util::JavaClass& network_transport_response_class() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_response; - } - inline static const jni_util::JavaClass& long_pointer_wrapper() { return instance()->m_io_realm_kotlin_internal_interop_long_pointer_wrapper; } - - inline static const jni_util::JavaClass& sync_error() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_sync_error; - } - - inline static const jni_util::JavaClass& core_compensating_write_info() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_core_compensating_write_info; - } - - inline static const jni_util::JavaClass& app_error() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_app_error; - } - - inline static const jni_util::JavaClass& connection_state() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_connection_state; - } - inline static const jni_util::JavaClass& log_callback() { return instance()->m_io_realm_kotlin_internal_interop_log_callback; } - inline static const jni_util::JavaClass& sync_error_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_error_callback; - } - - inline static const jni_util::JavaClass& sync_session_transfer_completion_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_jvm_sync_session_transfer_completion_callback; - }; - - inline static const jni_util::JavaClass& app_response_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_response_callback_impl; - }; - - inline static const jni_util::JavaClass& subscriptionset_changed_callback() { - return instance()->m_io_realm_kotlin_internal_interop_subscription_set_callback; - } - - inline static const jni_util::JavaClass& sync_before_client_reset() { - return instance()->m_io_realm_kotlin_internal_interop_sync_before_client_reset_handler; - } - - inline static const jni_util::JavaClass& sync_after_client_reset() { - return instance()->m_io_realm_kotlin_internal_interop_sync_after_client_reset_handler; - } - inline static const jni_util::JavaClass& core_error_converter() { return instance()->m_io_realm_kotlin_internal_interop_core_error_converter; } - inline static const jni_util::JavaClass& async_open_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_async_open_callback; - } - - inline static const jni_util::JavaClass& progress_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_progress_callback; - } - - inline static const jni_util::JavaClass& app_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_app_callback; - } - - inline static const jni_util::JavaClass& connection_state_change_callback() - { - return instance()->m_io_realm_kotlin_internal_interop_connection_state_change_callback; - } - - inline static const jni_util::JavaClass& sync_thread_observer() - { - return instance()->m_io_realm_kotlin_internal_interop_sync_thread_observer; - } - inline static const jni_util::JavaClass& notification_callback() { return instance()->m_io_realm_kotlin_internal_interop_notification_callback; @@ -254,14 +132,6 @@ class JavaClassGlobalDef { return jni_util::JavaMethod(env, instance()->m_kotlin_jvm_functions_function1, "invoke", "(Ljava/lang/Object;)Ljava/lang/Object;"); } - - inline static const jni_util::JavaClass& sync_websocket_transport() { - return instance()->m_io_realm_kotlin_internal_interop_sync_websocket_transport; - } - - inline static const jni_util::JavaClass& sync_websocket_client() { - return instance()->m_io_realm_kotlin_internal_interop_sync_websocket_client; - } }; } // namespace realm diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ClassFlags.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ClassFlags.kt index bc0cf17331..04ab279c73 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ClassFlags.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ClassFlags.kt @@ -19,5 +19,4 @@ package io.realm.kotlin.internal.interop actual object ClassFlags { actual val RLM_CLASS_NORMAL = realm_class_flags_e.RLM_CLASS_NORMAL actual val RLM_CLASS_EMBEDDED = realm_class_flags_e.RLM_CLASS_EMBEDDED - actual val RLM_CLASS_ASYMMETRIC = realm_class_flags_e.RLM_CLASS_ASYMMETRIC } diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt index c7afdac182..9bd1e05b6c 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt @@ -24,15 +24,7 @@ actual enum class ErrorCategory( RLM_ERR_CAT_RUNTIME("Runtime", realm_error_category_e.RLM_ERR_CAT_RUNTIME), RLM_ERR_CAT_INVALID_ARG("InvalidArg", realm_error_category_e.RLM_ERR_CAT_INVALID_ARG), RLM_ERR_CAT_FILE_ACCESS("File", realm_error_category_e.RLM_ERR_CAT_FILE_ACCESS), - RLM_ERR_CAT_SYSTEM_ERROR("System", realm_error_category_e.RLM_ERR_CAT_SYSTEM_ERROR), - RLM_ERR_CAT_APP_ERROR("App", realm_error_category_e.RLM_ERR_CAT_APP_ERROR), - RLM_ERR_CAT_CLIENT_ERROR("Client", realm_error_category_e.RLM_ERR_CAT_CLIENT_ERROR), - RLM_ERR_CAT_JSON_ERROR("Json", realm_error_category_e.RLM_ERR_CAT_JSON_ERROR), - RLM_ERR_CAT_SERVICE_ERROR("Service", realm_error_category_e.RLM_ERR_CAT_SERVICE_ERROR), - RLM_ERR_CAT_HTTP_ERROR("Http", realm_error_category_e.RLM_ERR_CAT_HTTP_ERROR), - RLM_ERR_CAT_CUSTOM_ERROR("Custom", realm_error_category_e.RLM_ERR_CAT_CUSTOM_ERROR), - RLM_ERR_CAT_WEBSOCKET_ERROR("Websocket", realm_error_category_e.RLM_ERR_CAT_WEBSOCKET_ERROR), - RLM_ERR_CAT_SYNC_ERROR("Sync", realm_error_category_e.RLM_ERR_CAT_SYNC_ERROR); + RLM_ERR_CAT_SYSTEM_ERROR("System", realm_error_category_e.RLM_ERR_CAT_SYSTEM_ERROR); actual companion object { internal actual fun of(nativeValue: Int): ErrorCategory? = diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index 849e75f94d..b127f9b710 100644 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -17,21 +17,6 @@ package io.realm.kotlin.internal.interop import io.realm.kotlin.internal.interop.Constants.ENCRYPTION_KEY_LENGTH -import io.realm.kotlin.internal.interop.sync.ApiKeyWrapper -import io.realm.kotlin.internal.interop.sync.AuthProvider -import io.realm.kotlin.internal.interop.sync.CoreConnectionState -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState -import io.realm.kotlin.internal.interop.sync.CoreUserState -import io.realm.kotlin.internal.interop.sync.JVMSyncSessionTransferCompletionCallback -import io.realm.kotlin.internal.interop.sync.MetadataMode -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.ProgressDirection -import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode -import io.realm.kotlin.internal.interop.sync.SyncUserIdentity -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.interop.sync.WebsocketCallbackResult -import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -241,18 +226,6 @@ actual object RealmInterop { return Pair(realmPtr, fileCreated) } - actual fun realm_open_synchronized(config: RealmConfigurationPointer): RealmAsyncOpenTaskPointer { - return LongPointerWrapper(realmc.realm_open_synchronized(config.cptr())) - } - - actual fun realm_async_open_task_start(task: RealmAsyncOpenTaskPointer, callback: AsyncOpenCallback) { - realmc.realm_async_open_task_start(task.cptr(), callback) - } - - actual fun realm_async_open_task_cancel(task: RealmAsyncOpenTaskPointer) { - realmc.realm_async_open_task_cancel(task.cptr()) - } - actual fun realm_add_realm_changed_callback(realm: LiveRealmPointer, block: () -> Unit): RealmCallbackTokenPointer { return LongPointerWrapper( realmc.realm_add_realm_changed_callback(realm.cptr(), block), @@ -1152,215 +1125,6 @@ actual object RealmInterop { builder.initModifications(modifiedKeys.toTypedArray()) } - actual fun realm_app_get( - appConfig: RealmAppConfigurationPointer, - basePath: String - ): RealmAppPointer { - return LongPointerWrapper(realmc.realm_app_create(appConfig.cptr()), managed = true) - } - - actual fun realm_app_log_in_with_credentials( - app: RealmAppPointer, - credentials: RealmCredentialsPointer, - callback: AppCallback - ) { - realmc.realm_app_log_in_with_credentials(app.cptr(), credentials.cptr(), callback) - } - - actual fun realm_app_log_out( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - realmc.realm_app_log_out(app.cptr(), user.cptr(), callback) - } - - actual fun realm_app_remove_user( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - realmc.realm_app_remove_user(app.cptr(), user.cptr(), callback) - } - - actual fun realm_app_delete_user( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - realmc.realm_app_delete_user(app.cptr(), user.cptr(), callback) - } - - actual fun realm_app_link_credentials( - app: RealmAppPointer, - user: RealmUserPointer, - credentials: RealmCredentialsPointer, - callback: AppCallback - ) { - realmc.realm_app_link_user(app.cptr(), user.cptr(), credentials.cptr(), callback) - } - - actual fun realm_app_switch_user(app: RealmAppPointer, user: RealmUserPointer) { - realmc.realm_app_switch_user(app.cptr(), user.cptr()) - } - - actual fun realm_app_get_current_user(app: RealmAppPointer): RealmUserPointer? { - val ptr = realmc.realm_app_get_current_user(app.cptr()) - return nativePointerOrNull(ptr) - } - - actual fun realm_app_get_all_users(app: RealmAppPointer): List { - // We get the current amount of users by providing a zero-sized array and `out_n` - // argument. Then the current count is written to `out_n`. - // See https://github.com/realm/realm-core/blob/master/src/realm.h#L2634 - val capacityCount = LongArray(1) - realmc.realm_app_get_all_users(app.cptr(), LongArray(0), 0, capacityCount) - - // Read actual users. We don't care about the small chance of missing a new user - // between these two calls as that indicate two sections of user code running on - // different threads and not coordinating. - val actualUsersCount = LongArray(1) - val users = LongArray(capacityCount[0].toInt()) - realmc.realm_app_get_all_users(app.cptr(), users, capacityCount[0], actualUsersCount) - val result: MutableList = mutableListOf() - for (i in 0 until actualUsersCount[0].toInt()) { - users[i].let { ptr: Long -> - result.add(LongPointerWrapper(ptr, managed = true)) - } - } - return result - } - - actual fun realm_app_get_base_url( - app: RealmAppPointer, - ): String = realmc.realm_app_get_base_url(app.cptr()) - - actual fun realm_app_update_base_url( - app: RealmAppPointer, - baseUrl: String?, - callback: AppCallback, - ) { - realmc.realm_app_update_base_url(app.cptr(), baseUrl, callback) - } - - actual fun realm_user_get_all_identities(user: RealmUserPointer): List { - val count = AuthProvider.values().size.toLong() // Optimistically allocate the max size of the array - val keys = realmc.new_identityArray(count.toInt()) - val outCount = longArrayOf(0) - realmc.realm_user_get_all_identities(user.cptr(), keys, count, outCount) - return if (outCount[0] > 0) { - (0 until outCount[0]).map { i -> - with(realmc.identityArray_getitem(keys, i.toInt())) { - SyncUserIdentity(this.id, AuthProvider.of(this.provider_type)) - } - } - } else { - emptyList() - }.also { - realmc.delete_identityArray(keys) - } - } - - actual fun realm_user_get_identity(user: RealmUserPointer): String { - return realmc.realm_user_get_identity(user.cptr()) - } - - actual fun realm_user_get_access_token(user: RealmUserPointer): String { - return realmc.realm_user_get_access_token(user.cptr()) - } - - actual fun realm_user_get_refresh_token(user: RealmUserPointer): String { - return realmc.realm_user_get_refresh_token(user.cptr()) - } - - actual fun realm_user_get_device_id(user: RealmUserPointer): String { - return realmc.realm_user_get_device_id(user.cptr()) - } - - actual fun realm_user_is_logged_in(user: RealmUserPointer): Boolean { - return realmc.realm_user_is_logged_in(user.cptr()) - } - - actual fun realm_user_log_out(user: RealmUserPointer) { - realmc.realm_user_log_out(user.cptr()) - } - - actual fun realm_user_get_state(user: RealmUserPointer): CoreUserState { - return CoreUserState.of(realmc.realm_user_get_state(user.cptr())) - } - - actual fun realm_user_get_profile(user: RealmUserPointer): String = - realmc.realm_user_get_profile_data(user.cptr()) - - actual fun realm_user_get_custom_data(user: RealmUserPointer): String? = - realmc.realm_user_get_custom_data(user.cptr()) - - actual fun realm_user_refresh_custom_data(app: RealmAppPointer, user: RealmUserPointer, callback: AppCallback) { - realmc.realm_app_refresh_custom_data(app.cptr(), user.cptr(), callback) - } - - actual fun realm_clear_cached_apps() { - realmc.realm_clear_cached_apps() - } - - actual fun realm_app_sync_client_get_default_file_path_for_realm( - syncConfig: RealmSyncConfigurationPointer, - overriddenName: String? - ): String { - return realmc.realm_app_sync_client_get_default_file_path_for_realm( - syncConfig.cptr(), - overriddenName - ) - } - - actual fun realm_app_config_get_sync_client_config(configPointer: RealmAppConfigurationPointer): RealmSyncClientConfigurationPointer { - // The configuration is owned by Core so don't track and release it through garbage collection of the NativePointer - return LongPointerWrapper(realmc.realm_app_config_get_sync_client_config(configPointer.cptr()), false) - } - - actual fun realm_sync_client_config_set_default_binding_thread_observer(syncClientConfig: RealmSyncClientConfigurationPointer, appId: String) { - realmc.realm_sync_client_config_set_default_binding_thread_observer( - syncClientConfig.cptr(), - object : SyncThreadObserver { - override fun threadName(): String { - return "SyncThread-$appId" - } - - override fun onCreated() { - // We cannot set the name on JNI side as it would require access to JNIEnv before - // we attach it, so we set the thread name after it is created. - Thread.currentThread().name = threadName() - } - - override fun onDestroyed() { - // Do nothing - // Thread is destroyed in the JNI side - } - - @Suppress("TooGenericExceptionThrown") - override fun onError(error: String) { - // TODO Wait for https://github.com/realm/realm-core/issues/4194 to correctly - // log errors. For now, just throw an Error as exceptions from the Sync Client - // indicate that something is fundamentally wrong on the Sync Thread. - // In Realm Java this has only been reported during development of new - // features, so throwing an Error seems appropriate to increase visibility. - throw Error("[${threadName()}] Error on sync thread : $error") - } - } - ) - } - - actual fun realm_app_config_set_base_file_path( - appConfig: RealmAppConfigurationPointer, - basePath: String - ) { - realmc.realm_app_config_set_base_file_path(appConfig.cptr(), basePath) - } - - actual fun realm_sync_client_config_set_multiplex_sessions(syncClientConfig: RealmSyncClientConfigurationPointer, enabled: Boolean) { - realmc.realm_sync_client_config_set_multiplex_sessions(syncClientConfig.cptr(), enabled) - } - actual fun realm_set_log_callback(callback: LogCallback) { realmc.set_log_callback(callback) } @@ -1382,394 +1146,6 @@ actual object RealmInterop { return names.asList() } - actual fun realm_app_config_set_metadata_mode( - appConfig: RealmAppConfigurationPointer, - metadataMode: MetadataMode, - ) { - realmc.realm_app_config_set_metadata_mode( - appConfig.cptr(), - metadataMode.nativeValue - ) - } - - actual fun realm_app_config_set_metadata_encryption_key( - appConfig: RealmAppConfigurationPointer, - encryptionKey: ByteArray - ) { - realmc.realm_app_config_set_metadata_encryption_key( - appConfig.cptr(), - encryptionKey - ) - } - - actual fun realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - bindingInfo: String - ) { - realmc.realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig.cptr(), - bindingInfo - ) - } - - actual fun realm_sync_client_config_set_user_agent_application_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - applicationInfo: String - ) { - realmc.realm_sync_client_config_set_user_agent_application_info( - syncClientConfig.cptr(), - applicationInfo - ) - } - - actual fun realm_sync_client_config_set_connect_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realmc.realm_sync_client_config_set_connect_timeout(syncClientConfig.cptr(), timeoutMs.toLong()) - } - - actual fun realm_sync_client_config_set_connection_linger_time(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realmc.realm_sync_client_config_set_connection_linger_time(syncClientConfig.cptr(), timeoutMs.toLong()) - } - - actual fun realm_sync_client_config_set_ping_keepalive_period(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realmc.realm_sync_client_config_set_ping_keepalive_period(syncClientConfig.cptr(), timeoutMs.toLong()) - } - - actual fun realm_sync_client_config_set_pong_keepalive_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realmc.realm_sync_client_config_set_pong_keepalive_timeout(syncClientConfig.cptr(), timeoutMs.toLong()) - } - - actual fun realm_sync_client_config_set_fast_reconnect_limit(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realmc.realm_sync_client_config_set_fast_reconnect_limit(syncClientConfig.cptr(), timeoutMs.toLong()) - } - - actual fun realm_network_transport_new(networkTransport: NetworkTransport): RealmNetworkTransportPointer { - return LongPointerWrapper(realmc.realm_network_transport_new(networkTransport)) - } - - actual fun realm_sync_config_set_error_handler( - syncConfig: RealmSyncConfigurationPointer, - errorHandler: SyncErrorCallback - ) { - realmc.sync_set_error_handler(syncConfig.cptr(), errorHandler) - } - - actual fun realm_sync_config_set_resync_mode( - syncConfig: RealmSyncConfigurationPointer, - resyncMode: SyncSessionResyncMode - ) { - realmc.realm_sync_config_set_resync_mode(syncConfig.cptr(), resyncMode.nativeValue) - } - - actual fun realm_sync_config_set_before_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - beforeHandler: SyncBeforeClientResetHandler - ) { - realmc.sync_before_client_reset_handler(syncConfig.cptr(), beforeHandler) - } - - actual fun realm_sync_config_set_after_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - afterHandler: SyncAfterClientResetHandler - ) { - realmc.sync_after_client_reset_handler(syncConfig.cptr(), afterHandler) - } - - actual fun realm_sync_immediately_run_file_actions(app: RealmAppPointer, syncPath: String): Boolean { - val didRun = booleanArrayOf(false) - realmc.realm_sync_immediately_run_file_actions(app.cptr(), syncPath, didRun) - return didRun.first() - } - - actual fun realm_sync_session_get(realm: RealmPointer): RealmSyncSessionPointer { - return LongPointerWrapper(realmc.realm_sync_session_get(realm.cptr())) - } - - actual fun realm_sync_session_wait_for_download_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) { - realmc.realm_sync_session_wait_for_download_completion( - syncSession.cptr(), - JVMSyncSessionTransferCompletionCallback(callback) - ) - } - - actual fun realm_sync_session_wait_for_upload_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) { - realmc.realm_sync_session_wait_for_upload_completion( - syncSession.cptr(), - JVMSyncSessionTransferCompletionCallback(callback) - ) - } - - actual fun realm_sync_session_state(syncSession: RealmSyncSessionPointer): CoreSyncSessionState { - return CoreSyncSessionState.of(realmc.realm_sync_session_get_state(syncSession.cptr())) - } - actual fun realm_sync_connection_state(syncSession: RealmSyncSessionPointer): CoreConnectionState { - return CoreConnectionState.of(realmc.realm_sync_session_get_connection_state(syncSession.cptr())) - } - - actual fun realm_sync_session_pause(syncSession: RealmSyncSessionPointer) { - realmc.realm_sync_session_pause(syncSession.cptr()) - } - - actual fun realm_sync_session_resume(syncSession: RealmSyncSessionPointer) { - realmc.realm_sync_session_resume(syncSession.cptr()) - } - - actual fun realm_sync_session_handle_error_for_testing( - syncSession: RealmSyncSessionPointer, - error: ErrorCode, - errorMessage: String, - isFatal: Boolean - ) { - realmc.realm_sync_session_handle_error_for_testing( - syncSession.cptr(), - error.nativeValue, - errorMessage, - isFatal - ) - } - - actual fun realm_sync_session_register_progress_notifier( - syncSession: RealmSyncSessionPointer, - direction: ProgressDirection, - isStreaming: Boolean, - callback: ProgressCallback, - ): RealmNotificationTokenPointer { - return LongPointerWrapper( - realmc.realm_sync_session_register_progress_notifier_wrapper( - syncSession.cptr(), - direction.nativeValue, - isStreaming, - callback - ), - managed = false - ) - } - - actual fun realm_sync_session_register_connection_state_change_callback( - syncSession: RealmSyncSessionPointer, - callback: ConnectionStateChangeCallback, - ): RealmNotificationTokenPointer { - return LongPointerWrapper( - realmc.realm_sync_session_register_connection_state_change_callback( - syncSession.cptr(), - callback - ), - managed = false - ) - } - - @Suppress("LongParameterList") - actual fun realm_app_config_new( - appId: String, - networkTransport: RealmNetworkTransportPointer, - baseUrl: String?, - connectionParams: SyncConnectionParams - ): RealmAppConfigurationPointer { - val config = realmc.realm_app_config_new(appId, networkTransport.cptr()) - - baseUrl?.let { realmc.realm_app_config_set_base_url(config, it) } - - // Sync Connection Parameters - realmc.realm_app_config_set_sdk(config, connectionParams.sdkName) - realmc.realm_app_config_set_sdk_version(config, connectionParams.sdkVersion) - realmc.realm_app_config_set_platform_version(config, connectionParams.platformVersion) - realmc.realm_app_config_set_device_name(config, connectionParams.device) - realmc.realm_app_config_set_device_version(config, connectionParams.deviceVersion) - realmc.realm_app_config_set_framework_name(config, connectionParams.framework) - realmc.realm_app_config_set_framework_version(config, connectionParams.frameworkVersion) - realmc.realm_app_config_set_bundle_id(config, connectionParams.bundleId) - - return LongPointerWrapper(config) - } - - actual fun realm_app_config_set_base_url(appConfig: RealmAppConfigurationPointer, baseUrl: String) { - realmc.realm_app_config_set_base_url(appConfig.cptr(), baseUrl) - } - - actual fun realm_app_credentials_new_anonymous(reuseExisting: Boolean): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_anonymous(reuseExisting)) - } - - actual fun realm_app_credentials_new_email_password(username: String, password: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_email_password(username, password)) - } - - actual fun realm_app_credentials_new_api_key(key: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_api_key(key)) - } - - actual fun realm_app_credentials_new_apple(idToken: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_apple(idToken)) - } - - actual fun realm_app_credentials_new_facebook(accessToken: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_facebook(accessToken)) - } - - actual fun realm_app_credentials_new_google_id_token(idToken: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_google_id_token(idToken)) - } - - actual fun realm_app_credentials_new_google_auth_code(authCode: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_google_auth_code(authCode)) - } - - actual fun realm_app_credentials_new_jwt(jwtToken: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_jwt(jwtToken)) - } - - actual fun realm_app_credentials_new_custom_function(serializedEjsonPayload: String): RealmCredentialsPointer { - return LongPointerWrapper(realmc.realm_app_credentials_new_function(serializedEjsonPayload)) - } - - actual fun realm_auth_credentials_get_provider(credentials: RealmCredentialsPointer): AuthProvider { - return AuthProvider.of(realmc.realm_auth_credentials_get_provider(credentials.cptr())) - } - - actual fun realm_app_credentials_serialize_as_json(credentials: RealmCredentialsPointer): String { - return realmc.realm_app_credentials_serialize_as_json(credentials.cptr()) - } - - actual fun realm_app_email_password_provider_client_register_email( - app: RealmAppPointer, - email: String, - password: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_register_email( - app.cptr(), - email, - password, - callback - ) - } - - actual fun realm_app_email_password_provider_client_confirm_user( - app: RealmAppPointer, - token: String, - tokenId: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_confirm_user( - app.cptr(), - token, - tokenId, - callback - ) - } - - actual fun realm_app_email_password_provider_client_resend_confirmation_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_resend_confirmation_email( - app.cptr(), - email, - callback - ) - } - - actual fun realm_app_email_password_provider_client_retry_custom_confirmation( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_retry_custom_confirmation( - app.cptr(), - email, - callback - ) - } - - actual fun realm_app_email_password_provider_client_send_reset_password_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_send_reset_password_email( - app.cptr(), - email, - callback - ) - } - - actual fun realm_app_email_password_provider_client_reset_password( - app: RealmAppPointer, - token: String, - tokenId: String, - newPassword: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_reset_password( - app.cptr(), - token, - tokenId, - newPassword, - callback - ) - } - - @Suppress("LongParameterList") - actual fun realm_app_call_function( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - serviceName: String?, - serializedEjsonArgs: String, - callback: AppCallback - ) { - realmc.realm_app_call_function(app.cptr(), user.cptr(), name, serializedEjsonArgs, serviceName, callback) - } - - actual fun realm_app_call_reset_password_function( - app: RealmAppPointer, - email: String, - newPassword: String, - serializedEjsonPayload: String, - callback: AppCallback - ) { - realmc.realm_app_email_password_provider_client_call_reset_password_function( - app.cptr(), - email, - newPassword, - serializedEjsonPayload, - callback - ) - } - - actual fun realm_app_sync_client_reconnect(app: RealmAppPointer) { - realmc.realm_app_sync_client_reconnect(app.cptr()) - } - actual fun realm_app_sync_client_has_sessions(app: RealmAppPointer): Boolean { - return realmc.realm_app_sync_client_has_sessions(app.cptr()) - } - - actual fun realm_app_sync_client_wait_for_sessions_to_terminate(app: RealmAppPointer) { - realmc.realm_app_sync_client_wait_for_sessions_to_terminate(app.cptr()) - } - - actual fun realm_sync_config_new(user: RealmUserPointer, partition: String): RealmSyncConfigurationPointer { - return LongPointerWrapper(realmc.realm_sync_config_new(user.cptr(), partition)).also { ptr -> - // Stop the session immediately when the Realm is closed, so the lifecycle of the - // Sync Client thread is manageable. - realmc.realm_sync_config_set_session_stop_policy(ptr.cptr(), realm_sync_session_stop_policy_e.RLM_SYNC_SESSION_STOP_POLICY_IMMEDIATELY) - } - } - - actual fun realm_flx_sync_config_new(user: RealmUserPointer): RealmSyncConfigurationPointer { - return LongPointerWrapper(realmc.realm_flx_sync_config_new(user.cptr())) - } - - actual fun realm_config_set_sync_config(realmConfiguration: RealmConfigurationPointer, syncConfiguration: RealmSyncConfigurationPointer) { - realmc.realm_config_set_sync_config(realmConfiguration.cptr(), syncConfiguration.cptr()) - } - private fun classInfo(realm: RealmPointer, className: String): realm_class_info_t { val found = booleanArrayOf(false) val classInfo = realm_class_info_t() @@ -1988,214 +1364,6 @@ actual object RealmInterop { realmc.realm_object_delete(obj.cptr()) } - actual fun realm_sync_subscription_id(subscription: RealmSubscriptionPointer): ObjectId { - val nativeBytes: ShortArray = realmc.realm_sync_subscription_id(subscription.cptr()).bytes - val byteArray = ByteArray(nativeBytes.size) - nativeBytes.mapIndexed { index, b -> byteArray[index] = b.toByte() } - return ObjectId(byteArray) - } - - actual fun realm_sync_subscription_name(subscription: RealmSubscriptionPointer): String? { - return realmc.realm_sync_subscription_name(subscription.cptr()) - } - - actual fun realm_sync_subscription_object_class_name(subscription: RealmSubscriptionPointer): String { - return realmc.realm_sync_subscription_object_class_name(subscription.cptr()) - } - - actual fun realm_sync_subscription_query_string(subscription: RealmSubscriptionPointer): String { - return realmc.realm_sync_subscription_query_string(subscription.cptr()) - } - - actual fun realm_sync_subscription_created_at(subscription: RealmSubscriptionPointer): Timestamp { - val ts: realm_timestamp_t = realmc.realm_sync_subscription_created_at(subscription.cptr()) - return TimestampImpl(ts.seconds, ts.nanoseconds) - } - - actual fun realm_sync_subscription_updated_at(subscription: RealmSubscriptionPointer): Timestamp { - val ts: realm_timestamp_t = realmc.realm_sync_subscription_updated_at(subscription.cptr()) - return TimestampImpl(ts.seconds, ts.nanoseconds) - } - - actual fun realm_sync_get_latest_subscriptionset(realm: RealmPointer): RealmSubscriptionSetPointer { - return LongPointerWrapper(realmc.realm_sync_get_latest_subscription_set(realm.cptr())) - } - - actual fun realm_sync_on_subscriptionset_state_change_async( - subscriptionSet: RealmSubscriptionSetPointer, - destinationState: CoreSubscriptionSetState, - callback: SubscriptionSetCallback - ) { - val jvmWrapper: (Int) -> Any = { value: Int -> - callback.onChange(CoreSubscriptionSetState.of(value)) - } - realmc.realm_sync_on_subscription_set_state_change_async( - subscriptionSet.cptr(), - destinationState.nativeValue, - jvmWrapper - ) - } - - actual fun realm_sync_subscriptionset_version(subscriptionSet: RealmBaseSubscriptionSetPointer): Long { - return realmc.realm_sync_subscription_set_version(subscriptionSet.cptr()) - } - - actual fun realm_sync_subscriptionset_state(subscriptionSet: RealmBaseSubscriptionSetPointer): CoreSubscriptionSetState { - return CoreSubscriptionSetState.of(realmc.realm_sync_subscription_set_state(subscriptionSet.cptr())) - } - - actual fun realm_sync_subscriptionset_error_str(subscriptionSet: RealmBaseSubscriptionSetPointer): String? { - return realmc.realm_sync_subscription_set_error_str(subscriptionSet.cptr()) - } - - actual fun realm_sync_subscriptionset_size(subscriptionSet: RealmBaseSubscriptionSetPointer): Long { - return realmc.realm_sync_subscription_set_size(subscriptionSet.cptr()) - } - - actual fun realm_sync_subscription_at( - subscriptionSet: RealmBaseSubscriptionSetPointer, - index: Long - ): RealmSubscriptionPointer { - return LongPointerWrapper(realmc.realm_sync_subscription_at(subscriptionSet.cptr(), index)) - } - - actual fun realm_sync_find_subscription_by_name( - subscriptionSet: RealmBaseSubscriptionSetPointer, - name: String - ): RealmSubscriptionPointer? { - val ptr = realmc.realm_sync_find_subscription_by_name(subscriptionSet.cptr(), name) - return nativePointerOrNull(ptr) - } - - actual fun realm_sync_find_subscription_by_query( - subscriptionSet: RealmBaseSubscriptionSetPointer, - query: RealmQueryPointer - ): RealmSubscriptionPointer? { - val ptr = realmc.realm_sync_find_subscription_by_query(subscriptionSet.cptr(), query.cptr()) - return nativePointerOrNull(ptr) - } - - actual fun realm_sync_subscriptionset_refresh(subscriptionSet: RealmSubscriptionSetPointer): Boolean { - return realmc.realm_sync_subscription_set_refresh(subscriptionSet.cptr()) - } - - actual fun realm_sync_make_subscriptionset_mutable( - subscriptionSet: RealmSubscriptionSetPointer - ): RealmMutableSubscriptionSetPointer { - return LongPointerWrapper( - realmc.realm_sync_make_subscription_set_mutable(subscriptionSet.cptr()), - managed = false - ) - } - - actual fun realm_sync_subscriptionset_clear( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): Boolean { - val erased = realmc.realm_sync_subscription_set_size(mutableSubscriptionSet.cptr()) > 0 - realmc.realm_sync_subscription_set_clear(mutableSubscriptionSet.cptr()) - return erased - } - - actual fun realm_sync_subscriptionset_insert_or_assign( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer, - name: String? - ): Pair { - val outIndex = longArrayOf(1) - val outInserted = BooleanArray(1) - realmc.realm_sync_subscription_set_insert_or_assign_query( - mutableSubscriptionSet.cptr(), - query.cptr(), - name, - outIndex, - outInserted - ) - @Suppress("UNCHECKED_CAST") - return Pair( - realm_sync_subscription_at( - mutableSubscriptionSet as RealmSubscriptionSetPointer, - outIndex[0] - ), - outInserted[0] - ) - } - - actual fun realm_sync_subscriptionset_erase_by_name( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - name: String - ): Boolean { - val erased = BooleanArray(1) - realmc.realm_sync_subscription_set_erase_by_name( - mutableSubscriptionSet.cptr(), - name, - erased - ) - return erased[0] - } - - actual fun realm_sync_subscriptionset_erase_by_query( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer - ): Boolean { - val erased = BooleanArray(1) - realmc.realm_sync_subscription_set_erase_by_query( - mutableSubscriptionSet.cptr(), - query.cptr(), - erased - ) - return erased[0] - } - - actual fun realm_sync_subscriptionset_erase_by_id( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - sub: RealmSubscriptionPointer - ): Boolean { - val id = realmc.realm_sync_subscription_id(sub.cptr()) - val erased = BooleanArray(1) - realmc.realm_sync_subscription_set_erase_by_id( - mutableSubscriptionSet.cptr(), - id, - erased - ) - return erased[0] - } - - actual fun realm_sync_subscriptionset_commit( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): RealmSubscriptionSetPointer { - return LongPointerWrapper(realmc.realm_sync_subscription_set_commit(mutableSubscriptionSet.cptr())) - } - - actual fun realm_sync_set_websocket_transport( - syncClientConfig: RealmSyncClientConfigurationPointer, - webSocketTransport: WebSocketTransport - ) { - realmc.realm_sync_websocket_new(syncClientConfig.cptr(), webSocketTransport) - } - - actual fun realm_sync_socket_callback_complete(nativePointer: RealmWebsocketHandlerCallbackPointer, cancelled: Boolean, status: WebsocketCallbackResult, reason: String) { - realmc.realm_sync_websocket_callback_complete(cancelled, nativePointer.cptr(), status.nativeValue, reason) - } - - actual fun realm_sync_socket_websocket_connected(nativePointer: RealmWebsocketProviderPointer, protocol: String) { - realmc.realm_sync_websocket_connected(nativePointer.cptr(), protocol) - } - - actual fun realm_sync_socket_websocket_error(nativePointer: RealmWebsocketProviderPointer) { - realmc.realm_sync_websocket_error(nativePointer.cptr()) - } - - actual fun realm_sync_socket_websocket_message( - nativePointer: RealmWebsocketProviderPointer, - data: ByteArray - ): Boolean { - return realmc.realm_sync_websocket_message(nativePointer.cptr(), data, data.size.toLong()) - } - - actual fun realm_sync_socket_websocket_closed(nativePointer: RealmWebsocketProviderPointer, wasClean: Boolean, errorCode: WebsocketErrorCode, reason: String) { - realmc.realm_sync_websocket_closed(nativePointer.cptr(), wasClean, errorCode.nativeValue, reason) - } - fun NativePointer.cptr(): Long { return (this as LongPointerWrapper).ptr } @@ -2207,88 +1375,6 @@ actual object RealmInterop { null } } - - actual fun realm_app_user_apikey_provider_client_create_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - callback: AppCallback - ) { - realmc.realm_app_user_apikey_provider_client_create_apikey( - app.cptr(), - user.cptr(), - name, - callback - ) - } - - actual fun realm_app_user_apikey_provider_client_delete_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback - ) { - realmc.realm_app_user_apikey_provider_client_delete_apikey( - app.cptr(), - user.cptr(), - id.asRealmObjectIdT(), - callback - ) - } - - actual fun realm_app_user_apikey_provider_client_disable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback - ) { - realmc.realm_app_user_apikey_provider_client_disable_apikey( - app.cptr(), - user.cptr(), - id.asRealmObjectIdT(), - callback - ) - } - - actual fun realm_app_user_apikey_provider_client_enable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback - ) { - realmc.realm_app_user_apikey_provider_client_enable_apikey( - app.cptr(), - user.cptr(), - id.asRealmObjectIdT(), - callback - ) - } - - actual fun realm_app_user_apikey_provider_client_fetch_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback, - ) { - realmc.realm_app_user_apikey_provider_client_fetch_apikey( - app.cptr(), - user.cptr(), - id.asRealmObjectIdT(), - callback - ) - } - - actual fun realm_app_user_apikey_provider_client_fetch_apikeys( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback>, - ) { - realmc.realm_app_user_apikey_provider_client_fetch_apikeys( - app.cptr(), - user.cptr(), - callback - ) - } } fun realm_value_t.asTimestamp(): Timestamp { diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt deleted file mode 100644 index 063f666b1c..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_auth_provider_e - -actual enum class AuthProvider(override val nativeValue: Int) : NativeEnumerated { - RLM_AUTH_PROVIDER_ANONYMOUS(realm_auth_provider_e.RLM_AUTH_PROVIDER_ANONYMOUS), - RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE(realm_auth_provider_e.RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE), - RLM_AUTH_PROVIDER_FACEBOOK(realm_auth_provider_e.RLM_AUTH_PROVIDER_FACEBOOK), - RLM_AUTH_PROVIDER_GOOGLE(realm_auth_provider_e.RLM_AUTH_PROVIDER_GOOGLE), - RLM_AUTH_PROVIDER_APPLE(realm_auth_provider_e.RLM_AUTH_PROVIDER_APPLE), - RLM_AUTH_PROVIDER_CUSTOM(realm_auth_provider_e.RLM_AUTH_PROVIDER_CUSTOM), - RLM_AUTH_PROVIDER_EMAIL_PASSWORD(realm_auth_provider_e.RLM_AUTH_PROVIDER_EMAIL_PASSWORD), - RLM_AUTH_PROVIDER_FUNCTION(realm_auth_provider_e.RLM_AUTH_PROVIDER_FUNCTION), - RLM_AUTH_PROVIDER_API_KEY(realm_auth_provider_e.RLM_AUTH_PROVIDER_API_KEY); - - companion object { - // TODO Optimize - fun of(id: Int): AuthProvider { - for (value in AuthProvider.values()) { - if (value.nativeValue == id) { - return value - } - } - error("Unknown authentication provider $id") - } - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt deleted file mode 100644 index 4ac2ce056e..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.RealmValue -import io.realm.kotlin.internal.interop.wrapPtrAsRealmValueT - -actual class CoreCompensatingWriteInfo( - actual val reason: String, - actual val objectName: String, - primaryKeyPtr: Long -) { - - actual val primaryKey: RealmValue = RealmValue(primaryKeyPtr.wrapPtrAsRealmValueT()) -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt deleted file mode 100644 index bac74a88e6..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.realm_sync_connection_state_e - -actual enum class CoreConnectionState(val value: Int) { - RLM_SYNC_CONNECTION_STATE_DISCONNECTED(realm_sync_connection_state_e.RLM_SYNC_CONNECTION_STATE_DISCONNECTED), - RLM_SYNC_CONNECTION_STATE_CONNECTING(realm_sync_connection_state_e.RLM_SYNC_CONNECTION_STATE_CONNECTING), - RLM_SYNC_CONNECTION_STATE_CONNECTED(realm_sync_connection_state_e.RLM_SYNC_CONNECTION_STATE_CONNECTED); - - companion object { - @JvmStatic - fun of(value: Int): CoreConnectionState = - entries.find { it.value == value } - ?: error("Unknown connection state: $value") - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt deleted file mode 100644 index dbcfe0f8ca..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_flx_sync_subscription_set_state_e - -actual enum class CoreSubscriptionSetState(override val nativeValue: Int) : NativeEnumerated { - RLM_SYNC_SUBSCRIPTION_UNCOMMITTED(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_UNCOMMITTED), - RLM_SYNC_SUBSCRIPTION_PENDING(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_PENDING), - RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING), - RLM_SYNC_SUBSCRIPTION_COMPLETE(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_COMPLETE), - RLM_SYNC_SUBSCRIPTION_ERROR(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_ERROR), - RLM_SYNC_SUBSCRIPTION_SUPERSEDED(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_SUPERSEDED), - RLM_SYNC_SUBSCRIPTION_AWAITING_MARK(realm_flx_sync_subscription_set_state_e.RLM_SYNC_SUBSCRIPTION_AWAITING_MARK); - - companion object { - fun of(state: Int): CoreSubscriptionSetState { - for (value in entries) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown subscription set state: $state") - } - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt deleted file mode 100644 index 4120e446b5..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_sync_session_state_e - -actual enum class CoreSyncSessionState(override val nativeValue: Int) : NativeEnumerated { - RLM_SYNC_SESSION_STATE_DYING(realm_sync_session_state_e.RLM_SYNC_SESSION_STATE_DYING), - RLM_SYNC_SESSION_STATE_ACTIVE(realm_sync_session_state_e.RLM_SYNC_SESSION_STATE_ACTIVE), - RLM_SYNC_SESSION_STATE_INACTIVE(realm_sync_session_state_e.RLM_SYNC_SESSION_STATE_INACTIVE), - RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN(realm_sync_session_state_e.RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN), - RLM_SYNC_SESSION_STATE_PAUSED(realm_sync_session_state_e.RLM_SYNC_SESSION_STATE_PAUSED); - - companion object { - fun of(state: Int): CoreSyncSessionState = - entries.find { it.nativeValue == state } - ?: error("Unknown sync session state: $state") - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt deleted file mode 100644 index 96f72ee39e..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_user_state_e - -actual enum class CoreUserState( - override val nativeValue: Int -) : NativeEnumerated { - - RLM_USER_STATE_LOGGED_OUT(realm_user_state_e.RLM_USER_STATE_LOGGED_OUT), - RLM_USER_STATE_LOGGED_IN(realm_user_state_e.RLM_USER_STATE_LOGGED_IN), - RLM_USER_STATE_REMOVED(realm_user_state_e.RLM_USER_STATE_REMOVED); - - companion object { - // TODO Optimize - fun of(state: Int): CoreUserState { - for (value in values()) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown user state: $state") - } - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/JVMSyncSessionTransferCompletionCallback.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/JVMSyncSessionTransferCompletionCallback.kt deleted file mode 100644 index 8c3cf2c981..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/JVMSyncSessionTransferCompletionCallback.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.CoreError -import io.realm.kotlin.internal.interop.SyncSessionTransferCompletionCallback - -// Interface used internally as a bridge between Kotlin (JVM) and JNI. -// We pass all required primitive parameters to JVM and construct the objects there, rather than -// having to do this on the JNI side, which is both a ton of boilerplate, but also expensive in -// terms of the number of JNI traversals. -internal class JVMSyncSessionTransferCompletionCallback( - private val callback: SyncSessionTransferCompletionCallback -) { - fun onSuccess() { - callback.invoke(null) - } - fun onError(categoryFlags: Int, value: Int, message: String) { - callback.invoke(CoreError(categoryFlags, value, message)) - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt deleted file mode 100644 index 7f19585dc8..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_sync_client_metadata_mode_e - -actual enum class MetadataMode(override val nativeValue: Int) : NativeEnumerated { - RLM_SYNC_CLIENT_METADATA_MODE_DISABLED(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_DISABLED), - RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT), - RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED); - - companion object { - fun of(state: Int): MetadataMode { - for (value in values()) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown metadata mode state: $state") - } - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt deleted file mode 100644 index 0db767f44a..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_sync_progress_direction_e - -actual enum class ProgressDirection(override val nativeValue: Int) : NativeEnumerated { - RLM_SYNC_PROGRESS_DIRECTION_UPLOAD(realm_sync_progress_direction_e.RLM_SYNC_PROGRESS_DIRECTION_UPLOAD), - RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD(realm_sync_progress_direction_e.RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD), -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt deleted file mode 100644 index 452136341e..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.CodeDescription -import io.realm.kotlin.internal.interop.realm_sync_errno_connection_e -import io.realm.kotlin.internal.interop.realm_sync_errno_session_e -import io.realm.kotlin.internal.interop.realm_sync_socket_callback_result_e -import io.realm.kotlin.internal.interop.realm_web_socket_errno_e - -actual enum class SyncConnectionErrorCode( - actual override val description: String?, - actual override val nativeValue: Int -) : CodeDescription { - RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED("ConnectionClosed", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED), - RLM_SYNC_ERR_CONNECTION_OTHER_ERROR("OtherError", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_OTHER_ERROR), - RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE("UnknownMessage", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE), - RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX("BadSyntax", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX), - RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED("LimitsExceeded", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED), - RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION("WrongBadSyncPartitionValueProtocolVersion", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION), - RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT("BadSessionIdent", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT), - RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT("ReuseOfSessionIdent", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT), - RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION("BoundInOtherSession", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION), - RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER("BadMessageOrder", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER), - RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION("BadDecompression", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION), - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX("BadChangesetHeaderSyntax", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX), - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE("BadChangesetSize", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE), - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC("SwitchToFlxSync", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC), - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS("SwitchToPbs", realm_sync_errno_connection_e.RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS); - - companion object { - internal fun of(nativeValue: Int): SyncConnectionErrorCode? = - entries.firstOrNull { value -> - value.nativeValue == nativeValue - } - } -} - -actual enum class SyncSessionErrorCode( - actual override val description: String?, - actual override val nativeValue: Int -) : CodeDescription { - RLM_SYNC_ERR_SESSION_SESSION_CLOSED("SessionClosed", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_SESSION_CLOSED), - RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR("OtherSessioError", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR), - RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED("TokenExpired", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED), - RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION("BadAuthentication", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION), - RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH("IllegalRealmPath", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH), - RLM_SYNC_ERR_SESSION_NO_SUCH_REALM("NoSuchRealm", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_NO_SUCH_REALM), - RLM_SYNC_ERR_SESSION_PERMISSION_DENIED("PermissionDenied", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_PERMISSION_DENIED), - RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT("BadServerFileIdent", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT("BadClientFileIdent", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION("BadServerVersion", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION("BadClientVersion", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION), - RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES("DivergingHistories", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES), - RLM_SYNC_ERR_SESSION_BAD_CHANGESET("BadChangeset", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_CHANGESET), - RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED("PartialSyncDisabled", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED), - RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE("UnsupportedSessionFeature", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE), - RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT("BadOriginFileIdent", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE("BadClientFile", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE), - RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED("ServerFileDeleted", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED), - RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED("ClientFileBlacklisted", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED), - RLM_SYNC_ERR_SESSION_USER_BLACKLISTED("UserBlacklisted", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_USER_BLACKLISTED), - RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD("TransactBeforeUpload", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD), - RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED("ClientFileExpired", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED), - RLM_SYNC_ERR_SESSION_USER_MISMATCH("UserMismatch", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_USER_MISMATCH), - RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS("TooManySession", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS), - RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE("InvalidSchemaChange", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE), - RLM_SYNC_ERR_SESSION_BAD_QUERY("BadQuery", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_QUERY), - RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS("ObjectAlreadyExists", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS), - RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED("ServerPermissionsChanged", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED), - RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED("InitialSyncNotCompleted", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED), - RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED("WriteNotAllowed", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED), - RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE("CompensatingWrite", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE), - RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX("MigrateToFlexibleSync", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX), - RLM_SYNC_ERR_SESSION_BAD_PROGRESS("BadProgress", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_PROGRESS), - RLM_SYNC_ERR_SESSION_REVERT_TO_PBS("RevertToPartitionBasedSync", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_REVERT_TO_PBS), - RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION("BadSchemaVersion", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION), - RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED("SchemaVersionChanged", realm_sync_errno_session_e.RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED); - - companion object { - internal fun of(nativeValue: Int): SyncSessionErrorCode? = - entries.firstOrNull { value -> - value.nativeValue == nativeValue - } - } -} - -actual enum class WebsocketErrorCode( - actual override val description: String?, - actual override val nativeValue: Int -) : CodeDescription { - RLM_ERR_WEBSOCKET_OK("Ok", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_OK), - RLM_ERR_WEBSOCKET_GOINGAWAY("GoingAway", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_GOINGAWAY), - RLM_ERR_WEBSOCKET_PROTOCOLERROR("ProtocolError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_PROTOCOLERROR), - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA("UnsupportedData", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA), - RLM_ERR_WEBSOCKET_RESERVED("Reserved", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_RESERVED), - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED("NoStatusReceived", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED), - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE("AbnormalClosure", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_ABNORMALCLOSURE), - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA("InvalidPayloadData", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA), - RLM_ERR_WEBSOCKET_POLICYVIOLATION("PolicyViolation", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_POLICYVIOLATION), - RLM_ERR_WEBSOCKET_MESSAGETOOBIG("MessageToBig", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_MESSAGETOOBIG), - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION("InvalidExtension", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_INAVALIDEXTENSION), - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR("InternalServerError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_INTERNALSERVERERROR), - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED("TlsHandshakeFailed", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED), - - RLM_ERR_WEBSOCKET_UNAUTHORIZED("Unauthorized", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_UNAUTHORIZED), - RLM_ERR_WEBSOCKET_FORBIDDEN("Forbidden", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_FORBIDDEN), - RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY("MovedPermanently", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY), - - RLM_ERR_WEBSOCKET_RESOLVE_FAILED("ResolveFailed", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_RESOLVE_FAILED), - RLM_ERR_WEBSOCKET_CONNECTION_FAILED("ConnectionFailed", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_CONNECTION_FAILED), - RLM_ERR_WEBSOCKET_READ_ERROR("ReadError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_READ_ERROR), - RLM_ERR_WEBSOCKET_WRITE_ERROR("WriteError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_WRITE_ERROR), - RLM_ERR_WEBSOCKET_RETRY_ERROR("RetryError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_RETRY_ERROR), - RLM_ERR_WEBSOCKET_FATAL_ERROR("FatalError", realm_web_socket_errno_e.RLM_ERR_WEBSOCKET_FATAL_ERROR); - - companion object { - fun of(nativeValue: Int): WebsocketErrorCode? = - entries.firstOrNull { value -> - value.nativeValue == nativeValue - } - } -} - -actual enum class WebsocketCallbackResult(actual override val description: String?, actual override val nativeValue: Int) : CodeDescription { - - RLM_ERR_SYNC_SOCKET_SUCCESS( - "Websocket callback success", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_SUCCESS - ), - RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED( - "Websocket callback aborted", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED - ), - RLM_ERR_SYNC_SOCKET_RUNTIME( - "Websocket Runtime error", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_RUNTIME - ), - RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY( - "Websocket out of memory ", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY - ), - RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED( - "Websocket address space exhausted", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED - ), - RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED( - "Websocket connection closed", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED - ), - RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED( - "Websocket not supported", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED - ), - RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT( - "Websocket invalid argument", - realm_sync_socket_callback_result_e.RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT - ); - - companion object { - fun of(nativeValue: Int): WebsocketCallbackResult? = - entries.firstOrNull { value -> - value.nativeValue == nativeValue - } - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ResponseCallbackImpl.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ResponseCallbackImpl.kt deleted file mode 100644 index a45b68ff3b..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/ResponseCallbackImpl.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.realmc - -// Implementation of network response callback that is initialized from JNI and passed to -// NetworkTransport.sendRequest to signal response back to JNI -class ResponseCallbackImpl(val userData: NetworkTransport, val requestContext: Long) : - ResponseCallback { - override fun response(response: Response) { - realmc.complete_http_request(requestContext, response) - } -} diff --git a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt b/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt deleted file mode 100644 index 77186f4cde..0000000000 --- a/packages/cinterop/src/jvm/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnumerated -import io.realm.kotlin.internal.interop.realm_sync_session_resync_mode_e - -actual enum class SyncSessionResyncMode(override val nativeValue: Int) : NativeEnumerated { - RLM_SYNC_SESSION_RESYNC_MODE_MANUAL(realm_sync_session_resync_mode_e.RLM_SYNC_SESSION_RESYNC_MODE_MANUAL), - RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL(realm_sync_session_resync_mode_e.RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL), - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER(realm_sync_session_resync_mode_e.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER), - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD(realm_sync_session_resync_mode_e.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD); - - companion object { - fun of(nativeValue: Int): SyncSessionResyncMode { - for (value in entries) { - if (value.nativeValue == nativeValue) { - return value - } - } - error("Unknown session resync mode: $nativeValue") - } - } -} diff --git a/packages/cinterop/src/native/realm.def b/packages/cinterop/src/native/realm.def index 925f075c04..8cd5753cfb 100644 --- a/packages/cinterop/src/native/realm.def +++ b/packages/cinterop/src/native/realm.def @@ -1,6 +1,5 @@ headers = realm.h realm/error_codes.h headerFilter = realm.h realm/error_codes.h -compilerOpts = -DREALM_APP_SERVICES=1 // Relative paths in def file depends are resolved differently dependent on execution // location // https://youtrack.jetbrains.com/issue/KT-43439 @@ -12,7 +11,7 @@ compilerOpts = -DREALM_APP_SERVICES=1 // libraryPaths.macos_x64 = ../external/core/build-macos_x64/src/realm/object-store/c_api ../external/core/build-macos_x64/src/realm ../external/core/build-macos_x64/src/realm/parser ../external/core/build-macos_x64/src/realm/object-store/ // libraryPaths.ios_x64 = ../external/core/build-macos_x64/src/realm/object-store/c_api ../external/core/build-macos_x64/src/realm ../external/core/build-macos_x64/src/realm/parser ../external/core/build-macos_x64/src/realm/object-store/ linkerOpts = -lcompression -lz -framework Foundation -framework CoreFoundation -framework Security -strictEnums = realm_errno realm_error_category realm_sync_errno_client realm_sync_errno_connection realm_sync_errno_session realm_web_socket_errno realm_sync_socket_callback_result +strictEnums = realm_errno realm_error_category // We don't want to convert Websocket binary data to String noStringConversion = realm_sync_socket_websocket_message diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt index 6b50a14d6c..905f74b5ae 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/ErrorCategory.kt @@ -28,15 +28,7 @@ actual enum class ErrorCategory( RLM_ERR_CAT_RUNTIME("Runtime", realm_error_category.RLM_ERR_CAT_RUNTIME.value.toInt()), RLM_ERR_CAT_INVALID_ARG("InvalidArg", realm_error_category.RLM_ERR_CAT_INVALID_ARG.value.toInt()), RLM_ERR_CAT_FILE_ACCESS("File", realm_error_category.RLM_ERR_CAT_FILE_ACCESS.value.toInt()), - RLM_ERR_CAT_SYSTEM_ERROR("System", realm_error_category.RLM_ERR_CAT_SYSTEM_ERROR.value.toInt()), - RLM_ERR_CAT_APP_ERROR("App", realm_error_category.RLM_ERR_CAT_APP_ERROR.value.toInt()), - RLM_ERR_CAT_CLIENT_ERROR("Client", realm_error_category.RLM_ERR_CAT_CLIENT_ERROR.value.toInt()), - RLM_ERR_CAT_JSON_ERROR("Json", realm_error_category.RLM_ERR_CAT_JSON_ERROR.value.toInt()), - RLM_ERR_CAT_SERVICE_ERROR("Service", realm_error_category.RLM_ERR_CAT_SERVICE_ERROR.value.toInt()), - RLM_ERR_CAT_HTTP_ERROR("Http", realm_error_category.RLM_ERR_CAT_HTTP_ERROR.value.toInt()), - RLM_ERR_CAT_CUSTOM_ERROR("Custom", realm_error_category.RLM_ERR_CAT_CUSTOM_ERROR.value.toInt()), - RLM_ERR_CAT_WEBSOCKET_ERROR("Websocket", realm_error_category.RLM_ERR_CAT_WEBSOCKET_ERROR.value.toInt()), - RLM_ERR_CAT_SYNC_ERROR("Sync", realm_error_category.RLM_ERR_CAT_SYNC_ERROR.value.toInt()); + RLM_ERR_CAT_SYSTEM_ERROR("System", realm_error_category.RLM_ERR_CAT_SYSTEM_ERROR.value.toInt()); actual companion object { diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt index a6b1a413ff..0112512925 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmEnums.kt @@ -48,7 +48,6 @@ actual enum class SchemaMode(override val nativeValue: realm_schema_mode) : Nati actual object ClassFlags { actual val RLM_CLASS_NORMAL = realm_wrapper.RLM_CLASS_NORMAL.toInt() actual val RLM_CLASS_EMBEDDED = realm_wrapper.RLM_CLASS_EMBEDDED.toInt() - actual val RLM_CLASS_ASYMMETRIC = realm_wrapper.RLM_CLASS_ASYMMETRIC.toInt() } actual enum class PropertyType(override val nativeValue: UInt) : NativeEnumerated { diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt index f39b2e8772..e55b4fe91d 100644 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt +++ b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/RealmInterop.kt @@ -19,27 +19,6 @@ package io.realm.kotlin.internal.interop import io.realm.kotlin.internal.interop.Constants.ENCRYPTION_KEY_LENGTH -import io.realm.kotlin.internal.interop.sync.ApiKeyWrapper -import io.realm.kotlin.internal.interop.sync.AppError -import io.realm.kotlin.internal.interop.sync.AuthProvider -import io.realm.kotlin.internal.interop.sync.CancellableTimer -import io.realm.kotlin.internal.interop.sync.CoreCompensatingWriteInfo -import io.realm.kotlin.internal.interop.sync.CoreConnectionState -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState -import io.realm.kotlin.internal.interop.sync.CoreUserState -import io.realm.kotlin.internal.interop.sync.MetadataMode -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.ProgressDirection -import io.realm.kotlin.internal.interop.sync.Response -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode -import io.realm.kotlin.internal.interop.sync.SyncUserIdentity -import io.realm.kotlin.internal.interop.sync.WebSocketClient -import io.realm.kotlin.internal.interop.sync.WebSocketObserver -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.interop.sync.WebsocketCallbackResult -import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode import kotlinx.atomicfu.AtomicBoolean import kotlinx.atomicfu.atomic import kotlinx.cinterop.AutofreeScope @@ -59,7 +38,6 @@ import kotlinx.cinterop.MemScope import kotlinx.cinterop.StableRef import kotlinx.cinterop.UIntVar import kotlinx.cinterop.ULongVar -import kotlinx.cinterop.ULongVarOf import kotlinx.cinterop.addressOf import kotlinx.cinterop.alloc import kotlinx.cinterop.allocArray @@ -78,7 +56,6 @@ import kotlinx.cinterop.refTo import kotlinx.cinterop.set import kotlinx.cinterop.staticCFunction import kotlinx.cinterop.toCStringArray -import kotlinx.cinterop.toCValues import kotlinx.cinterop.toKString import kotlinx.cinterop.useContents import kotlinx.cinterop.usePinned @@ -87,16 +64,13 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.ObjectId import platform.posix.memcpy import platform.posix.posix_errno import platform.posix.pthread_threadid_np -import platform.posix.size_t import platform.posix.size_tVar import platform.posix.strerror import platform.posix.uint64_t import platform.posix.uint8_tVar -import realm_wrapper.realm_app_error_t import realm_wrapper.realm_binary_t import realm_wrapper.realm_class_info_t import realm_wrapper.realm_class_key_tVar @@ -105,12 +79,7 @@ import realm_wrapper.realm_clone import realm_wrapper.realm_dictionary_t import realm_wrapper.realm_error_t import realm_wrapper.realm_find_property -import realm_wrapper.realm_flx_sync_subscription_set_state_e import realm_wrapper.realm_get_last_error -import realm_wrapper.realm_http_header_t -import realm_wrapper.realm_http_request_method -import realm_wrapper.realm_http_request_t -import realm_wrapper.realm_http_response_t import realm_wrapper.realm_link_t import realm_wrapper.realm_list_t import realm_wrapper.realm_object_id_t @@ -122,21 +91,11 @@ import realm_wrapper.realm_results_t import realm_wrapper.realm_scheduler_t import realm_wrapper.realm_set_t import realm_wrapper.realm_string_t -import realm_wrapper.realm_sync_session_state_e -import realm_wrapper.realm_sync_session_stop_policy_e -import realm_wrapper.realm_sync_socket_post_callback_t -import realm_wrapper.realm_sync_socket_t -import realm_wrapper.realm_sync_socket_timer_callback_t -import realm_wrapper.realm_sync_socket_timer_t -import realm_wrapper.realm_sync_socket_websocket_t -import realm_wrapper.realm_sync_socket_write_callback_t import realm_wrapper.realm_t -import realm_wrapper.realm_user_t import realm_wrapper.realm_value_t import realm_wrapper.realm_value_type import realm_wrapper.realm_version_id_t import realm_wrapper.realm_work_queue_t -import kotlin.collections.set import kotlin.experimental.ExperimentalNativeApi import kotlin.native.ref.createCleaner @@ -556,6 +515,38 @@ actual object RealmInterop { return Pair(realmPtr, fileCreated.value) } + actual fun realm_add_realm_changed_callback(realm: LiveRealmPointer, block: () -> Unit): RealmCallbackTokenPointer { + return CPointerWrapper( + realm_wrapper.realm_add_realm_changed_callback( + realm.cptr(), + staticCFunction { userData -> + safeUserData<() -> Unit>(userData)() + }, + StableRef.create(block).asCPointer(), + staticCFunction { userdata -> + disposeUserData<(() -> Unit) -> Unit>(userdata) + } + ), + managed = false + ) + } + + actual fun realm_add_schema_changed_callback(realm: LiveRealmPointer, block: (RealmSchemaPointer) -> Unit): RealmCallbackTokenPointer { + return CPointerWrapper( + realm_wrapper.realm_add_schema_changed_callback( + realm.cptr(), + staticCFunction { userData, schema -> + safeUserData<(RealmSchemaPointer) -> Unit>(userData)(CPointerWrapper(realm_clone(schema))) + }, + StableRef.create(block).asCPointer(), + staticCFunction { userdata -> + disposeUserData<(RealmSchemaPointer) -> Unit>(userdata) + } + ), + managed = false + ) + } + actual fun realm_create_scheduler(): RealmSchedulerPointer { // If there is no notification dispatcher use the default scheduler. // Re-verify if this is actually needed when notification scheduler is fully in place. @@ -619,76 +610,6 @@ actual object RealmInterop { return CPointerWrapper(capi_scheduler) } - actual fun realm_open_synchronized(config: RealmConfigurationPointer): RealmAsyncOpenTaskPointer { - return CPointerWrapper(realm_wrapper.realm_open_synchronized(config.cptr())) - } - - actual fun realm_async_open_task_start(task: RealmAsyncOpenTaskPointer, callback: AsyncOpenCallback) { - realm_wrapper.realm_async_open_task_start( - task.cptr(), - staticCFunction { userData, realm, error -> - memScoped { - var exception: Throwable? = null - if (error != null) { - val err = alloc() - realm_wrapper.realm_get_async_error(error, err.ptr) - exception = CoreErrorConverter.asThrowable( - categoriesNativeValue = err.categories.toInt(), - errorCodeNativeValue = err.error.value.toInt(), - messageNativeValue = err.message?.toKString(), - path = err.path?.toKString(), - userError = err.user_code_error?.asStableRef()?.get() - ) - err.user_code_error?.let { disposeUserData(it) } - } else { - realm_release(realm) - } - safeUserData(userData).invoke(exception) - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> - disposeUserData(userData) - } - ) - } - - actual fun realm_async_open_task_cancel(task: RealmAsyncOpenTaskPointer) { - realm_wrapper.realm_async_open_task_cancel(task.cptr()) - } - - actual fun realm_add_realm_changed_callback(realm: LiveRealmPointer, block: () -> Unit): RealmCallbackTokenPointer { - return CPointerWrapper( - realm_wrapper.realm_add_realm_changed_callback( - realm.cptr(), - staticCFunction { userData -> - safeUserData<() -> Unit>(userData)() - }, - StableRef.create(block).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(LiveRealmPointer, SyncErrorCallback) -> Unit>(userdata) - } - ), - managed = false - ) - } - - actual fun realm_add_schema_changed_callback(realm: LiveRealmPointer, block: (RealmSchemaPointer) -> Unit): RealmCallbackTokenPointer { - return CPointerWrapper( - realm_wrapper.realm_add_schema_changed_callback( - realm.cptr(), - staticCFunction { userData, schema -> - safeUserData<(RealmSchemaPointer) -> Unit>(userData)(CPointerWrapper(realm_clone(schema))) - }, - StableRef.create(block).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(RealmSchemaT, SyncErrorCallback) -> Unit>(userdata) - } - ), - managed = false - ) - } - actual fun realm_freeze(liveRealm: LiveRealmPointer): FrozenRealmPointer { return CPointerWrapper(realm_wrapper.realm_freeze(liveRealm.cptr())) } @@ -2060,459 +1981,6 @@ actual object RealmInterop { } } - actual fun realm_app_get( - appConfig: RealmAppConfigurationPointer, - basePath: String - ): RealmAppPointer { - return CPointerWrapper(realm_wrapper.realm_app_create(appConfig.cptr()), managed = true) - } - - actual fun realm_app_get_current_user(app: RealmAppPointer): RealmUserPointer? { - val currentUserPtr: CPointer? = realm_wrapper.realm_app_get_current_user(app.cptr()) - return nativePointerOrNull(currentUserPtr) - } - - actual fun realm_app_get_all_users(app: RealmAppPointer): List { - memScoped { - // We get the current amount of users by providing a `null` array and `out_n` - // argument. Then the current count is written to `out_n`. - // See https://github.com/realm/realm-core/blob/master/src/realm.h#L2634 - val capacityCount: ULongVarOf = alloc() - checkedBooleanResult( - realm_wrapper.realm_app_get_all_users( - app.cptr(), - null, - 0UL, - capacityCount.ptr - ) - ) - - // Read actual users. We don't care about the small chance of missing a new user - // between these two calls as that indicate two sections of user code running on - // different threads and not coordinating. - val actualUsersCount: ULongVarOf = alloc() - val users = allocArray>(capacityCount.value.toInt()) - checkedBooleanResult(realm_wrapper.realm_app_get_all_users(app.cptr(), users, capacityCount.value, actualUsersCount.ptr)) - val result: MutableList = mutableListOf() - for (i in 0 until actualUsersCount.value.toInt()) { - users[i]?.let { ptr: CPointer -> - result.add(CPointerWrapper(ptr, managed = true)) - } - } - return result - } - } - - actual fun realm_app_log_in_with_credentials( - app: RealmAppPointer, - credentials: RealmCredentialsPointer, - callback: AppCallback - ) { - realm_wrapper.realm_app_log_in_with_credentials( - app.cptr(), - credentials.cptr(), - staticCFunction { userData, user, error: CPointer? -> - // Remember to clone user object or else it will go out of scope right after we leave this callback - handleAppCallback(userData, error) { CPointerWrapper(realm_clone(user)) } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> disposeUserData>(userdata) } - ) - } - - actual fun realm_app_user_apikey_provider_client_create_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_create_apikey( - app.cptr(), - user.cptr(), - name, - staticCFunction { userData: CPointer?, apiKey: CPointer?, error: CPointer? -> - handleAppCallback(userData, error) { - apiKey!!.pointed.let { - ApiKeyWrapper( - ObjectId( - it.id.bytes.readBytes(OBJECT_ID_BYTES_SIZE), - ), - it.key.safeKString(), - it.name.safeKString(), - it.disabled - ) - } - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> disposeUserData>(userdata) } - ) - ) - } - - actual fun realm_app_user_apikey_provider_client_delete_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: BsonObjectId, - callback: AppCallback, - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_delete_apikey( - app.cptr(), - user.cptr(), - id.realm_object_id_t(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - - actual fun realm_app_user_apikey_provider_client_disable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: BsonObjectId, - callback: AppCallback, - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_disable_apikey( - app.cptr(), - user.cptr(), - id.realm_object_id_t(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - - actual fun realm_app_user_apikey_provider_client_enable_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: BsonObjectId, - callback: AppCallback, - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_enable_apikey( - app.cptr(), - user.cptr(), - id.realm_object_id_t(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - - actual fun realm_app_user_apikey_provider_client_fetch_apikey( - app: RealmAppPointer, - user: RealmUserPointer, - id: ObjectId, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_fetch_apikey( - app.cptr(), - user.cptr(), - id.realm_object_id_t(), - staticCFunction { userData: CPointer?, apiKey: CPointer?, error: CPointer? -> - handleAppCallback(userData, error) { - apiKey!!.pointed.let { - ApiKeyWrapper( - ObjectId( - it.id.bytes.readBytes(OBJECT_ID_BYTES_SIZE), - ), - null, - it.name.safeKString(), - it.disabled - ) - } - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> disposeUserData>(userdata) } - ) - ) - } - - actual fun realm_app_user_apikey_provider_client_fetch_apikeys( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback>, - ) { - checkedBooleanResult( - realm_wrapper.realm_app_user_apikey_provider_client_fetch_apikeys( - app.cptr(), - user.cptr(), - staticCFunction { userData: CPointer?, apiKeys: CPointer?, count: size_t, error: CPointer? -> - handleAppCallback(userData, error) { - val result = arrayOfNulls(count.toInt()) - for (i in 0 until count.toInt()) { - apiKeys!![i].let { - result[i] = ApiKeyWrapper( - ObjectId( - it.id.bytes.readBytes(OBJECT_ID_BYTES_SIZE), - ), - null, - it.name.safeKString(), - it.disabled - ) - } - } - result - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> disposeUserData>>(userdata) } - ) - ) - } - - actual fun realm_app_log_out( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_log_out( - app.cptr(), - user.cptr(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> disposeUserData>(userdata) } - ) - ) - } - - actual fun realm_app_remove_user( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_remove_user( - app.cptr(), - user.cptr(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData>(userdata) - } - ) - ) - } - - actual fun realm_app_delete_user( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_delete_user( - app.cptr(), - user.cptr(), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData>(userdata) - } - ) - ) - } - - actual fun realm_app_link_credentials( - app: RealmAppPointer, - user: RealmUserPointer, - credentials: RealmCredentialsPointer, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_link_user( - app.cptr(), - user.cptr(), - credentials.cptr(), - staticCFunction { userData, user, error: CPointer? -> - // Remember to clone user object or else it will go out of scope right after we leave this callback - handleAppCallback(userData, error) { CPointerWrapper(realm_clone(user)) } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData>(userdata) - } - ) - ) - } - - actual fun realm_app_switch_user(app: RealmAppPointer, user: RealmUserPointer) { - checkedBooleanResult(realm_wrapper.realm_app_switch_user(app.cptr(), user.cptr())) - } - - actual fun realm_clear_cached_apps() { - realm_wrapper.realm_clear_cached_apps() - } - - actual fun realm_app_sync_client_get_default_file_path_for_realm( - syncConfig: RealmSyncConfigurationPointer, - overriddenName: String? - ): String { - val cPath = realm_wrapper.realm_app_sync_client_get_default_file_path_for_realm( - syncConfig.cptr(), - overriddenName - ) - return cPath.safeKString() - .also { realm_wrapper.realm_free(cPath) } - } - - actual fun realm_app_get_base_url( - app: RealmAppPointer, - ): String = realm_wrapper.realm_app_get_base_url(app.cptr())?.toKString()!! - - actual fun realm_app_update_base_url( - app: RealmAppPointer, - baseUrl: String?, - callback: AppCallback, - ) { - checkedBooleanResult( - realm_wrapper.realm_app_update_base_url( - app.cptr(), - baseUrl, - callback = staticCFunction { userData, error -> - handleAppCallback( - userData, - error - ) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData>(userdata) - } - ) - ) - } - - actual fun realm_user_get_all_identities(user: RealmUserPointer): List { - memScoped { - val count = AuthProvider.values().size - val properties = allocArray(count) - val outCount: ULongVarOf = alloc() - realm_wrapper.realm_user_get_all_identities( - user.cptr(), - properties, - count.convert(), - outCount.ptr - ) - outCount.value.toLong().let { count -> - return if (count > 0) { - (0 until outCount.value.toLong()).map { - with(properties[it]) { - SyncUserIdentity(this.id!!.toKString(), AuthProvider.of(this.provider_type)) - } - } - } else { - emptyList() - } - } - } - } - - actual fun realm_user_get_identity(user: RealmUserPointer): String { - return realm_wrapper.realm_user_get_identity(user.cptr()).safeKString("identity") - } - - actual fun realm_user_is_logged_in(user: RealmUserPointer): Boolean { - return realm_wrapper.realm_user_is_logged_in(user.cptr()) - } - - actual fun realm_user_log_out(user: RealmUserPointer) { - checkedBooleanResult(realm_wrapper.realm_user_log_out(user.cptr())) - } - - actual fun realm_user_get_state(user: RealmUserPointer): CoreUserState { - return CoreUserState.of(realm_wrapper.realm_user_get_state(user.cptr())) - } - - actual fun realm_user_get_profile(user: RealmUserPointer): String = - realm_wrapper.realm_user_get_profile_data(user.cptr()).safeKString() - - actual fun realm_user_get_custom_data(user: RealmUserPointer): String? = - realm_wrapper.realm_user_get_custom_data(user.cptr())?.toKString() - - actual fun realm_user_refresh_custom_data( - app: RealmAppPointer, - user: RealmUserPointer, - callback: AppCallback - ) { - checkedBooleanResult( - realm_wrapper.realm_app_refresh_custom_data( - app = app.cptr(), - user = user.cptr(), - callback = staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - userdata = StableRef.create(callback).asCPointer(), - userdata_free = staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - - actual fun realm_app_config_get_sync_client_config(configPointer: RealmAppConfigurationPointer): RealmSyncClientConfigurationPointer { - // The configuration is owned by Core so don't track and release it through garbage collection of the NativePointer - return CPointerWrapper(realm_wrapper.realm_app_config_get_sync_client_config(configPointer.cptr()), false) - } - - actual fun realm_sync_client_config_set_default_binding_thread_observer( - syncClientConfig: RealmSyncClientConfigurationPointer, - appId: String - ) { - realm_wrapper.realm_sync_client_config_set_default_binding_thread_observer( - config = syncClientConfig.cptr(), - on_thread_create = staticCFunction { _ -> - // Do nothing - }, - on_thread_destroy = staticCFunction { _ -> - // Do nothing. Threads in Kotlin Native are cleaned up correctly without us - // having to do anything. - }, - on_error = staticCFunction { userdata, error -> - // TODO Wait for https://github.com/realm/realm-core/issues/4194 to correctly - // log errors. For now, just throw an Error as exceptions from the Sync Client - // indicate that something is fundamentally wrong on the Sync Thread. - // In Realm Java this has only been reported during development of new - // features, so throwing an Error seems appropriate to increase visibility. - val threadId = safeUserData(userdata) - throw Error("[SyncThread-$threadId] Error on sync thread: ${error?.toKString()}") - }, - user_data = StableRef.create(appId).asCPointer(), - free_userdata = staticCFunction { userdata -> - disposeUserData(userdata) - } - ) - } - - actual fun realm_app_config_set_base_file_path( - appConfig: RealmAppConfigurationPointer, - basePath: String - ) { - realm_wrapper.realm_app_config_set_base_file_path(appConfig.cptr(), basePath) - } - - actual fun realm_sync_client_config_set_multiplex_sessions(syncClientConfig: RealmSyncClientConfigurationPointer, enabled: Boolean) { - realm_wrapper.realm_sync_client_config_set_multiplex_sessions(syncClientConfig.cptr(), enabled) - } - actual fun realm_set_log_callback(callback: LogCallback) { realm_wrapper.realm_set_log_callback( staticCFunction { userData, category, logLevel, message -> @@ -2547,1004 +2015,6 @@ actual object RealmInterop { } } - actual fun realm_app_config_set_metadata_mode( - appConfig: RealmAppConfigurationPointer, - metadataMode: MetadataMode, - ) { - realm_wrapper.realm_app_config_set_metadata_mode( - appConfig.cptr(), - metadataMode.metadataValue - ) - } - - actual fun realm_app_config_set_metadata_encryption_key( - appConfig: RealmAppConfigurationPointer, - encryptionKey: ByteArray - ) { - memScoped { - val encryptionKeyPointer = encryptionKey.refTo(0).getPointer(memScope) - @Suppress("UNCHECKED_CAST") - realm_wrapper.realm_app_config_set_metadata_encryption_key( - appConfig.cptr(), - encryptionKeyPointer as CPointer - ) - } - } - - actual fun realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - bindingInfo: String - ) { - realm_wrapper.realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig.cptr(), - bindingInfo - ) - } - - actual fun realm_sync_client_config_set_user_agent_application_info( - syncClientConfig: RealmSyncClientConfigurationPointer, - applicationInfo: String - ) { - realm_wrapper.realm_sync_client_config_set_user_agent_application_info( - syncClientConfig.cptr(), - applicationInfo - ) - } - - actual fun realm_sync_client_config_set_connect_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realm_wrapper.realm_sync_client_config_set_connect_timeout(syncClientConfig.cptr(), timeoutMs) - } - - actual fun realm_sync_client_config_set_connection_linger_time(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realm_wrapper.realm_sync_client_config_set_connection_linger_time(syncClientConfig.cptr(), timeoutMs) - } - - actual fun realm_sync_client_config_set_ping_keepalive_period(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realm_wrapper.realm_sync_client_config_set_ping_keepalive_period(syncClientConfig.cptr(), timeoutMs) - } - - actual fun realm_sync_client_config_set_pong_keepalive_timeout(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realm_wrapper.realm_sync_client_config_set_pong_keepalive_timeout(syncClientConfig.cptr(), timeoutMs) - } - - actual fun realm_sync_client_config_set_fast_reconnect_limit(syncClientConfig: RealmSyncClientConfigurationPointer, timeoutMs: ULong) { - realm_wrapper.realm_sync_client_config_set_fast_reconnect_limit(syncClientConfig.cptr(), timeoutMs) - } - - actual fun realm_sync_config_set_error_handler( - syncConfig: RealmSyncConfigurationPointer, - errorHandler: SyncErrorCallback - ) { - realm_wrapper.realm_sync_config_set_error_handler( - syncConfig.cptr(), - staticCFunction { userData, syncSession, error -> - val syncError: SyncError = error.useContents { - val code = CoreError( - this.status.categories.toInt(), - this.status.error.value.toInt(), - this.status.message.safeKString() - ) - - val userInfoMap = (0 until user_info_length.toInt()) - .mapNotNull { - user_info_map?.get(it) - }.mapNotNull { - when { - it.key != null && it.value != null -> - Pair(it.key.safeKString(), it.value.safeKString()) - else -> null - } - }.toMap() - - val compensatingWrites = - Array(compensating_writes_length.toInt()) { index -> - compensating_writes!![index].let { compensatingWriteInfo -> - CoreCompensatingWriteInfo( - reason = compensatingWriteInfo.reason.safeKString(), - objectName = compensatingWriteInfo.object_name.safeKString(), - primaryKey = RealmValue(compensatingWriteInfo.primary_key) - ) - } - } - - SyncError( - errorCode = code, - originalFilePath = userInfoMap[c_original_file_path_key.safeKString()], - recoveryFilePath = userInfoMap[c_recovery_file_path_key.safeKString()], - isFatal = is_fatal, - isUnrecognizedByClient = is_unrecognized_by_client, - isClientResetRequested = is_client_reset_requested, - compensatingWrites = compensatingWrites, - userError = user_code_error?.asStableRef()?.get() - ).also { - user_code_error?.let { disposeUserData(it) } - } - } - val errorCallback = safeUserData(userData) - val session = CPointerWrapper(realm_clone(syncSession)) - errorCallback.onSyncError(session, syncError) - }, - StableRef.create(errorHandler).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(RealmSyncSessionPointer, SyncErrorCallback) -> Unit>(userdata) - } - ) - } - - actual fun realm_sync_config_set_resync_mode( - syncConfig: RealmSyncConfigurationPointer, - resyncMode: SyncSessionResyncMode - ) { - realm_wrapper.realm_sync_config_set_resync_mode( - syncConfig.cptr(), - resyncMode.value - ) - } - - actual fun realm_sync_config_set_before_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - beforeHandler: SyncBeforeClientResetHandler - ) { - realm_wrapper.realm_sync_config_set_before_client_reset_handler( - syncConfig.cptr(), - staticCFunction { userData, beforeRealm -> - stableUserDataWithErrorPropagation(userData) { - val beforeDb = CPointerWrapper(beforeRealm, false) - onBeforeReset(beforeDb) - true - } - }, - StableRef.create(beforeHandler).asCPointer(), - staticCFunction { userdata -> - disposeUserData(userdata) - } - ) - } - - actual fun realm_sync_config_set_after_client_reset_handler( - syncConfig: RealmSyncConfigurationPointer, - afterHandler: SyncAfterClientResetHandler - ) { - realm_wrapper.realm_sync_config_set_after_client_reset_handler( - syncConfig.cptr(), - staticCFunction { userData, beforeRealm, afterRealm, didRecover -> - stableUserDataWithErrorPropagation(userData) { - val beforeDb = CPointerWrapper(beforeRealm, false) - - // afterRealm is wrapped inside a ThreadSafeReference so the pointer needs to be resolved - val afterRealmPtr = realm_wrapper.realm_from_thread_safe_reference(afterRealm, null) - val afterDb = CPointerWrapper(afterRealmPtr, false) - - try { - onAfterReset(beforeDb, afterDb, didRecover) - true - } finally { - realm_wrapper.realm_close(afterRealmPtr) - } - } - }, - StableRef.create(afterHandler).asCPointer(), - staticCFunction { userdata -> - disposeUserData(userdata) - } - ) - } - - actual fun realm_sync_immediately_run_file_actions(app: RealmAppPointer, syncPath: String): Boolean { - memScoped { - val didRun = alloc() - checkedBooleanResult( - realm_wrapper.realm_sync_immediately_run_file_actions(app.cptr(), syncPath, didRun.ptr) - ) - return didRun.value - } - } - - actual fun realm_sync_session_get(realm: RealmPointer): RealmSyncSessionPointer { - return CPointerWrapper(realm_wrapper.realm_sync_session_get(realm.cptr())) - } - - actual fun realm_sync_session_wait_for_download_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) { - realm_wrapper.realm_sync_session_wait_for_download_completion( - syncSession.cptr(), - staticCFunction?, Unit> { userData, error -> - handleCompletionCallback(userData, error) - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(RealmSyncSessionPointer, SyncSessionTransferCompletionCallback) -> Unit>(userdata) - } - ) - } - - actual fun realm_sync_session_wait_for_upload_completion( - syncSession: RealmSyncSessionPointer, - callback: SyncSessionTransferCompletionCallback - ) { - realm_wrapper.realm_sync_session_wait_for_upload_completion( - syncSession.cptr(), - staticCFunction?, Unit> { userData, error -> - handleCompletionCallback(userData, error) - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(RealmSyncSessionPointer, SyncSessionTransferCompletionCallback) -> Unit>(userdata) - } - ) - } - - actual fun realm_sync_session_state(syncSession: RealmSyncSessionPointer): CoreSyncSessionState { - val value: realm_sync_session_state_e = - realm_wrapper.realm_sync_session_get_state(syncSession.cptr()) - return CoreSyncSessionState.of(value) - } - - actual fun realm_sync_connection_state(syncSession: RealmSyncSessionPointer): CoreConnectionState = - CoreConnectionState.of( - realm_wrapper.realm_sync_session_get_connection_state(syncSession.cptr()) - ) - - actual fun realm_sync_session_pause(syncSession: RealmSyncSessionPointer) { - realm_wrapper.realm_sync_session_pause(syncSession.cptr()) - } - - actual fun realm_sync_session_resume(syncSession: RealmSyncSessionPointer) { - realm_wrapper.realm_sync_session_resume(syncSession.cptr()) - } - - actual fun realm_sync_session_handle_error_for_testing( - syncSession: RealmSyncSessionPointer, - error: ErrorCode, - errorMessage: String, - isFatal: Boolean - ) { - realm_wrapper.realm_sync_session_handle_error_for_testing( - syncSession.cptr(), - error.asNativeEnum, - errorMessage, - isFatal - ) - } - - actual fun realm_sync_session_register_progress_notifier( - syncSession: RealmSyncSessionPointer, - direction: ProgressDirection, - isStreaming: Boolean, - callback: ProgressCallback, - ): RealmNotificationTokenPointer { - return CPointerWrapper( - realm_wrapper.realm_sync_session_register_progress_notifier( - syncSession.cptr(), - staticCFunction { userData, _, _, progress_estimate -> - safeUserData(userData).run { - onChange(progress_estimate) - } - }, - direction.nativeValue, - isStreaming, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData(userdata) - } - ), - managed = false - ) - } - - actual fun realm_sync_session_register_connection_state_change_callback( - syncSession: RealmSyncSessionPointer, - callback: ConnectionStateChangeCallback, - ): RealmNotificationTokenPointer { - return CPointerWrapper( - realm_wrapper.realm_sync_session_register_connection_state_change_callback( - syncSession.cptr(), - staticCFunction { userData, oldState, newState -> - safeUserData(userData).run { - onChange(CoreConnectionState.of(oldState), CoreConnectionState.of(newState)) - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData(userdata) - } - ), - managed = false - ) - } - - private fun handleCompletionCallback( - userData: CPointer?, - error: CPointer? - ) { - val completionCallback = safeUserData(userData) - if (error != null) { - val category = error.pointed.categories.toInt() - val value: Int = error.pointed.error.value.toInt() - val message = error.pointed.message.safeKString() - completionCallback.invoke(CoreError(category, value, message)) - } else { - completionCallback.invoke(null) - } - } - - actual fun realm_network_transport_new(networkTransport: NetworkTransport): RealmNetworkTransportPointer { - return CPointerWrapper( - realm_wrapper.realm_http_transport_new( - newRequestLambda, - StableRef.create(networkTransport).asCPointer(), - staticCFunction { userdata: CPointer? -> - disposeUserData(userdata) - } - ) - ) - } - - actual fun realm_sync_set_websocket_transport( - syncClientConfig: RealmSyncClientConfigurationPointer, - webSocketTransport: WebSocketTransport - ) { - val realmSyncSocketNew: CPointer = - checkedPointerResult( - realm_wrapper.realm_sync_socket_new( - userdata = StableRef.create(webSocketTransport).asCPointer(), - userdata_free = staticCFunction { userdata: CPointer? -> - safeUserData(userdata).close() - disposeUserData(userdata) - }, - post_func = staticCFunction { userdata: CPointer?, syncSocketCallback: CPointer? -> - val callback: WebsocketFunctionHandlerCallback = { cancelled, _, _ -> - realm_wrapper.realm_sync_socket_post_complete( - syncSocketCallback, - if (cancelled) WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED.asNativeEnum else WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS.asNativeEnum, - "" - ) - } - - safeUserData(userdata).post( - CPointerWrapper(StableRef.create(callback).asCPointer()) - ) - }, - create_timer_func = staticCFunction { userdata: CPointer?, delayInMilliseconds: uint64_t, syncSocketCallback: CPointer? -> - val callback: WebsocketFunctionHandlerCallback = { cancelled, _, _ -> - if (cancelled) { - realm_wrapper.realm_sync_socket_timer_canceled(syncSocketCallback) - } else { - realm_wrapper.realm_sync_socket_timer_complete( - syncSocketCallback, - WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS.asNativeEnum, - "" - ) - } - } - - safeUserData(userdata).let { ws -> - val job: CancellableTimer = ws.createTimer( - delayInMilliseconds.toLong(), - CPointerWrapper(StableRef.create(callback).asCPointer()) - ) - StableRef.create(job).asCPointer() - } - }, - cancel_timer_func = staticCFunction { userdata: CPointer?, timer: realm_sync_socket_timer_t? -> - safeUserData(timer).cancel() - }, - free_timer_func = staticCFunction { userdata: CPointer?, timer: realm_sync_socket_timer_t? -> - disposeUserData(timer) - }, - websocket_connect_func = staticCFunction { userdata: CPointer?, endpoint: CValue, observer: CPointer? -> - safeUserData(userdata).let { websocketTransport -> - endpoint.useContents { - val managedObserver = WebSocketObserver(CPointerWrapper(observer)) - - val supportedProtocols = mutableListOf() - for (i in 0 until this.num_protocols.toInt()) { - val protocol: CPointer>? = - this.protocols?.get(i) - supportedProtocols.add(protocol.safeKString()) - } - val webSocketClient: WebSocketClient = websocketTransport.connect( - managedObserver, - this.path.safeKString(), - this.address.safeKString(), - this.port.toLong(), - this.is_ssl, - this.num_protocols.toLong(), - supportedProtocols.joinToString(", ") - ) - StableRef.create(webSocketClient).asCPointer() - } - } - }, - websocket_write_func = staticCFunction { userdata: CPointer?, websocket: realm_sync_socket_websocket_t?, data: CPointer?, length: size_t, callback: CPointer? -> - val postWriteCallback: WebsocketFunctionHandlerCallback = - { cancelled, status, reason -> - realm_wrapper.realm_sync_socket_write_complete( - callback, - if (cancelled) WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED.asNativeEnum else WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS.asNativeEnum, - reason - ) - } - - safeUserData(userdata).let { websocketTransport -> - safeUserData(websocket).let { webSocketClient -> - data?.readBytes(length.toInt())?.run { - websocketTransport.write( - webSocketClient, - this, - length.toLong(), - CPointerWrapper( - StableRef.create(postWriteCallback).asCPointer() - ) - ) - } - } - } - Unit - }, - websocket_free_func = staticCFunction { userdata: CPointer?, websocket: realm_sync_socket_websocket_t? -> - safeUserData(websocket).close() - disposeUserData(websocket) - } - ) - ) ?: error("Couldn't create Sync Socket") - realm_wrapper.realm_sync_client_config_set_sync_socket( - syncClientConfig.cptr(), - realmSyncSocketNew - ) - realm_release(realmSyncSocketNew) - } - - actual fun realm_sync_socket_callback_complete(nativePointer: RealmWebsocketHandlerCallbackPointer, cancelled: Boolean, status: WebsocketCallbackResult, reason: String) { - safeUserData(nativePointer.cptr())(cancelled, status, reason) - disposeUserData(nativePointer.cptr()) - } - - actual fun realm_sync_socket_websocket_connected(nativePointer: RealmWebsocketProviderPointer, protocol: String) { - realm_wrapper.realm_sync_socket_websocket_connected(nativePointer.cptr(), protocol) - } - - actual fun realm_sync_socket_websocket_error(nativePointer: RealmWebsocketProviderPointer) { - realm_wrapper.realm_sync_socket_websocket_error(nativePointer.cptr()) - } - - actual fun realm_sync_socket_websocket_message( - nativePointer: RealmWebsocketProviderPointer, - data: ByteArray - ): Boolean { - return realm_wrapper.realm_sync_socket_websocket_message( - nativePointer.cptr(), - data.toCValues(), - data.size.toULong() - ) - } - - actual fun realm_sync_socket_websocket_closed(nativePointer: RealmWebsocketProviderPointer, wasClean: Boolean, errorCode: WebsocketErrorCode, reason: String) { - realm_wrapper.realm_sync_socket_websocket_closed(nativePointer.cptr(), wasClean, errorCode.asNativeEnum, reason) - } - - @Suppress("LongParameterList") - actual fun realm_app_config_new( - appId: String, - networkTransport: RealmNetworkTransportPointer, - baseUrl: String?, - connectionParams: SyncConnectionParams - ): RealmAppConfigurationPointer { - val appConfig = realm_wrapper.realm_app_config_new(appId, networkTransport.cptr()) - baseUrl?.let { realm_wrapper.realm_app_config_set_base_url(appConfig, it) } - - // Sync Connection Parameters - realm_wrapper.realm_app_config_set_sdk(appConfig, connectionParams.sdkName) - realm_wrapper.realm_app_config_set_sdk_version(appConfig, connectionParams.sdkVersion) - realm_wrapper.realm_app_config_set_platform_version(appConfig, connectionParams.platformVersion) - realm_wrapper.realm_app_config_set_device_name(appConfig, connectionParams.device) - realm_wrapper.realm_app_config_set_device_version(appConfig, connectionParams.deviceVersion) - realm_wrapper.realm_app_config_set_framework_name(appConfig, connectionParams.framework) - realm_wrapper.realm_app_config_set_framework_version(appConfig, connectionParams.frameworkVersion) - realm_wrapper.realm_app_config_set_bundle_id(appConfig, connectionParams.bundleId) - - return CPointerWrapper(appConfig) - } - - actual fun realm_app_config_set_base_url(appConfig: RealmAppConfigurationPointer, baseUrl: String) { - realm_wrapper.realm_app_config_set_base_url(appConfig.cptr(), baseUrl) - } - - actual fun realm_app_credentials_new_anonymous(reuseExisting: Boolean): RealmCredentialsPointer { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_anonymous(reuseExisting)) - } - - actual fun realm_app_credentials_new_email_password( - username: String, - password: String - ): RealmCredentialsPointer { - memScoped { - val realmStringPassword = password.toRString(this) - return CPointerWrapper( - realm_wrapper.realm_app_credentials_new_email_password( - username, - realmStringPassword - ) - ) - } - } - - actual fun realm_app_credentials_new_api_key(key: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_api_key(key)) - } - } - - actual fun realm_app_credentials_new_apple(idToken: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_apple(idToken)) - } - } - - actual fun realm_app_credentials_new_facebook(accessToken: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_facebook(accessToken)) - } - } - - actual fun realm_app_credentials_new_google_id_token(idToken: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_google_id_token(idToken)) - } - } - - actual fun realm_app_credentials_new_google_auth_code(authCode: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_google_auth_code(authCode)) - } - } - - actual fun realm_app_credentials_new_jwt(jwtToken: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_jwt(jwtToken)) - } - } - - actual fun realm_app_credentials_new_custom_function(serializedEjsonPayload: String): RealmCredentialsPointer { - memScoped { - return CPointerWrapper(realm_wrapper.realm_app_credentials_new_function(serializedEjsonPayload)) - } - } - - actual fun realm_auth_credentials_get_provider(credentials: RealmCredentialsPointer): AuthProvider { - return AuthProvider.of(realm_wrapper.realm_auth_credentials_get_provider(credentials.cptr())) - } - - actual fun realm_user_get_access_token(user: RealmUserPointer): String { - return realm_wrapper.realm_user_get_access_token(user.cptr()).safeKString() - } - - actual fun realm_user_get_refresh_token(user: RealmUserPointer): String { - return realm_wrapper.realm_user_get_refresh_token(user.cptr()).safeKString() - } - - actual fun realm_user_get_device_id(user: RealmUserPointer): String { - return realm_wrapper.realm_user_get_device_id(user.cptr()).safeKString() - } - - actual fun realm_app_credentials_serialize_as_json(credentials: RealmCredentialsPointer): String { - return realm_wrapper - .realm_app_credentials_serialize_as_json(credentials.cptr()) - .safeKString("credentials") - } - - actual fun realm_app_email_password_provider_client_register_email( - app: RealmAppPointer, - email: String, - password: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_register_email( - app.cptr(), - email, - password.toRString(this), - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - actual fun realm_app_email_password_provider_client_confirm_user( - app: RealmAppPointer, - token: String, - tokenId: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_confirm_user( - app.cptr(), - token, - tokenId, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_app_email_password_provider_client_resend_confirmation_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_resend_confirmation_email( - app.cptr(), - email, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_app_email_password_provider_client_retry_custom_confirmation( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_retry_custom_confirmation( - app.cptr(), - email, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_app_email_password_provider_client_send_reset_password_email( - app: RealmAppPointer, - email: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_send_reset_password_email( - app.cptr(), - email, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_app_email_password_provider_client_reset_password( - app: RealmAppPointer, - token: String, - tokenId: String, - newPassword: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_reset_password( - app.cptr(), - newPassword.toRString(this), - token, - tokenId, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_app_call_function( - app: RealmAppPointer, - user: RealmUserPointer, - name: String, - serviceName: String?, - serializedEjsonArgs: String, - callback: AppCallback - ) { - realm_wrapper.realm_app_call_function( - app.cptr(), - user.cptr(), - name, - serializedEjsonArgs, - serviceName, - staticCFunction { userData: CPointer?, data: CPointer>?, error: CPointer? -> - handleAppCallback(userData, error) { - data.safeKString() - } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - } - - actual fun realm_app_call_reset_password_function( - app: RealmAppPointer, - email: String, - newPassword: String, - serializedEjsonPayload: String, - callback: AppCallback - ) { - memScoped { - checkedBooleanResult( - realm_wrapper.realm_app_email_password_provider_client_call_reset_password_function( - app.cptr(), - email, - newPassword.toRString(this), - serializedEjsonPayload, - staticCFunction { userData, error -> - handleAppCallback(userData, error) { /* No-op, returns Unit */ } - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userData -> disposeUserData>(userData) } - ) - ) - } - } - - actual fun realm_sync_config_new( - user: RealmUserPointer, - partition: String - ): RealmSyncConfigurationPointer { - return CPointerWrapper(realm_wrapper.realm_sync_config_new(user.cptr(), partition)).also { ptr -> - // Stop the session immediately when the Realm is closed, so the lifecycle of the - // Sync Client thread is manageable. - realm_wrapper.realm_sync_config_set_session_stop_policy(ptr.cptr(), realm_sync_session_stop_policy_e.RLM_SYNC_SESSION_STOP_POLICY_IMMEDIATELY) - } - } - - actual fun realm_flx_sync_config_new(user: RealmUserPointer): RealmSyncConfigurationPointer { - return CPointerWrapper(realm_wrapper.realm_flx_sync_config_new((user.cptr()))) - } - - actual fun realm_app_sync_client_reconnect(app: RealmAppPointer) { - realm_wrapper.realm_app_sync_client_reconnect(app.cptr()) - } - actual fun realm_app_sync_client_has_sessions(app: RealmAppPointer): Boolean { - return realm_wrapper.realm_app_sync_client_has_sessions(app.cptr()) - } - - actual fun realm_app_sync_client_wait_for_sessions_to_terminate(app: RealmAppPointer) { - realm_wrapper.realm_app_sync_client_wait_for_sessions_to_terminate(app.cptr()) - } - - actual fun realm_config_set_sync_config(realmConfiguration: RealmConfigurationPointer, syncConfiguration: RealmSyncConfigurationPointer) { - realm_wrapper.realm_config_set_sync_config(realmConfiguration.cptr(), syncConfiguration.cptr()) - } - - actual fun realm_sync_subscription_id(subscription: RealmSubscriptionPointer): ObjectId { - return ObjectId(realm_wrapper.realm_sync_subscription_id(subscription.cptr()).getBytes()) - } - - actual fun realm_sync_subscription_name(subscription: RealmSubscriptionPointer): String? { - return realm_wrapper.realm_sync_subscription_name(subscription.cptr()).useContents { - this.toNullableKotlinString() - } - } - - actual fun realm_sync_subscription_object_class_name(subscription: RealmSubscriptionPointer): String { - return realm_wrapper.realm_sync_subscription_object_class_name(subscription.cptr()).useContents { - this.toKotlinString() - } - } - - actual fun realm_sync_subscription_query_string(subscription: RealmSubscriptionPointer): String { - return realm_wrapper.realm_sync_subscription_query_string(subscription.cptr()).useContents { - this.toKotlinString() - } - } - - actual fun realm_sync_subscription_created_at(subscription: RealmSubscriptionPointer): Timestamp { - return realm_wrapper.realm_sync_subscription_created_at(subscription.cptr()).useContents { - TimestampImpl(this.seconds, this.nanoseconds) - } - } - - actual fun realm_sync_subscription_updated_at(subscription: RealmSubscriptionPointer): Timestamp { - return realm_wrapper.realm_sync_subscription_updated_at(subscription.cptr()).useContents { - TimestampImpl(this.seconds, this.nanoseconds) - } - } - - actual fun realm_sync_get_latest_subscriptionset(realm: RealmPointer): RealmSubscriptionSetPointer { - return CPointerWrapper(realm_wrapper.realm_sync_get_latest_subscription_set(realm.cptr())) - } - - actual fun realm_sync_on_subscriptionset_state_change_async( - subscriptionSet: RealmSubscriptionSetPointer, - destinationState: CoreSubscriptionSetState, - callback: SubscriptionSetCallback - ) { - realm_wrapper.realm_sync_on_subscription_set_state_change_async( - subscriptionSet.cptr(), - destinationState.nativeValue, - staticCFunction { userData, state -> - val callback = safeUserData(userData) - callback.onChange(CoreSubscriptionSetState.of(state)) - }, - StableRef.create(callback).asCPointer(), - staticCFunction { userdata -> - disposeUserData<(SubscriptionSetCallback) -> Unit>(userdata) - } - ) - } - - actual fun realm_sync_subscriptionset_version(subscriptionSet: RealmBaseSubscriptionSetPointer): Long { - return realm_wrapper.realm_sync_subscription_set_version(subscriptionSet.cptr()) - } - - actual fun realm_sync_subscriptionset_state(subscriptionSet: RealmBaseSubscriptionSetPointer): CoreSubscriptionSetState { - val value: realm_flx_sync_subscription_set_state_e = - realm_wrapper.realm_sync_subscription_set_state(subscriptionSet.cptr()) - return CoreSubscriptionSetState.of(value) - } - - actual fun realm_sync_subscriptionset_error_str(subscriptionSet: RealmBaseSubscriptionSetPointer): String? { - return realm_wrapper.realm_sync_subscription_set_error_str(subscriptionSet.cptr())?.toKString() - } - - actual fun realm_sync_subscriptionset_size(subscriptionSet: RealmBaseSubscriptionSetPointer): Long { - return realm_wrapper.realm_sync_subscription_set_size(subscriptionSet.cptr()).toLong() - } - - actual fun realm_sync_subscription_at( - subscriptionSet: RealmBaseSubscriptionSetPointer, - index: Long - ): RealmSubscriptionPointer { - return CPointerWrapper(realm_wrapper.realm_sync_subscription_at(subscriptionSet.cptr(), index.toULong())) - } - - actual fun realm_sync_find_subscription_by_name( - subscriptionSet: RealmBaseSubscriptionSetPointer, - name: String - ): RealmSubscriptionPointer? { - val ptr = realm_wrapper.realm_sync_find_subscription_by_name(subscriptionSet.cptr(), name) - return nativePointerOrNull(ptr) - } - - actual fun realm_sync_find_subscription_by_query( - subscriptionSet: RealmBaseSubscriptionSetPointer, - query: RealmQueryPointer - ): RealmSubscriptionPointer? { - val ptr = realm_wrapper.realm_sync_find_subscription_by_query(subscriptionSet.cptr(), query.cptr()) - return nativePointerOrNull(ptr) - } - - actual fun realm_sync_subscriptionset_refresh(subscriptionSet: RealmSubscriptionSetPointer): Boolean { - return realm_wrapper.realm_sync_subscription_set_refresh(subscriptionSet.cptr()) - } - - actual fun realm_sync_make_subscriptionset_mutable( - subscriptionSet: RealmSubscriptionSetPointer - ): RealmMutableSubscriptionSetPointer { - return CPointerWrapper( - realm_wrapper.realm_sync_make_subscription_set_mutable(subscriptionSet.cptr()), - managed = false - ) - } - - actual fun realm_sync_subscriptionset_clear( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): Boolean { - val erased = realm_wrapper.realm_sync_subscription_set_size(mutableSubscriptionSet.cptr()).toLong() > 0 - checkedBooleanResult( - realm_wrapper.realm_sync_subscription_set_clear(mutableSubscriptionSet.cptr()) - ) - return erased - } - - actual fun realm_sync_subscriptionset_insert_or_assign( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer, - name: String? - ): Pair { - memScoped { - val outIndex = alloc() - val outInserted = alloc() - realm_wrapper.realm_sync_subscription_set_insert_or_assign_query( - mutableSubscriptionSet.cptr(), - query.cptr(), - name, - outIndex.ptr, - outInserted.ptr - ) - @Suppress("UNCHECKED_CAST") - return Pair( - realm_sync_subscription_at( - mutableSubscriptionSet as RealmSubscriptionSetPointer, - outIndex.value.toLong() - ), - outInserted.value - ) - } - } - - actual fun realm_sync_subscriptionset_erase_by_name( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - name: String - ): Boolean { - memScoped { - val erased = alloc() - checkedBooleanResult( - realm_wrapper.realm_sync_subscription_set_erase_by_name( - mutableSubscriptionSet.cptr(), - name, - erased.ptr - ) - ) - return erased.value - } - } - - actual fun realm_sync_subscriptionset_erase_by_query( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - query: RealmQueryPointer - ): Boolean { - memScoped { - val erased = alloc() - checkedBooleanResult( - realm_wrapper.realm_sync_subscription_set_erase_by_query( - mutableSubscriptionSet.cptr(), - query.cptr(), - erased.ptr - ) - ) - return erased.value - } - } - - actual fun realm_sync_subscriptionset_erase_by_id( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer, - sub: RealmSubscriptionPointer - ): Boolean { - memScoped { - val id = realm_wrapper.realm_sync_subscription_id(sub.cptr()) - val erased = alloc() - checkedBooleanResult( - realm_wrapper.realm_sync_subscription_set_erase_by_id( - mutableSubscriptionSet.cptr(), - id, - erased.ptr - ) - ) - return erased.value - } - } - - actual fun realm_sync_subscriptionset_commit( - mutableSubscriptionSet: RealmMutableSubscriptionSetPointer - ): RealmSubscriptionSetPointer { - return CPointerWrapper(realm_wrapper.realm_sync_subscription_set_commit(mutableSubscriptionSet.cptr())) - } - /** * C-API functions for queries receive a pointer to one or more 'realm_query_arg_t' query * arguments. In turn, said arguments contain individual values or lists of values (in @@ -3621,85 +2091,6 @@ actual object RealmInterop { ?: throw NullPointerException(identifier?.let { "'$identifier' shouldn't be null." }) } - private fun handleAppCallback( - userData: COpaquePointer?, - error: CPointer?, - getValue: () -> R - ) { - val userDataCallback = safeUserData>(userData) - if (error == null) { - userDataCallback.onSuccess(getValue()) - } else { - val err: realm_app_error_t = error.pointed - val ex = AppError.newInstance( - err.categories.toInt(), - err.error.value.toInt(), - err.http_status_code, - err.message?.toKString(), - err.link_to_server_logs?.toKString() - ) - userDataCallback.onError(ex) - } - } - - private val newRequestLambda = staticCFunction, - COpaquePointer?, - Unit> - { userdata, request, requestContext -> - safeUserData(userdata).let { networkTransport -> - request.useContents { // this : realm_http_request_t -> - val headerMap = mutableMapOf() - for (i in 0 until num_headers.toInt()) { - headers?.get(i)?.let { header -> - headerMap[header.name!!.toKString()] = header.value!!.toKString() - } ?: error("Header at index $i within range ${num_headers.toInt()} should not be null") - } - - networkTransport.sendRequest( - method = when (method) { - realm_http_request_method.RLM_HTTP_REQUEST_METHOD_GET -> NetworkTransport.GET - realm_http_request_method.RLM_HTTP_REQUEST_METHOD_POST -> NetworkTransport.POST - realm_http_request_method.RLM_HTTP_REQUEST_METHOD_PATCH -> NetworkTransport.PATCH - realm_http_request_method.RLM_HTTP_REQUEST_METHOD_PUT -> NetworkTransport.PUT - realm_http_request_method.RLM_HTTP_REQUEST_METHOD_DELETE -> NetworkTransport.DELETE - else -> error("Unknown method: $method") - }, - url = url!!.toKString(), - headers = headerMap, - body = body!!.toKString() - ) { response: Response -> - memScoped { - val headersSize = response.headers.entries.size - val cResponseHeaders = - allocArray(headersSize) - - response.headers.entries.forEachIndexed { i, entry -> - cResponseHeaders[i].let { header -> - header.name = entry.key.cstr.getPointer(memScope) - header.value = entry.value.cstr.getPointer(memScope) - } - } - - val cResponse = - alloc { - body = response.body.cstr.getPointer(memScope) - body_size = response.body.cstr.getBytes().size.toULong() - custom_status_code = response.customResponseCode - status_code = response.httpResponseCode - num_headers = response.headers.entries.size.toULong() - headers = cResponseHeaders - } - realm_wrapper.realm_http_transport_complete_request( - requestContext, - cResponse.ptr - ) - } - } - } - } - } - interface Scheduler { fun notify(work_queue: CPointer?) } @@ -3770,8 +2161,6 @@ actual object RealmInterop { } } -private typealias WebsocketFunctionHandlerCallback = (Boolean, WebsocketCallbackResult, String) -> Unit - fun realm_value_t.asByteArray(): ByteArray { if (this.type != realm_value_type.RLM_TYPE_BINARY) { error("Value is not of type ByteArray: $this.type") diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt deleted file mode 100644 index a04efe1835..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/AuthProvider.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnum -import realm_wrapper.realm_auth_provider_e - -actual enum class AuthProvider( - override val nativeValue: realm_auth_provider_e -) : NativeEnum { - RLM_AUTH_PROVIDER_ANONYMOUS(realm_auth_provider_e.RLM_AUTH_PROVIDER_ANONYMOUS), - RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE(realm_auth_provider_e.RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE), - RLM_AUTH_PROVIDER_FACEBOOK(realm_auth_provider_e.RLM_AUTH_PROVIDER_FACEBOOK), - RLM_AUTH_PROVIDER_GOOGLE(realm_auth_provider_e.RLM_AUTH_PROVIDER_GOOGLE), - RLM_AUTH_PROVIDER_APPLE(realm_auth_provider_e.RLM_AUTH_PROVIDER_APPLE), - RLM_AUTH_PROVIDER_CUSTOM(realm_auth_provider_e.RLM_AUTH_PROVIDER_CUSTOM), - RLM_AUTH_PROVIDER_EMAIL_PASSWORD(realm_auth_provider_e.RLM_AUTH_PROVIDER_EMAIL_PASSWORD), - RLM_AUTH_PROVIDER_FUNCTION(realm_auth_provider_e.RLM_AUTH_PROVIDER_FUNCTION), - RLM_AUTH_PROVIDER_API_KEY(realm_auth_provider_e.RLM_AUTH_PROVIDER_API_KEY); - - companion object { - // TODO Optimize - fun of(id: realm_auth_provider_e): AuthProvider { - for (value in AuthProvider.values()) { - if (value.nativeValue == id) { - return value - } - } - error("Unknown authentication provider $id") - } - } -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt deleted file mode 100644 index 0a7684c8f3..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.RealmValue - -actual class CoreCompensatingWriteInfo( - actual val reason: String, - actual val objectName: String, - actual val primaryKey: RealmValue -) diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt deleted file mode 100644 index 3a8b43cbfb..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreConnectionState.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_sync_connection_state - -actual enum class CoreConnectionState(val value: realm_sync_connection_state) { - RLM_SYNC_CONNECTION_STATE_DISCONNECTED(realm_sync_connection_state.RLM_SYNC_CONNECTION_STATE_DISCONNECTED), - RLM_SYNC_CONNECTION_STATE_CONNECTING(realm_sync_connection_state.RLM_SYNC_CONNECTION_STATE_CONNECTING), - RLM_SYNC_CONNECTION_STATE_CONNECTED(realm_sync_connection_state.RLM_SYNC_CONNECTION_STATE_CONNECTED), - ; - - companion object { - fun of(nativeValue: realm_sync_connection_state): CoreConnectionState = - entries.find { it.value == nativeValue } - ?: error("Unknown property type: $nativeValue") - } -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt deleted file mode 100644 index 02bbaf2cfc..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSubscriptionSetState.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_flx_sync_subscription_set_state_e - -actual enum class CoreSubscriptionSetState( - val nativeValue: realm_flx_sync_subscription_set_state_e -) { - RLM_SYNC_SUBSCRIPTION_UNCOMMITTED(realm_wrapper.RLM_SYNC_SUBSCRIPTION_UNCOMMITTED), - RLM_SYNC_SUBSCRIPTION_PENDING(realm_wrapper.RLM_SYNC_SUBSCRIPTION_PENDING), - RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING(realm_wrapper.RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING), - RLM_SYNC_SUBSCRIPTION_COMPLETE(realm_wrapper.RLM_SYNC_SUBSCRIPTION_COMPLETE), - RLM_SYNC_SUBSCRIPTION_ERROR(realm_wrapper.RLM_SYNC_SUBSCRIPTION_ERROR), - RLM_SYNC_SUBSCRIPTION_SUPERSEDED(realm_wrapper.RLM_SYNC_SUBSCRIPTION_SUPERSEDED), - RLM_SYNC_SUBSCRIPTION_AWAITING_MARK(realm_wrapper.RLM_SYNC_SUBSCRIPTION_AWAITING_MARK); - - companion object { - fun of(state: realm_flx_sync_subscription_set_state_e): CoreSubscriptionSetState { - for (value in entries) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown subscription set state: $state") - } - } -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt deleted file mode 100644 index a266928c58..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreSyncSessionState.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_sync_session_state -import realm_wrapper.realm_sync_session_state_e - -actual enum class CoreSyncSessionState( - val nativeValue: realm_sync_session_state_e -) { - RLM_SYNC_SESSION_STATE_DYING(realm_sync_session_state.RLM_SYNC_SESSION_STATE_DYING), - RLM_SYNC_SESSION_STATE_ACTIVE(realm_sync_session_state.RLM_SYNC_SESSION_STATE_ACTIVE), - RLM_SYNC_SESSION_STATE_INACTIVE(realm_sync_session_state.RLM_SYNC_SESSION_STATE_INACTIVE), - RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN(realm_sync_session_state.RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN), - RLM_SYNC_SESSION_STATE_PAUSED(realm_sync_session_state.RLM_SYNC_SESSION_STATE_PAUSED); - - companion object { - fun of(state: realm_sync_session_state_e): CoreSyncSessionState { - for (value in entries) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown sync session state: $state") - } - } -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt deleted file mode 100644 index 4038850a7b..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/CoreUserState.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import io.realm.kotlin.internal.interop.NativeEnum -import realm_wrapper.realm_user_state_e - -actual enum class CoreUserState( - override val nativeValue: realm_user_state_e -) : NativeEnum { - - RLM_USER_STATE_LOGGED_OUT(realm_user_state_e.RLM_USER_STATE_LOGGED_OUT), - RLM_USER_STATE_LOGGED_IN(realm_user_state_e.RLM_USER_STATE_LOGGED_IN), - RLM_USER_STATE_REMOVED(realm_user_state_e.RLM_USER_STATE_REMOVED); - - companion object { - // TODO Optimize - fun of(state: realm_user_state_e): CoreUserState { - for (value in values()) { - if (value.nativeValue == state) { - return value - } - } - error("Unknown user state: $state") - } - } -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt deleted file mode 100644 index 9cab4fb912..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/MetadataMode.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_sync_client_metadata_mode_e - -actual enum class MetadataMode(val metadataValue: realm_sync_client_metadata_mode_e) { - RLM_SYNC_CLIENT_METADATA_MODE_DISABLED(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_DISABLED), - RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT), - RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED(realm_sync_client_metadata_mode_e.RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED) -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt deleted file mode 100644 index 6aec8e5bd4..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProgressDirection.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_sync_progress_direction_e - -actual enum class ProgressDirection(val nativeValue: realm_sync_progress_direction_e) { - RLM_SYNC_PROGRESS_DIRECTION_UPLOAD(realm_sync_progress_direction_e.RLM_SYNC_PROGRESS_DIRECTION_UPLOAD), - RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD(realm_sync_progress_direction_e.RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD), -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt deleted file mode 100644 index cdb56f3669..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/ProtocolErrorCode.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync -import io.realm.kotlin.internal.interop.CodeDescription -import realm_wrapper.realm_sync_errno_connection -import realm_wrapper.realm_sync_errno_session -import realm_wrapper.realm_sync_socket_callback_result -import realm_wrapper.realm_web_socket_errno - -actual enum class SyncConnectionErrorCode( - actual override val description: String?, - errorCode: realm_sync_errno_connection -) : CodeDescription { - RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED("ConnectionClosed", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_CONNECTION_CLOSED), - RLM_SYNC_ERR_CONNECTION_OTHER_ERROR("OtherError", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_OTHER_ERROR), - RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE("UnknownMessage", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_UNKNOWN_MESSAGE), - RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX("BadSyntax", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_SYNTAX), - RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED("LimitsExceeded", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_LIMITS_EXCEEDED), - RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION("WrongProtocolVersion", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_WRONG_PROTOCOL_VERSION), - RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT("BadSessionIdent", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_SESSION_IDENT), - RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT("ReuseOfSessionIdent", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_REUSE_OF_SESSION_IDENT), - RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION("BoundInOtherSession", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BOUND_IN_OTHER_SESSION), - RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER("BadMessageOrder", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_MESSAGE_ORDER), - RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION("BadDecompression", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_DECOMPRESSION), - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX("BadChangesetHeaderSyntax", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_HEADER_SYNTAX), - RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE("BadChangesetSize", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_BAD_CHANGESET_SIZE), - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC("SwitchToFlxSync", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_SWITCH_TO_FLX_SYNC), - RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS("SwitchToPbs", realm_sync_errno_connection.RLM_SYNC_ERR_CONNECTION_SWITCH_TO_PBS); - - actual override val nativeValue: Int = errorCode.value.toInt() -} - -actual enum class SyncSessionErrorCode( - actual override val description: String?, - errorCode: realm_sync_errno_session -) : CodeDescription { - RLM_SYNC_ERR_SESSION_SESSION_CLOSED("SessionClosed", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_SESSION_CLOSED), - RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR("OtherSessioError", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_OTHER_SESSION_ERROR), - RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED("TokenExpired", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_TOKEN_EXPIRED), - RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION("BadAuthentication", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_AUTHENTICATION), - RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH("IllegalRealmPath", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_ILLEGAL_REALM_PATH), - RLM_SYNC_ERR_SESSION_NO_SUCH_REALM("NoSuchRealm", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_NO_SUCH_REALM), - RLM_SYNC_ERR_SESSION_PERMISSION_DENIED("PermissionDenied", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_PERMISSION_DENIED), - RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT("BadServerFileIdent", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_SERVER_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT("BadClientFileIdent", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION("BadServerVersion", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_SERVER_VERSION), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION("BadClientVersion", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION), - RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES("DivergingHistories", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES), - RLM_SYNC_ERR_SESSION_BAD_CHANGESET("BadChangeset", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_CHANGESET), - RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED("PartialSyncDisabled", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED), - RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE("UnsupportedSessionFeature", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE), - RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT("BadOriginFileIdent", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT), - RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE("BadClientFile", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_CLIENT_FILE), - RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED("ServerFileDeleted", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_SERVER_FILE_DELETED), - RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED("ClientFileBlacklisted", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_CLIENT_FILE_BLACKLISTED), - RLM_SYNC_ERR_SESSION_USER_BLACKLISTED("UserBlacklisted", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_USER_BLACKLISTED), - RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD("TransactBeforeUpload", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_TRANSACT_BEFORE_UPLOAD), - RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED("ClientFileExpired", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_CLIENT_FILE_EXPIRED), - RLM_SYNC_ERR_SESSION_USER_MISMATCH("UserMismatch", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_USER_MISMATCH), - RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS("TooManySession", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_TOO_MANY_SESSIONS), - RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE("InvalidSchemaChange", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_INVALID_SCHEMA_CHANGE), - RLM_SYNC_ERR_SESSION_BAD_QUERY("BadQuery", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_QUERY), - RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS("ObjectAlreadyExists", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_OBJECT_ALREADY_EXISTS), - RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED("ServerPermissionsChanged", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_SERVER_PERMISSIONS_CHANGED), - RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED("InitialSyncNotCompleted", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_INITIAL_SYNC_NOT_COMPLETED), - RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED("WriteNotAllowed", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_WRITE_NOT_ALLOWED), - RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE("CompensatingWrite", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_COMPENSATING_WRITE), - RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX("MigrateToFlexibleSync", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_MIGRATE_TO_FLX), - RLM_SYNC_ERR_SESSION_BAD_PROGRESS("BadProgress", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_PROGRESS), - RLM_SYNC_ERR_SESSION_REVERT_TO_PBS("RevertToPartitionBasedSync", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_REVERT_TO_PBS), - RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION("BadSchemaVersion", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_BAD_SCHEMA_VERSION), - RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED("SchemaVersionChanged", realm_sync_errno_session.RLM_SYNC_ERR_SESSION_SCHEMA_VERSION_CHANGED); - - actual override val nativeValue: Int = errorCode.value.toInt() -} - -actual enum class WebsocketErrorCode( - actual override val description: String?, - errorCode: realm_web_socket_errno, -) : CodeDescription { - RLM_ERR_WEBSOCKET_OK("Ok", realm_web_socket_errno.RLM_ERR_WEBSOCKET_OK), - RLM_ERR_WEBSOCKET_GOINGAWAY("GoingAway", realm_web_socket_errno.RLM_ERR_WEBSOCKET_GOINGAWAY), - RLM_ERR_WEBSOCKET_PROTOCOLERROR("ProtocolError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_PROTOCOLERROR), - RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA("UnsupportedData", realm_web_socket_errno.RLM_ERR_WEBSOCKET_UNSUPPORTEDDATA), - RLM_ERR_WEBSOCKET_RESERVED("Reserved", realm_web_socket_errno.RLM_ERR_WEBSOCKET_RESERVED), - RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED("NoStatusReceived", realm_web_socket_errno.RLM_ERR_WEBSOCKET_NOSTATUSRECEIVED), - RLM_ERR_WEBSOCKET_ABNORMALCLOSURE("AbnormalClosure", realm_web_socket_errno.RLM_ERR_WEBSOCKET_ABNORMALCLOSURE), - RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA("InvalidPayloadData", realm_web_socket_errno.RLM_ERR_WEBSOCKET_INVALIDPAYLOADDATA), - RLM_ERR_WEBSOCKET_POLICYVIOLATION("PolicyViolation", realm_web_socket_errno.RLM_ERR_WEBSOCKET_POLICYVIOLATION), - RLM_ERR_WEBSOCKET_MESSAGETOOBIG("MessageToBig", realm_web_socket_errno.RLM_ERR_WEBSOCKET_MESSAGETOOBIG), - RLM_ERR_WEBSOCKET_INAVALIDEXTENSION("InvalidExtension", realm_web_socket_errno.RLM_ERR_WEBSOCKET_INAVALIDEXTENSION), - RLM_ERR_WEBSOCKET_INTERNALSERVERERROR("InternalServerError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_INTERNALSERVERERROR), - RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED("TlsHandshakeFailed", realm_web_socket_errno.RLM_ERR_WEBSOCKET_TLSHANDSHAKEFAILED), - RLM_ERR_WEBSOCKET_UNAUTHORIZED("Unauthorized", realm_web_socket_errno.RLM_ERR_WEBSOCKET_UNAUTHORIZED), - RLM_ERR_WEBSOCKET_FORBIDDEN("Forbidden", realm_web_socket_errno.RLM_ERR_WEBSOCKET_FORBIDDEN), - RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY("MovedPermanently", realm_web_socket_errno.RLM_ERR_WEBSOCKET_MOVEDPERMANENTLY), - RLM_ERR_WEBSOCKET_RESOLVE_FAILED("ResolveFailed", realm_web_socket_errno.RLM_ERR_WEBSOCKET_RESOLVE_FAILED), - RLM_ERR_WEBSOCKET_CONNECTION_FAILED("ConnectionFailed", realm_web_socket_errno.RLM_ERR_WEBSOCKET_CONNECTION_FAILED), - RLM_ERR_WEBSOCKET_READ_ERROR("ReadError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_READ_ERROR), - RLM_ERR_WEBSOCKET_WRITE_ERROR("WriteError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_WRITE_ERROR), - RLM_ERR_WEBSOCKET_RETRY_ERROR("RetryError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_RETRY_ERROR), - RLM_ERR_WEBSOCKET_FATAL_ERROR("FatalError", realm_web_socket_errno.RLM_ERR_WEBSOCKET_FATAL_ERROR); - - actual override val nativeValue: Int = errorCode.value.toInt() - - val asNativeEnum: realm_web_socket_errno = errorCode -} - -actual enum class WebsocketCallbackResult( - actual override val description: String?, - nativeError: realm_sync_socket_callback_result -) : CodeDescription { - - RLM_ERR_SYNC_SOCKET_SUCCESS( - "Websocket callback success", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_SUCCESS - ), - RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED( - "Websocket callback aborted", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED - ), - RLM_ERR_SYNC_SOCKET_RUNTIME( - "Websocket Runtime error", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_RUNTIME - ), - RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY( - "Websocket out of memory ", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_OUT_OF_MEMORY - ), - RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED( - "Websocket address space exhausted", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_ADDRESS_SPACE_EXHAUSTED - ), - RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED( - "Websocket connection closed", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED - ), - RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED( - "Websocket not supported", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_NOT_SUPPORTED - ), - RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT( - "Websocket invalid argument", - realm_sync_socket_callback_result.RLM_ERR_SYNC_SOCKET_INVALID_ARGUMENT - ); - - actual override val nativeValue: Int = nativeError.value.toInt() - val asNativeEnum: realm_sync_socket_callback_result = nativeError -} diff --git a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt b/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt deleted file mode 100644 index 15ee968e73..0000000000 --- a/packages/cinterop/src/nativeDarwin/kotlin/io/realm/kotlin/internal/interop/sync/SyncSessionResyncMode.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.internal.interop.sync - -import realm_wrapper.realm_sync_session_resync_mode -import realm_wrapper.realm_sync_session_resync_mode_e - -actual enum class SyncSessionResyncMode(val value: realm_sync_session_resync_mode_e) { - RLM_SYNC_SESSION_RESYNC_MODE_MANUAL(realm_sync_session_resync_mode.RLM_SYNC_SESSION_RESYNC_MODE_MANUAL), - RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL(realm_sync_session_resync_mode.RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL), - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER(realm_sync_session_resync_mode.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER), - RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD(realm_sync_session_resync_mode.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD); - - companion object { - fun of(mode: realm_sync_session_resync_mode_e): SyncSessionResyncMode { - for (entry in entries) { - if (entry.value.value == mode.value) { - return entry - } - } - error("Unknown session resync mode: $mode") - } - } -} diff --git a/packages/external/core b/packages/external/core index c2552e1d36..d8a6840028 160000 --- a/packages/external/core +++ b/packages/external/core @@ -1 +1 @@ -Subproject commit c2552e1d36867cb42b28130e894a81fc17081062 +Subproject commit d8a68400288245c01be3dcb0ca3bcd4922fee680 diff --git a/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/RealmCompilerSubplugin.kt b/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/RealmCompilerSubplugin.kt index 270c67c93b..2c36e5ff5f 100644 --- a/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/RealmCompilerSubplugin.kt +++ b/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/RealmCompilerSubplugin.kt @@ -16,68 +16,13 @@ package io.realm.kotlin.gradle -import com.android.build.gradle.BaseExtension -import io.realm.kotlin.gradle.analytics.AnalyticsErrorCatcher -import io.realm.kotlin.gradle.analytics.AnalyticsService -import io.realm.kotlin.gradle.analytics.AnalyticsService.Companion.UNKNOWN -import io.realm.kotlin.gradle.analytics.AnalyticsService.Companion.unknown -import io.realm.kotlin.gradle.analytics.BuilderId -import io.realm.kotlin.gradle.analytics.ComputerId -import io.realm.kotlin.gradle.analytics.HOST_ARCH_NAME -import io.realm.kotlin.gradle.analytics.HOST_OS_NAME -import io.realm.kotlin.gradle.analytics.ProjectConfiguration -import io.realm.kotlin.gradle.analytics.TargetInfo -import io.realm.kotlin.gradle.analytics.hexStringify -import io.realm.kotlin.gradle.analytics.sha256Hash -import org.gradle.api.Project -import org.gradle.api.plugins.ExtensionAware import org.gradle.api.provider.Provider -import org.gradle.api.services.BuildServiceSpec -import org.gradle.util.GradleVersion -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact import org.jetbrains.kotlin.gradle.plugin.SubpluginOption -import org.jetbrains.kotlin.gradle.plugin.cocoapods.CocoapodsExtension -import org.jetbrains.kotlin.gradle.plugin.cocoapods.KotlinCocoapodsPlugin.Companion.COCOAPODS_EXTENSION_NAME -import org.jetbrains.kotlin.gradle.plugin.kotlinToolingVersion -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinSharedNativeCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation -import org.jetbrains.kotlin.konan.target.Architecture -import org.jetbrains.kotlin.konan.target.Family -import org.jetbrains.kotlin.konan.target.KonanTarget -import java.io.File -internal val gradleVersion: GradleVersion = GradleVersion.current().baseVersion -internal val gradle70: GradleVersion = GradleVersion.version("7.0") -internal val gradle75: GradleVersion = GradleVersion.version("7.5") - -class RealmCompilerSubplugin : KotlinCompilerPluginSupportPlugin, AnalyticsErrorCatcher { - - /** - * Flag indicating whether we should submit analytics data to the remote endpoint - */ - private var submitAnalytics: Boolean = true - /** - * Flag indicating whether we should submit analytics data to the remote endpoint - */ - private var printAnalytics: Boolean = false - - /** - * Flag to control if an exception in analytics collection should be causing the whole build to - * fail or just be logged and submitted as [UNKNOWN]. - */ - override var failOnAnalyticsError: Boolean = false - - private var analyticsServiceProvider: Provider? = null - - private lateinit var anonymizedBundleId: String +class RealmCompilerSubplugin : KotlinCompilerPluginSupportPlugin { companion object { // TODO LATER Consider embedding these from the build.gradle's versionConstants task just @@ -86,100 +31,11 @@ class RealmCompilerSubplugin : KotlinCompilerPluginSupportPlugin, AnalyticsError const val groupId = "io.realm.kotlin" const val artifactId = "plugin-compiler" const val version = PLUGIN_VERSION - const val coreVersion = CORE_VERSION // The id used for passing compiler options from command line const val compilerPluginId = "io.realm.kotlin" - - // Must match io.realm.kotlin.compiler.bundleIdKey - const val bundleIdKey = "bundleId" - - // Must match io.realm.kotlin.compiler. - const val featureListPathKey = "featureListPath" - } - - @Suppress("NestedBlockDepth") - override fun apply(target: Project) { - super.apply(target) - - // We build the anonymized bundle id here and pass it to the compiler plugin to ensure - // that the metrics and sync connection parameters are aligned. - val bundleId = target.rootProject.name + ":" + target.name - anonymizedBundleId = hexStringify(sha256Hash(bundleId.toByteArray())) - - printAnalytics = target.providers.environmentVariable("REALM_PRINT_ANALYTICS").getBoolean() - submitAnalytics = !target.gradle.startParameter.isOffline && !target.providers.environmentVariable("REALM_DISABLE_ANALYTICS").getBoolean() - // We never want to break a user's build if collecting/submitting some data fail, so by - // default we are suppressing errors. This flag can control if errors are suppressed or will - // break the build. This allows us to catch errors during development and on CI builds. - failOnAnalyticsError = target.providers.environmentVariable("REALM_FAIL_ON_ANALYTICS_ERRORS").getBoolean() - - // Only register analytics service provider if we either want to submit or print the info - if (submitAnalytics || printAnalytics) { - analyticsServiceProvider = provider(target) - } } - private fun provider(target: Project) = - target.gradle.sharedServices.registerIfAbsent( - "Realm Analytics", - AnalyticsService::class.java - ) { spec: BuildServiceSpec -> - // Identify if project is using sync by inspecting dependencies. - // We cannot use resolved configurations here as this code is called in - // afterEvaluate, and resolving it prevents other plugins from modifying - // them. E.g the KMP plugin will crash if we resolve the configurations - // in `afterEvaluate`. This means we can only see dependencies directly set, - // and not their transitive dependencies. This should be fine as we only - // want to track builds directly using Realm. - var usesSync: Boolean = withDefaultOnError("Uses Sync", false) { - var usesSync = false - outer@ - for (conf in target.configurations) { - for (dependency in conf.dependencies) { - if (dependency.group == "io.realm.kotlin" && dependency.name == "library-sync") { - // In Java we can detect Sync through a Gradle configuration closure. - // In Kotlin, this choice is currently determined by which dependency - // people include - usesSync = true - break@outer - } - } - } - usesSync - } - - // Host identifiers collects information through exec/file operations. failOnError - // option is propagated to the actual tasks to ensure that the don't break build if - // collection fail. - val userId: String = target.providers.of(ComputerId::class.java) { - it.parameters.failOnAnalyticsError.set(failOnAnalyticsError) - }.safeProvider().get() - val builderId: String = target.providers.of(BuilderId::class.java) { - it.parameters.failOnAnalyticsError.set(failOnAnalyticsError) - }.safeProvider().get() - - val languageVersion = - withDefaultOnError("Language version", UNKNOWN) { target.kotlinToolingVersion } - val hostOsType = withDefaultOnError("Host Os Type", UNKNOWN) { HOST_OS_NAME } - val hostOsVersion = withDefaultOnError( - "Host Os Version", - UNKNOWN - ) { target.providers.systemProperty("os.version").safeProvider().get() } - val hostCpuArch = withDefaultOnError("Host CPU Arch", UNKNOWN) { HOST_ARCH_NAME } - - spec.parameters.run { - this.appId.set(anonymizedBundleId) - this.userId.set(userId) - this.builderId.set(builderId) - this.hostOsType.set(hostOsType) - this.hostOsVersion.set(hostOsVersion) - this.hostCpuArch.set(hostCpuArch) - this.usesSync.set(usesSync) - this.languageVersion.set(languageVersion.toString()) - } - } - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { return kotlinCompilation.target.project.plugins.findPlugin(RealmCompilerSubplugin::class.java) != null } @@ -200,183 +56,6 @@ class RealmCompilerSubplugin : KotlinCompilerPluginSupportPlugin, AnalyticsError override fun applyToCompilation(kotlinCompilation: KotlinCompilation<*>): Provider> { val project = kotlinCompilation.target.project - // Compiler plugin options - val options = mutableListOf( - SubpluginOption(key = bundleIdKey, anonymizedBundleId), - ) - // Only bother collecting info if the analytics service is registered - analyticsServiceProvider?.let { provider -> - // Enable feature collection in compiler plugin by setting a path for the feature list - // file and pass it to the compiler plugin as a compiler plugin option - val featureListPath = listOf( - project.buildDir.path, - "outputs", - "realm-features", - kotlinCompilation.defaultSourceSet.name - ).joinToString(File.separator) - options.add(SubpluginOption(key = featureListPathKey, featureListPath)) - - // Gather target specific information - val targetInfo: TargetInfo? = muteErrors { - gatherTargetInfo(kotlinCompilation) - } - - // If we have something to submit register it for submission after the compilation has - // gathered the feature list information - targetInfo?.let { - kotlinCompilation.compileTaskProvider.get().doLast { - muteErrors { - val analyticsService = provider.get() - val json = analyticsService.toJson(targetInfo) - if (printAnalytics) { - analyticsService.print(json) - } - if (submitAnalytics) { - analyticsService.submit(json) - } - } - } - } - } - return project.provider { - options - } - } - - /** - * Wrapper that ignores error if `failOnAnalyticsError=true`. - */ - private fun muteErrors(block: () -> R): R? { - return try { - block() - } catch (e: Throwable) { - when { - failOnAnalyticsError -> { throw e } - else -> { null } - } - } + return project.provider { emptyList() } } } - -/** - * Wrapper to safely obtain provider for usage in configuration phases to support configuration - * cache across Gradle versions. - */ -private fun Provider.safeProvider(): Provider = this.let { - when { - gradleVersion < gradle70 -> { - @Suppress("DEPRECATION") - it.forUseAtConfigurationTime() - } - else -> it - } -} - -@Suppress("ComplexMethod", "NestedBlockDepth") -private fun gatherTargetInfo(kotlinCompilation: KotlinCompilation<*>): TargetInfo? { - val project = kotlinCompilation.target.project - return when (kotlinCompilation) { - // We don't send metrics for common targets but still collect features as the - // target specific features are a union of common and target specific features - is KotlinCommonCompilation, - is KotlinSharedNativeCompilation -> - null - - is KotlinJvmAndroidCompilation -> { - val androidExtension = - project.extensions.findByName("android") as BaseExtension? - val defaultConfig = androidExtension?.defaultConfig - val minSDK = defaultConfig?.minSdkVersion?.apiString - val targetSDK = defaultConfig?.targetSdkVersion?.apiString - val targetCpuArch: String = - defaultConfig?.ndk?.abiFilters?.singleOrNull()?.let { androidArch(it) } - ?: "Universal" - TargetInfo("Android", targetCpuArch, targetSDK, minSDK) - } - - is KotlinJvmCompilation -> { - val jvmTarget = kotlinCompilation.kotlinOptions.jvmTarget - TargetInfo("JVM", "Universal", jvmTarget, jvmTarget) - } - - is KotlinNativeCompilation -> { - // We currently only support Darwin targets, so assume that we can pull minSdk - // from the given deploymentTarget. Non-CocoaPod Xcode project have this in its - // pdxproj-file as OS_DEPLOYMENT_TARGET, but assuming that most people use - // CocoaPods as it is the default. Reevaluate if we see too many missing values. - val kotlinExtension: KotlinMultiplatformExtension = - project.extensions.getByType(KotlinMultiplatformExtension::class.java) - val cocoapodsExtension = - (kotlinExtension as ExtensionAware).extensions.findByName( - COCOAPODS_EXTENSION_NAME - ) as CocoapodsExtension? - val minSdk = cocoapodsExtension?.let { cocoapods -> - when (kotlinCompilation.konanTarget.family) { - Family.OSX -> cocoapods.osx.deploymentTarget - Family.IOS -> cocoapods.ios.deploymentTarget - Family.TVOS -> cocoapods.tvos.deploymentTarget - Family.WATCHOS -> cocoapods.watchos.deploymentTarget - Family.LINUX, - Family.MINGW, - Family.ANDROID -> null // Not supported yet - // TODO 1.9-DEPRECATION Revert to exhaustive branch strategy when leaving 1.9 support - // Remaining options are removed in Kotlin 2, so cannot reference them but need - // an else clause to be exhaustive - // Family.WASM, - // Family.ZEPHYR, - else -> null - } - } - TargetInfo( - nativeTarget(kotlinCompilation.konanTarget), - nativeArch(kotlinCompilation.konanTarget), - null, - minSdk - ) - } - // Not supported yet so don't try to gather target information - is KotlinJsCompilation, - is KotlinWithJavaCompilation<*, *> -> null - - else -> { - null - } - } -} - -// Helper method to ensure that we align target type string for native builds -fun nativeTarget(target: KonanTarget) = when (target.family) { - Family.OSX -> "macOS" - Family.IOS -> "iOS" - Family.TVOS -> "tvOS" - Family.WATCHOS -> "watchOS" - Family.LINUX -> "Linux" - Family.MINGW -> "MinGW" - Family.ANDROID -> "Android(native)" - else -> unknown(target.family.name) -} - -// Helper method to ensure that we align architecture strings for Kotlin native builds -fun nativeArch(target: KonanTarget): String = try { - when (target.architecture) { - Architecture.X64 -> io.realm.kotlin.gradle.analytics.Architecture.X64.serializedName - Architecture.X86 -> io.realm.kotlin.gradle.analytics.Architecture.X86.serializedName - Architecture.ARM64 -> io.realm.kotlin.gradle.analytics.Architecture.ARM64.serializedName - Architecture.ARM32 -> io.realm.kotlin.gradle.analytics.Architecture.ARM.serializedName - else -> unknown(target.architecture.name) - } -} catch (e: Throwable) { - unknown(target.architecture.name) -} - -// Helper method to ensure that we align architecture strings for Android platforms -fun androidArch(target: String): String = when (target) { - "armeabi-v7a" -> io.realm.kotlin.gradle.analytics.Architecture.ARM.serializedName - "arm64-v8a" -> io.realm.kotlin.gradle.analytics.Architecture.ARM64.serializedName - "x86" -> io.realm.kotlin.gradle.analytics.Architecture.X86.serializedName - "x86_64" -> io.realm.kotlin.gradle.analytics.Architecture.X64.serializedName - else -> unknown(target) -} - -fun Provider.getBoolean(): Boolean = - this.safeProvider().getOrElse("false").equals("true", ignoreCase = true) diff --git a/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/analytics/AnalyticsService.kt b/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/analytics/AnalyticsService.kt deleted file mode 100644 index 644e5d9e8c..0000000000 --- a/packages/gradle-plugin/src/main/kotlin/io/realm/kotlin/gradle/analytics/AnalyticsService.kt +++ /dev/null @@ -1,455 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.gradle.analytics - -import io.realm.kotlin.gradle.RealmCompilerSubplugin -import io.realm.kotlin.gradle.analytics.AnalyticsService.Companion.unknown -import io.realm.kotlin.gradle.gradle75 -import io.realm.kotlin.gradle.gradleVersion -import org.gradle.api.logging.Logger -import org.gradle.api.logging.Logging -import org.gradle.api.provider.Property -import org.gradle.api.provider.ValueSource -import org.gradle.api.provider.ValueSourceParameters -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters -import org.gradle.process.ExecOperations -import org.jetbrains.kotlin.com.google.gson.GsonBuilder -import org.jetbrains.kotlin.com.google.gson.JsonObject -import org.jetbrains.kotlin.com.google.gson.JsonPrimitive -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.UnsupportedEncodingException -import java.net.HttpURLConnection -import java.net.NetworkInterface -import java.net.URL -import java.nio.charset.Charset -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.util.Scanner -import javax.inject.Inject -import javax.xml.bind.DatatypeConverter -import kotlin.experimental.and - -/** - * Analytics Build Service holding cross-target project specific info and methods for dispatching - * analytics. - * Build Services are only marked stable from Gradle 7.4, so additional logging has been added - * to this class to catch catches where types are different than expected. We do _NOT_ - * want analytics to take down a users build, so exceptions are avoided on purpose. - * - * Build Services was added in Gradle 6.1. They can be called by multiple tasks, so must - * be implemented to be thread-safe - * - * **See:** [Build Services](https://docs.gradle.org/current/userguide/build_services.html) - */ - -// Asynchronously submits build information to Realm when the gradle compile task run -// -// To be clear: this does *not* run when your app is in production or on -// your end-user's devices; it will only run when you build your app from source. -// -// Why are we doing this? Because it helps us build a better product for you. -// None of the data personally identifies you, your employer or your app, but it -// *will* help us understand what Realm version you use, what host OS you use, -// etc. Having this info will help with prioritizing our time, adding new -// features and deprecating old features. Collecting an anonymized bundle & -// anonymized MAC is the only way for us to count actual usage of the other -// metrics accurately. If we don't have a way to deduplicate the info reported, -// it will be useless, as a single developer building their app on Windows ten -// times would report 10 times more than a single developer that only builds -// once from Mac OS X, making the data all but useless. No one likes sharing -// data unless it's necessary, we get it, and we've debated adding this for a -// long long time. Since Realm is a free product without an email signup, we -// feel this is a necessary step so we can collect relevant data to build a -// better product for you. -// -// Currently the following information is reported: -// - What version of Realm is being used -// - What host you are running on -// - What targets you are building for -// - An anonymized MAC address and bundle ID to aggregate the other information on. -// -// The collected information will be printed as info messages to the Gradle logger named -// [realm-analytics] if settings the system environment variable -// REALM_PRINT_ANALYTICS=true -// Collection and submission of data can be fully disabled by setting the system environment variable -// REALM_DISABLE_ANALYTICS=true - -private const val TOKEN = "ce0fac19508f6c8f20066d345d360fd0" -private const val EVENT_NAME = "Run" -private const val URL_PREFIX = "https://data.mongodb-api.com/app/realmsdkmetrics-zmhtm/endpoint/metric_webhook/metric?data=" - -// Container for the project specific details, thus equal across all platforms. -interface ProjectConfiguration : BuildServiceParameters { - val appId: Property - val userId: Property - val builderId: Property - val hostOsType: Property - val hostOsVersion: Property - val hostCpuArch: Property - val usesSync: Property - val languageVersion: Property -} - -// Container object for target specific details that varies across compilation targets. -data class TargetInfo( - val targetOsType: String, - val targetCpuArch: String, - val targetOSVersion: String?, - val targetOSMinVersion: String?, -) - -abstract class AnalyticsService : BuildService { - - private val projectInfo = JsonObject() - - init { - val parameters = parameters - projectInfo.add("event", JsonPrimitive(EVENT_NAME)) - projectInfo.add( - "properties", - JsonObject().apply { - add("token", JsonPrimitive(TOKEN)) - add("distinct_id", JsonPrimitive(parameters.userId.get())) - add("builder_id", JsonPrimitive(parameters.builderId.get())) - add("Anonymized Bundle ID", JsonPrimitive(parameters.appId.get())) - add("Binding", JsonPrimitive("kotlin")) - add("Language", JsonPrimitive("kotlin")) - add("Host OS Type", JsonPrimitive(parameters.hostOsType.get())) - add("Host OS Version", JsonPrimitive(parameters.hostOsVersion.get())) - add("Host CPU Arch", JsonPrimitive(parameters.hostCpuArch.get())) - add("Realm Version", JsonPrimitive(RealmCompilerSubplugin.version)) - add("Core Version", JsonPrimitive(RealmCompilerSubplugin.coreVersion)) - add( - "Sync Enabled", - JsonPrimitive(if (parameters.usesSync.get()) "true" else "false") - ) - add("Language Version", JsonPrimitive(parameters.languageVersion.get())) - } - ) - } - - internal fun toJson( - targetInfo: TargetInfo? = null - ): String { - val targetSpecificJson = projectInfo.deepCopy() - val properties = targetSpecificJson.getAsJsonObject("properties") - targetInfo?.targetCpuArch?.let { properties.add("Target OS Arch", JsonPrimitive(it)) } - targetInfo?.targetOsType?.let { properties.add("Target OS Type", JsonPrimitive(it)) } - targetInfo?.targetOSMinVersion?.let { properties.add("Target OS Minimum Version", JsonPrimitive(it)) } - targetInfo?.targetOSVersion?.let { properties.add("Target OS Version", JsonPrimitive(it)) } - return GsonBuilder().create().toJson(targetSpecificJson) - } - - internal fun print(json: String) { - info("[realm-analytics] Payload: $json") - } - - @Suppress("TooGenericExceptionCaught") - internal fun submit(json: String) { - try { - debug("Submitting analytics payload: $json") - Thread { - try { - val url = URL(URL_PREFIX + base64Encode(json)) - val connection = url.openConnection() as HttpURLConnection - connection.requestMethod = "GET" - connection.connect() - debug("Analytics payload sent") - } catch (e: InterruptedException) { - debug("Submitting analytics was interrupted") - } catch (e: Throwable) { - debug("Error submitting analytics: ${e.message}") - } - }.apply { - isDaemon = true - }.start() - } catch (e: Exception) { - // Analytics failing for any reason should not crash the build - debug("Submitting analytics payload failed: $e") - } - } - - private fun debug(message: String) = LOGGER.debug(message) - private fun info(message: String) = LOGGER.info(message) - - companion object { - internal val LOGGER: Logger = Logging.getLogger("realm-analytics") - internal const val UNKNOWN = "Unknown" - internal fun unknown(message: String? = null) = "$UNKNOWN${message?.let { "($it)" } ?: ""}" - } -} - -interface AnalyticsErrorCatcher { - /** - * Property controlling whether an error happening in the code `block` of [withDefaultOnError] - * should be causing thrown or ignored and reported as the `default` value instead. - */ - val failOnAnalyticsError: Boolean - - /** - * Utility method to wrap property collection in a common pattern that either returns a default - * value or rethrows if collection gathering throws depending on the [failOnAnalyticsError] property. - */ - fun withDefaultOnError(name: String, default: T, block: () -> T): T = - when (failOnAnalyticsError) { - true -> block() - false -> try { - block() - } catch (e: Throwable) { - AnalyticsService.LOGGER.debug("Error collecting '$name': ${e.message}"); default - } - } -} - -/** - * [HostIdentifier] parameter to control if errors should trigger default values or propagate out - * and fail the build. - */ -interface HostIdentifierParameters : ValueSourceParameters { - val failOnAnalyticsError: Property -} - -/** - * Abstraction of shell execution to support Gradle configuration cache and hide Gradle version - * differentiation, especially https://github.com/gradle/gradle/issues/18213 - */ -interface Executor { - - val execOperations: ExecOperations - fun exec(args: List): String { - return when { - // Differentiate by gradle version as earlier version does not support ExecOperation - // https://github.com/gradle/gradle/issues/18213 - gradleVersion < gradle75 -> { - val runtime = Runtime.getRuntime() - val process = runtime.exec(args.toTypedArray()) - String(process.inputStream.readBytes()) - } - else -> { - val output = ByteArrayOutputStream() - execOperations.exec { - it.commandLine(args) - it.standardOutput = output - } - String(output.toByteArray(), Charset.defaultCharset()) - } - } - } -} - -/** - * Common abstraction of tasks that collects host identifiers through various exec/file operations. - */ -abstract class HostIdentifier : ValueSource, Executor, AnalyticsErrorCatcher { - - @get:Inject - abstract override val execOperations: ExecOperations - override val failOnAnalyticsError: Boolean - get() = parameters.failOnAnalyticsError.get() - - val identifier: String - get() { - return when (HOST_OS) { - Host.WINDOWS -> windowsIdentifier - Host.MACOS -> macOsIdentifier - Host.LINUX -> linuxIdentifier - else -> throw IllegalStateException("Unknown host identifier") - } - } - abstract val linuxIdentifier: String - abstract val macOsIdentifier: String - abstract val windowsIdentifier: String -} - -/** - * Provider of a unique identifier for a computer. The method being used depends on the platform: - * - OS X: Mac address of en0 - * - Windows: BIOS identifier - * - Linux: Machine ID provided by the OS - */ -abstract class ComputerId : HostIdentifier() { - override val linuxIdentifier: String - get() { - var machineId = File("/var/lib/dbus/machine-id") - if (!machineId.exists()) { - machineId = File("/etc/machine-id") - } - if (!machineId.exists()) { - throw IllegalStateException("Cannot locate machine identifier in ${machineId.absolutePath}") - } - var scanner: Scanner? = null - return try { - scanner = Scanner(machineId) - val id = scanner.useDelimiter("\\A").next() - id - } finally { - scanner?.close() - } - } - - override val macOsIdentifier: String - get() { - val networkInterface = NetworkInterface.getByName("en0") - val hardwareAddress = networkInterface.hardwareAddress - return String(hardwareAddress, Charset.defaultCharset()) - } - - override val windowsIdentifier: String - get() { - val output = exec(listOf("wmic", "csproduct", "get", "UUID")) - val sc = Scanner(output) - var result: String? = null - while (sc.hasNext()) { - val next = sc.next() - if (next.contains("UUID")) { - result = sc.next().trim { it <= ' ' } - break - } - } - return result!! - } - - override fun obtain(): String? = withDefaultOnError("ComputerId", unknown()) { - hexStringify(sha256Hash(identifier.toByteArray())) - } -} - -/** - * Provider of a unique builder identifier for a computer. - * - * Successor of [ComputerId] standardized across SDKs. - */ -abstract class BuilderId : HostIdentifier() { - - override val linuxIdentifier: String - get() { - return File("/etc/machine-id").inputStream().readBytes().toString().trim() - } - - override val macOsIdentifier: String - get() { - val output = exec(listOf("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")) - val regEx = ".*\"IOPlatformUUID\"\\s=\\s\"(.+)\"".toRegex() - val find: MatchResult? = regEx.find(output) - return find?.groups?.get(1)?.value!! - } - - override val windowsIdentifier: String - get() { - val output = exec(listOf("reg", "QUERY", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid")) - // Manually expanded [:alnum:] as ([[:alnum:]-]+) didn't seems to work - // Output from Windows will be something like `MachineGuid REG_SZ 1c197ec7-adbd-4c3a-8386-306c20e0f686` - val regEx = "\\s*MachineGuid\\s*\\w*\\s*([A-Za-z0-9-]+)".toRegex() - val find: MatchResult? = regEx.find(output) - return find?.groups?.get(1)?.value!! - } - - override fun obtain(): String = withDefaultOnError("BuilderID", unknown()) { - val id = identifier - val data = "Realm is great$id" - base64Encode(sha256Hash(data.toByteArray()))!! - } -} - -/** - * Encode the given string with Base64 - * @param data the string to encode - * @return the encoded string - * @throws UnsupportedEncodingException - */ -@Throws(UnsupportedEncodingException::class) -internal fun base64Encode(data: String): String? { - return base64Encode(data.toByteArray(charset("UTF-8"))) -} - -internal fun base64Encode(data: ByteArray): String? { - return DatatypeConverter.printBase64Binary(data) -} - -/** - * Compute the SHA-256 hash of the given byte array - * @param data the byte array to hash - * @return the hashed byte array - * @throws NoSuchAlgorithmException - */ -@Throws(NoSuchAlgorithmException::class) -internal fun sha256Hash(data: ByteArray?): ByteArray { - val messageDigest = MessageDigest.getInstance("SHA-256") - return messageDigest.digest(data) -} - -/** - * Convert a byte array to its hex-string - * @param data the byte array to convert - * @return the hex-string of the byte array - */ -@Suppress("MagicNumber") -internal fun hexStringify(data: ByteArray): String { - val stringBuilder = java.lang.StringBuilder() - for (singleByte: Byte in data) { - stringBuilder.append(((singleByte and 0xff.toByte()) + 0x100).toString(16).substring(1)) - } - return stringBuilder.toString() -} - -enum class Host(val serializedName: String) { - WINDOWS("Windows"), LINUX("Linux"), MACOS("macOs"); -} - -/** - * Define which Host OS the build is running on. - */ -val HOST_OS: Host - get() { - val hostOs = System.getProperty("os.name") - return when { - hostOs.contains("windows", ignoreCase = true) -> Host.WINDOWS - hostOs.contains("inux", ignoreCase = true) -> Host.LINUX - hostOs.contains("mac", ignoreCase = true) -> Host.MACOS - else -> throw IllegalArgumentException(hostOs) - } - } - -val HOST_OS_NAME: String - get() = try { - HOST_OS.serializedName - } catch (e: Throwable) { - unknown(System.getProperty("os.name")) - } - -enum class Architecture(val serializedName: String) { - X86("x86"), - X64("x64"), - ARM("Arm"), - ARM64("Arm64"), -} - -/** - * String that represents the architecture of the host the build is running on. - */ -val HOST_ARCH_NAME: String - get() = run { - val hostArch = System.getProperty("os.arch") - when { - hostArch.contains("x86") && hostArch.contains("64") -> Architecture.X64.serializedName - hostArch.contains("x86") -> Architecture.X64.serializedName - hostArch.contains("aarch") && hostArch.contains("64") -> Architecture.ARM64.serializedName - hostArch.contains("aarch") -> Architecture.ARM.serializedName - else -> unknown(hostArch) - } - } diff --git a/packages/gradle.properties b/packages/gradle.properties index 63e45d7e03..07cc3fe090 100644 --- a/packages/gradle.properties +++ b/packages/gradle.properties @@ -82,16 +82,4 @@ testRepository=build/m2-buildrepo/ # Must either be `debug` or `debugMinified` testBuildType=debug -# Properties controlling which test server to run sync tests against. Default is a local -# test server that has been started by calling `/tools/sync_test_server/start_local_server.sh` -syncTestUrl=http://localhost:9090 -syncTestAppNamePrefix=test-app -syncTestLoginEmail=unique_user@domain.com -syncTestLoginPassword=password - -# If the public/private apiKey is set, it will take precedence when logging into the Admin API that controls the app -# syncTestUrl=https://realm-qa.mongodb.com -# syncTestLoginPublicApiKey=replace-with-value -# synctestLoginPrivateApiKey=replace-with-value -# syncTestClusterName=replace-with-value android.disableAutomaticComponentCreation=true diff --git a/packages/jni-swig-stub/realm.i b/packages/jni-swig-stub/realm.i index 3e8d2af436..d47abfa26c 100644 --- a/packages/jni-swig-stub/realm.i +++ b/packages/jni-swig-stub/realm.i @@ -1,7 +1,5 @@ %module(directors="1") realmc -#define REALM_APP_SERVICES 1 - %{ #include "realm.h" #include @@ -123,19 +121,6 @@ protected void finalize() { }; } -// reuse void callback type as template for `realm_sync_wait_for_completion_func_t` -%apply (realm_app_void_completion_func_t callback, void* userdata, realm_free_userdata_func_t userdata_free) { -(realm_sync_wait_for_completion_func_t, void* userdata, realm_free_userdata_func_t) -}; -%typemap(in) (realm_sync_wait_for_completion_func_t, void* userdata, realm_free_userdata_func_t) { - auto jenv = get_env(true); - $1 = reinterpret_cast(transfer_completion_callback); - $2 = static_cast(jenv->NewGlobalRef($input)); - $3 = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; -} - // reuse void callback type as template for `realm_migration_func_t` function %apply (realm_app_void_completion_func_t callback, void* userdata, realm_free_userdata_func_t userdata_free) { (realm_migration_func_t, void* userdata, realm_free_userdata_func_t userdata_free) @@ -214,63 +199,6 @@ protected void finalize() { }; } -// reuse void callback type as template for `realm_async_open_task_completion_func_t` function -%apply (realm_app_void_completion_func_t callback, void* userdata, realm_free_userdata_func_t userdata_free) { -(realm_async_open_task_completion_func_t, void* userdata, realm_free_userdata_func_t userdata_free) -}; -%typemap(in) (realm_async_open_task_completion_func_t, void* userdata, realm_free_userdata_func_t userdata_free) { - auto jenv = get_env(true); - $1 = reinterpret_cast(realm_async_open_task_callback); - $2 = static_cast(jenv->NewGlobalRef($input)); - $3 = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; -} - - -// reuse void callback type as template for `realm_sync_connection_state_changed_func_t` function -%apply (realm_app_void_completion_func_t callback, void* userdata, realm_free_userdata_func_t userdata_free) { - (realm_sync_connection_state_changed_func_t, void* userdata, realm_free_userdata_func_t userdata_free) -}; -%typemap(in) (realm_sync_connection_state_changed_func_t, void* userdata, realm_free_userdata_func_t userdata_free) { - auto jenv = get_env(true); - $1 = reinterpret_cast(realm_sync_session_connection_state_change_callback); - $2 = static_cast(jenv->NewGlobalRef($input)); - $3 = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; -} - -// Core isn't strict about naming their callbacks, so sometimes SWIG cannot map correctly :/ -%typemap(jstype) (realm_sync_on_subscription_state_changed_t, void* userdata, realm_free_userdata_func_t userdata_free) "Object" ; -%typemap(jtype) (realm_sync_on_subscription_state_changed_t, void* userdata, realm_free_userdata_func_t userdata_free) "Object" ; -%typemap(javain) (realm_sync_on_subscription_state_changed_t, void* userdata, realm_free_userdata_func_t userdata_free) "$javainput"; -%typemap(jni) (realm_sync_on_subscription_state_changed_t, void* userdata, realm_free_userdata_func_t userdata_free) "jobject"; -%typemap(in) (realm_sync_on_subscription_state_changed_t, void* userdata, realm_free_userdata_func_t userdata_free) { - auto jenv = get_env(true); - $1 = reinterpret_cast(realm_subscriptionset_changed_callback); - $2 = static_cast(jenv->NewGlobalRef($input)); - $3 = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; -} - -// Thread Observer callback -%typemap(jstype) (realm_on_object_store_thread_callback_t on_thread_create, realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, void* user_data, realm_free_userdata_func_t free_userdata) "Object" ; -%typemap(jtype) (realm_on_object_store_thread_callback_t on_thread_create, realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, void* user_data, realm_free_userdata_func_t free_userdata) "Object" ; -%typemap(javain) (realm_on_object_store_thread_callback_t on_thread_create, realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, void* user_data, realm_free_userdata_func_t free_userdata) "$javainput"; -%typemap(jni) (realm_on_object_store_thread_callback_t on_thread_create, realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, void* user_data, realm_free_userdata_func_t free_userdata) "jobject"; -%typemap(in) (realm_on_object_store_thread_callback_t on_thread_create, realm_on_object_store_thread_callback_t on_thread_destroy, realm_on_object_store_error_callback_t on_error, void* user_data, realm_free_userdata_func_t free_userdata) { - auto jenv = get_env(true); - $1 = reinterpret_cast(realm_sync_thread_created); - $2 = reinterpret_cast(realm_sync_thread_destroyed); - $3 = reinterpret_cast(realm_sync_thread_error); - $4 = static_cast(jenv->NewGlobalRef($input)); - $5 = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; -} - // String handling typedef jstring realm_string_t; // Typemap used for passing realm_string_t into the C-API in situations where the string buffer @@ -303,15 +231,9 @@ typedef jstring realm_string_t; // Reuse above type maps on other pointers too %apply void* { realm_t*, realm_config_t*, realm_schema_t*, realm_object_t* , realm_query_t*, realm_results_t*, realm_notification_token_t*, realm_object_changes_t*, - realm_list_t*, realm_app_credentials_t*, realm_app_config_t*, realm_app_t*, - realm_sync_client_config_t*, realm_user_t*, realm_sync_config_t*, - realm_sync_session_t*, realm_http_completion_func_t, realm_http_transport_t*, - realm_collection_changes_t*, realm_callback_token_t*, - realm_flx_sync_subscription_t*, realm_flx_sync_subscription_set_t*, - realm_flx_sync_mutable_subscription_set_t*, realm_flx_sync_subscription_desc_t*, + realm_list_t*, realm_collection_changes_t*, realm_callback_token_t*, realm_set_t*, realm_async_open_task_t*, realm_dictionary_t*, - realm_sync_session_connection_state_notification_token_t*, - realm_dictionary_changes_t*, realm_scheduler_t*, realm_sync_socket_t*, + realm_dictionary_changes_t*, realm_scheduler_t*, realm_key_path_array_t* }; // For all functions returning a pointer or bool, check for null/false and throw an error if @@ -342,10 +264,6 @@ bool realm_object_is_valid(const realm_object_t*); jresult = (jboolean)result; } -%typemap(javaimports) realm_sync_socket_callback_result %{ -import static io.realm.kotlin.internal.interop.realm_errno_e.*; -%} - // Just showcasing a wrapping concept. Maybe we should just go with `long` (apply void* as above) //%typemap(jstype) realm_t* "LongPointerWrapper" //%typemap(javain) realm_t* "$javainput.ptr()" @@ -366,8 +284,6 @@ import static io.realm.kotlin.internal.interop.realm_errno_e.*; %array_functions(realm_index_range_t, indexRangeArray); %array_functions(realm_collection_move_t, collectionMoveArray); %array_functions(realm_query_arg_t, queryArgArray); -%array_functions(realm_user_identity_t, identityArray); -%array_functions(realm_app_user_apikey_t, apiKeyArray); // Workaround for updated Swig behavior with 4.2.0 // https://github.com/swig/swig/commit/ecaa052f3d319834a66aaa07047be3662e5e52e2#diff-cd2fcc891412baae0fc46479c0870cbdd18133d06d68dcd216be8a37ecf77b37R10 %clear size_t nelements, size_t index; @@ -395,9 +311,6 @@ SWIG_JavaArrayArgoutSchar(jenv, jarr$argnum, (signed char *)$1, $input); %} %typemap(freearg) uint8_t*; -// Reuse above typemap for passing uint8_t[64] parameter for realm_sync_client_config_set_metadata_encryption_key as Byte[] -%apply uint8_t* {uint8_t [64]}; - // Enable passing void** as long[] %apply int64_t[] {void **}; %typemap(in) void ** (jlong *jarr) %{ @@ -532,6 +445,10 @@ $result = SWIG_JavaArrayOutLonglong(jenv, (long long *)result, 2); %ignore "realm_set_add_notification_callback"; %ignore "realm_dictionary_add_notification_callback"; %ignore "realm_results_add_notification_callback"; +%ignore "realm_sync_socket_callback_result_e"; +%ignore "realm_sync_errno_connection_e"; +%ignore "realm_sync_errno_session_e"; +%ignore "realm_web_socket_errno_e"; // Swig doesn't understand __attribute__ so eliminate it #define __attribute__(x) diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp index bd4ad3a654..be545d2951 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.cpp @@ -372,194 +372,6 @@ realm_create_scheduler(jobject dispatchScheduler) { throw std::runtime_error("Null dispatchScheduler"); } -jobject convert_to_jvm_app_error(JNIEnv* env, const realm_app_error_t* error) { - static JavaMethod app_error_constructor(env, - JavaClassGlobalDef::app_error(), - "newInstance", - "(IIILjava/lang/String;Ljava/lang/String;)Lio/realm/kotlin/internal/interop/sync/AppError;", - true); - env->PushLocalFrame(3); - jint category = static_cast(error->categories); - jint code = static_cast(error->error); - jint httpCode = static_cast(error->http_status_code); - jstring message = to_jstring(env, error->message); - jstring serverLogs = to_jstring(env, error->link_to_server_logs); - - auto result = env->CallStaticObjectMethod(JavaClassGlobalDef::app_error(), - app_error_constructor, - category, - code, - httpCode, - message, - serverLogs); - jni_check_exception(env); - return env->PopLocalFrame(result); -} - -jobject core_connection_state(JNIEnv* env, realm_sync_connection_state_e state) { - static JavaMethod connection_state_constructor(env, - JavaClassGlobalDef::connection_state(), - "of", - "(I)Lio/realm/kotlin/internal/interop/sync/CoreConnectionState;", - true); - env->PushLocalFrame(1); - auto result = env->CallStaticObjectMethod(JavaClassGlobalDef::connection_state(), connection_state_constructor, jint(state)); - jni_check_exception(env); - return env->PopLocalFrame(result); -} - -void app_complete_void_callback(void *userdata, const realm_app_error_t *error) { - auto env = get_env(true); - static JavaMethod java_notify_onerror(env, JavaClassGlobalDef::app_callback(), "onError", - "(Lio/realm/kotlin/internal/interop/sync/AppError;)V"); - static JavaMethod java_notify_onsuccess(env, JavaClassGlobalDef::app_callback(), "onSuccess", - "(Ljava/lang/Object;)V"); - static JavaClass unit_class(env, "kotlin/Unit"); - static JavaMethod unit_constructor(env, unit_class, "", "()V"); - - env->PushLocalFrame(1); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->PopLocalFrame(NULL); - throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); - } else if (error) { - jobject app_error = convert_to_jvm_app_error(env, error); - env->CallVoidMethod(static_cast(userdata), java_notify_onerror, app_error); - } else { - jobject unit = env->NewObject(unit_class, unit_constructor); - env->CallVoidMethod(static_cast(userdata), java_notify_onsuccess, unit); - } - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -void app_complete_result_callback(void* userdata, void* result, const realm_app_error_t* error) { - auto env = get_env(true); - static JavaMethod java_notify_onerror(env, JavaClassGlobalDef::app_callback(), "onError", - "(Lio/realm/kotlin/internal/interop/sync/AppError;)V"); - static JavaMethod java_notify_onsuccess(env, JavaClassGlobalDef::app_callback(), "onSuccess", - "(Ljava/lang/Object;)V"); - - static JavaClass native_pointer_class(env, "io/realm/kotlin/internal/interop/LongPointerWrapper"); - static JavaMethod native_pointer_constructor(env, native_pointer_class, "", "(JZ)V"); - - env->PushLocalFrame(1); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->PopLocalFrame(NULL); - throw std::runtime_error("An unexpected Error was thrown from Java. See LogCat"); - } else if (error) { - jobject app_exception = convert_to_jvm_app_error(env, error); - env->CallVoidMethod(static_cast(userdata), java_notify_onerror, app_exception); - } else { - // Remember to clone user object or else it will be invalidated right after we leave this callback - void* cloned_result = realm_clone(result); - jobject pointer = env->NewObject(native_pointer_class, native_pointer_constructor, - reinterpret_cast(cloned_result), false); - env->CallVoidMethod(static_cast(userdata), java_notify_onsuccess, pointer); - } - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -jobject create_api_key_wrapper(JNIEnv* env, const realm_app_user_apikey_t* key_data) { - static JavaClass api_key_wrapper_class(env, "io/realm/kotlin/internal/interop/sync/ApiKeyWrapper"); - static JavaMethod api_key_wrapper_constructor(env, api_key_wrapper_class, "", "([BLjava/lang/String;Ljava/lang/String;Z)V"); - auto id_size = sizeof(key_data->id.bytes); - jbyteArray id = env->NewByteArray(id_size); - env->SetByteArrayRegion(id, 0, id_size, reinterpret_cast(key_data->id.bytes)); - jstring key = to_jstring(env, key_data->key); - jstring name = to_jstring(env, key_data->name); - jboolean disabled = key_data->disabled; - auto result = env->NewObject(api_key_wrapper_class, - api_key_wrapper_constructor, - id, - key, - name, - disabled, - false); - return result; -} - -void app_apikey_callback(realm_userdata_t userdata, realm_app_user_apikey_t* apikey, const realm_app_error_t* error) { - auto env = get_env(true); - static JavaMethod java_notify_onerror(env, JavaClassGlobalDef::app_callback(), "onError", - "(Lio/realm/kotlin/internal/interop/sync/AppError;)V"); - static JavaMethod java_notify_onsuccess(env, JavaClassGlobalDef::app_callback(), "onSuccess", - "(Ljava/lang/Object;)V"); - env->PushLocalFrame(1); - if (error) { - jobject app_exception = convert_to_jvm_app_error(env, error); - env->CallVoidMethod(static_cast(userdata), java_notify_onerror, app_exception); - } else { - jobject api_key_wrapper_obj = create_api_key_wrapper(env, apikey); - env->CallVoidMethod(static_cast(userdata), java_notify_onsuccess, api_key_wrapper_obj); - } - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -void app_string_callback(realm_userdata_t userdata, const char *serialized_ejson_response, - const realm_app_error_t *error) { - auto env = get_env(true); - static JavaMethod java_notify_onerror( - env, - JavaClassGlobalDef::app_callback(), - "onError", - - "(Lio/realm/kotlin/internal/interop/sync/AppError;)V" - ); - static JavaMethod java_notify_onsuccess( - env, - JavaClassGlobalDef::app_callback(), - "onSuccess", - "(Ljava/lang/Object;)V" - ); - - env->PushLocalFrame(1); - if (error) { - jobject app_exception = convert_to_jvm_app_error(env, error); - env->CallVoidMethod(static_cast(userdata), java_notify_onerror, app_exception); - } else { - jstring jserialized_ejson_response = to_jstring(env, serialized_ejson_response); - env->CallVoidMethod(static_cast(userdata), java_notify_onsuccess, jserialized_ejson_response); - } - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -void app_apikey_list_callback(realm_userdata_t userdata, realm_app_user_apikey_t* keys, size_t count, realm_app_error_t* error) { - auto env = get_env(true); - static JavaClass api_key_wrapper_class(env, "io/realm/kotlin/internal/interop/sync/ApiKeyWrapper"); - - static JavaMethod java_notify_onerror(env, JavaClassGlobalDef::app_callback(), "onError", - "(Lio/realm/kotlin/internal/interop/sync/AppError;)V"); - static JavaMethod java_notify_onsuccess(env, JavaClassGlobalDef::app_callback(), "onSuccess", - "(Ljava/lang/Object;)V"); - - env->PushLocalFrame(1); - if (error) { - jobject app_exception = convert_to_jvm_app_error(env, error); - env->CallVoidMethod(static_cast(userdata), java_notify_onerror, app_exception); - } else { - // Create Object[] array - jobjectArray key_array = env->NewObjectArray(count, api_key_wrapper_class, nullptr); - - // For each ApiKey, create the Kotlin Wrapper and insert into array - for (int i = 0; i < count; i++) { - realm_app_user_apikey_t api_key = keys[i]; - jobject api_key_wrapper_obj = create_api_key_wrapper(env, &api_key); - env->SetObjectArrayElement(key_array, i, api_key_wrapper_obj); - env->DeleteLocalRef(api_key_wrapper_obj); - } - - // Return Object[] to Kotlin - env->CallVoidMethod(static_cast(userdata), java_notify_onsuccess, key_array); - } - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - bool realm_should_compact_callback(void* userdata, uint64_t total_bytes, uint64_t used_bytes) { auto env = get_env(true); static JavaClass java_should_compact_class(env, "io/realm/kotlin/internal/interop/CompactOnLaunchCallback"); @@ -580,396 +392,6 @@ bool realm_data_initialization_callback(void* userdata, realm_t*) { return jni_check_exception_for_callback(env); } -static void send_request_via_jvm_transport(JNIEnv *jenv, jobject network_transport, const realm_http_request_t request, jobject j_response_callback) { - static JavaMethod m_send_request_method(jenv, - JavaClassGlobalDef::network_transport_class(), - "sendRequest", - "(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/lang/String;Lio/realm/kotlin/internal/interop/sync/ResponseCallback;)V"); - - // Prepare request fields to be consumable by JVM - std::string method; - switch (request.method) { - case realm_http_request_method::RLM_HTTP_REQUEST_METHOD_GET: - method = "get"; - break; - case realm_http_request_method::RLM_HTTP_REQUEST_METHOD_POST: - method = "post"; - break; - case realm_http_request_method::RLM_HTTP_REQUEST_METHOD_PATCH: - method = "patch"; - break; - case realm_http_request_method::RLM_HTTP_REQUEST_METHOD_PUT: - method = "put"; - break; - case realm_http_request_method::RLM_HTTP_REQUEST_METHOD_DELETE: - method = "delete"; - break; - } - - static JavaMethod init(jenv, - JavaClassGlobalDef::java_util_hashmap(), - "", - "(I)V"); - - static JavaMethod put_method(jenv, - JavaClassGlobalDef::java_util_hashmap(), - "put", - "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - - size_t map_size = request.num_headers; - push_local_frame(jenv, 1); - jobject request_headers = jenv->NewObject(JavaClassGlobalDef::java_util_hashmap(), init, (jsize) map_size); - for (int i = 0; i < map_size; i++) { - push_local_frame(jenv, 2); - - realm_http_header_t header_pair = request.headers[i]; - - jstring key = to_jstring(jenv, header_pair.name); - jstring value = to_jstring(jenv, header_pair.value); - - jenv->CallObjectMethod(request_headers, put_method, key, value); - jni_check_exception(jenv); - jenv->PopLocalFrame(NULL); - } - - // Send request - jenv->CallVoidMethod(network_transport, - m_send_request_method, - to_jstring(jenv, method), - to_jstring(jenv, request.url), - request_headers, - to_jstring(jenv, request.body), - j_response_callback - ); - jni_check_exception(jenv); - jenv->PopLocalFrame(NULL); -} - -void complete_http_request(void* request_context, jobject j_response) { - auto jenv = get_env(false); // will always be attached - static JavaMethod get_http_code_method(jenv, - JavaClassGlobalDef::network_transport_response_class(), - "getHttpResponseCode", - "()I"); - static JavaMethod get_custom_code_method(jenv, - JavaClassGlobalDef::network_transport_response_class(), - "getCustomResponseCode", - "()I"); - static JavaMethod get_headers_method(jenv, - JavaClassGlobalDef::network_transport_response_class(), - "getJNIFriendlyHeaders", - "()[Ljava/lang/String;"); - static JavaMethod get_body_method(jenv, - JavaClassGlobalDef::network_transport_response_class(), - "getBody", "()Ljava/lang/String;"); - - // Extract JVM response fields - jint http_code = jenv->CallIntMethod(j_response, get_http_code_method); - jint custom_code = jenv->CallIntMethod(j_response, get_custom_code_method); - JStringAccessor java_body(jenv, (jstring) jenv->CallObjectMethod(j_response, get_body_method), true); - std::string body = java_body; - - JObjectArrayAccessor java_headers(jenv, static_cast(jenv->CallObjectMethod( - j_response, get_headers_method))); - - auto stacked_headers = std::vector(); // Pins headers to function stack - for (int i = 0; i < java_headers.size(); i = i + 2) { - JStringAccessor key = java_headers[i]; - JStringAccessor value = java_headers[i + 1]; - stacked_headers.push_back(std::move(key)); - stacked_headers.push_back(std::move(value)); - } - auto response_headers = std::vector(); - for (int i = 0; i < java_headers.size(); i = i + 2) { - // FIXME REFACTOR when C++20 will be available - realm_http_header header; - header.name = stacked_headers[i].c_str(); - header.value = stacked_headers[i + 1].c_str(); - response_headers.push_back(header); - } - - realm_http_response response; - response.status_code = http_code; - response.custom_status_code = custom_code; - response.headers = response_headers.data(); - response.num_headers = response_headers.size(); - response.body = body.c_str(); - response.body_size = body.size(); - - realm_http_transport_complete_request(request_context, &response); -} - -/** - * Perform a network request on JVM - * - * 1. Cast userdata to the network transport - * 2. Transform core request to JVM request - * 3. Perform request - * 4. Transform JVM response to core response - */ -static void network_request_lambda_function(void* userdata, - const realm_http_request_t request, - void* request_context) { - auto jenv = get_env(true); - - // Initialize pointer to JVM class and methods - jobject network_transport = static_cast(userdata); - - try { - jclass response_callback_class = JavaClassGlobalDef::app_response_callback(); - static jmethodID response_callback_constructor = jenv->GetMethodID(response_callback_class, - "", - "(Lio/realm/kotlin/internal/interop/sync/NetworkTransport;J)V"); - push_local_frame(jenv, 1); - jobject response_callback = jenv->NewObject(response_callback_class, - response_callback_constructor, - reinterpret_cast(userdata), - reinterpret_cast(request_context)); - - send_request_via_jvm_transport(jenv, network_transport, request, response_callback); - jenv->PopLocalFrame(NULL); - } catch (std::runtime_error &e) { - // Runtime exception while processing the request/response - realm_http_response_t response_error; - // FIXME: validate we propagate the custom codes as an actual exception to the user - // see: https://github.com/realm/realm-kotlin/issues/451 - response_error.custom_status_code = -4; - response_error.num_headers = 0; - response_error.body_size = 0; - - realm_http_transport_complete_request(request_context, &response_error); - } -} - -realm_http_transport_t* realm_network_transport_new(jobject network_transport) { - auto jenv = get_env(false); // Always called from JVM - return realm_http_transport_new(&network_request_lambda_function, - jenv->NewGlobalRef(network_transport), // userdata is the transport object - [](void* userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }); -} - -// *** BEGIN - WebSocket Client (Platform Networking) *** // - -using WebsocketFunctionHandlerCallback = std::function; - -static void websocket_post_func(realm_userdata_t userdata, - realm_sync_socket_post_callback_t* realm_callback) { - // Some calls to 'post' happens from the external commit helper which is not necessarily attached yet to a JVM thread - auto jenv = get_env(true, true); // attach as daemon thread - - WebsocketFunctionHandlerCallback* lambda = new WebsocketFunctionHandlerCallback([realm_callback=std::move(realm_callback)](bool cancelled, int status, const char* reason) { - realm_sync_socket_post_complete(realm_callback, - cancelled ? realm_sync_socket_callback_result::RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED : realm_sync_socket_callback_result::RLM_ERR_SYNC_SOCKET_SUCCESS, - ""); - }); - jobject lambda_callback_pointer_wrapper = wrap_pointer(jenv,reinterpret_cast(lambda)); - - static JavaMethod post_method(jenv, JavaClassGlobalDef::sync_websocket_transport(), "post", - "(Lio/realm/kotlin/internal/interop/NativePointer;)V"); - jobject websocket_transport = static_cast(userdata); - jenv->CallVoidMethod(websocket_transport, post_method, lambda_callback_pointer_wrapper); - jni_check_exception(jenv); - - jenv->DeleteLocalRef(lambda_callback_pointer_wrapper); -} - -static realm_sync_socket_timer_t websocket_create_timer_func( - realm_userdata_t userdata, uint64_t delay_ms, - realm_sync_socket_timer_callback_t *realm_callback) { - // called from main thread/event loop which should be already attached to JVM - auto jenv = get_env(false); - - WebsocketFunctionHandlerCallback *lambda = new WebsocketFunctionHandlerCallback( - [realm_callback = std::move(realm_callback)](bool cancel, int status, - const char *reason) { - if (cancel) { - realm_sync_socket_timer_canceled(realm_callback); - } else { - realm_sync_socket_timer_complete(realm_callback, - realm_sync_socket_callback_result::RLM_ERR_SYNC_SOCKET_SUCCESS, - ""); - } - }); - jobject lambda_callback_pointer_wrapper = wrap_pointer(jenv,reinterpret_cast(lambda)); - - static JavaMethod create_timer_method (jenv, JavaClassGlobalDef::sync_websocket_transport(), "createTimer", - "(JLio/realm/kotlin/internal/interop/NativePointer;)Lio/realm/kotlin/internal/interop/sync/CancellableTimer;"); - jobject websocket_transport = static_cast(userdata); - jobject cancellable_timer = jenv->CallObjectMethod(websocket_transport, create_timer_method, jlong(delay_ms), lambda_callback_pointer_wrapper); - jni_check_exception(jenv); - - jenv->DeleteLocalRef(lambda_callback_pointer_wrapper); - return reinterpret_cast(jenv->NewGlobalRef(cancellable_timer)); -} - -static void websocket_cancel_timer_func(realm_userdata_t userdata, - realm_sync_socket_timer_t timer_userdata) { - if (timer_userdata != nullptr) { - auto jenv = get_env(false); - jobject cancellable_timer = static_cast(timer_userdata); - - static JavaClass cancellable_timer_class(jenv, "io/realm/kotlin/internal/interop/sync/CancellableTimer"); - static JavaMethod cancel_method(jenv, cancellable_timer_class, "cancel", "()V"); - jenv->CallVoidMethod(cancellable_timer, cancel_method); - jni_check_exception(jenv); - - jenv->DeleteGlobalRef(cancellable_timer); - } -} - -static realm_sync_socket_websocket_t websocket_connect_func( - realm_userdata_t userdata, realm_websocket_endpoint_t endpoint, - realm_websocket_observer_t* realm_websocket_observer) { - - auto jenv = get_env(false); - - jobject observer_pointer = wrap_pointer(jenv,reinterpret_cast(realm_websocket_observer)); - - static JavaClass websocket_observer_class(jenv, "io/realm/kotlin/internal/interop/sync/WebSocketObserver"); - static JavaMethod websocket_observer_constructor(jenv, websocket_observer_class, "", - "(Lio/realm/kotlin/internal/interop/NativePointer;)V"); - jobject websocket_observer = jenv->NewObject(websocket_observer_class, websocket_observer_constructor, observer_pointer); - - static JavaMethod connect_method(jenv, JavaClassGlobalDef::sync_websocket_transport(), "connect", - "(Lio/realm/kotlin/internal/interop/sync/WebSocketObserver;Ljava/lang/String;Ljava/lang/String;JZJLjava/lang/String;)Lio/realm/kotlin/internal/interop/sync/WebSocketClient;"); - jobject websocket_transport = static_cast(userdata); - - std::ostringstream supported_protocol; - for (size_t i = 0; i < endpoint.num_protocols; ++i) { - supported_protocol << endpoint.protocols[i] << ", "; - } - - jobject websocket_client = jenv->CallObjectMethod(websocket_transport, connect_method, - websocket_observer, - to_jstring(jenv, endpoint.path), - to_jstring(jenv, endpoint.address), - jlong(endpoint.port), - endpoint.is_ssl, - jlong(endpoint.num_protocols), - to_jstring(jenv, supported_protocol.str().c_str())); - jni_check_exception(jenv); - - realm_sync_socket_websocket_t global_websocket_ref = reinterpret_cast(jenv->NewGlobalRef(websocket_client)); - - jenv->DeleteLocalRef(websocket_observer); - jenv->DeleteLocalRef(observer_pointer); - - return global_websocket_ref; -} - -static void websocket_async_write_func(realm_userdata_t userdata, - realm_sync_socket_websocket_t websocket_userdata, - const char* data, size_t size, - realm_sync_socket_write_callback_t* realm_callback) { - auto jenv = get_env(false); - - WebsocketFunctionHandlerCallback* lambda = new WebsocketFunctionHandlerCallback([realm_callback=std::move(realm_callback)](bool cancelled, int status, const char* reason) { - realm_sync_socket_write_complete(realm_callback, - cancelled ? realm_sync_socket_callback_result::RLM_ERR_SYNC_SOCKET_OPERATION_ABORTED: realm_sync_socket_callback_result::RLM_ERR_SYNC_SOCKET_SUCCESS, - ""); - }); - jobject lambda_callback_pointer_wrapper = wrap_pointer(jenv,reinterpret_cast(lambda)); - - static jmethodID write_method = jenv->GetMethodID(JavaClassGlobalDef::sync_websocket_transport(), "write", - "(Lio/realm/kotlin/internal/interop/sync/WebSocketClient;[BJLio/realm/kotlin/internal/interop/NativePointer;)V"); - jobject websocket_transport = static_cast(userdata); - - jbyteArray byteArray = jenv->NewByteArray(size); - jenv->SetByteArrayRegion(byteArray, 0, size, reinterpret_cast(data)); - - jenv->CallVoidMethod(websocket_transport, write_method, - static_cast(websocket_userdata), - byteArray, - jlong(size), - lambda_callback_pointer_wrapper); - jni_check_exception(jenv); - - jenv->DeleteLocalRef(byteArray); - jenv->DeleteLocalRef(lambda_callback_pointer_wrapper); - -} - -static void realm_sync_websocket_free(realm_userdata_t userdata, - realm_sync_socket_websocket_t websocket_userdata) { - if (websocket_userdata != nullptr) { - auto jenv = get_env(false); - static jmethodID close_method = jenv->GetMethodID(JavaClassGlobalDef::sync_websocket_client(), "close", "()V"); - - jobject websocket_client = static_cast(websocket_userdata); - jenv->CallVoidMethod(websocket_client, close_method); - jni_check_exception(jenv); - - jenv->DeleteGlobalRef(websocket_client); - } -} - -static void realm_sync_userdata_free(realm_userdata_t userdata) { - if (userdata != nullptr) { - auto jenv = get_env(false); - - static jmethodID close_method = jenv->GetMethodID(JavaClassGlobalDef::sync_websocket_transport(), "close", "()V"); - - jobject websocket_transport = static_cast(userdata); - jenv->CallVoidMethod(websocket_transport, close_method); - jni_check_exception(jenv); - - jenv->DeleteGlobalRef(websocket_transport); - } -} - -// This should run in the context of CoroutineScope -void realm_sync_websocket_callback_complete(bool cancelled, int64_t lambda_ptr, int status, const char* reason) { - WebsocketFunctionHandlerCallback* callback = reinterpret_cast(lambda_ptr); - (*callback)(cancelled, status, reason); - delete callback; -} - -void realm_sync_websocket_connected(int64_t observer_ptr, const char* protocol) { - realm_sync_socket_websocket_connected(reinterpret_cast(observer_ptr), protocol); -} - -void realm_sync_websocket_error(int64_t observer_ptr) { - realm_sync_socket_websocket_error(reinterpret_cast(observer_ptr)); -} - -bool realm_sync_websocket_message(int64_t observer_ptr, jbyteArray data, size_t size) { - auto jenv = get_env(false); - jbyte* byteData = jenv->GetByteArrayElements(data, NULL); - std::unique_ptr charData(new char[size]); // not null terminated (used in util::Span with size parameter) - std::memcpy(charData.get(), byteData, size); - bool close_websocket = !realm_sync_socket_websocket_message(reinterpret_cast(observer_ptr), charData.get(), size); - jenv->ReleaseByteArrayElements(data, byteData, JNI_ABORT); - return close_websocket; -} - -void realm_sync_websocket_closed(int64_t observer_ptr, bool was_clean, int error_code, const char* reason) { - realm_sync_socket_websocket_closed(reinterpret_cast(observer_ptr), was_clean, static_cast(error_code), reason); -} - -realm_sync_socket_t* realm_sync_websocket_new(int64_t sync_client_config_ptr, jobject websocket_transport) { - auto jenv = get_env(false); // Always called from JVM - realm_sync_socket_t* socket_provider = realm_sync_socket_new(jenv->NewGlobalRef(websocket_transport), /*userdata*/ - realm_sync_userdata_free/*userdata_free*/, - websocket_post_func/*post_func*/, - websocket_create_timer_func/*create_timer_func*/, - websocket_cancel_timer_func/*cancel_timer_func*/, - [](realm_userdata_t userdata, - realm_sync_socket_timer_t timer_userdata){ - }/*free_timer_func*/, - websocket_connect_func/*websocket_connect_func*/, - websocket_async_write_func/*websocket_write_func*/, - realm_sync_websocket_free/*websocket_free_func*/); - jni_check_exception(jenv); - - realm_sync_client_config_set_sync_socket(reinterpret_cast(sync_client_config_ptr)/*config*/, socket_provider); - realm_release(socket_provider); - return socket_provider; -} - -// *** END - WebSocket Client (Platform Networking) *** // - void set_log_callback(jobject log_callback) { auto jenv = get_env(false); realm_set_log_callback([](void *userdata, const char *category, realm_log_level_e level, const char *message) { @@ -1010,313 +432,6 @@ realm_set_log_callback([](void *userdata, const char *category, realm_log_level_ }); } -jobject convert_to_jvm_sync_error(JNIEnv* jenv, const realm_sync_error_t& error) { - - static JavaMethod sync_error_constructor(jenv, - JavaClassGlobalDef::sync_error(), - "", - "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZZ[Lio/realm/kotlin/internal/interop/sync/CoreCompensatingWriteInfo;Ljava/lang/Throwable;)V"); - - jint category = static_cast(error.status.categories); - jint value = static_cast(error.status.error); - jstring msg = to_jstring(jenv, error.status.message); - jstring joriginal_file_path = nullptr; - jstring jrecovery_file_path = nullptr; - jboolean is_fatal = error.is_fatal; - jboolean is_unrecognized_by_client = error.is_unrecognized_by_client; - jboolean is_client_reset_requested = error.is_client_reset_requested; - - auto user_info_map = std::map(); - for (int i = 0; i < error.user_info_length; i++) { - realm_sync_error_user_info_t user_info = error.user_info_map[i]; - user_info_map.insert(std::make_pair(user_info.key, user_info.value)); - } - - static JavaMethod core_compensating_write_info_constructor( - jenv, - JavaClassGlobalDef::core_compensating_write_info(), - "", - "(Ljava/lang/String;Ljava/lang/String;J)V" - ); - - push_local_frame(jenv, 3); - auto j_compensating_write_info_array = jenv->NewObjectArray( - error.compensating_writes_length, - JavaClassGlobalDef::core_compensating_write_info(), - NULL - ); - - for (int index = 0; index < error.compensating_writes_length; index++) { - realm_sync_error_compensating_write_info_t& compensating_write_info = error.compensating_writes[index]; - - push_local_frame(jenv, 3); - - auto reason = to_jstring(jenv, compensating_write_info.reason); - auto object_name = to_jstring(jenv, compensating_write_info.object_name); - - jobject j_compensating_write_info = jenv->NewObject( - JavaClassGlobalDef::core_compensating_write_info(), - core_compensating_write_info_constructor, - reason, - object_name, - &compensating_write_info.primary_key - ); - - jenv->SetObjectArrayElement( - j_compensating_write_info_array, - index, - j_compensating_write_info - ); - - jenv->PopLocalFrame(NULL); - } - - // We can't only rely on 'error.is_client_reset_requested' (even though we should) to extract - // user info from the error since 'PermissionDenied' are fatal (non-client-reset) errors that - // mark the file for deletion. Having 'original path' in the user_info_map is a side effect of - // using the same code for client reset. - if (error.user_info_length > 0) { - auto end_it = user_info_map.end(); - - auto original_it = user_info_map.find(error.c_original_file_path_key); - if (end_it != original_it) { - auto original_file_path = original_it->second; - joriginal_file_path = to_jstring(jenv, original_file_path); - } - - // Sync errors may not have the path to the recovery file unless a Client Reset is requested - auto recovery_it = user_info_map.find(error.c_recovery_file_path_key); - if (error.is_client_reset_requested && (end_it != recovery_it)) { - auto recovery_file_path = recovery_it->second; - jrecovery_file_path = to_jstring(jenv, recovery_file_path); - } - } - - jobject result = jenv->NewObject( - JavaClassGlobalDef::sync_error(), - sync_error_constructor, - category, - value, - msg, - joriginal_file_path, - jrecovery_file_path, - is_fatal, - is_unrecognized_by_client, - is_client_reset_requested, - j_compensating_write_info_array, - static_cast(error.user_code_error) - ); - - jni_check_exception(jenv); - if(error.user_code_error) { - jenv->DeleteGlobalRef(static_cast(error.user_code_error)); - } - return jenv->PopLocalFrame(result); -} - -void sync_set_error_handler(realm_sync_config_t* sync_config, jobject error_handler) { - realm_sync_config_set_error_handler(sync_config, - [](void* userdata, realm_sync_session_t* session, const realm_sync_error_t error) { - auto jenv = get_env(true); - auto sync_error_callback = static_cast(userdata); - static JavaMethod sync_error_method(jenv, - JavaClassGlobalDef::sync_error_callback(), - "onSyncError", - "(Lio/realm/kotlin/internal/interop/NativePointer;Lio/realm/kotlin/internal/interop/sync/SyncError;)V"); - - push_local_frame(jenv, 2); - - jobject session_pointer_wrapper = wrap_pointer(jenv,reinterpret_cast(session)); - jobject sync_error = convert_to_jvm_sync_error(jenv, error); - - jenv->CallVoidMethod(sync_error_callback, - sync_error_method, - session_pointer_wrapper, - sync_error); - jni_check_exception(jenv); - jenv->PopLocalFrame(NULL); - }, - static_cast(get_env()->NewGlobalRef(error_handler)), - [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }); -} - -void transfer_completion_callback(void* userdata, realm_error_t* error) { - auto env = get_env(true); - static JavaMethod java_success_callback_method(env, - JavaClassGlobalDef::sync_session_transfer_completion_callback(), - "onSuccess", - "()V"); - static JavaMethod java_error_callback_method(env, - JavaClassGlobalDef::sync_session_transfer_completion_callback(), - "onError", - "(IILjava/lang/String;)V"); - if (error) { - jint category = static_cast(error->categories); - jint value = error->error; - env->PushLocalFrame(1); - env->CallVoidMethod(static_cast(userdata), java_error_callback_method, category, value, to_jstring(env, error->message)); - jni_check_exception(env); - env->PopLocalFrame(NULL); - } else { - env->CallVoidMethod(static_cast(userdata), java_success_callback_method); - } - jni_check_exception(env); -} - -void realm_subscriptionset_changed_callback(void* userdata, realm_flx_sync_subscription_set_state_e state) { - auto env = get_env(true); - env->PushLocalFrame(1); - jobject state_value = JavaClassGlobalDef::new_int(env, static_cast(state)); - env->CallObjectMethod( - static_cast(userdata), - JavaClassGlobalDef::function1Method(env), - state_value - ); - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -void realm_async_open_task_callback(void* userdata, realm_thread_safe_reference_t* realm, const realm_async_error_t* error) { - auto env = get_env(true); - static JavaMethod java_invoke_method(env, - JavaClassGlobalDef::async_open_callback(), - "invoke", - "(Ljava/lang/Throwable;)V"); - jobject callback = static_cast(userdata); - - env->PushLocalFrame(1); - jobject exception = nullptr; - if (error) { - realm_error_t err; - realm_get_async_error(error, &err); - exception = create_java_exception(env, err); - } else { - realm_release(realm); - } - env->CallVoidMethod(callback, java_invoke_method, exception); - jni_check_exception(env); - env->PopLocalFrame(NULL); -} - -bool -before_client_reset(void* userdata, realm_t* before_realm) { - auto env = get_env(true); - static JavaMethod java_before_callback_function(env, - JavaClassGlobalDef::sync_before_client_reset(), - "onBeforeReset", - "(Lio/realm/kotlin/internal/interop/NativePointer;)V"); - env->PushLocalFrame(1); - jobject before_pointer = wrap_pointer(env, reinterpret_cast(before_realm), false); - env->CallVoidMethod(static_cast(userdata), java_before_callback_function, before_pointer); - bool result = jni_check_exception_for_callback(env); - env->PopLocalFrame(NULL); - return result; -} - -bool -after_client_reset(void* userdata, realm_t* before_realm, - realm_thread_safe_reference_t* after_realm, bool did_recover) { - auto env = get_env(true); - static JavaMethod java_after_callback_function(env, - JavaClassGlobalDef::sync_after_client_reset(), - "onAfterReset", - "(Lio/realm/kotlin/internal/interop/NativePointer;Lio/realm/kotlin/internal/interop/NativePointer;Z)V"); - env->PushLocalFrame(2); - jobject before_pointer = wrap_pointer(env, reinterpret_cast(before_realm), false); - // Reuse the scheduler from the beforeRealm, otherwise Core will attempt to recreate a new one, - // which will fail on platforms that hasn't defined a default scheduler factory. - realm_scheduler_t scheduler = realm_scheduler(before_realm->get()->scheduler()); - realm_t* after_realm_ptr = realm_from_thread_safe_reference(after_realm, &scheduler); - - jobject after_pointer = wrap_pointer(env, reinterpret_cast(after_realm_ptr), false); - env->CallVoidMethod(static_cast(userdata), java_after_callback_function, before_pointer, after_pointer, did_recover); - realm_close(after_realm_ptr); - bool result = jni_check_exception_for_callback(env); - env->PopLocalFrame(NULL); - return result; -} - -void -sync_before_client_reset_handler(realm_sync_config_t* config, jobject before_handler) { - // TODO use typemap patterns in realm.i - auto jenv = get_env(true); - auto before_func = reinterpret_cast(before_client_reset); - void* user_data = static_cast(jenv->NewGlobalRef(before_handler)); - realm_free_userdata_func_t free_func = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; - realm_sync_config_set_before_client_reset_handler(config, before_func, user_data, free_func); -} - -void -sync_after_client_reset_handler(realm_sync_config_t* config, jobject after_handler) { - // TODO use typemap patterns in realm.i - auto jenv = get_env(true); - auto after_func = reinterpret_cast(after_client_reset); - void* user_data = static_cast(jenv->NewGlobalRef(after_handler)); - realm_free_userdata_func_t free_func = [](void *userdata) { - get_env(true)->DeleteGlobalRef(static_cast(userdata)); - }; - realm_sync_config_set_after_client_reset_handler(config, after_func, user_data, free_func); -} - -void -realm_sync_session_progress_notifier_callback(void *userdata, uint64_t, uint64_t, double progress_estimate) { - auto env = get_env(true); - - static JavaMethod java_callback_method(env, JavaClassGlobalDef::progress_callback(), "onChange", "(D)V"); - - jni_check_exception(env); - env->CallVoidMethod(static_cast(userdata), java_callback_method, jdouble(progress_estimate)); - jni_check_exception(env); -} - -void -realm_sync_session_connection_state_change_callback(void *userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state) { - auto env = get_env(true); - - static JavaMethod java_callback_method(env, JavaClassGlobalDef::connection_state_change_callback(), "onChange", "(Lio/realm/kotlin/internal/interop/sync/CoreConnectionState;Lio/realm/kotlin/internal/interop/sync/CoreConnectionState;)V"); - - jobject jold_state = core_connection_state(env, old_state); - jobject jnew_state = core_connection_state(env, new_state); - - jni_check_exception(env); - env->CallVoidMethod(static_cast(userdata), java_callback_method, jold_state, jnew_state); - jni_check_exception(env); -} - -jlong -realm_sync_session_register_progress_notifier_wrapper( - realm_sync_session_t* session, realm_sync_progress_direction_e direction, bool is_streaming, jobject callback -) { - auto jenv = get_env(true); - jlong jresult = 0; - realm_sync_session_connection_state_notification_token_t *result = - realm_sync_session_register_progress_notifier( - session, - realm_sync_session_progress_notifier_callback, - direction, - is_streaming, - static_cast(jenv->NewGlobalRef( - callback)), - [](void *userdata) { - get_env(true)->DeleteGlobalRef( - static_cast(userdata)); - } - ); - if (!result) { - bool exception_thrown = throw_last_error_as_java_exception(jenv); - if (exception_thrown) { - // Return immediately if there was an error in which case the exception will be raised when returning to JVM - return 0; - } - } - *(realm_sync_session_connection_state_notification_token_t **)&jresult = result; - return jresult; -} - // Explicit clean up method for releasing heap allocated data of a realm_value_t instance void realm_value_t_cleanup(realm_value_t* value) { @@ -1336,43 +451,6 @@ realm_value_t_cleanup(realm_value_t* value) { } } -void -realm_sync_thread_created(realm_userdata_t userdata) { - // Attach the sync client thread to the JVM so errors can be returned properly - // Note, we need to hardcode the name as there is no good way to inject it from JVM as that itself - // would require access to the JNiEnv. - auto env = get_env(true, false, util::Optional("SyncThread")); - static JavaMethod java_callback_method(env, JavaClassGlobalDef::sync_thread_observer(), "onCreated", "()V"); - jni_check_exception(env); - env->CallVoidMethod(static_cast(userdata), java_callback_method); - jni_check_exception(env); -} - -void -realm_sync_thread_destroyed(realm_userdata_t userdata) { - auto env = get_env(true); - // Avoid touching any JNI methods if we have a pending exception - // otherwise we will crash with "JNI called with pending exception" instead of the real - // error. - if (env->ExceptionCheck() == JNI_FALSE) { - static JavaMethod java_callback_method(env, JavaClassGlobalDef::sync_thread_observer(), "onDestroyed", "()V"); - env->CallVoidMethod(static_cast(userdata), java_callback_method); - jni_check_exception(env); - } - // Detach from the Java thread associated with the Sync Client Thread, otherwise - // the JVM will not be able to shutdown. - detach_current_thread(); -} - -void -realm_sync_thread_error(realm_userdata_t userdata, const char* error) { - JNIEnv* env = get_env(true); - std::string msg = util::format("An exception has been thrown on the sync client thread:\n%1", error); - static JavaMethod java_callback_method(env, JavaClassGlobalDef::sync_thread_observer(), "onError", "(Ljava/lang/String;)V"); - env->CallVoidMethod(static_cast(userdata), java_callback_method, to_jstring(env, msg)); - jni_check_exception(env); -} - realm_scheduler_t* realm_create_generic_scheduler() { return new realm_scheduler_t { realm::util::Scheduler::make_dummy() }; diff --git a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h index a6bf096d4e..04603c8b0b 100644 --- a/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h +++ b/packages/jni-swig-stub/src/main/jni/realm_api_helpers.h @@ -47,9 +47,6 @@ register_notification_cb( int64_t key_path_array_ptr, jobject callback); -realm_http_transport_t* -realm_network_transport_new(jobject network_transport); - void set_log_callback(jobject log_callback); @@ -65,81 +62,10 @@ realm_data_initialization_callback(void* userdata, realm_t* realm); void invoke_core_notify_callback(int64_t core_notify_function); -void -app_complete_void_callback(void* userdata, const realm_app_error_t* error); - -void -app_complete_result_callback(void* userdata, void* result, const realm_app_error_t* error); - -void -sync_set_error_handler(realm_sync_config_t* sync_config, jobject error_handler); - -void -complete_http_request(void* request_context, jobject j_response); - -void -transfer_completion_callback(void* userdata, realm_error_t* error); - -void -realm_subscriptionset_changed_callback(void* userdata, - realm_flx_sync_subscription_set_state_e state); - -void -realm_async_open_task_callback(void* userdata, - realm_thread_safe_reference_t* realm, - const realm_async_error_t* error); - -void -realm_subscriptionset_changed_callback(void* userdata, - realm_flx_sync_subscription_set_state_e state); - -bool -before_client_reset(void* userdata, realm_t* before_realm); - -bool -after_client_reset(void* userdata, realm_t* before_realm, - realm_thread_safe_reference_t* after_realm, bool did_recover); - -void -sync_before_client_reset_handler(realm_sync_config_t* config, jobject before_handler); - -void -sync_after_client_reset_handler(realm_sync_config_t* config, jobject after_handler); - -void -realm_sync_session_progress_notifier_callback(void *userdata, uint64_t, uint64_t, double progress_estimate); - -void -realm_sync_session_connection_state_change_callback(void *userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state); - // Explicit clean up method for releasing heap allocated data of a realm_value_t instance void realm_value_t_cleanup(realm_value_t* value); -void -app_apikey_callback(realm_userdata_t userdata, realm_app_user_apikey_t*, const realm_app_error_t*); - -void -app_apikey_list_callback(realm_userdata_t userdata, realm_app_user_apikey_t[], size_t count, realm_app_error_t*); - -void -app_string_callback(realm_userdata_t userdata, const char* serialized_ejson_response, const realm_app_error_t*); - -jlong -realm_sync_session_register_progress_notifier_wrapper( - realm_sync_session_t* session, realm_sync_progress_direction_e direction, bool is_streaming, - jobject callback -); - -void -realm_sync_thread_created(realm_userdata_t userdata); - -void -realm_sync_thread_destroyed(realm_userdata_t userdata); - -void -realm_sync_thread_error(realm_userdata_t userdata, const char* error); - realm_scheduler_t* realm_create_generic_scheduler(); @@ -149,18 +75,6 @@ realm_property_info_t_cleanup(realm_property_info_t* value); void realm_class_info_t_cleanup(realm_class_info_t * value); -realm_sync_socket_t* realm_sync_websocket_new(int64_t sync_client_config_ptr, jobject websocket_transport); - -void realm_sync_websocket_callback_complete(bool cancelled, int64_t lambda_ptr, int status, const char* reason); - -void realm_sync_websocket_connected(int64_t observer_ptr, const char* protocol); - -void realm_sync_websocket_error(int64_t observer_ptr); - -bool realm_sync_websocket_message(int64_t observer_ptr, jbyteArray data, size_t size); - -void realm_sync_websocket_closed(int64_t observer_ptr, bool was_clean, int error_code, const char* reason); - jobjectArray realm_get_log_category_names(); #endif //TEST_REALM_API_HELPERS_H diff --git a/packages/library-base/proguard-rules-consumer-common.pro b/packages/library-base/proguard-rules-consumer-common.pro index aed375ecbb..22337cbb6d 100644 --- a/packages/library-base/proguard-rules-consumer-common.pro +++ b/packages/library-base/proguard-rules-consumer-common.pro @@ -35,51 +35,15 @@ -keep class io.realm.kotlin.internal.interop.JVMScheduler { *; } -# Interop, sync-specific classes --keep class io.realm.kotlin.internal.interop.sync.NetworkTransport { - # TODO OPTIMIZE Only keep actually required symbols - *; -} --keep class io.realm.kotlin.internal.interop.sync.Response { - # TODO OPTIMIZE Only keep actually required symbols - *; -} + -keep class io.realm.kotlin.internal.interop.LongPointerWrapper { # TODO OPTIMIZE Only keep actually required symbols *; } --keep class io.realm.kotlin.internal.interop.sync.AppError { - # TODO OPTIMIZE Only keep actually required symbols - *; -} --keep class io.realm.kotlin.internal.interop.sync.CoreConnectionState { - # TODO OPTIMIZE Only keep actually required symbols - *; -} --keep class io.realm.kotlin.internal.interop.sync.SyncError { - # TODO OPTIMIZE Only keep actually required symbols - *; -} -keep class io.realm.kotlin.internal.interop.LogCallback { # TODO OPTIMIZE Only keep actually required symbols *; } --keep class io.realm.kotlin.internal.interop.SyncErrorCallback { - # TODO OPTIMIZE Only keep actually required symbols - *; -} --keep class io.realm.kotlin.internal.interop.sync.JVMSyncSessionTransferCompletionCallback { - *; -} --keep class io.realm.kotlin.internal.interop.sync.ResponseCallback { - *; -} --keep class io.realm.kotlin.internal.interop.sync.ResponseCallbackImpl { - *; -} --keep class io.realm.kotlin.internal.interop.AppCallback { - *; -} -keep class io.realm.kotlin.internal.interop.CompactOnLaunchCallback { *; } @@ -89,36 +53,12 @@ -keep class io.realm.kotlin.internal.interop.DataInitializationCallback { *; } --keep class io.realm.kotlin.internal.interop.SubscriptionSetCallback { - *; -} --keep class io.realm.kotlin.internal.interop.SyncBeforeClientResetHandler { - *; -} --keep class io.realm.kotlin.internal.interop.SyncAfterClientResetHandler { - *; -} --keep class io.realm.kotlin.internal.interop.AsyncOpenCallback { - *; -} -keep class io.realm.kotlin.internal.interop.NativePointer { *; } --keep class io.realm.kotlin.internal.interop.ProgressCallback { - *; -} --keep class io.realm.kotlin.internal.interop.sync.ApiKeyWrapper { - *; -} --keep class io.realm.kotlin.internal.interop.ConnectionStateChangeCallback { - *; -} -keep class io.realm.kotlin.internal.interop.SyncThreadObserver { *; } --keep class io.realm.kotlin.internal.interop.sync.CoreCompensatingWriteInfo { - *; -} # Preserve Function methods as they back various functional interfaces called from JNI -keep class kotlin.jvm.functions.Function* { *; @@ -127,20 +67,6 @@ *; } -# Platform networking callback --keep class io.realm.kotlin.internal.interop.sync.WebSocketTransport { - *; -} --keep class io.realm.kotlin.internal.interop.sync.CancellableTimer { - *; -} --keep class io.realm.kotlin.internal.interop.sync.WebSocketClient { - *; -} --keep class io.realm.kotlin.internal.interop.sync.WebSocketObserver { - *; -} - # Un-comment for debugging #-printconfiguration /tmp/full-r8-config.txt #-keepattributes LineNumberTable,SourceFile diff --git a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt index 45291eeabd..cfd73e17da 100644 --- a/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt +++ b/packages/library-base/src/androidMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsAndroid.kt @@ -3,7 +3,6 @@ package io.realm.kotlin.internal.platform import android.os.Build import io.realm.kotlin.internal.RealmInitializer import io.realm.kotlin.internal.RealmInstantImpl -import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant @@ -12,19 +11,7 @@ import java.io.InputStream @Suppress("MayBeConst") // Cannot make expect/actual const -public actual val RUNTIME: SyncConnectionParams.Runtime = SyncConnectionParams.Runtime.ANDROID -public actual val RUNTIME_VERSION: String = android.os.Build.VERSION.SDK_INT.toString() -public actual val CPU_ARCH: String = - if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) { - @Suppress("DEPRECATION") - android.os.Build.CPU_ABI - } else { - android.os.Build.SUPPORTED_ABIS[0] - } public actual val OS_NAME: String = "Android" -public actual val OS_VERSION: String = android.os.Build.VERSION.RELEASE -public actual val DEVICE_MANUFACTURER: String = android.os.Build.MANUFACTURER -public actual val DEVICE_MODEL: String = android.os.Build.MODEL // Returns the root directory of the platform's App data public actual fun appFilesDirectory(): String = RealmInitializer.filesDir.absolutePath diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt index 6b2d3a234a..7c0240743c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/ConfigurationImpl.kt @@ -46,9 +46,8 @@ import io.realm.kotlin.migration.RealmMigration import io.realm.kotlin.types.BaseRealmObject import kotlin.reflect.KClass -// TODO Public due to being accessed from `library-sync` @Suppress("LongParameterList") -public open class ConfigurationImpl( +internal open class ConfigurationImpl( directory: String, name: String, schema: Set>, @@ -62,7 +61,6 @@ public open class ConfigurationImpl( private val userMigration: RealmMigration?, automaticBacklinkHandling: Boolean, initialDataCallback: InitialDataCallback?, - override val isFlexibleSyncConfiguration: Boolean, inMemory: Boolean, initialRealmFileConfiguration: InitialRealmFileConfiguration?, override val logger: ContextLogger diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalConfiguration.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalConfiguration.kt index 34c29c49bc..b3adaa280b 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalConfiguration.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalConfiguration.kt @@ -37,9 +37,6 @@ public interface InternalConfiguration : Configuration { public val schemaMode: SchemaMode public val logger: ContextLogger - // Temporary work-around for https://github.com/realm/realm-kotlin/issues/724 - public val isFlexibleSyncConfiguration: Boolean - /** * Creates a new native Config object based on all the settings in this configuration. * Each pointer should only be used to open _one_ realm. If you want to open multiple realms diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalMutableRealm.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalMutableRealm.kt index 5179aa419e..27cbf9a93f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalMutableRealm.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/InternalMutableRealm.kt @@ -19,7 +19,6 @@ import io.realm.kotlin.Deleteable import io.realm.kotlin.MutableRealm import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.ext.isValid -import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.types.BaseRealmObject import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.TypedRealmObject @@ -74,9 +73,7 @@ internal interface InternalMutableRealm : MutableRealm { } override fun deleteAll() { - schema().classes.filter { - it.kind != RealmClassKind.ASYMMETRIC - }.forEach { + schema().classes.forEach { val clazz: KClass? = realmReference.schemaMetadata[it.name]?.clazz if (clazz != null) { delete(clazz) diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt index 4ca59cb4e7..fb429059d2 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/RealmConfigurationImpl.kt @@ -63,7 +63,6 @@ internal class RealmConfigurationImpl( migration, automaticBacklinkHandling, initialDataCallback, - false, inMemory, initialRealmFileConfiguration, logger diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmImpl.kt index 6ebb8f128d..65e3e77c9f 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicMutableRealmImpl.kt @@ -34,7 +34,6 @@ import io.realm.kotlin.internal.schema.RealmSchemaImpl import io.realm.kotlin.internal.toRealmObject import io.realm.kotlin.query.RealmQuery import io.realm.kotlin.schema.RealmClass -import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.schema.RealmSchema // Public due to tests needing to access `close` and trying to make the class visible through @@ -59,7 +58,6 @@ public open class DynamicMutableRealmImpl( query: String, vararg args: Any? ): RealmQuery { - checkAsymmetric(className, "Queries on asymmetric objects are not allowed: $className") return ObjectQuery( realmReference, realmReference.schemaMetadata.getOrThrow(className).classKey, @@ -76,7 +74,6 @@ public open class DynamicMutableRealmImpl( obj: DynamicRealmObject, updatePolicy: UpdatePolicy ): DynamicMutableRealmObject { - checkAsymmetric(obj.type, "Asymmetric Realm objects can only be added using the `insert()` method.") return io.realm.kotlin.internal.copyToRealm(configuration.mediator, realmReference, obj, updatePolicy, mutableMapOf()) as DynamicMutableRealmObject } @@ -102,22 +99,13 @@ public open class DynamicMutableRealmImpl( } override fun delete(className: String) { - checkAsymmetric(className, "Asymmetric Realm objects cannot be deleted manually: $className") delete(query(className).find()) } - private fun checkAsymmetric(className: String, errorMessage: String) { - if (realmReference.owner.schema()[className]?.kind == RealmClassKind.ASYMMETRIC) { - throw IllegalArgumentException(errorMessage) - } - } - override fun deleteAll() { schema().let { schema: RealmSchema -> for (schemaClass: RealmClass in schema.classes) { - if (schema[schemaClass.name]?.kind != RealmClassKind.ASYMMETRIC) { - delete(schemaClass.name) - } + delete(schemaClass.name) } } } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicRealmImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicRealmImpl.kt index 61f1fe60e5..38361ae264 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicRealmImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/dynamic/DynamicRealmImpl.kt @@ -26,7 +26,6 @@ import io.realm.kotlin.internal.interop.FrozenRealmPointer import io.realm.kotlin.internal.query.ObjectQuery import io.realm.kotlin.internal.schema.RealmSchemaImpl import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.schema.RealmClassKind import io.realm.kotlin.schema.RealmSchema internal open class DynamicRealmImpl( @@ -41,9 +40,6 @@ internal open class DynamicRealmImpl( query: String, vararg args: Any? ): RealmQuery { - if (realmReference.owner.schema()[className]?.kind == RealmClassKind.ASYMMETRIC) { - throw IllegalArgumentException("Queries on asymmetric objects are not allowed: $className") - } return ObjectQuery( realmReference, realmReference.schemaMetadata.getOrThrow(className).classKey, diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 79322194e7..84aecc741c 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -1,58 +1,17 @@ @file:JvmName("SystemUtilsJvm") package io.realm.kotlin.internal.platform -import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant import kotlin.jvm.JvmName import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KType -// TODO All methods and properties in this file are public as they are used by both `library-sync` -// and tests. - -/** - * Runtime identifier. Either 'JVM', 'Android' or 'Native' - */ -public expect val RUNTIME: SyncConnectionParams.Runtime - -/** - * Version of the runtime. On Android this this the Android version, e.g. `33`. - * On JVM this is the JVM version, e.g. `11.0.15` - * On Native, the empty string is returned. - */ -public expect val RUNTIME_VERSION: String - -/** - * Which CPU architecture is the code running on. - */ -public expect val CPU_ARCH: String - /** * Operating system name. */ public expect val OS_NAME: String -/** - * Operating system version. - */ -public expect val OS_VERSION: String - -/** - * For mobile devices, this will be the manufacturer of the phone. - * - * On Desktop, this returns the empty string. - */ -public expect val DEVICE_MANUFACTURER: String - -/** - * For mobile devices, this will return the unique model number of - * the phone, e.g `GT-I9100` - * - * On Desktop, this returns the empty string.. - */ -public expect val DEVICE_MODEL: String - /** * Path separator. */ diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt index f6c91c7770..1c742f48c0 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/internal/schema/RealmClassImpl.kt @@ -42,7 +42,6 @@ public data class RealmClassImpl( override val kind: RealmClassKind get() = when { cinteropClass.isEmbedded -> RealmClassKind.EMBEDDED - cinteropClass.isAsymmetric -> RealmClassKind.ASYMMETRIC else -> RealmClassKind.STANDARD } diff --git a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClassKind.kt b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClassKind.kt index 0bf6c32d96..614072d2e4 100644 --- a/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClassKind.kt +++ b/packages/library-base/src/commonMain/kotlin/io/realm/kotlin/schema/RealmClassKind.kt @@ -35,13 +35,4 @@ public enum class RealmClassKind { * See [io.realm.kotlin.types.EmbeddedRealmObject] for more details. */ EMBEDDED, - /** - * Asymmetric Realm objects extend the [io.realm.kotlin.types.mongodb.AsymmetricRealmObject] interface. - * - * These kind of classes can only be used in a synced realm and are "write-only", i.e. once - * you written an asymmetric object to a Realm, it is no longer possible access or query them. - * - * See [io.realm.kotlin.types.mongodb.AsymmetricRealmObject] for more details. - */ - ASYMMETRIC } diff --git a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt index b1eedc3a6f..bc5be42b25 100644 --- a/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt +++ b/packages/library-base/src/jvmMain/kotlin/io/realm/kotlin/internal/platform/SystemUtilsJvm.kt @@ -2,7 +2,6 @@ package io.realm.kotlin.internal.platform import io.realm.kotlin.Realm import io.realm.kotlin.internal.RealmInstantImpl -import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant @@ -10,13 +9,7 @@ import java.io.InputStream import java.net.URL import java.time.Clock -public actual val RUNTIME: SyncConnectionParams.Runtime = SyncConnectionParams.Runtime.JVM -public actual val RUNTIME_VERSION: String = System.getProperty("java.version") -public actual val CPU_ARCH: String = System.getProperty("os.arch") public actual val OS_NAME: String = System.getProperty("os.name") -public actual val OS_VERSION: String = System.getProperty("os.version") -public actual val DEVICE_MANUFACTURER: String = "" -public actual val DEVICE_MODEL: String = "" @Suppress("FunctionOnlyReturningConstant") public actual fun appFilesDirectory(): String = System.getProperty("user.dir") ?: "." diff --git a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index a1c269ebb4..08f5670fd9 100644 --- a/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/nativeDarwin/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -16,7 +16,6 @@ package io.realm.kotlin.internal.platform import io.realm.kotlin.internal.RealmInstantImpl -import io.realm.kotlin.internal.interop.SyncConnectionParams import io.realm.kotlin.internal.util.Exceptions import io.realm.kotlin.log.RealmLogger import io.realm.kotlin.types.RealmInstant @@ -49,12 +48,6 @@ import kotlin.native.identityHashCode import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KType -@Suppress("MayBeConst") // Cannot make expect/actual const -public actual val RUNTIME: SyncConnectionParams.Runtime = SyncConnectionParams.Runtime.NATIVE - -@Suppress("MayBeConst") // Cannot make expect/actual const -public actual val RUNTIME_VERSION: String = "" - @Suppress("MayBeConst") // Cannot make expect/actual const public actual val PATH_SEPARATOR: String = "/" diff --git a/packages/library-base/src/nativeIos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/nativeIos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 9fc4fe70b6..162d659c98 100644 --- a/packages/library-base/src/nativeIos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/nativeIos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -1,38 +1,11 @@ package io.realm.kotlin.internal.platform -import kotlinx.cinterop.alloc -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.ptr -import kotlinx.cinterop.toKString import platform.Foundation.NSDocumentDirectory import platform.Foundation.NSFileManager -import platform.Foundation.NSProcessInfo import platform.Foundation.NSURL import platform.Foundation.NSUserDomainMask -import platform.UIKit.UIDevice -import platform.posix.uname -import platform.posix.utsname public actual val OS_NAME: String = "iOS" -public actual val OS_VERSION: String by lazy { NSProcessInfo.Companion.processInfo().operatingSystemVersionString } -public actual val DEVICE_MANUFACTURER: String by lazy { UIDevice.currentDevice.model } -public actual val DEVICE_MODEL: String by lazy { - try { - // On iOS devices this will report the underlying phone model: - // https://stackoverflow.com/a/11197770/1389357 - // On simulators this will report the underlying architecture like x86_64 or arm64. - memScoped { - val systemInfo = alloc() - uname(systemInfo.ptr) - systemInfo.machine.toKString() - } - } catch (e: Exception) { - "Unknown" - } -} -// CPU architecture can by looking at DEVICE_MODEL, but encoding the mapping in code will be -// difficult to maintain, so is left out. -public actual val CPU_ARCH: String = "" public actual fun appFilesDirectory(): String { return ( diff --git a/packages/library-base/src/nativeMacos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt b/packages/library-base/src/nativeMacos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt index 9b5ba128b3..d61ba506fb 100644 --- a/packages/library-base/src/nativeMacos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt +++ b/packages/library-base/src/nativeMacos/kotlin/io/realm/kotlin/internal/platform/SystemUtils.kt @@ -15,30 +15,7 @@ */ package io.realm.kotlin.internal.platform -import kotlinx.cinterop.alloc -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.ptr -import kotlinx.cinterop.toKString -import platform.Foundation.NSProcessInfo -import platform.posix.uname -import platform.posix.utsname - public actual val OS_NAME: String = "MacOS" -// These causes memory mapping rendering MemoryTests to fail, so only initialize them if actually needed -public actual val OS_VERSION: String by lazy { NSProcessInfo.Companion.processInfo().operatingSystemVersionString } -public actual val DEVICE_MANUFACTURER: String = "" -public actual val DEVICE_MODEL: String = "" -public actual val CPU_ARCH: String by lazy { - try { - memScoped { - val systemInfo = alloc() - uname(systemInfo.ptr) - systemInfo.machine.toKString() - } - } catch (e: Exception) { - "Unknown" - } -} @Suppress("FunctionOnlyReturningConstant") public actual fun appFilesDirectory(): String { diff --git a/packages/library-sync/build.gradle.kts b/packages/library-sync/build.gradle.kts deleted file mode 100644 index 779a76db52..0000000000 --- a/packages/library-sync/build.gradle.kts +++ /dev/null @@ -1,228 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -/* - * Copyright 2020 Realm Inc. - * - * 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. - */ - -plugins { - id("org.jetbrains.kotlin.multiplatform") - id("com.android.library") - id("realm-publisher") - id("org.jetbrains.dokka") -} -buildscript { - dependencies { - classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${Versions.atomicfu}") - } -} -apply(plugin = "kotlinx-atomicfu") -// AtomicFu cannot transform JVM code. Maybe an issue with using IR backend. Throws -// ClassCastException: org.objectweb.asm.tree.InsnList cannot be cast to java.lang.Iterable -project.extensions.configure(kotlinx.atomicfu.plugin.gradle.AtomicFUPluginExtension::class) { - transformJvm = false -} - -// Common Kotlin configuration -@Suppress("UNUSED_VARIABLE") -kotlin { - jvm() - androidTarget { - // Changing this will also requires an update to the publishCIPackages task - // in /packages/build.gradle.kts - publishLibraryVariants("release") - } - iosX64() - iosArm64() - iosSimulatorArm64() - macosX64() - macosArm64() - sourceSets { - val commonMain by getting { - dependencies { - api(project(":library-base")) - implementation(kotlin("stdlib-common")) - implementation(kotlin("reflect")) - // If runtimeapi is merged with cinterop then we will be exposing both to the users - // Runtime holds annotations, etc. that has to be exposed to users - // Cinterop does not hold anything required by users - implementation(project(":cinterop")) - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") - implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfu}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:${Versions.serialization}") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") - - implementation("io.ktor:ktor-client-core:${Versions.ktor}") - implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") - implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") - implementation("io.ktor:ktor-client-logging:${Versions.ktor}") - - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") - implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfu}") - } - } - - commonTest { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - val jvm by creating { - dependsOn(commonMain) - dependencies { - implementation("io.ktor:ktor-client-okhttp:${Versions.ktor}") - } - } - val jvmMain by getting { - dependsOn(jvm) - } - val androidMain by getting { - dependsOn(jvm) - dependencies { - api(project(":cinterop")) - implementation("androidx.startup:startup-runtime:${Versions.androidxStartup}") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}") - } - } - val nativeDarwin by creating { - dependsOn(commonMain) - dependencies { - implementation("io.ktor:ktor-client-darwin:${Versions.ktor}") - } - } - val macosX64Main by getting { dependsOn(nativeDarwin) } - val macosArm64Main by getting { dependsOn(nativeDarwin) } - val iosSimulatorArm64Main by getting { dependsOn(nativeDarwin) } - val iosArm64Main by getting { dependsOn(nativeDarwin) } - val iosX64Main by getting { dependsOn(nativeDarwin) } - } - - // Require that all methods in the API have visibility modifiers and return types. - // Anything inside `io.realm.kotlin.internal.*` is considered internal regardless of their - // visibility modifier and will be stripped from Dokka, but will unfortunately still - // leak into auto-complete in the IDE. - explicitApi = org.jetbrains.kotlin.gradle.dsl.ExplicitApiMode.Strict -} - -// Using a custom name module for internal methods to avoid default name mangling in Kotlin compiler which uses the module -// name and build type variant as a suffix, this default behaviour can cause mismatch at runtime https://github.com/realm/realm-kotlin/issues/621 -tasks.withType>().configureEach { - compilerOptions { - freeCompilerArgs.add("-Xexpect-actual-classes") - } -} - -tasks.withType().configureEach { - compilerOptions.moduleName.set("io.realm.kotlin.library") -} - -// Android configuration -android { - namespace = "io.realm.kotlin.mongodb" - compileSdk = Versions.Android.compileSdkVersion - buildToolsVersion = Versions.Android.buildToolsVersion - - defaultConfig { - minSdk = Versions.Android.minSdk - targetSdk = Versions.Android.targetSdk - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - - sourceSets { - getByName("main") { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - jniLibs.srcDir("src/androidMain/jniLibs") - } - } - ndk { - abiFilters += setOf("x86_64", "arm64-v8a") - } - } - - buildTypes { - getByName("release") { - consumerProguardFiles("proguard-rules-consumer-common.pro") - } - } - compileOptions { - sourceCompatibility = Versions.sourceCompatibilityVersion - targetCompatibility = Versions.targetCompatibilityVersion - } - // Skip BuildConfig generation as it overlaps with io.realm.kotlin.BuildConfig from realm-java - buildFeatures { - buildConfig = false - } - - packagingOptions { - jniLibs { - useLegacyPackaging = true - } - } -} - -realmPublish { - pom { - name = "Sync Library" - description = "Sync Library code for Realm Kotlin. This artifact is not " + - "supposed to be consumed directly, but through " + - "'io.realm.kotlin:gradle-plugin:${Realm.version}' instead." - } -} - -tasks.withType().configureEach { - moduleName.set("Realm Kotlin SDK - Sync") - moduleVersion.set(Realm.version) - dokkaSourceSets { - configureEach { - moduleVersion.set(Realm.version) - reportUndocumented.set(true) - skipEmptyPackages.set(true) - perPackageOption { - matchingRegex.set(""".*\.internal.*""") - suppress.set(true) - } - jdkVersion.set(8) - includes.from("overview.md") - } - } -} - -tasks.register("dokkaJar", Jar::class) { - val dokkaTask = "dokkaHtmlPartial" - dependsOn(dokkaTask) - archiveClassifier.set("dokka") - from(tasks.named(dokkaTask).get().outputs) -} - -val javadocJar by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") -} - -// Make sure that docs are published for the Metadata publication as well. This is required -// by Maven Central -publishing { - // See https://dev.to/kotlin/how-to-build-and-publish-a-kotlin-multiplatform-library-going-public-4a8k - publications.withType { - // Stub javadoc.jar artifact - artifact(javadocJar.get()) - } - - // TODO: configure DOKKA so that it's only published for sync and not base - val common = publications.getByName("kotlinMultiplatform") as MavenPublication - // Configuration through examples/kmm-sample does not work if we do not resolve the tasks - // completely, hence the .get() below. - common.artifact(tasks.named("dokkaJar").get()) -} diff --git a/packages/library-sync/gradle.properties b/packages/library-sync/gradle.properties deleted file mode 100644 index 63de1180aa..0000000000 --- a/packages/library-sync/gradle.properties +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2024 Realm Inc. -# -# 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. -# -kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/packages/library-sync/overview.md b/packages/library-sync/overview.md deleted file mode 100644 index c08d30dac4..0000000000 --- a/packages/library-sync/overview.md +++ /dev/null @@ -1,14 +0,0 @@ -# Module Realm Kotlin Multiplatform Sync SDK - -Module containing documentation on how to setup and work with Atlas App Services and Device Sync. - -You can read more about App Services [here](https://www.mongodb.com/docs/atlas/app-services/) and -Device Sync [here](https://www.mongodb.com/docs/atlas/app-services/sync/learn/overview/). - -# package io.realm.kotlin.mongodb - -Classes for setting up and authentication with an App Services Application. - -# package io.realm.kotlin.mongodb.sync - -Classes for setting up and working with Device Sync. diff --git a/packages/library-sync/proguard-rules-consumer-common.pro b/packages/library-sync/proguard-rules-consumer-common.pro deleted file mode 100644 index baac1cac56..0000000000 --- a/packages/library-sync/proguard-rules-consumer-common.pro +++ /dev/null @@ -1,12 +0,0 @@ -# Add sync-exclusive proguard exceptions here -# DO NOT PUT THEM IN THIS CONSUMER IN CASE THEY ARE UNDER 'cinterop' -# in which case they should be added to the 'library-base' proguard consumer instead. --keep class io.realm.kotlin.internal.interop.sync.ResponseCallbackImpl { - # TODO OPTIMIZE Only keep actually required symbols - *; -} - --keep class io.realm.kotlin.internal.interop.sync.ApiKeyWrapper { - # TODO OPTIMIZE Only keep actually required symbols - *; -} diff --git a/packages/library-sync/src/androidMain/AndroidManifest.xml b/packages/library-sync/src/androidMain/AndroidManifest.xml deleted file mode 100644 index a01e9d54ca..0000000000 --- a/packages/library-sync/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt deleted file mode 100644 index b89325a7a1..0000000000 --- a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -internal actual fun registerSystemNetworkObserver() { - // Registering network state listeners are done in io.realm.kotlin.mongodb.RealmSyncInitializer - // so we do not have to store the Android Context. -} diff --git a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt b/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt deleted file mode 100644 index 49b07d01d7..0000000000 --- a/packages/library-sync/src/androidMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncInitializer.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.internal - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.content.pm.PackageManager -import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import android.os.Build -import androidx.startup.Initializer -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.internal.RealmInitializer - -/** - * An **initializer** for Sync specific functionality that does not fit into the `RealmInitializer` - * in cinterop.o allow Realm to access context properties. - */ -internal class RealmSyncInitializer : Initializer { - - companion object { - val logger: ContextLogger = ContextLogger("RealmSyncInitializer") - - @Suppress("DEPRECATION") // Should only be called below API 21 - fun isConnected(cm: ConnectivityManager?): Boolean { - return cm?.let { - val networkInfo: android.net.NetworkInfo? = cm.activeNetworkInfo - networkInfo != null && networkInfo.isConnectedOrConnecting || isEmulator() - } ?: true - } - - // Credit: http://stackoverflow.com/questions/2799097/how-can-i-detect-when-an-android-application-is-running-in-the-emulator - fun isEmulator(): Boolean { - return Build.FINGERPRINT.startsWith("generic") || - Build.FINGERPRINT.startsWith("unknown") || - Build.MODEL.contains("google_sdk") || - Build.MODEL.contains("Emulator") || - Build.MODEL.contains("Android SDK built for x86") || - Build.MANUFACTURER.contains("Genymotion") || - (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) || - "google_sdk" == Build.PRODUCT - } - } - - @Suppress("invisible_member", "invisible_reference", "NestedBlockDepth") - override fun create(context: Context): Context { - val result: Int = context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) - if (result == PackageManager.PERMISSION_GRANTED) { - try { - val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager? - // There has been a fair amount of changes and deprecations with regard to how to listen - // to the network status. ConnectivityManager#CONNECTIVITY_ACTION was deprecated in API 28 - // but ConnectivityManager.NetworkCallback became available a lot sooner in API 21, so - // we default to this as soon as possible. - // - // On later versions of Android (need reference), these callbacks will also only trigger - // if the app is in the foreground. - // - // The current implementation is a best-effort in detecting when the network is available - // again. - // - // See https://developer.android.com/training/basics/network-ops/reading-network-state - // See https://developer.android.com/reference/android/net/ConnectivityManager#CONNECTIVITY_ACTION - // See https://developer.android.com/reference/android/net/ConnectivityManager.NetworkCallback - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP /* 21 */) { - val request = NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M /* 23 */) { - request.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - } - logger.info("Register ConnectivityManager network callbacks") - connectivityManager?.registerNetworkCallback( - request.build(), - object : NetworkCallback() { - override fun onAvailable(network: Network) { - NetworkStateObserver.notifyConnectionChange(true) - } - - override fun onUnavailable() { - NetworkStateObserver.notifyConnectionChange(false) - } - } - ) - } else { - logger.info("Register BroadcastReceiver connectivity callbacks") - @Suppress("DEPRECATION") - context.registerReceiver( - object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - val isConnected: Boolean = isConnected(connectivityManager) - NetworkStateObserver.notifyConnectionChange(isConnected) - } - }, - IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) - ) - } - } catch (ex: Exception) { - logger.warn("Something went wrong trying to register a network state listener: $ex") - } - } else { - logger.warn( - "It was not possible to register a network state listener. " + - "ACCESS_NETWORK_STATE was not granted." - ) - } - return context - } - - override fun dependencies(): MutableList>> { - return mutableListOf(RealmInitializer::class.java) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/App.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/App.kt deleted file mode 100644 index bbb72a76ba..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/App.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb - -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.mongodb.annotations.ExperimentalEdgeServerApi -import io.realm.kotlin.mongodb.auth.EmailPasswordAuth -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.exceptions.AuthException -import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.internal.AppImpl -import io.realm.kotlin.mongodb.sync.Sync -import kotlinx.coroutines.flow.Flow - -/** - * An **App** is the main client-side entry point for interacting with an **Atlas App Services - * Application**. - * - * The **App** can be used to: - * - Register and authenticate users. - * - Synchronize data between the local device and Atlas using Device Sync. - * - * This can be done as shown below: - * - * ``` - * class MyApplication { - * val app: App = App.create("") - * val realm: Realm - * - * init { - * realm = runBlocking { - * val user = app.login(Credentials.anonymous()) - * val config = SyncConfiguration.Builder( - * user = user, - * partitionValue = "my-partition" - * schema = setOf(YourRealmObject::class), - * ).build() - * - * Realm.open(config) - * } - * } - * } - * ``` - */ -public interface App { - - public val configuration: AppConfiguration - - /** - * Wrapper for interacting with functionality related to users either being created or logged - * in using the [AuthenticationProvider.EMAIL_PASSWORD] identity provider. - */ - public val emailPasswordAuth: EmailPasswordAuth - - /** - * Returns the current user that is logged in and still valid. - * - * Users are invalidated when they log out or when their refresh tokens expire or are revoked. - * - * If two or more users are logged in, it is the last valid user that is returned by this - * property. - * - * The value of this property will be `null` if no user is logged in or the user has expired. - */ - public val currentUser: User? - - /** - * Returns a Device Sync manager that control functionality across all open realms associated - * with this app. - */ - public val sync: Sync - - /** - * Current base URL to communicate with App Services. - */ - @ExperimentalEdgeServerApi - public val baseUrl: String - - /** - * Sets the App Services base url. - * - * *NOTE* Changing the URL would trigger a client reset. - * - * @param baseUrl The new App Services base url. If `null` it will be using the default value - * ([AppConfiguration.DEFAULT_BASE_URL]). - */ - @ExperimentalEdgeServerApi - public suspend fun updateBaseUrl(baseUrl: String?) - - /** - * Returns all known users that are either [User.State.LOGGED_IN] or [User.State.LOGGED_OUT]. - * Only users that at some point logged into this device will be returned. - * - * @return a list of locally known users. - */ - public fun allUsers(): List - - /** - * Log in as a user with the given credentials associated with an authentication provider. - * - * @param credentials the credentials representing the type of login. - * @return the logged in [User]. - * @throws AuthException if a problem occurred when logging in. See the exception message for - * further details. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun login(credentials: Credentials): User - - /** - * Switch current user. - * - * @param user the user that should be the new current user. The user must be one of the users - * that are already logged in. - * @throws IllegalStateException If the [user] is not logged in. - */ - public fun switchUser(user: User) - - /** - * Create a [Flow] of [AuthenticationChange]-events to receive notifications of updates to all - * app user authentication states: login, logout and removal. - * - * @return a [Flow] of authentication events for users associated with this app. - */ - public fun authenticationChangeAsFlow(): Flow - - /** - * Close the app instance and release all underlying resources. - * - * This class maintains a number of thread pools, these should normally run for the entire - * lifetime of the application, but in some cases, like unit tests, it could be beneficial to - * manually cleanup these resources. - * - * If not closed manually, these resources will be freed when the [App] instance is GC'ed. - */ - public fun close() - - public companion object { - /** - * Create an [App] with default settings. - * @param appId the App Services App ID. - */ - public fun create(appId: String): App { - Validation.checkEmpty(appId, "appId") - // We cannot rewire this to create(appId, bundleId) and just have REPLACED_BY_IR here, - // as these calls might be in a module where the compiler plugin hasn't been applied. - // In that case we don't setup the correct bundle ID. If this is an issue we could maybe - // just force users to apply our plugin. - return create(AppConfiguration.Builder(appId).build()) - } - - /** - * Create an [App] according to the given [AppConfiguration]. - * - * @param configuration the configuration to use for this [App] instance. - * @see AppConfiguration.Builder - */ - public fun create(configuration: AppConfiguration): App = - AppImpl(configuration as AppConfigurationImpl) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt deleted file mode 100644 index 41dbad1494..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AppConfiguration.kt +++ /dev/null @@ -1,453 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb - -import io.ktor.client.plugins.logging.Logger -import io.realm.kotlin.Realm -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.internal.interop.sync.MetadataMode -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.platform.appFilesDirectory -import io.realm.kotlin.internal.platform.canWrite -import io.realm.kotlin.internal.platform.directoryExists -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.internal.platform.prepareRealmDirectoryPath -import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.internal.util.DispatcherHolder -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.mongodb.ext.customData -import io.realm.kotlin.mongodb.ext.profile -import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.internal.KtorNetworkTransport -import io.realm.kotlin.mongodb.internal.LogObfuscatorImpl -import io.realm.kotlin.mongodb.internal.RealmWebSocketTransport -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncTimeoutOptions -import io.realm.kotlin.mongodb.sync.SyncTimeoutOptionsBuilder -import kotlinx.coroutines.CoroutineDispatcher -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -/** - * An **AppConfiguration** is used to setup linkage to an Atlas App Services Application. - * - * Instances of an AppConfiguration can be created by using the [AppConfiguration.Builder] and - * calling its [AppConfiguration.Builder.build] method or by using [AppConfiguration.create]. - */ -public interface AppConfiguration { - - public val appId: String - - // TODO Consider replacing with URL type, but didn't want to include io.ktor.http.Url as it - // requires ktor as api dependency - public val baseUrl: String - public val encryptionKey: ByteArray? - public val metadataMode: MetadataMode - public val syncRootDirectory: String - - /** - * Authorization header name used for Atlas App services requests. - */ - public val authorizationHeaderName: String - - /** - * Custom configured headers that will be sent alongside other headers when - * making network requests towards Atlas App services. - */ - public val customRequestHeaders: Map - - /** - * The name of app. This is only used for debugging. - * - * @see [AppConfiguration.Builder.appName] - */ - public val appName: String? - - /** - * The version of the app. This is only used for debugging. - * - * @see [AppConfiguration.Builder.appVersion] - */ - public val appVersion: String? - - /** - * The default EJson decoder that would be used to encode and decode arguments and results - * when calling remote App [Functions], authenticating with a [customFunction], and retrieving - * a user [profile] or [customData]. - * - * It can be set with [Builder.ejson] if a certain configuration, such as contextual classes, is - * required. - */ - @OptIn(ExperimentalKBsonSerializerApi::class) - public val ejson: EJson - - /** - * The configured [HttpLogObfuscator] for this app. If this property returns `null` no - * obfuscator is being used. - */ - public val httpLogObfuscator: HttpLogObfuscator? - - /** - * If enabled, a single connection is used for all Realms opened - * with a single sync user. If disabled, a separate connection is used for each - * Realm. - * - * Session multiplexing reduces resources used and typically improves - * performance. When multiplexing is enabled, the connection is not immediately - * closed when the last session is closed, but remains open for - * [SyncTimeoutOptions.connectionLingerTime] defined in [syncTimeoutOptions]. - */ - public val enableSessionMultiplexing: Boolean - - /** - * The configured timeouts for various aspects of the sync connection from realms. - */ - public val syncTimeoutOptions: SyncTimeoutOptions - - public companion object { - /** - * The default url for App Services applications. - * - * @see Builder#baseUrl(String) - */ - public const val DEFAULT_BASE_URL: String = "https://services.cloud.mongodb.com" - - /** - * The default header name used to carry authorization data when making network requests - * towards App Services. - */ - public const val DEFAULT_AUTHORIZATION_HEADER_NAME: String = "Authorization" - - /** - * Creates an app configuration with a given [appId] with default values for all - * optional configuration parameters. - * - * @param appId the application id of the App Services Application. - */ - public fun create(appId: String): AppConfiguration = AppConfiguration.Builder(appId).build() - } - - /** - * Builder used to construct instances of an [AppConfiguration] in a fluent manner. - * - * @param appId the application id of the App Services Application. - */ - public class Builder( - private val appId: String, - ) { - - private var baseUrl: String = DEFAULT_BASE_URL - private var dispatcher: CoroutineDispatcher? = null - private var encryptionKey: ByteArray? = null - private var syncRootDirectory: String = appFilesDirectory() - private var networkTransport: NetworkTransport? = null - private var websocketTransport: WebSocketTransport? = null - private var appName: String? = null - private var appVersion: String? = null - - @OptIn(ExperimentalKBsonSerializerApi::class) - private var ejson: EJson = EJson - private var httpLogObfuscator: HttpLogObfuscator? = LogObfuscatorImpl - private val customRequestHeaders = mutableMapOf() - private var authorizationHeaderName: String = DEFAULT_AUTHORIZATION_HEADER_NAME - private var enableSessionMultiplexing: Boolean = false - private var syncTimeoutOptions: SyncTimeoutOptions = SyncTimeoutOptionsBuilder().build() - private var usePlatformNetworking: Boolean = false - - /** - * Sets the encryption key used to encrypt the user metadata Realm only. Individual - * Realms need to use [SyncConfiguration.Builder.encryptionKey] to encrypt them. - * - * @param key a 64 byte encryption key. - * @return the Builder instance used. - * @throws IllegalArgumentException if the key is not 64 bytes long. - */ - public fun encryptionKey(key: ByteArray): Builder = apply { - if (key.size != Realm.ENCRYPTION_KEY_LENGTH) { - throw IllegalArgumentException("The provided key must be ${Realm.ENCRYPTION_KEY_LENGTH} bytes. Yours was: ${key.size}.") - } - - this.encryptionKey = key.copyOf() - } - - /** - * Sets the base url for the App Services Application. The default value is - * [DEFAULT_BASE_URL]. - * - * @param baseUrl the base url for the App Services Application. - * @return the Builder instance used. - */ - public fun baseUrl(baseUrl: String): Builder = apply { this.baseUrl = baseUrl } - - /** - * The dispatcher used to execute internal tasks; most notably remote HTTP requests. - * - * @return the Builder instance used. - */ - public fun dispatcher(dispatcher: CoroutineDispatcher): Builder = apply { - this.dispatcher = dispatcher - } - - /** - * Configures the root folder that marks the location of a `mongodb-realm` folder. This - * folder contains all files and realms used when synchronizing data between the device and - * Atlas using Device Sync. - * - * The default root directory is platform-dependent: - * ``` - * // For Android the default directory is obtained using - * val dir = "${Context.getFilesDir()}" - * - * // For JVM platforms the default directory is obtained using - * val dir = "${System.getProperty("user.dir")}" - * - * // For macOS the default directory is obtained using - * val dir = "${NSFileManager.defaultManager.currentDirectoryPath}" - * - * // For iOS the default directory is obtained using - * val dir = "${NSFileManager.defaultManager.URLForDirectory( - * NSDocumentDirectory, - * NSUserDomainMask, - * null, - * true, - * null - * )}" - * ``` - * - * @param rootDir the directory where a `mongodb-realm` directory will be created. - * @return the Builder instance used. - */ - public fun syncRootDirectory(rootDir: String): Builder = apply { - val directoryExists = directoryExists(rootDir) - if (!directoryExists && fileExists(rootDir)) { - throw IllegalArgumentException("'rootDir' is a file, not a directory: $rootDir.") - } - if (!directoryExists) { - prepareRealmDirectoryPath(rootDir) - } - if (!canWrite(rootDir)) { - throw IllegalArgumentException("Realm directory is not writable: $rootDir.") - } - this.syncRootDirectory = rootDir - } - - /** - * Sets the debug app name which is added to debug headers for App Services network - * requests. The default is `null`. - * - * @param appName app name used to identify the application. - * @throws IllegalArgumentException if an empty [appName] is provided. - * @return the Builder instance used. - */ - public fun appName(appName: String): Builder = apply { - Validation.checkEmpty(appName, "appName") - this.appName = appName - } - - /** - * Sets the debug app version which is added to debug headers for App Services network - * requests. The default is `null` - * - * @param appVersion app version used to identify the application. - * @throws IllegalArgumentException if an empty [appVersion] is provided. - * @return the Builder instance used. - */ - public fun appVersion(appVersion: String): Builder = apply { - Validation.checkEmpty(appVersion, "appVersion") - this.appVersion = appVersion - } - - /** - * Sets the a [HttpLogObfuscator] used to keep sensitive information in HTTP requests from - * being displayed in the log. Logs containing tokens, passwords or custom function - * arguments and the result of computing these will be obfuscated by default. Logs will not - * be obfuscated if the value is set to `null`. - * - * @param httpLogObfuscator the HTTP log obfuscator to be used or `null` if obfuscation - * should be disabled. - * @return the Builder instance used. - */ - public fun httpLogObfuscator(httpLogObfuscator: HttpLogObfuscator?): Builder = apply { - this.httpLogObfuscator = httpLogObfuscator - } - - /** - * Sets the name of the HTTP header used to send authorization data in when making requests to - * Atlas App Services. The Atlas App or firewall must have been configured to expect a - * custom authorization header. - * - * The default authorization header is named [DEFAULT_AUTHORIZATION_HEADER_NAME]. - * - * @param name name of the header. - * @throws IllegalArgumentException if an empty [name] is provided. - */ - public fun authorizationHeaderName(name: String): Builder = apply { - require(name.isNotEmpty()) { "Non-empty 'name' required." } - authorizationHeaderName = name - } - - /** - * Update the custom headers that would be appended to every request to an Atlas App Services Application. - * - * @param block lambda with the the custom header map update instructions. - * @throws IllegalArgumentException if an empty header name is provided. - */ - public fun customRequestHeaders( - block: MutableMap.() -> Unit, - ): Builder = apply { - customRequestHeaders.block() - require(!customRequestHeaders.containsKey("")) { "Non-empty custom header name required." } - } - - /** - * Sets the default EJson decoder that would be use to encode and decode arguments and results - * when calling remote Atlas [Functions], authenticating with a [customFunction], and retrieving - * a user [profile] or [customData]. - */ - @ExperimentalRealmSerializerApi - @OptIn(ExperimentalKBsonSerializerApi::class) - public fun ejson(ejson: EJson): Builder = apply { - this.ejson = ejson - } - - /** - * If enabled, a single connection is used for all Realms opened - * with a single sync user. If disabled, a separate connection is used for each - * Realm. - * - * Session multiplexing reduces resources used and typically improves - * performance. When multiplexing is enabled, the connection is not immediately - * closed when the last session is closed, but remains open for - * [SyncTimeoutOptions.connectionLingerTime] as defined by [syncTimeouts] (30 seconds by - * default). - */ - public fun enableSessionMultiplexing(enabled: Boolean): Builder { - this.enableSessionMultiplexing = enabled - return this - } - - /** - * Configure the assorted types of connection timeouts for sync connections. - * See [SyncTimeoutOptionsBuilder] for a description of each option. - */ - public fun syncTimeouts(action: SyncTimeoutOptionsBuilder.() -> Unit): Builder { - val builder = SyncTimeoutOptionsBuilder() - action(builder) - syncTimeoutOptions = builder.build() - return this - } - - /** - * Platform Networking offer improved support for proxies and firewalls that require authentication, - * instead of Realm's built-in WebSocket client for Sync traffic. This will become the default in a future version. - * - * Note: Only Android and JVM targets are supported so far. - */ - public fun usePlatformNetworking(enable: Boolean = true): Builder = - apply { - this.usePlatformNetworking = enable - } - - /** - * Allows defining a custom network transport. It is used by some tests that require simulating - * network responses. - */ - internal fun networkTransport(networkTransport: NetworkTransport?): Builder = apply { - this.networkTransport = networkTransport - } - - /** - * Creates the AppConfiguration from the properties of the builder. - * - * @return the AppConfiguration that can be used to create a [App]. - */ - public fun build(): AppConfiguration { - // We cannot rewire this to build(bundleId) and just have REPLACED_BY_IR here, - // as these calls might be in a module where the compiler plugin hasn't been applied. - // In that case we don't setup the correct bundle ID. If this is an issue we could maybe - // just force users to apply our plugin. - return build("UNKNOWN_BUNDLE_ID") - } - - // This method is used to inject bundleId to the sync configuration. The - // SyncLoweringExtension is replacing calls to SyncConfiguration.Builder.build() with calls - // to this method. - @OptIn(ExperimentalKBsonSerializerApi::class) - public fun build(bundleId: String): AppConfiguration { - val appNetworkDispatcherFactory = if (dispatcher != null) { - CoroutineDispatcherFactory.unmanaged(dispatcher!!) - } else { - // TODO We should consider using a multi threaded dispatcher. Ktor already does - // this under the hood though, so it is unclear exactly what benefit there is. - // https://github.com/realm/realm-kotlin/issues/501 - CoroutineDispatcherFactory.managed("app-dispatcher-$appId") - } - - val appLogger = ContextLogger() - val networkTransport: (dispatcher: DispatcherHolder) -> NetworkTransport = - { dispatcherHolder -> - val logger: Logger = object : Logger { - override fun log(message: String) { - val obfuscatedMessage = httpLogObfuscator?.obfuscate(message) - appLogger.debug(obfuscatedMessage ?: message) - } - } - networkTransport ?: KtorNetworkTransport( - // FIXME Add AppConfiguration.Builder option to set timeout as a Duration with default \ - // constant in AppConfiguration.Companion - // https://github.com/realm/realm-kotlin/issues/408 - timeoutMs = 120_000, - dispatcherHolder = dispatcherHolder, - logger = logger, - customHeaders = customRequestHeaders, - authorizationHeaderName = authorizationHeaderName - ) - } - - val websocketTransport: WebSocketTransport? = - if (usePlatformNetworking) { - websocketTransport ?: RealmWebSocketTransport( - timeoutMs = 60000 - ) - } else null - - return AppConfigurationImpl( - appId = appId, - baseUrl = baseUrl, - encryptionKey = encryptionKey, - metadataMode = if (encryptionKey == null) - MetadataMode.RLM_SYNC_CLIENT_METADATA_MODE_PLAINTEXT - else MetadataMode.RLM_SYNC_CLIENT_METADATA_MODE_ENCRYPTED, - appNetworkDispatcherFactory = appNetworkDispatcherFactory, - networkTransportFactory = networkTransport, - websocketTransport = websocketTransport, - syncRootDirectory = syncRootDirectory, - appName = appName, - appVersion = appVersion, - bundleId = bundleId, - ejson = ejson, - httpLogObfuscator = httpLogObfuscator, - customRequestHeaders = customRequestHeaders, - authorizationHeaderName = authorizationHeaderName, - enableSessionMultiplexing = enableSessionMultiplexing, - syncTimeoutOptions = syncTimeoutOptions, - logger = appLogger - ) - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationChange.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationChange.kt deleted file mode 100644 index 6b89fc69f1..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationChange.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb - -import kotlinx.coroutines.flow.Flow - -/** - * This sealed class describe the possible events that can be observed on the [Flow] created by - * calling [App.authenticationChangeAsFlow]. - * - * The specific states are represented by these subclasses [LoggedIn], [LoggedOut] and - * [Removed]. - * - * Changes can thus be consumed this way: - * - * ``` - * app.authenticationChangeAsFlow().collect { change: AuthenticationChange -> - * when(change) { - * is LoggedIn -> handleLogin(change.user) - * is LoggedOut -> handleLogOut(change.user) - * is Removed -> handleRemove(change.user) - * } - * } - * ``` - */ -public sealed interface AuthenticationChange { - /** - * A reference to the [User] this event happened to. - * - * *Warning:* This is the live user object, so the [User.state] might have diverged from the - * event it is associated with, i.e. if a users logs out and back in while the event is - * propagating, the state of the user might be [User.State.LOGGED_IN], even though it was - * reported as a [LoggedOut] event. - */ - public val user: User -} - -/** - * Event emitted when a user logs into the app. - */ -public interface LoggedIn : AuthenticationChange - -/** - * Event emitted when a user is logged out. - */ -public interface LoggedOut : AuthenticationChange - -/** - * Event emitted when a user is removed, which also logs them out. - */ -public interface Removed : AuthenticationChange diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationProvider.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationProvider.kt deleted file mode 100644 index 1e52901b3a..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/AuthenticationProvider.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb - -import io.realm.kotlin.internal.interop.sync.AuthProvider - -/** - * This enum contains the list of authentication providers supported by App Services. - * - * The authentication provider must be enabled in the App Services Application to work. - * - * **See:** [Authentication Providers](https://docs.mongodb.com/realm/authentication/providers/) - */ -public enum class AuthenticationProvider(id: AuthProvider) { - ANONYMOUS(AuthProvider.RLM_AUTH_PROVIDER_ANONYMOUS), - API_KEY(AuthProvider.RLM_AUTH_PROVIDER_API_KEY), - APPLE(AuthProvider.RLM_AUTH_PROVIDER_APPLE), - CUSTOM_FUNCTION(AuthProvider.RLM_AUTH_PROVIDER_FUNCTION), - EMAIL_PASSWORD(AuthProvider.RLM_AUTH_PROVIDER_EMAIL_PASSWORD), - FACEBOOK(AuthProvider.RLM_AUTH_PROVIDER_FACEBOOK), - GOOGLE(AuthProvider.RLM_AUTH_PROVIDER_GOOGLE), - JWT(AuthProvider.RLM_AUTH_PROVIDER_CUSTOM); - - internal val id: AuthProvider = id -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Credentials.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Credentials.kt deleted file mode 100644 index c75c9d027c..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Credentials.kt +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.mongodb.internal.AppImpl -import io.realm.kotlin.mongodb.internal.BsonEncoder -import io.realm.kotlin.mongodb.internal.CredentialsImpl -import io.realm.kotlin.mongodb.internal.CustomEJsonCredentialsImpl -import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer -import kotlinx.serialization.KSerializer -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.Bson - -/** - * This enum contains the list of Google authentication types supported by App Services. - * - * **See:** [Google Authentication](https://docs.mongodb.com/realm/authentication/google) - */ -public enum class GoogleAuthType { - /** - * This signals that an Authentication Code OAuth 2.0 login flow is to be used. - */ - AUTH_CODE, - - /** - * This signals that an OpenID Connect OAuth 2.0 login flow is to be used. - */ - ID_TOKEN -} - -/** - * Credentials represent a login with a given login provider. - * - * Credentials are used by an App Services Application to verify the user and grant access. The - * credentials are only usable if the corresponding authentication provider is enabled in the - * [App Services UI](https://docs.mongodb.com/realm/authentication/providers/). - */ -public interface Credentials { - - public val authenticationProvider: AuthenticationProvider - - public companion object { - /** - * Creates credentials representing an anonymous user. - * - * @param reuseExisting indicates whether anonymous users should be reused. Passing `true` - * means that multiple calls to [App.login] with this function will return the same - * anonymous user as long as that user hasn't logged out. If [reuseExisting] is `false`, - * calls to [App.login] will create a new user on the server. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun anonymous(reuseExisting: Boolean = true): Credentials { - return CredentialsImpl(CredentialsImpl.anonymous(reuseExisting)) - } - - /** - * Creates credentials representing a login using email and password. - * - * @param email email of the user logging in. - * @param password password of the user logging in. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun emailPassword(email: String, password: String): Credentials { - return CredentialsImpl(CredentialsImpl.emailPassword(email, password)) - } - - /** - * Creates credentials representing a login using a user API key. - * - * @param key the user API key to use for login. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - * **See:** [API key authentication](https://www.mongodb.com/docs/realm/authentication/api-key/#api-key-authentication) - */ - public fun apiKey(key: String): Credentials { - return CredentialsImpl(CredentialsImpl.apiKey(key)) - } - - /** - * Creates credentials representing a login using an Apple ID token. - * - * @param idToken the ID token generated when using your Apple login. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun apple(idToken: String): Credentials { - return CredentialsImpl(CredentialsImpl.apple(idToken)) - } - - /** - * Creates credentials representing a login using a Facebook access token. - * - * @param accessToken the access token returned when logging in to Facebook. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun facebook(accessToken: String): Credentials { - return CredentialsImpl(CredentialsImpl.facebook(accessToken)) - } - - /** - * Creates credentials representing a login using a Google access token of a given - * [GoogleAuthType]. - * - * @param token the ID Token or Auth Code returned when logging in to Google. - * @param type the type of Google token used. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun google(token: String, type: GoogleAuthType): Credentials { - return CredentialsImpl(CredentialsImpl.google(token, type)) - } - - /** - * Creates credentials representing a login using a JWT Token. This token is normally - * generated after a custom OAuth2 login flow. - * - * @param jwtToken the jwt token returned after a custom login to a another service. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun jwt(jwtToken: String): Credentials { - return CredentialsImpl(CredentialsImpl.jwt(jwtToken)) - } - - /** - * Creates credentials representing a login using an App Services Function. The payload would - * be serialized and parsed as an argument to the remote function. The payload keys must - * match the format and names the function expects. - * - * @param payload The payload that will be passed as an argument to the server function. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun customFunction(payload: BsonDocument): Credentials = - customFunctionInternal(payload) - - /** - * Creates credentials representing a login using an App Services Function. The payload would - * be serialized and parsed as an argument to the remote function. The payload keys must - * match the format and names the function expects. - * - * @param payload The payload that will be passed as an argument to the server function. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - public fun customFunction(payload: Map): Credentials = - customFunctionInternal(payload) - - private fun customFunctionInternal(payload: Any): Credentials = - BsonEncoder.encodeToBsonValue(payload).let { bsonValue: BsonValue -> - require(bsonValue.bsonType == BsonType.DOCUMENT) { - "Invalid payload type '${payload::class.simpleName}', only BsonDocument and maps are supported." - } - - CredentialsImpl(CredentialsImpl.customFunction(Bson.toJson(bsonValue))) - } - - /** - * Creates credentials representing a login using an App Services Function. The payload would - * be serialized and parsed as an argument to the remote function. The payload keys must - * match the format and names the function expects. - * - * **Note** This method supports full document serialization. The payload will be serialized - * with [serializer] and encoded with [AppConfiguration.ejson]. - * - * @param T the payload type. - * @param payload The payload that will be passed as an argument to the server function. - * @param serializer serialization strategy for [T]. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - @ExperimentalRealmSerializerApi - @OptIn(ExperimentalKBsonSerializerApi::class) - public fun customFunction(payload: T, serializer: KSerializer): Credentials = - CustomEJsonCredentialsImpl { app: AppImpl -> - app.configuration.ejson.encodeToString(serializer, payload) - } - - /** - * Creates credentials representing a login using an App Services Function. The payload would - * be serialized and parsed as an argument to the remote function. The payload keys must - * match the format and names the function expects. - * - * **Note** This method supports full document serialization. The payload will be serialized - * with the built-in serializer for [T] and encoded with [AppConfiguration.ejson]. - * - * @param payload The payload that will be passed as an argument to the server function. - * @return a set of credentials that can be used to log into an App Services Application - * using [App.login]. - */ - @ExperimentalRealmSerializerApi - @OptIn(ExperimentalKBsonSerializerApi::class) - public inline fun customFunction(payload: T): Credentials = - CustomEJsonCredentialsImpl { app: AppImpl -> - app.configuration.ejson.run { - app.configuration.ejson.encodeToString( - serializer = serializersModule.serializerOrRealmBuiltInSerializer(), - value = payload - ) - } - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Functions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Functions.kt deleted file mode 100644 index 8899b70da0..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/Functions.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb - -import io.realm.kotlin.mongodb.ext.call - -/** - * A Functions manager to call remote Atlas Functions for the associated Atlas App Services Application. - * - * Functions are invoked using the extension function [Functions.call]. - * - * @see [User.functions] - */ -public interface Functions { - /** - * The [App] that this function manager is associated with. - */ - public val app: App - - /** - * The [User] that this function manager is authenticated with. - */ - public val user: User -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/HttpLogObfuscator.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/HttpLogObfuscator.kt deleted file mode 100644 index ca6cbf1898..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/HttpLogObfuscator.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb - -/** - * The `HttpLogObfuscator` keeps sensitive information from being displayed in output traces. - */ -public interface HttpLogObfuscator { - /** - * Obfuscates a log entry depending on whether the data being sent contains information deemed - * sensitive. The following information is considered sensitive data: - * - passwords - * - tokens of any kind - see [Credentials] for more information - * - custom function arguments - see [Functions] for more information - * - * @param input the original log entry to be obfuscated. - * @return the log entry to be shown - */ - public fun obfuscate(input: String): String -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/RealmExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/RealmExt.kt deleted file mode 100644 index c5eecb7d30..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/RealmExt.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb - -import io.realm.kotlin.Realm -import io.realm.kotlin.mongodb.internal.SyncedRealmContext -import io.realm.kotlin.mongodb.internal.executeInSyncContext -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.mongodb.sync.SyncSession - -/** - * This class contains extension methods that are available when using synced realms. - * - * Calling these methods on a local realms created using a [io.realm.RealmConfiguration] will - * throw an [IllegalStateException]. - */ - -/** - * Returns the [SyncSession] associated with this Realm. - */ -public val Realm.syncSession: SyncSession - get() { - return executeInSyncContext(this) { context: SyncedRealmContext -> - context.session - } - } - -/** - * Returns the latest [SubscriptionSet] associated with this Realm. - */ -public val Realm.subscriptions: SubscriptionSet - get() { - return executeInSyncContext(this) { context: SyncedRealmContext -> - if (context.config.syncMode != SyncMode.FLEXIBLE) { - throw IllegalStateException( - "Subscriptions are only available on Realms configured " + - "for Flexible Sync. This Realm was configured for Partition-based Sync: " + - "${context.config.path}" - ) - } - context.subscriptions - } - } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt deleted file mode 100644 index e7ce1120f8..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/User.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb - -import io.realm.kotlin.mongodb.auth.ApiKeyAuth -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument -import io.realm.kotlin.mongodb.ext.profileAsBsonDocument -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -/** - * A **user** holds the user's metadata and tokens for accessing App Services and Device Sync - * functionality. - * - * User profile and custom data can be accessed using the extension functions [User.profileAsBsonDocument] - * and [User.customDataAsBsonDocument]. - * - * The user is used to configure synchronized realms with [SyncConfiguration.Builder]. - * - * @see App.login - * @see SyncConfiguration.Builder - */ -@Suppress("EqualsWithHashCodeExist") // Only overwriting equals to make docs available to user -public interface User { - - /** - * The [App] this user is associated with. - */ - public val app: App - - /** - * Gives access to the [ApiKeyAuth] interface so that users can manage their API keys. - */ - public val apiKeyAuth: ApiKeyAuth - - /** - * The [State] this user is in. - */ - public val state: State - - /** - * The server id of the user. - */ - public val id: String - - /** - * Returns whether or not this user is still logged into the App Services Application. - */ - public val loggedIn: Boolean - - /** - * Returns a list of the user's identities as defined by the authentication providers enabled - * for this user. - * - * @return the list of user credential identities. - * @see UserIdentity - */ - public val identities: List - - /** - * Returns the current access token for the user. - * If a user logs out, an empty access token is returned. - */ - public val accessToken: String - - /** - * Returns the current refresh token for the user. - * If a user logs out an empty refresh token is returned. - */ - public val refreshToken: String - - /** - * Returns a unique identifier for the device the user logged in to. - */ - public val deviceId: String - - /** - * Returns a wrapper for invoking App Services Functions. - * - * [Atlas Functions documentation](https://www.mongodb.com/docs/atlas/app-services/functions/) - */ - public val functions: Functions - - /** - * Re-fetch custom user data from the Realm App. - */ - public suspend fun refreshCustomData() - - // FIXME Review around user state - /** - * Log the user out of the Realm App. This will unregister them on the device and stop any - * synchronization to and from the users' Realms. Any Realms owned by the user will - * not be deleted from the device before [User.remove] is called. - * - * Once the Realm App confirms the logout, any registered [AuthenticationListener] - * will be notified and user credentials will be deleted from this device. - * - * Logging out anonymous users will remove them immediately instead of marking them as - * [User.State.LOGGED_OUT]. - * - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException if a failure occurred when - * communicating with App Services. See [AppException] for details. - * @throws IllegalStateException if a consumer listening to [App.authenticationChangeAsFlow] - * is too slow consuming events. - */ - // FIXME add references to allUsers and remove when ready - // * All other users will be marked as [User.State.LOGGED_OUT] - // * and will still be returned by [App.allUsers]. They can be removed completely by - // * calling [User.remove].asd - // TODO Document how this method behave if offline - public suspend fun logOut() - - /** - * Removes the user and any Realms the user has from the device. No data is removed from the - * server. - * - * If the user is logged in when calling this method, the user will be logged out before any - * data is deleted. - * - * @return the user that was removed. - * @throws IllegalStateException if the user was already removed. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException if a failure occurred when - * communicating with App Services. See [AppException] for details. - * @throws IllegalStateException if a consumer listening to [App.authenticationChangeAsFlow] - * is too slow consuming events. - */ - // TODO Document how this method behave if offline - public suspend fun remove(): User - - /** - * Permanently deletes this user from your Atlas App Services app. - * - * If the user was deleted successfully on Atlas, the user state will be set to - * [State.REMOVED] and any local Realm files owned by the user will be deleted. If - * the server request fails, the local state will not be modified. - * - * All user realms should be closed before calling this method. - * - * @throws IllegalStateException if the user was already removed or not logged in. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException if a failure occurred when - * communicating with App Services. See [AppException] for details. - * @throws IllegalStateException if a consumer listening to [App.authenticationChangeAsFlow] - * is too slow consuming events. - */ - public suspend fun delete() - - /** - * Links the current user with a new user identity represented by the given credentials. - * - * Linking a user with more credentials mean the user can login with either of these - * credentials. It also makes it possible to upgrade an anonymous user by linking it with e.g. - * Email/Password credentials. - * - * Example: - * ``` - * val app = new App("app-id") - * val user: User = app.login(Credentials.anonymous()); - * user.linkCredentials(Credentials.emailPassword("email", "password")); - * ``` - * - * Note: It is not possible to link two existing Atlas App Service users. The provided - * credentials must not be used by another user. - * - * @param credentials the credentials to link with the current user. - * @return the [User] the credentials were linked to. - * - * @throws IllegalStateException if no user is currently logged in. - * @throws io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException if linking the - * two credentials are not supported. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException if a failure occurred when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun linkCredentials(credentials: Credentials): User - - /** - * Get a [MongoClient] for accessing documents from App Service's _Data Source_. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * that supports the [Kotlin Serialization framework](https://github.com/Kotlin/kotlinx.serialization) - * and handles serialization to and from classes marked with [Serializable]. Serialization of - * realm objects and links have some caveats and requires special configuration. For full - * details see [MongoClient]. - * - * @param serviceName the name of the data service. - * @param eJson the EJson serializer that the [MongoClient] should use to convert objects and - * primary keys with. Will default to the app's [EJson] instance configured with - * [AppConfiguration.Builder.ejson]. For details on configuration of serialization see - * [MongoClient]. - * throws IllegalStateException if trying to obtain a [MongoClient] from a logged out [User]. - */ - @ExperimentalKBsonSerializerApi - public fun mongoClient(serviceName: String, eJson: EJson? = null): MongoClient - - /** - * Two Users are considered equal if they have the same user identity and are associated - * with the same app. - */ - override fun equals(other: Any?): Boolean - - /** - * A user's potential states. - */ - public enum class State { - LOGGED_OUT, - LOGGED_IN, - REMOVED; - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/UserIdentity.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/UserIdentity.kt deleted file mode 100644 index f1e3e9e409..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/UserIdentity.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb - -/** - * Each [User] on Atlas App Services is uniquely identified by their [User.id], but this id cannot - * be used across multiple authentication providers, as they all have their own notation on what - * defines a user. This class thus represents a users identity towards one single authentication - * provider. - * - * A single [User] on App Services can have multiple user identities, one towards each - * authentication provider, e.g. an example would be an app user that can log in using either - * a custom email account or a Google account. - * - * The list of all user identities associated with an App Services user can be found through - * [User.identities]. It is possible to add more user identities through [User.linkCredentials]. - */ -public data class UserIdentity( - /** - * A unique identifier for this identity. The identifier is only unique for the given - * [provider]. - */ - val id: String, - /** - * The provider responsible for defining and managing this identity. - */ - val provider: AuthenticationProvider -) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalAsymmetricSyncApi.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalAsymmetricSyncApi.kt deleted file mode 100644 index b99424f32d..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalAsymmetricSyncApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * 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. - * - */ -package io.realm.kotlin.mongodb.annotations - -/** - * This annotation mark Realm APIs for Asymmetric Sync that are considered **experimental**, i.e. - * there are no guarantees given that these APIs cannot change without warning between minor and - * major versions. They will not change between patch versions. - * - * For all other purposes these APIs are considered stable, i.e. they undergo the same testing - * as other parts of the API and should behave as documented with no bugs. They are primarily - * marked as experimental because we are unsure if these APIs provide value and solve the use - * cases that people have. If not, they will be changed or removed altogether. - */ -@MustBeDocumented -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.FUNCTION, - AnnotationTarget.TYPEALIAS -) -@RequiresOptIn(level = RequiresOptIn.Level.ERROR) -public annotation class ExperimentalAsymmetricSyncApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalEdgeServerApi.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalEdgeServerApi.kt deleted file mode 100644 index 62d59c2d74..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalEdgeServerApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * 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. - * - */ -package io.realm.kotlin.mongodb.annotations - -/** - * This annotation mark Realm APIs specific to the Atlas Edge server and are considered - * **experimental**, i.e. there are no guarantees given that these APIs cannot change without - * warning between minor and major versions. They will not change between patch versions. - * - * For all other purposes these APIs are considered stable, i.e. they undergo the same testing - * as other parts of the API and should behave as documented with no bugs. They are primarily - * marked as experimental because we are unsure if these APIs provide value and solve the use - * cases that people have. If not, they will be changed or removed altogether. - */ -@MustBeDocumented -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.FUNCTION, - AnnotationTarget.TYPEALIAS -) -@RequiresOptIn(level = RequiresOptIn.Level.ERROR) -public annotation class ExperimentalEdgeServerApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalFlexibleSyncApi.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalFlexibleSyncApi.kt deleted file mode 100644 index a880ce54f0..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/annotations/ExperimentalFlexibleSyncApi.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * 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. - * - */ -package io.realm.kotlin.mongodb.annotations - -/** - * This annotation mark Realm APIs for Flexible Sync that are considered **experimental**, i.e. - * there are no guarantees given that these APIs cannot change without warning between minor and - * major versions. They will not change between patch versions. - * - * For all other purposes these APIs are considered stable, i.e. they undergo the same testing - * as other parts of the API and should behave as documented with no bugs. They are primarily - * marked as experimental because we are unsure if these APIs provide value and solve the use - * cases that people have. If not, they will be changed or removed altogether. - */ -@MustBeDocumented -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.FUNCTION, - AnnotationTarget.TYPEALIAS -) -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -public annotation class ExperimentalFlexibleSyncApi diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKey.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKey.kt deleted file mode 100644 index b35a1d07ec..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKey.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.auth - -import io.realm.kotlin.mongodb.User -import org.mongodb.kbson.BsonObjectId - -/** - * Class representing an API key for a [User]. An API key can be used to represent a - * user when logging in instead of using email and password. - * Note that the value of a key will only be available immediately after the key is created, after - * which point it is not visible anymore. This means that keys returned by [ApiKeyAuth.fetch] and - * [ApiKeyAuth.fetchAll] will have a `null` [value]. Anyone creating an API key is responsible for - * storing it safely after that. - * - * @param id an [BsonObjectId] uniquely identifying the key. - * @param value the value of this key, only returned when the key is created, `null` otherwise. - * @param name the name of the key. - * @param enabled whether the key is enabled or not. - */ -public data class ApiKey internal constructor( - public val id: BsonObjectId, - public val value: String?, - public val name: String, - public val enabled: Boolean -) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKeyAuth.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKeyAuth.kt deleted file mode 100644 index bf340a7714..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/ApiKeyAuth.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.auth - -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.AppException -import org.mongodb.kbson.BsonObjectId - -/** - * Exposes functionality for a user to manage API keys under their control. - */ -public interface ApiKeyAuth { - - /** - * The User that this instance in associated with. - */ - public val user: User - - /** - * The App that this instance in associated with. - */ - public val app: App - - /** - * Creates a user API key that can be used to authenticate as the user. - * The value of the key must be persisted at this time as this is the only time it is visible. - * The key is enabled when created. It can be disabled by calling the [disable] method. - * - * @param name the name of the key - * @return the new API key for the user. - * - * @throws IllegalArgumentException if an invalid name for the key is sent to the server. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun create(name: String): ApiKey - - /** - * Deletes a specific API key created by the user. - * Returns silently if no key is deleted. - * - * @param id the id of the key to delete. - * - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun delete(id: BsonObjectId) - - /** - * Deletes a specific API key created by the user. The function would complete normally if the provided key does not exist. - * - * @param id the id of the key to disable. - * - * @throws IllegalArgumentException if a non existing API key is disabled. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun disable(id: BsonObjectId) - - /** - * Enables a specific API key created by the user. - * - * @param id the id of the key to disable. - * - * @throws IllegalArgumentException if a non existing API key is enabled. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun enable(id: BsonObjectId) - - /** - * Fetches a specific user API key associated with the user. - * - * @param id the id of the key to fetch. - * - * @throws IllegalArgumentException if a non existing API key is fetched. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun fetch(id: BsonObjectId): ApiKey? - - /** - * Fetches all API keys associated with the user. - * Returns an empty list if no key is found. - * - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun fetchAll(): List -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/EmailPasswordAuth.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/EmailPasswordAuth.kt deleted file mode 100644 index dc7563c82b..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/auth/EmailPasswordAuth.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.auth - -import io.realm.kotlin.mongodb.exceptions.AppException - -/** - * Class encapsulating functionality for managing [User]s through the - * [AuthenticationProvider.EMAIL_PASSWORD] provider. - */ -public interface EmailPasswordAuth { - /** - * Registers a new user with the given email and password. - * - * @param email the email used to register a user. This will be the username used during log in. - * @param password the password associated with the email. The password must be between - * 6 and 128 characters long. - * @throws io.realm.kotlin.mongodb.exceptions.UserAlreadyExistsException if this email was already - * registered. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun registerUser(email: String, password: String) - - /** - * Confirms a user with the given token and token id. - * - * @param token the confirmation token. - * @param tokenId the id of the confirmation token. - * @throws io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException if this email was already - * confirmed. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun confirmUser(token: String, tokenId: String) - - /** - * Resend the confirmation for a user to the given email. - * - * @param email the email of the user. - * @throws io.realm.kotlin.mongodb.exceptions.UserNotFoundException if no user was registered with - * this email. - * @throws io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException if the user was already - * confirmed. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun resendConfirmationEmail(email: String) - - /** - * Retries the custom confirmation on a user for a given email. - * - * @param email the email of the user. - * @throws io.realm.kotlin.mongodb.exceptions.UserNotFoundException if no user was registered with - * this email. - * @throws io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException if the user was already - * confirmed. - * @throws io.realm.kotlin.mongodb.exceptions.BadRequestException if the custom function failed to - * confirm the user. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun retryCustomConfirmation(email: String) - - /** - * Sends a user a password reset email for the given email. - * - * @param email the email of the user. - * @throws io.realm.kotlin.mongodb.exceptions.UserNotFoundException if no user was registered with - * this email. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun sendResetPasswordEmail(email: String) - - /** - * Call the reset password function configured for the - * [Credentials.Provider.EMAIL_PASSWORD] provider. - * - * @param email the email of the user. - * @param newPassword the new password of the user. - * @param args any additional arguments provided to the reset function. - * All arguments must be able to be converted to a [BsonValue]. - * @throws ServiceException if the server failed to confirm the user. - */ - public suspend fun callResetPasswordFunction(email: String, newPassword: String, vararg args: Any?) - - /** - * Resets the password of a user with the given token, token id, and new password. - * - * @param token the reset password token. - * @param tokenId the id of the reset password token. - * @param newPassword the new password for the user identified by the `token`. The password - * must be between 6 and 128 characters long. - * @throws io.realm.kotlin.mongodb.exceptions.UserNotFoundException if the tokens do not map to an - * existing user. - * @throws io.realm.kotlin.mongodb.exceptions.BadRequestException if the input tokens where - * rejected by the server for being malformed. - * @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when - * communicating with App Services. See [AppException] for details. - */ - public suspend fun resetPassword(token: String, tokenId: String, newPassword: String) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt deleted file mode 100644 index 63dcf9e363..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/AppException.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.exceptions - -import io.realm.kotlin.exceptions.RealmException - -/** - * Top level class for all exceptions that are specific to using App Services and Device Sync. - * - * Subclasses of this class fall into two broad categories: [ServiceException] and [SyncException]. - * - * 1. [ServiceException]'s are thrown by all API's that talk directly to App Services - * through HTTP requests. The [io.realm.kotlin.mongodb.App], - * [io.realm.kotlin.mongodb.User] and [io.realm.kotlin.mongodb.auth.EmailPasswordAuth] classes - * handle HTTP requests for the SDK. - * - * 2. [SyncException]'s are thrown by errors caused when using Device Sync APIs, i.e - * realms opened using a [io.realm.kotlin.mongodb.SyncConfiguration]. These errors propagate - * through the [io.realm.kotlin.mongodb.SyncConfiguration.Builder.errorHandler]. - * - * Each of these categories are divided further: - * - * - [AppException] - * - [ServiceException] - * - [AuthException] - * - [UserNotFoundException] - * - [UserAlreadyConfirmedException] - * - [UserAlreadyExistsException] - * - [InvalidCredentialsException] - * - [BadRequestException] - * - [ConnectionException] - * - [CredentialsCannotBeLinkedException] - * - [SyncException] - * - [UnrecoverableSyncException] - * - [WrongSyncTypeException] - * - * Exceptions at the bottom of the hierarchy are _actionable_, i.e. it should be clear from the - * exception which action can be taken to resolve it. - * - * Exceptions further up the hierarchy categorize the errors. They can be - * harder to react to in a single way that will make sense to end users of an app, but should - * be logged for later inspection. - * - * In most cases, only exceptions at the bottom of the hierarchy will be documented in the API - * documentation, with one notable special case: [ConnectionException]. This exception is - * assumed to be thrown by all methods that mentions a [ServiceException] and covers all transport - * errors. These are often intermittent, so catching this exception and retrying should generally - * be safe, but more information can be found in the documentation for [ConnectionException]. - * - * With the above exception hierarchy in mind, a sensible way to handle errors could look like - * this: - * - * - * ``` - * val app = App.with("my-app-id") - * runCatching { - * app.login(Credentials.emailPassword("myemail@mail.com", "mypassword")) - * }.onSuccess { - * gotoMainScreen() - * }.onFailure { ex: Throwable -> - * when(ex) { - * is InvalidCredentialsException -> { - * showWrongPasswordDialog() - * } - * is ConnectionException -> { - * CrashLogger.log(ex.toString()) - * showRetryLoginDialog() - * } - * else -> { - * CrashLogger.log(ex.toString()) - * showContactAdministratorDialog() - * } - * } - * } - * ``` - * - * For more information about specific exceptions, see the documentation for that exception. - * - * @see ServiceException - * @see SyncException - */ -public open class AppException internal constructor( - message: String?, -) : RealmException(message) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt deleted file mode 100644 index 48a7fcfb72..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ClientResetRequiredException.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.exceptions - -import io.realm.kotlin.internal.interop.RealmAppPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.mongodb.internal.createMessageFromSyncError - -/** - * Class encapsulating information needed for handling a Client Reset event. - * - * **See:** [Client Reset](https://www.mongodb.com/docs/atlas/app-services/sync/error-handling/client-resets/) - * for more information about when and why Client Reset occurs and how to deal with it. - * - */ -public class ClientResetRequiredException constructor( - private val appPointer: RealmAppPointer, - error: SyncError, -) : Throwable( - message = createMessageFromSyncError(error.errorCode), - cause = error.userError, -) { - - /** - * Path to the original (local) copy of the realm when the Client Reset event was triggered. - * This realm may contain unsynced changes. - */ - public val originalFilePath: String = error.originalFilePath!! - - /** - * Path to the recovery (remote) copy of the realm downloaded from the backend. - */ - public val recoveryFilePath: String = error.recoveryFilePath!! - - /** - * Calling this method will execute the Client Reset manually instead of waiting until the next - * app restart. - * - * After this method returns, the backup file can be found in the location returned by - * [recoveryFilePath]. The file at [originalFilePath] will have been deleted, but will be - * recreated from scratch next time a Realm instance is opened. - * - * **WARNING:** To guarantee the backup file is generated correctly all Realm instances - * associated to the session in which this error is generated **must be closed**. Not doing so - * might result in unexpected file system errors. - * - * @return `true` if the Client Reset succeeded, `false` if not. - * @throws IllegalStateException if not all instances have been closed. - */ - public fun executeClientReset(): Boolean { - return RealmInterop.realm_sync_immediately_run_file_actions(appPointer, originalFilePath) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/DownloadingRealmTimeOutException.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/DownloadingRealmTimeOutException.kt deleted file mode 100644 index 36d13e3e1b..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/DownloadingRealmTimeOutException.kt +++ /dev/null @@ -1,16 +0,0 @@ -package io.realm.kotlin.mongodb.exceptions - -import io.realm.kotlin.exceptions.RealmException -import io.realm.kotlin.mongodb.sync.SyncConfiguration - -/** - * Thrown when opening a Realm and it didn't finish download server data in the allocated timeframe. - * - * This can only happen if [SyncConfiguration.Builder.waitForInitialRemoteData] is set. - */ -public class DownloadingRealmTimeOutException : RealmException { - internal constructor(syncConfig: SyncConfiguration) : super( - "Realm did not manage to download all initial data in time: ${syncConfig.path}, " + - "timeout: ${syncConfig.initialRemoteData!!.timeout}." - ) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt deleted file mode 100644 index 5b9e7f31d4..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/ServiceExceptions.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.exceptions - -import io.realm.kotlin.internal.interop.CodeDescription - -/** - * This exception is considered the top-level or "catch-all" for problems related to HTTP requests - * made towards App Services. This covers both HTTP transport problems, or the server considering - * the request invalid, for whatever reason. - * - * Generally, reacting to this exception will be hard, except to log the error for further - * analysis. But in many cases a more specific subtype will be thrown, which will be easier to - * react to. See the subclasses of this exception for more information about these. - * - * @see ConnectionException - * @see BadRequestException - * @see AuthException - */ -public open class ServiceException @PublishedApi internal constructor( - message: String, - internal val errorCode: CodeDescription? = null -) : AppException(message) - -/** - * Exception indicating that something went wrong with the underlying HTTP request to - * App Services. The exact cause is in the exception message. - * - * Errors resulting in this exception are outside the apps control and can be considered - * temporary. Retrying the action some time in the future should generally be safe, but since - * this potentially requires corrective measures in either the apps network environment - * or on the server, you should log this error for further analysis. - * - * Note, HTTP responses that indicate problems that can be fixed in the app will throw a more - * specific exception instead, e.g. `401 - AuthenticationRequired` will throw - * [InvalidCredentialsException]. - */ -public open class ConnectionException internal constructor(message: String) : - ServiceException(message) - -/** - * This exception is thrown when parameters sent to Atlas App Services are considered malformed. - * This can happen if e.g. tokens do not have the required length or contain garbage data. The - * exact reason will be in the error message. - * - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.resetPassword - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.retryCustomConfirmation - */ -public class BadRequestException internal constructor(message: String) : ServiceException(message) - -/** - * This exception is considered the top-level or "catch-all" for problems related to user account - * actions. The exact reason for the error can be found in [Throwable.message]. - * - * For some error scenarios there are more specific and descriptive subtypes available. - * These are documented for the relevant API methods where they can be thrown. - * - * @see UserAlreadyConfirmedException - * @see UserNotFoundException - * @see UserAlreadyExistsException - * @see InvalidCredentialsException - */ -public open class AuthException internal constructor(message: String) : ServiceException(message) - -/** - * Thrown when trying to confirm a user that was already confirmed on the server. - * - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.confirmUser - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.resendConfirmationEmail - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.retryCustomConfirmation - */ -public class UserAlreadyConfirmedException internal constructor(message: String) : - AuthException(message) - -/** - * Thrown when using an API that also require some kind of user identifier, but the server is - * not able to find the user. - * - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.resendConfirmationEmail - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.resetPassword - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.retryCustomConfirmation - * @see io.realm.kotlin.mongodb.auth.EmailPasswordAuth.sendResetPasswordEmail - */ -public class UserNotFoundException internal constructor(message: String) : AuthException(message) - -/** - * Thrown when trying to register a new user with email and password, and the user already - * exists. - * - * @see io.realm.kotlin.mongodb.EmailPasswordAuth.registerUser - */ -public class UserAlreadyExistsException internal constructor(message: String) : - AuthException(message) - -/** - * Thrown when credentials were rejected by the server when trying to log in. Only some - * authentication providers will return this error: - * - * - [io.realm.kotlin.mongodb.AuthenticationProvider.EMAIL_PASSWORD] - * - [io.realm.kotlin.mongodb.AuthenticationProvider.API_KEY] - * - [io.realm.kotlin.mongodb.AuthenticationProvider.JWT] - * - * The remaining authentication providers will throw a more general [AuthException] instead. - * - * @see io.realm.kotlin.mongodb.App.login - */ -public class InvalidCredentialsException internal constructor(message: String) : - AuthException(message) -/** - * Thrown when attempting to link credentials that are not supported. - * - * Reasons for this can e.g. be: - * - Linking an email account with an anonymous account. Only the other direction is supported, i.e. - * `anonymousUser.linkCredentials(Credentials.emailPassword(email, password))` - * - Linking two email accounts. - * - Linking an account with itself. - * - * The exact reason is found in the exception message. - * - * @see io.realm.kotlin.mongodb.User.linkCredentials - */ -public class CredentialsCannotBeLinkedException internal constructor(message: String) : - ServiceException(message) - -/** - * Exception indicating that an Atlas Function failed to execute. The exact reason for the error can - * be found in [Throwable.message]. - * - * @see [io.realm.kotlin.mongodb.Functions.call] - */ -public class FunctionExecutionException internal constructor(message: String) : - ServiceException(message) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt deleted file mode 100644 index 5350877d65..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/exceptions/SyncExceptions.kt +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") - -package io.realm.kotlin.mongodb.exceptions - -import io.realm.kotlin.internal.asPrimitiveRealmAnyOrElse -import io.realm.kotlin.internal.interop.sync.CoreCompensatingWriteInfo -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.types.RealmAny - -/** - * This exception is considered the top-level exception or general "catch-all" for problems related - * to using Device Sync. - * - * This exception and subclasses of it will be passed to users through - * [io.realm.kotlin.mongodb.sync.SyncConfiguration.Builder.errorHandler] and the the exact reason - * must be found in [Throwable.message]. - * - * @see io.realm.kotlin.mongodb.sync.SyncConfiguration.Builder.errorHandler - */ -public open class SyncException internal constructor(message: String?, isFatal: Boolean) : AppException(message) { - /** - * Flag to indicate that something has gone wrong with Device Sync in a way that is not - * recoverable and [SyncSession] will be [SyncSession.State.INACTIVE] until this error is - * resolved. - * - * It is still possible to use the Realm locally after receiving an error where this flag is - * true. However, this must be done with caution as data written to the realm after this point - * risk getting lost as many errors of this category will result in a Client Reset once the - * client re-connects to the server. - */ - public val isFatal: Boolean = isFatal -} - -/** - * Thrown when something has gone wrong with Device Sync in a way that is not recoverable. - * - * Generally, errors of this kind are due to incompatible versions of Realm and Atlas App Services - * being used or bugs in the library or on the server, and the only fix would be installing a new - * version of the app with a new version of Realm. - * - * It is still possible to use the Realm locally after this error occurs. However, this must be - * done with caution as data written to the realm after this point risk getting lost as - * many errors of this category will result in a Client Reset once the client - * re-connects to the server. - * - * @see io.realm.kotlin.mongodb.sync.SyncConfiguration.Builder.errorHandler - */ -@Deprecated("This will be removed in the future. Test for SyncException.isFatal instead.") -public open class UnrecoverableSyncException internal constructor(message: String) : - SyncException(message, true) - -/** - * Thrown when the type of sync used by the server does not match the one used by the client, i.e. - * the server and client disagrees whether to use Partition-based or Flexible Sync. - */ -@Suppress("DEPRECATION") -public class WrongSyncTypeException internal constructor(message: String) : - UnrecoverableSyncException(message) - -/** - * Thrown when the server does not support one or more of the queries defined in the - * [io.realm.kotlin.mongodb.sync.SubscriptionSet]. - */ -public class BadFlexibleSyncQueryException internal constructor(message: String?, isFatal: Boolean) : - SyncException(message, isFatal) - -/** - * Thrown when the server undoes one or more client writes. Details on undone writes can be found in - * [writes]. - */ -public class CompensatingWriteException internal constructor( - message: String, - compensatingWrites: Array, - isFatal: Boolean -) : SyncException(message, isFatal) { - /** - * List of all the objects created that has been reversed as part of triggering this exception. - */ - public val writes: List = compensatingWrites.map { - CompensatingWriteInfo( - reason = it.reason, - objectType = it.objectName, - primaryKey = it.primaryKey.asPrimitiveRealmAnyOrElse { - // We currently don't support objects as primary keys, return a String value to avoid - // throwing within an exception. - RealmAny.create("Unknown") - }, - ) - } - - /** - * Class that describes the details for a reversed write. - */ - public inner class CompensatingWriteInfo( - /** - * Reason for the compensating write. - */ - public val reason: String, - - /** - * Name of the object class for which a write has been reversed. - */ - public val objectType: String, - - /** - * Primary key value for the object for which a write has been reversed. - */ - public val primaryKey: RealmAny? - ) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/DynamicMutableRealmExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/DynamicMutableRealmExt.kt deleted file mode 100644 index 564381309c..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/DynamicMutableRealmExt.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.UpdatePolicy -import io.realm.kotlin.dynamic.DynamicMutableRealm -import io.realm.kotlin.dynamic.DynamicMutableRealmObject -import io.realm.kotlin.dynamic.DynamicRealmObject -import io.realm.kotlin.internal.dynamic.DynamicMutableRealmImpl -import io.realm.kotlin.mongodb.annotations.ExperimentalAsymmetricSyncApi -import io.realm.kotlin.schema.RealmClassKind -import io.realm.kotlin.types.AsymmetricRealmObject - -/** - * Insert a dynamic version of a [AsymmetricRealmObject] into a realm. Since asymmetric objects are - * "write-only", it is not possible to access the managed data after it has been inserted. - * - * @param obj the asymmetric object to insert. - * @throws IllegalArgumentException if the object is not an asymmetric object, the object graph - * of [obj] either contains an object with a primary key value that already exists or an object from - * a previous version, or if a property does not match the underlying schema. - */ -@ExperimentalAsymmetricSyncApi -public fun DynamicMutableRealm.insert(obj: DynamicRealmObject) { - val kind: RealmClassKind? = (this as DynamicMutableRealmImpl).realmReference.owner.schema()[obj.type]?.kind - if (kind != RealmClassKind.ASYMMETRIC) { - throw IllegalArgumentException("Only asymmetric objects are supported, ${obj.type} is a $kind") - } - @Suppress("invisible_member", "invisible_reference") - val obj = io.realm.kotlin.internal.copyToRealm(configuration.mediator, realmReference, obj, UpdatePolicy.ERROR, mutableMapOf()) as DynamicMutableRealmObject - @Suppress("invisible_member", "invisible_reference") - ((obj as io.realm.kotlin.internal.dynamic.DynamicMutableRealmObjectImpl).io_realm_kotlin_objectReference!!.objectPointer).release() -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt deleted file mode 100644 index d387dbb624..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/FunctionsExt.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.Functions -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.exceptions.FunctionExecutionException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.internal.BsonEncoder -import io.realm.kotlin.mongodb.internal.FunctionsImpl -import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer -import kotlinx.serialization.KSerializer -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.Bson -import org.mongodb.kbson.serialization.EJson - -/** - * Invokes an Atlas function. - * - * Since the serialization engine [does not support third-party libraries yet](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md), there are some - * limitations in what types can be used as arguments and return types: - * - * - Primitives, Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, Array, Collection, and Map are valid argument types. - * - Results can only be deserialized to Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny and primitive types - * - * The Bson implementations for arrays or maps are [BsonArray] and [BsonDocument], and they can be - * used as valid return types. - * - * @param name name of the function to call. - * @param args arguments to the function. - * @param T the function return value type. - * @return result of the function call. - * - * @throws FunctionExecutionException if the function failed in some way. - * @throws ServiceException for other failures that can happen when communicating with App Services. - * See [AppException] for details. - */ -public suspend inline fun Functions.call( - name: String, - vararg args: Any? -): T = with(this as FunctionsImpl) { - val serializedEjsonArgs = Bson.toJson(BsonEncoder.encodeToBsonValue(args.toList())) - val encodedResult = callInternal(name, serializedEjsonArgs) - - BsonEncoder.decodeFromBsonValue( - resultClass = T::class, - bsonValue = Bson(encodedResult) - ) as T -} - -/** - * Invokes an Atlas function using the EJson encoder defined in [AppConfiguration.ejson]. - * - * **Note** This method supports full document serialization. The call arguments are defined with the builder - * [CallBuilder]. This same builder also allows to bind manually any argument or the return type to - * a specific serializer. Arguments and the return value will be encoded and decoded with [AppConfiguration.ejson]. - * - * ``` - * val dog: Dog = user.functions.call("RetrieveDog") { - * add("a parameter") - * add(1.5, FloatSerializer) // sets the serializer for this particular argument - * returnValueSerializer = DogSerializer // sets the serializer for the return type - * } - * ``` - * - * We cannot use a generic because: - * - There is no serializer available for Any. - * - any [KClass.serializer() is marked as Internal](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/serializer.html) - * - * @param name name of the function to call. - * @param callBuilderBlock code block that sets the call arguments and serializers. - * @param T the function return value type. - * @return result of the function call. - */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public suspend inline fun Functions.call( - name: String, - callBuilderBlock: CallBuilder.() -> Unit -): T = with(this as FunctionsImpl) { - CallBuilder(app.configuration.ejson) - .apply(callBuilderBlock) - .run { - val serializedEjsonArgs = Bson.toJson(arguments) - - val encodedResult = callInternal(name, serializedEjsonArgs) - - val returnValueSerializer = - returnValueSerializer - ?: ejson.serializersModule.serializerOrRealmBuiltInSerializer() - - ejson.decodeFromString(returnValueSerializer, encodedResult) - } -} - -/** - * Builder used to construct a call defining serializers for the different arguments and return value. - */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public class CallBuilder -@PublishedApi -internal constructor( - @PublishedApi - internal val ejson: EJson, -) { - /** - * Contains all given arguments transformed as [BsonValue]. The encoding is done on each [add] call - * as in that context we have type information from the reified type. - * - * Usually we would store the arguments in a `List` and would serialize just before invoking - * the call, but that would require to do a runtime look up of the argument serializers an operation - * that unfortunately is internal to kserializer and not stable cross all platforms. - */ - @PublishedApi - internal val arguments: BsonArray = BsonArray() - - /** - * Serializer that would be used to deserialize the returned value, null by default. - * - * If null, the return value will be deserialized using the embedded type serializer. Note that - * Realm collection types must be set as they don't have an embedded serializer, for example: - * - * ```kotlin - * CallBuilder> { - * returnValueSerializer = RealmListKSerializer(String.serializer()) - * } - * ``` - */ - public var returnValueSerializer: KSerializer? = null - - /** - * Adds an argument with the default serializer for its type to the function call. - * - * @param T argument type. - * @param argument value. - */ - public inline fun add(argument: T) { - add(argument, ejson.serializersModule.serializerOrRealmBuiltInSerializer()) - } - - /** - * Adds an argument with a user defined serializer to the function call. - * - * @param T argument type. - * @param argument value. - * @param serializer argument serializer. - */ - public inline fun add(argument: T, serializer: KSerializer) { - arguments.add(ejson.encodeToBsonValue(serializer, argument)) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt deleted file mode 100644 index e64b01c6b1..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoClientExt.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.mongodb.internal.MongoClientCollection -import io.realm.kotlin.mongodb.internal.MongoClientImpl -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.types.BaseRealmObject -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -/** - * Get a [MongoCollection] that exposes methods to retrieve and update data from the remote - * collection of objects of schema type [T]. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. - * - * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and - * primary keys with. Will default to the databases [EJson] instance. - * @param T the schema type indicating which for which remote entities of the collection will be - * serialized from and to. - * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [T] values. - */ -@ExperimentalKBsonSerializerApi -public inline fun MongoClient.collection(eJson: EJson? = null): MongoCollection { - @Suppress("invisible_reference", "invisible_member") - return MongoClientCollection(this as MongoClientImpl, io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(T::class).io_realm_kotlin_className, eJson ?: this.eJson) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt deleted file mode 100644 index 60cba26cf2..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MongoCollectionExt.kt +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.internal.MongoCollectionImpl -import io.realm.kotlin.mongodb.internal.decodeFromBsonValue -import io.realm.kotlin.mongodb.internal.decodeFromBsonValueList -import io.realm.kotlin.mongodb.internal.encodeToBsonValue -import io.realm.kotlin.mongodb.internal.toAny -import io.realm.kotlin.mongodb.mongo.MongoCollection -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonValue -import kotlin.jvm.JvmName - -/** - * Returns the number of documents in the collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - * @throws ServiceException if the underlying App Service HTTP requests fails. - */ -public suspend fun MongoCollection<*>.count(filter: BsonDocument? = null, limit: Long? = null): Long { - Validation.isType>(this) - return count(filter, limit) -} - -/** - * Retrieve a single object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @return the result of the remote `findOne` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -public suspend inline fun MongoCollection.findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): T? { - Validation.isType>(this) - val bsonValue: BsonValue = findOne(filter, projection, sort) - val decodeFromBsonValue: T? = decodeFromBsonValue(bsonValue) - return decodeFromBsonValue -} - -/** - * Retrieve multiple object from the remote collection. - * - * @param filter a filter to select specific documents. If `null` then no filtering will be done. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param limit an upper bound of the number of documents to consider. If `null` then no limit is - * applied. - * @return the result of the remote `find` invocation deserialized into a list of [T]-instances. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to `List`. - */ -public suspend inline fun MongoCollection.find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): List { - Validation.isType>(this) - return find(filter, projection, sort, limit).asArray().map { decodeFromBsonValue(it) } -} - -/** - * Execute an aggregate pipeline on the remote collection. - * - * @param pipeline a list of aggregation pipeline stages. - * @return the result of the remote `aggregate` invocation deserialized into a list of [T]-instances. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to `List`. - */ -public suspend inline fun MongoCollection<*>.aggregate(pipeline: List): List { - Validation.isType>(this) - return decodeFromBsonValueList(aggregate(pipeline)) -} - -/** - * Insert a single object into the remote collection. - * - * @param document the object to serialize and insert into the remote collection. - * @return the `_id` value of the document insert in the collection deserialized to the most appropriate type. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if [document] could not be serialized into a EJson document or if - * the App Service response could not be parsed into a reasonable type. - */ -public suspend inline fun MongoCollection.insertOne(document: T): Any { - Validation.isType>(this) - return insertOne(encodeToBsonValue(document).asDocument()).toAny() ?: throw ServiceException("No primary key for inserted document") -} - -/** - * Insert a list of object into the remote collection. - * - * @param documents the objects to serialize and insert into the remote collection. - * @param T the type of object that should be serializer and inserted to the collection. - * @param R the type that the returned `_id` values should be deserialized into. - * @return the `_id` values of the documents inserted in the collection deserialized to a [R]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if [documents] could not be serialized into a EJson document or if - * the App Service response could not be deserialized to `List`. - */ -@JvmName("insertManyTyped") -public suspend inline fun MongoCollection<*>.insertMany(documents: Collection): List { - Validation.isType>(this) - val bsonValues: List = insertMany(documents.map { encodeToBsonValue(it).asDocument() }) - return bsonValues.map { it.toAny() ?: throw ServiceException("Response should not contain null values: $bsonValues") } -} - -/** - * Delete a single object from the remote collection. - * - * @param filter a filter to specify the documents to delete. - * @return a boolean indicating if a document was deleted or not. - * @throws ServiceException if the underlying App Service HTTP requests fails. - */ -public suspend fun MongoCollection<*>.deleteOne(filter: BsonDocument): Boolean { - Validation.isType>(this) - return deleteOne(filter) -} - -/** - * Delete multiple objects from the remote collection. - * - * @param filter a filter to specify the documents to delete. - * @return the number of documents that have been deleted. - * @throws ServiceException if the underlying App Service HTTP requests fails. - */ -public suspend fun MongoCollection<*>.deleteMany(filter: BsonDocument): Long { - Validation.isType>(this) - return deleteMany(filter) -} - -/** - * Wrapper of results of an [updateOne] call. - * - * @param updated boolean indicating that a document was updated. - * @param upsertedId primary key of the new document if created. - */ -public data class UpdateOneResult(val updated: Boolean, val upsertedId: Any?) - -/** - * Update or insert a single object in the remote collection. - * - * @param filter a filter to select the document to update. - * @param update a BsonDocument specifying the updates that should be applied to the document. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @return the result of the `updateOne` operation. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to - * [UpdateOneResult]. - */ -public suspend inline fun MongoCollection<*>.updateOne( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateOneResult { - Validation.isType>(this) - return updateOne(filter, update, upsert).let { (updated, upsertedId) -> - UpdateOneResult(updated, upsertedId?.let { it.toAny() }) - } -} - -/** - * Wrapper of results of an [updateMany] call. - * - * @param modifiedCount number of documents that was updated by the operation. - * @param upsertedId primary key of the new document if created. - */ -public data class UpdateManyResult(val modifiedCount: Long, val upsertedId: Any?) - -/** - * Update multiple objects or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @return the result of the `updateMany` operation. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to - * [UpdateManyResult]. - */ -public suspend inline fun MongoCollection<*>.updateMany( - filter: BsonDocument, - update: BsonDocument, - upsert: Boolean = false -): UpdateManyResult { - Validation.isType>(this) - return updateMany(filter, update, upsert).let { (updatedCount, upsertedId) -> - UpdateManyResult(updatedCount, upsertedId?.toAny()) - } -} - -/** - * Find and update or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param update a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @return the result of the remote `findOneAndUpdate` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - Validation.isType>(this) - return decodeFromBsonValue(findOneAndUpdate(filter, update, projection, sort, upsert, returnNewDoc)) -} - -/** - * Find and replace or insert a single new object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param document a BsonDocument specifying the updates that should be applied to the documents. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @param upsert a boolean indicating if a new document should be inserted if the [filter] does not - * match any existing documents in the collection. - * @param returnNewDoc a boolean indicating whether to return the document before or after the update. - * @return the result of the remote `findOneAndReplace` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -@Suppress("LongParameterList") -public suspend inline fun MongoCollection.findOneAndReplace( - filter: BsonDocument, - document: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, -): T? { - Validation.isType>(this) - return decodeFromBsonValue(findOneAndReplace(filter, document, projection, sort, upsert, returnNewDoc)) -} - -/** - * Find and delete a single object in the remote collection. - * - * @param filter a filter to select the documents to update. - * @param projection a BsonDocument that describes which fields that are returned from the server. - * If `null` then all fields will be returned. - * @param sort a document describing one or more fields used to sort documents before selecting the - * single document to return. If `null` then no sorting will be applied. - * @return the result of the remote `findOneAndDelete` invocation deserialized into a [T]-instance. - * @throws ServiceException if the underlying App Service HTTP requests fails. - * @throws SerializationException if App Service response could not be deserialized to [T]. - */ -public suspend inline fun MongoCollection.findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, -): T? { - Validation.isType>(this) - return decodeFromBsonValue(findOneAndDelete(filter, projection, sort)) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MutableRealmExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MutableRealmExt.kt deleted file mode 100644 index f96da5dcc7..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/MutableRealmExt.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.MutableRealm -import io.realm.kotlin.UpdatePolicy -import io.realm.kotlin.internal.RealmObjectInternal -import io.realm.kotlin.mongodb.annotations.ExperimentalAsymmetricSyncApi -import io.realm.kotlin.types.AsymmetricRealmObject - -/** - * Insert an [AsymmetricRealmObject] into Realm. Since asymmetric objects are "write-only", it is - * not possible to access the managed data after it has been inserted. - * - * @param obj the object to insert. - * @throws IllegalArgumentException if the object graph of [obj] either contains an object - * with a primary key value that already exists or an object from a previous version. - */ -@ExperimentalAsymmetricSyncApi -public fun MutableRealm.insert(obj: T) { - @Suppress("invisible_member", "invisible_reference") - if (this is io.realm.kotlin.internal.InternalMutableRealm) { - val obj = io.realm.kotlin.internal.copyToRealm( - configuration.mediator, - realmReference, - obj, - UpdatePolicy.ERROR - ) - (obj as RealmObjectInternal).io_realm_kotlin_objectReference!!.objectPointer.release() - } else { - throw IllegalStateException("Calling insert() on $this is not supported.") - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmQueryExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmQueryExt.kt deleted file mode 100644 index 425ae1ca7f..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmQueryExt.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi -import io.realm.kotlin.mongodb.internal.createSubscriptionFromQuery -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.WaitForSync -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.types.RealmObject -import kotlin.time.Duration - -/** - * Automatically create a named [Subscription] from a query in the background and return the - * result of running the same query against the local Realm file. - * - * This is a more streamlined alternative to doing something like this: - * - * ``` - * fun suspend getData(realm: Realm): RealmResults { - * realm.subscriptions.update { bgRealm -> - * add("myquery", bgRealm.query()) - * } - * realm.subscriptions.waitForSynchronization() - * return realm.query().find() - * } - * ``` - * - * It is possible to define whether or not to wait for the server to send all data before - * running the local query. This is relevant as there might be delay from creating a subscription - * to the data being available on the device due to either latency or because a large dataset needs - * to be downloaded. - * - * The default behaviour is that the first time [subscribe] is called, the query result will not - * be returned until data has been downloaded from the server. On subsequent calls to [subscribe] - * for the same query, the query will run immediately on the local database while any updates - * are downloaded in the background. - * - * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet]. - * @param mode mode used to resolve the subscription. See [WaitForSync] for more details. - * @param timeout How long to wait for the server to return the objects defined by the subscription. - * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME]. - * @return The result of running the query against the local Realm file. The results returned will - * depend on which [mode] was used. - * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before - * a query result could be returned. - * @throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync. - * @throws io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException if the server did not - * accept the set of queries. The exact reason is found in the exception message. - */ -@ExperimentalFlexibleSyncApi -public suspend fun RealmQuery.subscribe( - name: String, - updateExisting: Boolean = false, - mode: WaitForSync = WaitForSync.FIRST_TIME, - timeout: Duration = Duration.INFINITE -): RealmResults { - return createSubscriptionFromQuery(this, name, updateExisting, mode, timeout) -} - -/** - * Automatically create an anonymous [Subscription] from a local query result in the background and - * return the result of re-running the same query against the Realm file. This behaves the same - * as creating a named variant by calling [subscribe]. See this method for details about the - * exact behavior. - * - * @param mode mode used to resolve the subscription. See [WaitForSync] for more details. - * @param timeout How long to wait for the server to return the objects defined by the subscription. - * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME]. - * @return The result of running the query against the local Realm file. The results returned will - * depend on which [mode] was used. - * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before - * a query result could be returned. - * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync. - */ -@ExperimentalFlexibleSyncApi -public suspend fun RealmQuery.subscribe( - mode: WaitForSync = WaitForSync.FIRST_TIME, - timeout: Duration = Duration.INFINITE -): RealmResults { - return createSubscriptionFromQuery(this, null, false, mode, timeout) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmResultsExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmResultsExt.kt deleted file mode 100644 index d2c3aeb16e..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/RealmResultsExt.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.WaitForSync -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.query.TRUE_PREDICATE -import io.realm.kotlin.types.RealmObject -import kotlin.time.Duration - -/** - * Automatically create a named [Subscription] from a local query result in the background and - * return the result of re-running the same query against the Realm file. - * - * This is a more streamlined alternative to doing something like this: - * - * ``` - * fun suspend getData(realm: Realm): RealmResults { - * val results = realm.query().find() - * realm.subscriptions.update { bgRealm -> - * add("myquery", results.query("")) - * } - * realm.subscriptions.waitForSynchronization() - * return realm.query().find() - * } - * ``` - * - * It is possible to define whether or not to wait for the server to send all data before - * running the local query. This is relevant as there might be delay from creating a subscription - * to the data being available on the device due to either latency or because a large dataset needs - * be downloaded. - * - * The default behaviour is that the first time [subscribe] is called, the query result will not - * be returned until data has been downloaded from the server. On subsequent calls to [subscribe] - * for the same query, the query will run immediately on the local database while any updates - * are downloaded in the background. - * - * @param name name of the subscription. This can be used to identify it later in the [SubscriptionSet]. - * @param mode mode used to resolve the subscription. See [WaitForSync] for more details. - * @param timeout How long to wait for the server to return the objects defined by the subscription. - * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME]. - * @return The result of running the query against the local Realm file. The results returned will - * depend on which [mode] was used. - * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before - * a query result could be returned. - * @throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync. - * @throws io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException if the server did not - * accept the set of queries. The exact reason is found in the exception message. - */ -@ExperimentalFlexibleSyncApi -public suspend fun RealmResults.subscribe( - name: String, - updateExisting: Boolean = false, - mode: WaitForSync = WaitForSync.FIRST_TIME, - timeout: Duration = Duration.INFINITE -): RealmResults { - val query: RealmQuery = this.query(TRUE_PREDICATE) - return query.subscribe(name, updateExisting, mode, timeout) -} - -/** - * Automatically create an anonymous [Subscription] from a local query result in the background and - * return the result of re-running the same query against the Realm file. This behaves the same - * as creating a named variant by calling [subscribe]. See this method for details about the - * exact behavior. - * - * @param mode mode used to resolve the subscription. See [WaitForSync] for more details. - * @param timeout How long to wait for the server to return the objects defined by the subscription. - * This is only relevant for [WaitForSync.ALWAYS] and [WaitForSync.FIRST_TIME]. - * @return The result of running the query against the local Realm file. The results returned will - * depend on which [mode] was used. - * @throws kotlinx.coroutines.TimeoutCancellationException if the specified timeout was hit before - * a query result could be returned. - * @Throws IllegalStateException if this method is called on a Realm that isn't using Flexible Sync. - */ -@ExperimentalFlexibleSyncApi -public suspend fun RealmResults.subscribe( - mode: WaitForSync = WaitForSync.FIRST_TIME, - timeout: Duration = Duration.INFINITE -): RealmResults { - val query: RealmQuery = this.query(TRUE_PREDICATE) - return query.subscribe(mode, timeout) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt deleted file mode 100644 index 5846b130dc..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/ext/UserExt.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.ext - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.internal.UserImpl -import io.realm.kotlin.mongodb.internal.serializerOrRealmBuiltInSerializer -import kotlinx.serialization.KSerializer -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.Bson -import org.mongodb.kbson.serialization.EJson - -/** - * Returns the profile for this user as [BsonDocument]. - * - * @return The profile for this user. - */ -@Suppress("NOTHING_TO_INLINE") -public inline fun User.profileAsBsonDocument(): BsonDocument = - (this as UserImpl).profileInternal { ejson -> - Bson(ejson) as BsonDocument - } - -/** - * Return the custom user data associated with the user in the Realm App as [BsonDocument]. - * - * The data is only refreshed when the user's access token is refreshed or when explicitly - * calling [User.refreshCustomData]. - * - * @return The custom user data associated with the user. - */ -@Suppress("NOTHING_TO_INLINE") -public inline fun User.customDataAsBsonDocument(): BsonDocument? = - (this as UserImpl).customDataInternal { ejson -> - Bson(ejson) as BsonDocument - } - -/** - * Returns the profile for this user as a [T]. - * - * **Note** This method supports full document serialization. The user profile will be deserialized with - * [serializer] and decoded with [AppConfiguration.ejson]. - * - * @param T the type to decoded the user profile. - * @param serializer deserialization strategy for [T]. - * @return The profile for this user. - */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public inline fun User.profile(serializer: KSerializer = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T = - (this as UserImpl).app.configuration.ejson.let { ejson: EJson -> - profileInternal { ejsonEncodedProfile -> - ejson.decodeFromString(serializer, ejsonEncodedProfile) - } - } - -/** - * Returns the custom user data associated with the user in the Realm App as [T]. - * - * The data is only refreshed when the user's access token is refreshed or when explicitly - * calling [User.refreshCustomData]. - * - * **Note** This method supports full document serialization. Custom data will be deserialized - * with [serializer] and decoded with [AppConfiguration.ejson]. - * - * @param T the type to decoded the user custom data. - * @param serializer deserialization strategy for [T]. - * @return The custom user data associated with the user. - */ -@ExperimentalRealmSerializerApi -@OptIn(ExperimentalKBsonSerializerApi::class) -public inline fun User.customData(serializer: KSerializer = (this as UserImpl).app.configuration.ejson.serializersModule.serializerOrRealmBuiltInSerializer()): T? = - (this as UserImpl).app.configuration.ejson.let { ejson: EJson -> - customDataInternal { ejsonEncodedCustomData -> - ejson.decodeFromString( - deserializer = serializer, - string = ejsonEncodedCustomData - ) - } - } diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/ApiKeyAuthImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/ApiKeyAuthImpl.kt deleted file mode 100644 index f7ff26fc29..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/ApiKeyAuthImpl.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.sync.ApiKeyWrapper -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.auth.ApiKey -import io.realm.kotlin.mongodb.auth.ApiKeyAuth -import io.realm.kotlin.mongodb.exceptions.ServiceException -import kotlinx.coroutines.channels.Channel -import org.mongodb.kbson.BsonObjectId - -internal class ApiKeyAuthImpl(override val app: AppImpl, override val user: UserImpl) : ApiKeyAuth { - - private fun unwrap(apiKeyData: ApiKeyWrapper): ApiKey { - return ApiKey( - apiKeyData.id, - apiKeyData.value, - apiKeyData.name, - !apiKeyData.disabled - ) - } - - override suspend fun create(name: String): ApiKey { - try { - Channel>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_create_apikey( - app.nativePointer, - user.nativePointer, - name, - channelResultCallback(channel) { apiKeyData -> - unwrap(apiKeyData) - } - ) - return channel.receive() - .getOrThrow() - } - } catch (ex: ServiceException) { - // TODO in the future, change to comparing error codes rather than messages - if (ex.errorCode == ErrorCode.RLM_ERR_INVALID_PARAMETER || - ex.message?.contains("[Service][Unknown(4351)] 'name' is a required string.") == true // FIXME Unknown because the server does not provide with a error code - ) { - throw IllegalArgumentException(ex.message!!) - } else { - throw ex - } - } - } - - override suspend fun delete(id: BsonObjectId) { - try { - Channel>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_delete_apikey( - app.nativePointer, - user.nativePointer, - id, - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive().getOrThrow() - } - } catch (ex: ServiceException) { - if (ex.errorCode == ErrorCode.RLM_ERR_API_KEY_NOT_FOUND) { - // No-op - } else { - throw ex - } - } - } - - override suspend fun disable(id: BsonObjectId) { - try { - Channel>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_disable_apikey( - app.nativePointer, - user.nativePointer, - id, - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } catch (ex: ServiceException) { - if (ex.errorCode == ErrorCode.RLM_ERR_API_KEY_NOT_FOUND) { - throw IllegalArgumentException(ex.message!!) - } else { - throw ex - } - } - } - - override suspend fun enable(id: BsonObjectId) { - try { - Channel>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_enable_apikey( - app.nativePointer, - user.nativePointer, - id, - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } catch (ex: ServiceException) { - if (ex.errorCode == ErrorCode.RLM_ERR_API_KEY_NOT_FOUND) { - throw IllegalArgumentException(ex.message!!) - } else { - throw ex - } - } - } - - override suspend fun fetch(id: BsonObjectId): ApiKey? { - try { - Channel>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_fetch_apikey( - app.nativePointer, - user.nativePointer, - id, - channelResultCallback(channel) { apiKeyData: ApiKeyWrapper -> - unwrap(apiKeyData) - } - ) - return channel.receive() - .getOrThrow() - } - } catch (ex: ServiceException) { - if (ex.errorCode == ErrorCode.RLM_ERR_API_KEY_NOT_FOUND) { - return null - } else { - throw ex - } - } - } - - override suspend fun fetchAll(): List { - Channel>>(1).use { channel -> - RealmInterop.realm_app_user_apikey_provider_client_fetch_apikeys( - app.nativePointer, - user.nativePointer, - channelResultCallback, List>(channel) { apiKeys: Array -> - val result = mutableListOf() - apiKeys.map { apiKeydata: ApiKeyWrapper -> - result.add( - unwrap(apiKeydata) - ) - } - result - } - ) - return channel.receive() - .getOrThrow() - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt deleted file mode 100644 index fe6ce4d9a2..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppConfigurationImpl.kt +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.internal.SDK_VERSION -import io.realm.kotlin.internal.interop.RealmAppConfigurationPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSyncClientConfigurationPointer -import io.realm.kotlin.internal.interop.SyncConnectionParams -import io.realm.kotlin.internal.interop.sync.MetadataMode -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.platform.DEVICE_MANUFACTURER -import io.realm.kotlin.internal.platform.DEVICE_MODEL -import io.realm.kotlin.internal.platform.OS_VERSION -import io.realm.kotlin.internal.platform.RUNTIME -import io.realm.kotlin.internal.platform.RUNTIME_VERSION -import io.realm.kotlin.internal.platform.appFilesDirectory -import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.internal.util.DispatcherHolder -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.AppConfiguration.Companion.DEFAULT_BASE_URL -import io.realm.kotlin.mongodb.HttpLogObfuscator -import io.realm.kotlin.mongodb.sync.SyncTimeoutOptions -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -// TODO Public due to being a transitive dependency to AppImpl -@Suppress("LongParameterList") -public class AppConfigurationImpl @OptIn(ExperimentalKBsonSerializerApi::class) constructor( - override val appId: String, - override val baseUrl: String = DEFAULT_BASE_URL, - override val encryptionKey: ByteArray?, - private val appNetworkDispatcherFactory: CoroutineDispatcherFactory, - internal val networkTransportFactory: (dispatcher: DispatcherHolder) -> NetworkTransport, - private val websocketTransport: WebSocketTransport?, - override val metadataMode: MetadataMode, - override val syncRootDirectory: String, - override val appName: String?, - override val appVersion: String?, - internal val bundleId: String, - override val ejson: EJson, - override val httpLogObfuscator: HttpLogObfuscator?, - override val customRequestHeaders: Map, - override val authorizationHeaderName: String, - override val enableSessionMultiplexing: Boolean, - override val syncTimeoutOptions: SyncTimeoutOptions, - public val logger: ContextLogger, -) : AppConfiguration { - - /** - * Since the app configuration holds a reference to a network transport we want to delay - * construction of it to as late as possible. - * - * Thus this method should only be called from [AppImpl] and will create both a native - * AppConfiguration and App at the same time. - */ - public fun createNativeApp(): AppResources { - // Create a new network transport for each App instance. This which allow the App to control - // the lifecycle of any threadpools created by the network transport. Also, there should - // be no reason for people to have multiple app instances for the same app, so the net - // effect should be the same - val appDispatcher = appNetworkDispatcherFactory.create() - val networkTransport = networkTransportFactory(appDispatcher) - val appConfigPointer: RealmAppConfigurationPointer = - initializeRealmAppConfig(bundleId, networkTransport) - var applicationInfo: String? = null - // Define user agent strings sent when making the WebSocket connection to Device Sync - if (appName != null || appVersion == null) { - val info = StringBuilder() - appName?.let { info.append(appName) } ?: info.append("Unknown") - info.append("/") - appVersion?.let { info.append(appVersion) } ?: info.append("Unknown") - applicationInfo = info.toString() - } - val sdkInfo = "RealmKotlin/$SDK_VERSION" - - val synClientConfig: RealmSyncClientConfigurationPointer = initializeSyncClientConfig( - appConfigPointer, - websocketTransport, - sdkInfo, - applicationInfo.toString() - ) - - return AppResources( - appDispatcher, - networkTransport, - websocketTransport, - RealmInterop.realm_app_get( - appConfigPointer, - appFilesDirectory() - ) - ) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as AppConfigurationImpl - - if (appId != (other.appId)) return false - if (baseUrl != (other.baseUrl)) return false - if (metadataMode != (other.metadataMode)) return false - return true - } - - override fun hashCode(): Int { - var result = appId.hashCode() - result = 31 * result + baseUrl.hashCode() - result = 31 * result + metadataMode.hashCode() - return result - } - - // Only freeze anything after all properties are setup as this triggers freezing the actual - // AppConfigurationImpl instance itself - private fun initializeRealmAppConfig( - bundleId: String, - networkTransport: NetworkTransport - ): RealmAppConfigurationPointer { - val appConfigPtr = RealmInterop.realm_app_config_new( - appId = appId, - baseUrl = baseUrl, - networkTransport = RealmInterop.realm_network_transport_new(networkTransport), - connectionParams = SyncConnectionParams( - sdkVersion = SDK_VERSION, - bundleId = bundleId, - platformVersion = OS_VERSION, - device = DEVICE_MANUFACTURER, - deviceVersion = DEVICE_MODEL, - framework = RUNTIME, - frameworkVersion = RUNTIME_VERSION - ) - ) - RealmInterop.realm_app_config_set_base_file_path(appConfigPtr, syncRootDirectory) - RealmInterop.realm_app_config_set_metadata_mode(appConfigPtr, metadataMode) - encryptionKey?.let { - RealmInterop.realm_app_config_set_metadata_encryption_key( - appConfigPtr, - it - ) - } - return appConfigPtr - } - - private fun initializeSyncClientConfig( - appConfigPointer: RealmAppConfigurationPointer, - webSocketTransport: WebSocketTransport?, - sdkInfo: String?, - applicationInfo: String? - ): RealmSyncClientConfigurationPointer = - RealmInterop.realm_app_config_get_sync_client_config(appConfigPointer) - .also { syncClientConfig -> - // Initialize client configuration first - RealmInterop.realm_sync_client_config_set_default_binding_thread_observer(syncClientConfig, appId) - - sdkInfo?.let { - RealmInterop.realm_sync_client_config_set_user_agent_binding_info( - syncClientConfig, - it - ) - } - - applicationInfo?.let { - RealmInterop.realm_sync_client_config_set_user_agent_application_info( - syncClientConfig, - it - ) - } - - // Setup multiplexing - RealmInterop.realm_sync_client_config_set_multiplex_sessions(syncClientConfig, enableSessionMultiplexing) - - // Setup SyncTimeoutOptions - RealmInterop.realm_sync_client_config_set_connect_timeout( - syncClientConfig, - syncTimeoutOptions.connectTimeout.inWholeMilliseconds.toULong() - ) - RealmInterop.realm_sync_client_config_set_connection_linger_time( - syncClientConfig, - syncTimeoutOptions.connectionLingerTime.inWholeMilliseconds.toULong() - ) - RealmInterop.realm_sync_client_config_set_ping_keepalive_period( - syncClientConfig, - syncTimeoutOptions.pingKeepAlivePeriod.inWholeMilliseconds.toULong() - ) - RealmInterop.realm_sync_client_config_set_pong_keepalive_timeout( - syncClientConfig, - syncTimeoutOptions.pongKeepAlivePeriod.inWholeMilliseconds.toULong() - ) - RealmInterop.realm_sync_client_config_set_fast_reconnect_limit( - syncClientConfig, - syncTimeoutOptions.fastReconnectLimit.inWholeMilliseconds.toULong() - ) - - // Use platform networking for Sync client WebSockets if provided - webSocketTransport?.let { - RealmInterop.realm_sync_set_websocket_transport(syncClientConfig, it) - } - } - - internal companion object { - internal fun create(appId: String, bundleId: String): AppConfiguration = - AppConfiguration.Builder(appId).build(bundleId) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt deleted file mode 100644 index 8bc6ee91c4..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AppImpl.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmAppPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmUserPointer -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.toDuration -import io.realm.kotlin.internal.util.DispatcherHolder -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.AuthenticationChange -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.annotations.ExperimentalEdgeServerApi -import io.realm.kotlin.mongodb.auth.EmailPasswordAuth -import io.realm.kotlin.mongodb.sync.Sync -import io.realm.kotlin.types.RealmInstant -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -public data class AppResources( - val dispatcherHolder: DispatcherHolder, - val networkTransport: NetworkTransport, - val websocketTransport: WebSocketTransport?, - val realmAppPointer: RealmAppPointer -) - -// TODO Public due to being a transitive dependency to UserImpl -public class AppImpl( - override val configuration: AppConfigurationImpl, -) : App { - - internal val nativePointer: RealmAppPointer - internal val appNetworkDispatcher: DispatcherHolder - private val networkTransport: NetworkTransport - private val websocketTransport: WebSocketTransport? - - private var lastOnlineStateReported: Duration? = null - private var lastConnectedState: Boolean? = null // null = unknown, true = connected, false = disconnected - @Suppress("MagicNumber") - private val reconnectThreshold = 5.seconds - - @ExperimentalEdgeServerApi - override val baseUrl: String - get() = RealmInterop.realm_app_get_base_url(nativePointer) - - @ExperimentalEdgeServerApi - override suspend fun updateBaseUrl(baseUrl: String?) { - Channel>(1).use { channel -> - RealmInterop.realm_app_update_base_url( - app = nativePointer, - baseUrl = baseUrl?.trimEnd('/'), // trailing slashes are not handled properly in core - callback = channelResultCallback(channel) { - // No-op - } - ) - channel.receive().getOrThrow() - } - } - - @Suppress("invisible_member", "invisible_reference", "MagicNumber") - private val connectionListener = NetworkStateObserver.ConnectionListener { connectionAvailable -> - // In an ideal world, we would be able to reliably detect the network coming and - // going. Unfortunately that does not seem to be case (at least on Android). - // - // So instead of assuming that we have always detect the device going offline first, - // we just tell Realm Core to reconnect when we detect the network has come back. - // - // Due to the way network interfaces are re-enabled on Android, we might see multiple - // "isOnline" messages in short order. So in order to prevent resetting the network - // too often we throttle messages, so a reconnect can only happen ever 5 seconds. - configuration.logger.debug("Network state change detected. ConnectionAvailable = $connectionAvailable") - val now: Duration = RealmInstant.now().toDuration() - if (connectionAvailable && (lastOnlineStateReported == null || now.minus(lastOnlineStateReported!!) > reconnectThreshold) - ) { - configuration.logger.info("Trigger network reconnect.") - try { - sync.reconnect() - } catch (ex: Exception) { - configuration.logger.error(ex.toString()) - } - lastOnlineStateReported = now - } - lastConnectedState = connectionAvailable - } - - // Allow some delay between events being reported and them being consumed. - // When the (somewhat arbitrary) limit is hit, we will throw an exception, since we assume the - // consumer is doing something wrong. This is also needed because we don't - // want to block user events like logout, delete and remove. - @Suppress("MagicNumber") - private val authenticationChangeFlow = MutableSharedFlow( - replay = 0, - extraBufferCapacity = 8, - onBufferOverflow = BufferOverflow.SUSPEND - ) - - init { - val appResources: AppResources = configuration.createNativeApp() - appNetworkDispatcher = appResources.dispatcherHolder - networkTransport = appResources.networkTransport - websocketTransport = appResources.websocketTransport - nativePointer = appResources.realmAppPointer - NetworkStateObserver.addListener(connectionListener) - } - - override val emailPasswordAuth: EmailPasswordAuth by lazy { EmailPasswordAuthImpl(nativePointer) } - - override val currentUser: User? - get() = RealmInterop.realm_app_get_current_user(nativePointer) - ?.let { UserImpl(it, this) } - override val sync: Sync by lazy { SyncImpl(nativePointer) } - - override fun allUsers(): List = - RealmInterop.realm_app_get_all_users(nativePointer) - .map { ptr: RealmUserPointer -> - UserImpl(ptr, this) - } - - override suspend fun login(credentials: Credentials): User { - // suspendCoroutine doesn't allow freezing callback capturing continuation - // ... and cannot be resumed on another thread (we probably also want to guarantee that we - // are resuming on the same dispatcher), so run our own implementation using a channel - Channel>(1).use { channel -> - RealmInterop.realm_app_log_in_with_credentials( - nativePointer, - when (credentials) { - is CredentialsImpl -> credentials.nativePointer - is CustomEJsonCredentialsImpl -> credentials.nativePointer(this) - else -> throw IllegalArgumentException("Argument 'credentials' is of an invalid type ${credentials::class.simpleName}") - }, - channelResultCallback(channel) { userPointer -> - UserImpl(userPointer, this) - } - ) - return channel.receive() - .getOrThrow().also { user: User -> - reportAuthenticationChange(user, User.State.LOGGED_IN) - } - } - } - - override fun switchUser(user: User) { - Validation.isType(user) - RealmInterop.realm_app_switch_user(this.nativePointer, user.nativePointer) - } - - internal fun reportAuthenticationChange(user: User, change: User.State) { - val event: AuthenticationChange = when (change) { - User.State.LOGGED_OUT -> LoggedOutImpl(user) - User.State.LOGGED_IN -> LoggedInImpl(user) - User.State.REMOVED -> RemovedImpl(user) - } - if (!authenticationChangeFlow.tryEmit(event)) { - throw IllegalStateException( - "It wasn't possible to emit authentication changes " + - "because a consuming flow was blocked. Increase dispatcher processing resources " + - "or buffer `App.authenticationChangeAsFlow()` with buffer(...)." - ) - } - } - - override fun authenticationChangeAsFlow(): Flow { - return authenticationChangeFlow - } - - override fun close() { - // The native App instance is what keeps the underlying SyncClient thread alive. So closing - // it will close the Sync thread and close any network dispatchers. - // - // This is not required as the pointers will otherwise be released by the GC, but it can - // be beneficial in order to reason about the lifecycle of the Sync thread and dispatchers. - networkTransport.close() - nativePointer.release() - NetworkStateObserver.removeListener(connectionListener) - } - - internal companion object { - // This method is used to inject bundleId to the sync configuration. The - // SyncLoweringExtension is replacing calls to App.create(appId) with calls to this method. - internal fun create(appId: String, bundleId: String): App { - Validation.checkEmpty(appId, "appId") - return App.create(AppConfiguration.Builder(appId).build(bundleId)) - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationChangeImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationChangeImpl.kt deleted file mode 100644 index 4f2e880d5e..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationChangeImpl.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.mongodb.LoggedIn -import io.realm.kotlin.mongodb.LoggedOut -import io.realm.kotlin.mongodb.Removed -import io.realm.kotlin.mongodb.User - -internal class LoggedInImpl(override val user: User) : LoggedIn -internal class LoggedOutImpl(override val user: User) : LoggedOut -internal class RemovedImpl(override val user: User) : Removed diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationProviderImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationProviderImpl.kt deleted file mode 100644 index 0c7354c6db..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/AuthenticationProviderImpl.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.sync.AuthProvider -import io.realm.kotlin.mongodb.AuthenticationProvider - -internal class AuthenticationProviderImpl private constructor() { - internal companion object { - internal fun fromId(id: AuthProvider): AuthenticationProvider { - for (value in AuthenticationProvider.values()) { - if (fromNativeProvider(id) == value) { - return value - } - } - error("Unknown authentication provider: $id") - } - - private fun fromNativeProvider(provider: AuthProvider): AuthenticationProvider? { - return when (provider) { - // Collapse both anonymous providers under the same category to avoid exposing both to the public API - AuthProvider.RLM_AUTH_PROVIDER_ANONYMOUS, - AuthProvider.RLM_AUTH_PROVIDER_ANONYMOUS_NO_REUSE -> AuthenticationProvider.ANONYMOUS - AuthProvider.RLM_AUTH_PROVIDER_FACEBOOK -> AuthenticationProvider.FACEBOOK - AuthProvider.RLM_AUTH_PROVIDER_GOOGLE -> AuthenticationProvider.GOOGLE - AuthProvider.RLM_AUTH_PROVIDER_APPLE -> AuthenticationProvider.APPLE - AuthProvider.RLM_AUTH_PROVIDER_CUSTOM -> AuthenticationProvider.JWT - AuthProvider.RLM_AUTH_PROVIDER_EMAIL_PASSWORD -> AuthenticationProvider.EMAIL_PASSWORD - AuthProvider.RLM_AUTH_PROVIDER_API_KEY -> AuthenticationProvider.API_KEY - AuthProvider.RLM_AUTH_PROVIDER_FUNCTION -> AuthenticationProvider.CUSTOM_FUNCTION - else -> null - } - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BaseSubscriptionSetImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BaseSubscriptionSetImpl.kt deleted file mode 100644 index 5f8bd03bec..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BaseSubscriptionSetImpl.kt +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.internal.BaseRealmImpl -import io.realm.kotlin.internal.interop.RealmBaseSubscriptionSetPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSubscriptionPointer -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.mongodb.sync.BaseSubscriptionSet -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSetState -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmObject - -internal abstract class BaseSubscriptionSetImpl( - protected val realm: T, -) : BaseSubscriptionSet { - - protected abstract val nativePointer: RealmBaseSubscriptionSetPointer - - protected abstract fun getIteratorSafePointer(): RealmBaseSubscriptionSetPointer - - protected fun checkClosed() { - (realm as BaseRealmImpl).realmReference.checkClosed() - } - - @Suppress("invisible_reference", "invisible_member") - override fun findByQuery(query: RealmQuery): Subscription? { - val queryPointer = (query as io.realm.kotlin.internal.query.ObjectQuery).queryPointer - return nativePointer.let { subscriptionSetPointer: RealmBaseSubscriptionSetPointer -> - val subscriptionPointer: RealmSubscriptionPointer? = RealmInterop.realm_sync_find_subscription_by_query( - subscriptionSetPointer, - queryPointer - ) - if (subscriptionPointer == null) - null - else - SubscriptionImpl(realm, subscriptionSetPointer, subscriptionPointer) - } - } - - override fun findByName(name: String): Subscription? { - val sub: RealmSubscriptionPointer? = RealmInterop.realm_sync_find_subscription_by_name( - nativePointer, - name - ) - return if (sub == null) null else SubscriptionImpl(realm, nativePointer, sub) - } - - override val state: SubscriptionSetState - get() { - val state = RealmInterop.realm_sync_subscriptionset_state(nativePointer) - return stateFrom(state) - } - - override val errorMessage: String? - get() = RealmInterop.realm_sync_subscriptionset_error_str(nativePointer) - - override val size: Int - get() = RealmInterop.realm_sync_subscriptionset_size(nativePointer).toInt() - - override fun iterator(): Iterator { - // We want to keep iteration stable even if a SubscriptionSet is refreshed - // during iteration. In order to do so, the iterator needs to own the pointer. - // But since here doesn't seem to be a way to clone a subscription set at a - // given version we use the latest version instead. - // - // This means there is small chance the set of subscriptions is different - // than the one you called `iterator` on, but since that point to a race - // condition in the users logic, we accept it. - // - // For MutableSubscriptionSets, we just re-use the pointer as there is no - // API to refresh the set. It is still possible to get odd results if you - // add subscriptions during iteration, but this is no different than any - // other iterator. - val iteratorPointer = getIteratorSafePointer() - - return object : Iterator { - private val nativePointer: RealmBaseSubscriptionSetPointer = iteratorPointer - private var cursor = 0L - private val size: Long = RealmInterop.realm_sync_subscriptionset_size(nativePointer) - - override fun hasNext(): Boolean { - return cursor < size - } - - override fun next(): Subscription { - if (cursor >= size) { - throw NoSuchElementException( - "Iterator has no more elements. " + - "Tried index " + cursor + ". Size is " + size + "." - ) - } - val ptr = RealmInterop.realm_sync_subscription_at(nativePointer, cursor) - cursor++ - return SubscriptionImpl(realm, nativePointer, ptr) - } - } - } - - internal companion object { - internal fun stateFrom(coreState: CoreSubscriptionSetState): SubscriptionSetState { - return when (coreState) { - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_UNCOMMITTED -> - SubscriptionSetState.UNCOMMITTED - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_PENDING -> - SubscriptionSetState.PENDING - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_BOOTSTRAPPING -> - SubscriptionSetState.BOOTSTRAPPING - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_COMPLETE -> - SubscriptionSetState.COMPLETE - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_ERROR -> - SubscriptionSetState.ERROR - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_SUPERSEDED -> - SubscriptionSetState.SUPERSEDED - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_AWAITING_MARK -> - SubscriptionSetState.AWAITING_MARK - else -> TODO("Unsupported state: $coreState") - } - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt deleted file mode 100644 index 87f2daf2dd..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/BsonEncoder.kt +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.internal.toDuration -import io.realm.kotlin.internal.toRealmInstant -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmUUID -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBinarySubType -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDBPointer -import org.mongodb.kbson.BsonDateTime -import org.mongodb.kbson.BsonDecimal128 -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonDouble -import org.mongodb.kbson.BsonElement -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonInvalidOperationException -import org.mongodb.kbson.BsonJavaScript -import org.mongodb.kbson.BsonJavaScriptWithScope -import org.mongodb.kbson.BsonMaxKey -import org.mongodb.kbson.BsonMinKey -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonNumber -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.BsonRegularExpression -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonSymbol -import org.mongodb.kbson.BsonTimestamp -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonUndefined -import org.mongodb.kbson.BsonValue -import kotlin.reflect.KClass -import kotlin.time.Duration.Companion.milliseconds - -/** - * Bson encoder that avoids using any experimental `KSerializer` APIs. To maximize compatibility it - * only supports a limited type set: - * - Primitives, Realm, Bson, Collections and Map types for encoding. - * - Primitives, Realm and Bson types for decoding. - */ -@PublishedApi -internal object BsonEncoder { - /** - * Encodes a given value into a [BsonValue]. Only primitives, Realm, Bson, collections and maps types - * are supported. - * - * @param value value to encode. - * @return [BsonValue] representing [value]. - */ - @PublishedApi - internal fun encodeToBsonValue(value: Any?): BsonValue = toBsonValue(value) - - /** - * Decodes a [BsonValue] into a value of the [resultClass] class. Only primitives, Realm, Bson types are supported. - * - * @param resultClass class of the decoded value. - * @param bsonValue value to decode. - * @return decoded value. - */ - @PublishedApi - @Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth") - internal fun decodeFromBsonValue( - resultClass: KClass<*>?, - bsonValue: BsonValue, - ): Any? { - return when { - resultClass == null || bsonValue == BsonNull && resultClass != BsonNull::class -> null - else -> { - try { - when (resultClass) { - Byte::class -> { - deserializeNumber(bsonValue) { - it.intValue().toByte() - } - } - Short::class -> { - deserializeNumber(bsonValue) { - it.intValue().toShort() - } - } - Int::class -> { - deserializeNumber(bsonValue) { - it.intValue() - } - } - Long::class -> { - deserializeNumber(bsonValue) { - it.longValue() - } - } - Float::class -> { - deserializeNumber(bsonValue) { - it.doubleValue().toFloat() - } - } - Double::class -> { - deserializeNumber(bsonValue) { - it.doubleValue() - } - } - Boolean::class -> { - bsonValue.asBoolean().value - } - String::class -> { - bsonValue.asString().value - } - Char::class -> { - bsonValue.asString().value[0] - } - ByteArray::class -> { - val bsonBinary = bsonValue.asBinary() - require(bsonBinary.type == BsonBinarySubType.BINARY.value) { - "A 'BsonBinary' with subtype 'BsonBinarySubType.BINARY' is required to deserialize a 'ByteArray'." - } - bsonBinary.data - } - BsonArray::class -> bsonValue.asArray() - BsonBinary::class -> bsonValue.asBinary() - BsonBoolean::class -> bsonValue.asBoolean() - BsonDBPointer::class -> bsonValue.asDBPointer() - BsonDateTime::class -> bsonValue.asDateTime() - BsonDecimal128::class -> bsonValue.asDecimal128() - BsonDocument::class -> bsonValue - BsonDouble::class -> BsonDouble(bsonValue.asNumber().doubleValue()) - BsonInt32::class -> BsonInt32(bsonValue.asNumber().intValue()) - BsonInt64::class -> BsonInt64(bsonValue.asNumber().longValue()) - BsonJavaScript::class -> bsonValue.asJavaScript() - BsonJavaScriptWithScope::class -> bsonValue.asJavaScriptWithScope() - BsonMaxKey::class -> bsonValue.asBsonMaxKey() - BsonMinKey::class -> bsonValue.asBsonMinKey() - BsonNull::class -> bsonValue.asBsonNull() - BsonObjectId::class -> bsonValue.asObjectId() - BsonRegularExpression::class -> bsonValue.asRegularExpression() - BsonString::class -> bsonValue.asString() - BsonSymbol::class -> bsonValue.asSymbol() - BsonTimestamp::class -> bsonValue.asTimestamp() - BsonUndefined::class -> bsonValue.asBsonUndefined() - BsonValue::class -> bsonValue - MutableRealmInt::class -> { - deserializeNumber(bsonValue) { - MutableRealmInt.create(it.longValue()) - } - } - RealmUUID::class -> { - val bsonBinary = bsonValue.asBinary() - require(bsonBinary.type == BsonBinarySubType.UUID_STANDARD.value) { - "A 'BsonBinary' with subtype 'BsonBinarySubType.UUID_STANDARD' is required to deserialize a 'RealmUUID'" - } - RealmUUID.from(bsonBinary.data) - } - RealmInstant::class -> { - bsonValue.asDateTime().value.milliseconds.toRealmInstant() - } - RealmAny::class -> { - when (bsonValue.bsonType) { - BsonType.BOOLEAN -> RealmAny.create(bsonValue.asBoolean().value) - BsonType.INT32 -> RealmAny.create(bsonValue.asInt32().value) - BsonType.INT64 -> RealmAny.create(bsonValue.asInt64().value) - BsonType.STRING -> RealmAny.create(bsonValue.asString().value) - BsonType.DOUBLE -> RealmAny.create(bsonValue.asDouble().value) - BsonType.BINARY -> { - with(bsonValue.asBinary()) { - when (this.type) { - BsonBinarySubType.UUID_STANDARD.value -> - RealmAny.create(RealmUUID.Companion.from(this.data)) - else -> RealmAny.create(this.data) - } - } - } - BsonType.OBJECT_ID -> RealmAny.create(bsonValue.asObjectId()) - BsonType.DATE_TIME -> RealmAny.create(bsonValue.asDateTime().value) - else -> throw IllegalArgumentException("Cannot decode a ${bsonValue.bsonType} into RealmAny.") - } - } - else -> { - throw IllegalArgumentException("Unsupported type '${resultClass.simpleName}'. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, and primitives are valid decoding types.") - } - } - } catch (e: BsonInvalidOperationException) { - throw BsonInvalidOperationException("Cannot decode BsonValue \"$bsonValue\" of type ${bsonValue.bsonType} into ${resultClass.simpleName}", e) - } - } - } - } - - private inline fun deserializeNumber( - bsonValue: BsonValue, - block: (BsonNumber) -> T - ): T { - return block(bsonValue.asNumber()) - } - - private fun Collection<*>.asBsonArray(): BsonArray = BsonArray(map { toBsonValue(it) }) - - private fun Array<*>.asBsonArray(): BsonArray = BsonArray(map { toBsonValue(it) }) - - private fun Map<*, *>.asBsonDocument() = BsonDocument( - map { (key, value) -> - if (key == null) { - throw IllegalArgumentException("Failed to convert Map to BsonDocument. Keys don't support null values.") - } - if (!String::class.isInstance(key)) { - throw IllegalArgumentException("Failed to convert Map to BsonDocument. Key type must be String, ${key::class.simpleName} found.") - } - BsonElement(key as String, toBsonValue(value)) - } - ) - - private fun RealmAny.asBsonValue(): BsonValue = toBsonValue( - when (type) { - RealmAny.Type.INT -> asLong() - RealmAny.Type.BOOL -> asBoolean() - RealmAny.Type.STRING -> asString() - RealmAny.Type.BINARY -> asByteArray() - RealmAny.Type.TIMESTAMP -> asRealmInstant() - RealmAny.Type.FLOAT -> asFloat() - RealmAny.Type.DOUBLE -> asDouble() - RealmAny.Type.DECIMAL128 -> asDecimal128() - RealmAny.Type.OBJECT_ID -> asObjectId() - RealmAny.Type.UUID -> asRealmUUID() - RealmAny.Type.OBJECT -> asRealmObject() - else -> TODO("Unsupported type $type") - } - ) - - @Suppress("ComplexMethod") - private fun toBsonValue(value: Any?): BsonValue { - return when (value) { - is Byte -> BsonInt32(value.toInt()) - is Short -> BsonInt32(value.toInt()) - is Int -> BsonInt32(value.toInt()) - is Long -> BsonInt64(value) - is Float -> BsonDouble(value.toDouble()) - is Double -> BsonDouble(value) - is Boolean -> BsonBoolean(value) - is String -> BsonString(value) - is Char -> BsonString(value.toString()) - is ByteArray -> BsonBinary(BsonBinarySubType.BINARY, value) - is MutableRealmInt -> BsonInt64(value.toLong()) - is RealmUUID -> BsonBinary(BsonBinarySubType.UUID_STANDARD, value.bytes) - is RealmInstant -> BsonDateTime(value.toDuration().inWholeMilliseconds) - is BsonValue -> value - null -> BsonNull - is RealmAny -> value.asBsonValue() - is Array<*> -> value.asBsonArray() - is Collection<*> -> value.asBsonArray() - is Map<*, *> -> value.asBsonDocument() - else -> throw IllegalArgumentException("Failed to convert arguments, type '${value::class.simpleName}' not supported. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, Array, Collection, Map and primitives are valid arguments types.") - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CredentialsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CredentialsImpl.kt deleted file mode 100644 index 15163c504a..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CredentialsImpl.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmCredentialsPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.mongodb.AuthenticationProvider -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.GoogleAuthType - -@PublishedApi -internal class CredentialsImpl constructor( - internal val nativePointer: RealmCredentialsPointer -) : Credentials { - - override val authenticationProvider: AuthenticationProvider = - AuthenticationProviderImpl.fromId( - RealmInterop.realm_auth_credentials_get_provider(nativePointer) - ) - - internal fun asJson(): String { - return RealmInterop.realm_app_credentials_serialize_as_json(nativePointer) - } - - companion object { - internal fun anonymous(reuseExisting: Boolean): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_anonymous(reuseExisting) - - internal fun emailPassword(email: String, password: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_email_password( - Validation.checkEmpty(email, "email"), - Validation.checkEmpty(password, "password") - ) - - internal fun apiKey(key: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_api_key(Validation.checkEmpty(key, "key")) - - internal fun apple(idToken: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_apple(Validation.checkEmpty(idToken, "idToken")) - - internal fun facebook(accessToken: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_facebook( - Validation.checkEmpty( - accessToken, - "accessToken" - ) - ) - - internal fun google(token: String, type: GoogleAuthType): RealmCredentialsPointer { - Validation.checkEmpty(token, "token") - return when (type) { - GoogleAuthType.AUTH_CODE -> RealmInterop.realm_app_credentials_new_google_auth_code( - token - ) - GoogleAuthType.ID_TOKEN -> RealmInterop.realm_app_credentials_new_google_id_token( - token - ) - } - } - - internal fun jwt(jwtToken: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_jwt(Validation.checkEmpty(jwtToken, "jwtToken")) - - @PublishedApi - internal fun customFunction(ejsonEncodedPayload: String): RealmCredentialsPointer = - RealmInterop.realm_app_credentials_new_custom_function(ejsonEncodedPayload) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CustomEJsonCredentialsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CustomEJsonCredentialsImpl.kt deleted file mode 100644 index e2059d5468..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/CustomEJsonCredentialsImpl.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmCredentialsPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.mongodb.AuthenticationProvider -import io.realm.kotlin.mongodb.Credentials - -/** - * Credentials for a EJson payload. It solved the issue where payload serialization requires access to - * the app instance. This credentials implementation is late evaluated when the credentials are used by - * the app, then the app instance is passed to the lambda block that serializes the payload. - */ -@PublishedApi -internal class CustomEJsonCredentialsImpl constructor( - val serializeAsEJson: (appImpl: AppImpl) -> String -) : Credentials { - override val authenticationProvider: AuthenticationProvider = - AuthenticationProvider.CUSTOM_FUNCTION - - fun nativePointer(appImpl: AppImpl): RealmCredentialsPointer = - serializeAsEJson(appImpl).let { serializedEjsonPayload -> - RealmInterop.realm_app_credentials_new_custom_function(serializedEjsonPayload) - } - - internal fun asJson(appImpl: AppImpl): String { - return RealmInterop.realm_app_credentials_serialize_as_json(nativePointer(appImpl)) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/EmailPasswordAuthImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/EmailPasswordAuthImpl.kt deleted file mode 100644 index d1c451e2af..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/EmailPasswordAuthImpl.kt +++ /dev/null @@ -1,119 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmAppPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.auth.EmailPasswordAuth -import kotlinx.coroutines.channels.Channel -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.serialization.Bson - -internal class EmailPasswordAuthImpl(private val app: RealmAppPointer) : EmailPasswordAuth { - - override suspend fun registerUser(email: String, password: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_register_email( - app, - Validation.checkEmpty(email, "email"), - Validation.checkEmpty(password, "password"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override suspend fun confirmUser(token: String, tokenId: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_confirm_user( - app, - Validation.checkEmpty(token, "token"), - Validation.checkEmpty(tokenId, "tokenId"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override suspend fun resendConfirmationEmail(email: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_resend_confirmation_email( - app, - Validation.checkEmpty(email, "email"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override suspend fun retryCustomConfirmation(email: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_retry_custom_confirmation( - app, - Validation.checkEmpty(email, "email"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override suspend fun sendResetPasswordEmail(email: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_send_reset_password_email( - app, - Validation.checkEmpty(email, "email"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override suspend fun callResetPasswordFunction(email: String, newPassword: String, vararg args: Any?) { - Channel>(1).use { channel -> - BsonEncoder.encodeToBsonValue(args.asList()).let { bsonValue: BsonValue -> - RealmInterop.realm_app_call_reset_password_function( - app, - Validation.checkEmpty(email, "email"), - Validation.checkEmpty(newPassword, "newPassword"), - Bson.toJson(bsonValue), - channelResultCallback(channel) { - // No-op - } - ) - } - return channel.receive() - .getOrThrow() - } - } - - override suspend fun resetPassword(token: String, tokenId: String, newPassword: String) { - Channel>(1).use { channel -> - RealmInterop.realm_app_email_password_provider_client_reset_password( - app, - Validation.checkEmpty(newPassword, "newPassword"), - Validation.checkEmpty(token, "token"), - Validation.checkEmpty(tokenId, "tokenId"), - channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt deleted file mode 100644 index 2862672300..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/FunctionsImpl.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.Functions -import kotlinx.coroutines.channels.Channel -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.serialization.Bson - -@PublishedApi -internal class FunctionsImpl( - override val app: AppImpl, - override val user: UserImpl, - val serviceName: String? = null, -) : Functions { - @PublishedApi - internal suspend fun callInternal( - functionName: String, - serializedEjsonArgs: String - ): String = Channel>(1).use { channel -> - RealmInterop.realm_app_call_function( - app = app.nativePointer, - user = user.nativePointer, - name = functionName, - serviceName = serviceName, - serializedEjsonArgs = serializedEjsonArgs, - callback = channelResultCallback(channel) { ejsonEncodedObject: String -> - // First we decode from ejson -> BsonValue - // then from BsonValue -> T - ejsonEncodedObject - } - ) - - return channel.receive().getOrThrow() - } - - internal suspend fun callInternal(functionName: String, bsonValue: BsonValue): BsonValue = - Bson(callInternal(functionName, Bson.toJson(bsonValue))) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt deleted file mode 100644 index 208769c6f9..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.plugins.HttpRedirect -import io.ktor.client.plugins.HttpTimeout -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.plugins.logging.Logging - -/** - * Work-around for https://github.com/realm/realm-kotlin/issues/480 - * - * This allows us to either cache the Client or re-create it pr. request - * on a platform basis. - */ -internal fun createClient(timeoutMs: Long, customLogger: Logger?): HttpClient { - // Need to freeze value as it is used inside the client's init lambda block, which also - // freezes captured objects too, see: - // https://youtrack.jetbrains.com/issue/KTOR-1223#focus=Comments-27-4618681.0-0 - val frozenTimeout = timeoutMs - return createPlatformClient { - // Charset defaults to UTF-8 (https://ktor.io/docs/http-plain-text.html#configuration) - - install(HttpTimeout) { - connectTimeoutMillis = frozenTimeout - requestTimeoutMillis = frozenTimeout - socketTimeoutMillis = frozenTimeout - } - - customLogger?.let { - install(Logging) { - logger = customLogger - level = LogLevel.ALL - } - } - - // We should allow redirects for all types, not just GET and HEAD - // See https://github.com/ktorio/ktor/issues/1793 - install(HttpRedirect) { - checkHttpMethod = false - } - } -} - -internal expect class HttpClientCache(timeoutMs: Long, customLogger: Logger? = null) { - fun getClient(): HttpClient - fun close() // Close any resources stored in the cache. -} - -public expect fun createPlatformClient(block: HttpClientConfig<*>.() -> Unit): HttpClient diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/KtorNetworkTransport.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/KtorNetworkTransport.kt deleted file mode 100644 index 154eb04f49..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/KtorNetworkTransport.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.ktor.client.call.body -import io.ktor.client.plugins.ClientRequestException -import io.ktor.client.plugins.ServerResponseException -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.client.request.delete -import io.ktor.client.request.get -import io.ktor.client.request.headers -import io.ktor.client.request.patch -import io.ktor.client.request.post -import io.ktor.client.request.put -import io.ktor.client.statement.HttpResponse -import io.ktor.http.Headers -import io.ktor.http.HttpHeaders -import io.ktor.http.HttpMethod -import io.ktor.util.InternalAPI -import io.ktor.utils.io.errors.IOException -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.Response -import io.realm.kotlin.internal.interop.sync.ResponseCallback -import io.realm.kotlin.internal.util.DispatcherHolder -import io.realm.kotlin.mongodb.AppConfiguration.Companion.DEFAULT_AUTHORIZATION_HEADER_NAME -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.async -import kotlin.collections.set - -public class KtorNetworkTransport( - override val authorizationHeaderName: String = DEFAULT_AUTHORIZATION_HEADER_NAME, - override val customHeaders: Map = mapOf(), - // FIXME Rework timeout to take a Duration instead - // https://github.com/realm/realm-kotlin/issues/408 - timeoutMs: Long, - private val dispatcherHolder: DispatcherHolder, - logger: Logger? = null, -) : NetworkTransport { - - private val clientCache: HttpClientCache = HttpClientCache(timeoutMs, logger) - - @Suppress("ComplexMethod", "TooGenericExceptionCaught") - override fun sendRequest( - method: String, - url: String, - headers: Map, - body: String, - callback: ResponseCallback, - ) { - val client = clientCache.getClient() - CoroutineScope(dispatcherHolder.dispatcher).async { - val response = try { - val requestBuilderBlock: HttpRequestBuilder.() -> Unit = { - headers { - // 1. First of all add all custom headers - customHeaders.forEach { - append(it.key, it.value) - } - - // 2. Then add all headers received from OS - headers.forEach { (key, value) -> - // It is not allowed to set content type on gets https://github.com/ktorio/ktor/issues/1127 - if (method != "get" || key != HttpHeaders.ContentType) { - append(key, value) - } - } - - // 3. Finally, if we have a non-default auth header name, replace the OS - // default with the custom one - if (authorizationHeaderName != DEFAULT_AUTHORIZATION_HEADER_NAME && - contains(DEFAULT_AUTHORIZATION_HEADER_NAME) - ) { - this[DEFAULT_AUTHORIZATION_HEADER_NAME]?.let { originalAuthValue -> - this[authorizationHeaderName] = originalAuthValue - } - this.remove(DEFAULT_AUTHORIZATION_HEADER_NAME) - } - } - - addBody(method, body) - addMethod(method) - } - when (method) { - "delete" -> client.delete(url, requestBuilderBlock) - "patch" -> client.patch(url, requestBuilderBlock) - "post" -> client.post(url, requestBuilderBlock) - "put" -> client.put(url, requestBuilderBlock) - "get" -> client.get(url, requestBuilderBlock) - else -> throw IllegalArgumentException("Wrong request method: '$method'") - }.let { - processHttpResponse(it) - } - } catch (e: ClientRequestException) { - processHttpResponse(e.response) - } catch (e: ServerResponseException) { - // 500s are thrown as ServerResponseException - processHttpResponse(e.response) - } catch (e: IOException) { - Response(0, ERROR_IO, mapOf(), e.toString()) - } catch (e: CancellationException) { - Response(0, ERROR_INTERRUPTED, mapOf(), e.toString()) - } catch (e: Exception) { - Response(0, ERROR_UNKNOWN, mapOf(), e.toString()) - } - callback.response(response) - } - } - - override fun close() { - clientCache.close() - dispatcherHolder.close() - } - - private suspend fun processHttpResponse(response: HttpResponse): Response { - val responseBody = response.body() - val responseStatusCode = response.status.value - val responseHeaders = parseHeaders(response.headers) - return createHttpResponse(responseStatusCode, responseHeaders, responseBody) - } - - @OptIn(InternalAPI::class) - private fun HttpRequestBuilder.addBody(method: String, body: String) { - when (method) { - "delete", "patch", "post", "put" -> this.body = body - } - } - - private fun HttpRequestBuilder.addMethod(method: String) { - when (method) { - "delete" -> this.method = HttpMethod.Delete - "patch" -> this.method = HttpMethod.Patch - "post" -> this.method = HttpMethod.Post - "put" -> this.method = HttpMethod.Put - "get" -> this.method = HttpMethod.Get - } - } - - private fun parseHeaders(headers: Headers): Map { - val parsedHeaders: MutableMap = mutableMapOf() - for (key in headers.names()) { - parsedHeaders[key] = requireNotNull(headers[key]) { "Header '$key' cannot be null" } - } - return parsedHeaders - } - - public companion object { - // Custom error codes. These must not match any HTTP response error codes - public const val ERROR_IO: Int = 1000 - public const val ERROR_INTERRUPTED: Int = 1001 - public const val ERROR_UNKNOWN: Int = 1002 - - private fun createHttpResponse( - responseStatusCode: Int, - responseHeaders: Map, - responseBody: String - ): Response = Response(responseStatusCode, 0, responseHeaders, responseBody) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/LogObfuscator.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/LogObfuscator.kt deleted file mode 100644 index dc71d5066b..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/LogObfuscator.kt +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.mongodb.HttpLogObfuscator -import io.realm.kotlin.mongodb.internal.LogReplacer.Companion.defaultFeatureToReplacerMap - -// Replaces any given regex pattern present in a given logger message matching a number of -// operations/features: register user, login with email and password, login with tokens and run -// custom functions with parameters -internal interface LogReplacer { - - // Uses a replacer to obfuscate (or not) a given logger message - fun findAndReplace(input: String): String - - companion object { - - // Defines the first part of the URL path to a feature - private const val AUTH = "auth" - internal const val FUNCTIONS = "functions/call" - - // Credentials feature - private const val CREDENTIALS_PROVIDER = "$AUTH/providers" - - // Credentials type - private const val PROVIDER_EMAIL_PASSWORD = "$CREDENTIALS_PROVIDER/local-userpass" - private const val PROVIDER_API_KEY = "$CREDENTIALS_PROVIDER/api-key" - private const val PROVIDER_APPLE = "$CREDENTIALS_PROVIDER/oauth2-apple" - private const val PROVIDER_FACEBOOK = "$CREDENTIALS_PROVIDER/oauth2-facebook" - private const val PROVIDER_GOOGLE = "$CREDENTIALS_PROVIDER/oauth2-google" - private const val PROVIDER_JWT = "$CREDENTIALS_PROVIDER/custom-token" - - // Email password provider operations - internal const val EMAIL_PASSWORD_REGISTER = "$PROVIDER_EMAIL_PASSWORD/register" - internal const val EMAIL_PASSWORD_LOGIN = "$PROVIDER_EMAIL_PASSWORD/login" - - // API key operations - internal const val API_KEY_REGISTER = "$AUTH/api_keys" // Key creation uses a different path - internal const val API_KEY_LOGIN = "$PROVIDER_API_KEY/login" - - // Apple token operations - internal const val APPLE_LOGIN = "$PROVIDER_APPLE/login" - - // Facebook token operations - internal const val FACEBOOK_LOGIN = "$PROVIDER_FACEBOOK/login" - - // Google token operations - internal const val GOOGLE_LOGIN = "$PROVIDER_GOOGLE/login" - - // JWT operations - internal const val JWT_LOGIN = "$PROVIDER_JWT/login" - - // Keys to be replaced by the replacer - private const val API_KEY_KEY = "key" - private const val PASSWORD_KEY = "password" - private const val AUTHCODE_KEY = "authCode" - private const val ID_TOKEN_KEY = "id_token" - private const val TOKEN_KEY = "token" - private const val FB_ACCESS_TOKEN_KEY = "accessToken" - - // Map of default feature operations to replacers - val defaultFeatureToReplacerMap: Map = mapOf( - EMAIL_PASSWORD_REGISTER to registerEmailPassword(), - EMAIL_PASSWORD_LOGIN to loginEmailPassword(), - API_KEY_REGISTER to createApiKey(), - API_KEY_LOGIN to loginApiKey(), - APPLE_LOGIN to loginApple(), - FACEBOOK_LOGIN to loginFacebook(), - GOOGLE_LOGIN to loginGoogle(), - JWT_LOGIN to loginJwt(), - FUNCTIONS to customFunction(), - ) - - // Patterns used when sending a register mail request: - // `"password":""` becomes `"password":"***"` - private fun registerEmailPassword(): LogReplacer = GenericRegexPatternReplacer( - """(("$PASSWORD_KEY"):(".+?"))""".toRegex() to """"$PASSWORD_KEY":"***"""" - ) - - // Patterns used when sending a login with mail request: - // `"password":""` becomes `"password":"***"` - private fun loginEmailPassword(): LogReplacer = GenericRegexPatternReplacer( - """(("$PASSWORD_KEY"):(".+?"))""".toRegex() to """"$PASSWORD_KEY":"***"""" - ) - - // Patterns used when sending a create API key request: - // `"key":""` becomes `"key":"***"` - private fun createApiKey(): LogReplacer = GenericRegexPatternReplacer( - """(("$API_KEY_KEY"):(\s?".+?"))""".toRegex() to """"$API_KEY_KEY":"***"""" - ) - - // Patterns used when sending a login with API key request: - // `"key":""` becomes `"key":"***"` - private fun loginApiKey(): LogReplacer = GenericRegexPatternReplacer( - """(("$API_KEY_KEY"):(\s?".+?"))""".toRegex() to """"$API_KEY_KEY":"***"""" - ) - - // Patterns used when sending a login with an Apple token request: - // `"id_token":""` becomes `"id_token":"***"` - private fun loginApple(): LogReplacer = GenericRegexPatternReplacer( - """(("$ID_TOKEN_KEY"):(\s?".+?"))""".toRegex() to """"$ID_TOKEN_KEY":"***"""" - ) - - // Patterns used when sending a login with a Facebook token request: - // `"accessToken":""` becomes `"accessToken":"***"` - private fun loginFacebook(): LogReplacer = GenericRegexPatternReplacer( - """(("$FB_ACCESS_TOKEN_KEY"):(\s?".+?"))""".toRegex() to """"$FB_ACCESS_TOKEN_KEY":"***"""" - ) - - // Patterns used when sending a login with a Google token request: - // `"authCode":""` becomes `"authCode":"***"` - // `"id_token":""` becomes `"id_token":"***"` - private fun loginGoogle(): LogReplacer = GenericRegexPatternReplacer( - """(("$AUTHCODE_KEY"):(\s?".+?"))""".toRegex() to """"$AUTHCODE_KEY":"***"""", - """(("$ID_TOKEN_KEY"):(\s?".+?"))""".toRegex() to """"$ID_TOKEN_KEY":"***"""" - ) - - // Patterns used when sending a login with a JWT request: - // `"token":""` becomes `"token":"***"` - private fun loginJwt(): LogReplacer = GenericRegexPatternReplacer( - """(("$TOKEN_KEY"):(\s?".+?"))""".toRegex() to """"$TOKEN_KEY":"***"""" - ) - - // Creates a replacer that hides parameters for custom functions - private fun customFunction(): LogReplacer = CustomFunctionPatternReplacer - } -} - -// Replacer for any given feature but custom functions -// FIXME Access and refresh tokens cannot be replaced by regex due to -// https://github.com/realm/realm-kotlin/issues/1284 -// so until we figure out what is wrong, do manual string replacement and hopefully nothing will -// change - if the message string changes our tests will hopefully catch it -internal class GenericRegexPatternReplacer( - vararg patternReplacements: Pair -) : LogReplacer { - - private val patternReplacementMap: Map - - init { - patternReplacementMap = patternReplacements.fold(mutableMapOf()) { acc, el -> - acc[el.first] = el.second - acc - } - } - - override fun findAndReplace(input: String): String { - return if ( - input.contains("RESPONSE: 200") && - input.contains("access_token") && - input.contains("refresh_token") - ) { - val beforeAccessToken = input.substringBefore(""""access_token"""") - val afterRefreshToken = input.substringAfter(""""user_id":""") - """$beforeAccessToken"access_token":"***","refresh_token":"***","user_id":$afterRefreshToken""" - } else { - var obfuscatedString = input - val entries: Set> = patternReplacementMap.entries - for (entry in entries) { - val pattern = entry.key - obfuscatedString = pattern.replace(obfuscatedString, entry.value) - } - obfuscatedString - } - } -} - -// Replacer for custom function arguments. It combs the request and hides both the parameters that -// are send and any possible received result -internal object CustomFunctionPatternReplacer : LogReplacer { - override fun findAndReplace(input: String): String { - val (pattern, replacement) = when { - input.contains("REQUEST: ") -> - """("arguments"):\[.*]""".toRegex() to """"arguments":[***]""" - input.contains("RESPONSE: 200") -> - """BODY START\n.*\nBODY END""".toRegex() to "BODY START\n***\nBODY END" - else -> return input - } - return pattern.replace(input, replacement) - } -} - -internal object LogObfuscatorImpl : HttpLogObfuscator { - - private val regexReplacerMap: Map = defaultFeatureToReplacerMap - - override fun obfuscate(input: String): String { - return regexReplacerMap - .filterKeys { input.contains(it) } - .values - .map { - it.findAndReplace(input) - } - .firstOrNull() ?: input - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt deleted file mode 100644 index aecc890765..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoClientImpl.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoDatabase -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -@PublishedApi -@OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoClientImpl( - internal val user: UserImpl, - override val serviceName: String, - val eJson: EJson, -) : MongoClient { - - val functions = FunctionsImpl(user.app, user, serviceName) - - override fun database(databaseName: String, eJson: EJson?): MongoDatabase = - MongoDatabaseImpl(this, databaseName, eJson ?: this.eJson) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt deleted file mode 100644 index f88504e6d2..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoCollectionImpl.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.mongo.MongoCollection -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson -import org.mongodb.kbson.serialization.decodeFromBsonValue -import org.mongodb.kbson.serialization.encodeToBsonValue - -@PublishedApi -@OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoDatabaseCollection(@PublishedApi internal val database: MongoDatabaseImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(database.client.functions, eJson) { - override val defaults: Map = mapOf( - "database" to BsonString(database.name), - "collection" to BsonString(name), - ) - override fun withDocumentClass(eJson: EJson?): MongoCollection { - return MongoDatabaseCollection(this.database, this.name, eJson ?: this.eJson) - } -} - -@PublishedApi -@OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoClientCollection(@PublishedApi internal val clientImpl: MongoClientImpl, override val name: String, eJson: EJson) : MongoCollectionImpl(clientImpl.functions, eJson) { - override val defaults: Map = mapOf( - "schema_name" to BsonString(name), - ) - override fun withDocumentClass(eJson: EJson?): MongoCollection { - return MongoClientCollection(clientImpl, name, eJson ?: this.eJson) - } -} - -@PublishedApi -@OptIn(ExperimentalKBsonSerializerApi::class) -internal abstract class MongoCollectionImpl constructor( - val functions: FunctionsImpl, - val eJson: EJson, -) : MongoCollection { - - // Default entries for the argument document submitted for the function call. - abstract val defaults: Map - - private suspend fun call(name: String, arguments: MutableMap.() -> Unit): BsonValue { - val doc = defaults.toMutableMap() - arguments(doc) - val argument = BsonDocument(doc) - return functions.callInternal(name, BsonArray(listOf(argument))) - } - - @PublishedApi - internal suspend fun count(filter: BsonDocument? = null, limit: Long? = null): Long { - return decodeFromBsonValue( - call("count") { - filter?.let { put("query", it) } - limit?.let { put("limit", BsonInt64(it)) } - } - ) - } - - @PublishedApi - internal suspend fun findOne(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null): BsonValue { - val call: BsonValue = call("findOne") { - filter?.let { put("query", it) } - projection?.let { put("project", it) } - sort?.let { put("sort", it) } - } - return call - } - - @PublishedApi - internal suspend fun find(filter: BsonDocument? = null, projection: BsonDocument? = null, sort: BsonDocument? = null, limit: Long? = null): BsonValue = - call("find") { - filter?.let { put("query", it) } - projection?.let { put("project", it) } - sort?.let { put("sort", it) } - limit?.let { put("limit", BsonInt64(it)) } - } - - @PublishedApi - internal suspend fun aggregate(pipeline: List): List = - call("aggregate") { put("pipeline", BsonArray(pipeline)) }.asArray().toList() - - @PublishedApi - internal suspend fun insertOne(document: BsonDocument): BsonValue = - call("insertOne") { put("document", document) }.asDocument()["insertedId"]!! - - @PublishedApi - internal suspend fun insertMany(documents: List): List = - call("insertMany") { - put("documents", BsonArray(documents)) - }.asDocument()["insertedIds"]!!.asArray().toList() - - @PublishedApi - internal suspend fun deleteOne(filter: BsonDocument): Boolean { - val deletedCountBson = call("deleteOne") { - put("query", filter) - }.asDocument()["deletedCount"]!! - val deletedCount = decodeFromBsonValue(deletedCountBson) - return when (deletedCount) { - 0L -> false - 1L -> true - else -> throw ServiceException("Unexpected response from deleteOne: deletedCount=$deletedCount") - } - } - - @PublishedApi - internal suspend fun deleteMany(filter: BsonDocument): Long { - val deletedCountBson = call("deleteMany") { - put("query", filter) - }.asDocument()["deletedCount"]!! - return decodeFromBsonValue(deletedCountBson) - } - - @PublishedApi - internal suspend fun updateOne(filter: BsonDocument, update: BsonDocument, upsert: Boolean = false): Pair { - val response: BsonValue = call("updateOne") { - put("query", filter) - put("update", update) - put("upsert", BsonBoolean(upsert)) - } - return response.asDocument().run { - val modifiedCount: Long? = get("modifiedCount")?.let { decodeFromBsonValue(it) } - val modified = when (modifiedCount) { - 0L -> false - 1L -> true - else -> throw ServiceException("Unexpected response from updateOne: modifiedCount=$modifiedCount") - } - modified to (get("upsertedId")) - } - } - - @PublishedApi - internal suspend fun updateMany(filter: BsonDocument, update: BsonDocument, upsert: Boolean = false): Pair { - val response = call("updateMany") { - put("query", filter) - put("update", update) - put("upsert", BsonBoolean(upsert)) - } - return response.asDocument().run { - decodeFromBsonValue(get("modifiedCount")!!) to (get("upsertedId")) - } - } - - @Suppress("LongParameterList") - @PublishedApi - internal suspend fun findOneAndUpdate( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, - ): BsonValue = call("findOneAndUpdate") { - put("filter", filter) - put("update", update) - projection?.let { put("projection", projection) } - sort?.let { put("sort", sort) } - put("upsert", BsonBoolean(upsert)) - put("returnNewDocument", BsonBoolean(returnNewDoc)) - } - - @Suppress("LongParameterList") - @PublishedApi - internal suspend fun findOneAndReplace( - filter: BsonDocument, - update: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - upsert: Boolean = false, - returnNewDoc: Boolean = false, - ): BsonValue = call("findOneAndReplace") { - put("filter", filter) - put("update", update) - projection?.let { put("projection", projection) } - sort?.let { put("sort", sort) } - put("upsert", BsonBoolean(upsert)) - put("returnNewDocument", BsonBoolean(returnNewDoc)) - } - - @PublishedApi - internal suspend fun findOneAndDelete( - filter: BsonDocument, - projection: BsonDocument? = null, - sort: BsonDocument? = null, - ): BsonValue = call("findOneAndDelete") { - put("filter", filter) - projection?.let { put("projection", projection) } - sort?.let { put("sort", sort) } - } -} - -@OptIn(ExperimentalKBsonSerializerApi::class) -@PublishedApi -internal inline fun MongoCollectionImpl<*>.encodeToBsonValue(value: R): BsonValue { - return eJson.encodeToBsonValue(value) -} - -@OptIn(ExperimentalKBsonSerializerApi::class) -@PublishedApi -internal inline fun MongoCollectionImpl<*>.decodeFromBsonValue(bsonValue: BsonValue): R = - when { - bsonValue == BsonNull -> null as R - R::class == BsonValue::class -> bsonValue as R - else -> eJson.decodeFromBsonValue(bsonValue) - } - -@OptIn(ExperimentalKBsonSerializerApi::class) -@PublishedApi -internal inline fun MongoCollectionImpl<*>.decodeFromBsonValueList(bsonValues: List): List { - return if (R::class == BsonValue::class) { - @Suppress("UNCHECKED_CAST") - bsonValues as List - } else { - bsonValues.map { eJson.decodeFromBsonValue(it) } - } -} - -@Suppress("ComplexMethod") -@PublishedApi -internal fun BsonValue.toAny(): Any? { - return when (this.bsonType) { - BsonType.NULL -> null - BsonType.INT32 -> asInt32().value - BsonType.INT64 -> asInt64().value - BsonType.OBJECT_ID -> this.asObjectId() - BsonType.STRING -> this.asString().value - BsonType.DOUBLE -> this.asDouble().value - BsonType.BINARY -> this.asBinary().data - BsonType.BOOLEAN -> this.asBoolean().value - BsonType.DATE_TIME -> this.asDateTime() - BsonType.ARRAY -> this.asArray().values.map { it.toAny() } - BsonType.DOCUMENT -> this.asDocument().mapValues { (k, v) -> v.toAny() } - BsonType.TIMESTAMP -> asTimestamp() - BsonType.DECIMAL128 -> asDecimal128() - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.SYMBOL, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.REGULAR_EXPRESSION, - BsonType.MIN_KEY, - BsonType.MAX_KEY, - BsonType.END_OF_DOCUMENT, - BsonType.UNDEFINED -> this - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt deleted file mode 100644 index 10f6a67074..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDBSerializer.kt +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.ext.toRealmDictionary -import io.realm.kotlin.ext.toRealmList -import io.realm.kotlin.ext.toRealmSet -import io.realm.kotlin.internal.RealmObjectCompanion -import io.realm.kotlin.internal.asBsonBinary -import io.realm.kotlin.internal.asBsonDateTime -import io.realm.kotlin.internal.asRealmInstant -import io.realm.kotlin.internal.asRealmUUID -import io.realm.kotlin.internal.interop.CollectionType -import io.realm.kotlin.internal.schema.collectionType -import io.realm.kotlin.internal.util.Validation.sdkError -import io.realm.kotlin.schema.RealmProperty -import io.realm.kotlin.schema.RealmPropertyType -import io.realm.kotlin.schema.RealmStorageType -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmUUID -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationException -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonDouble -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.Decimal128 -import org.mongodb.kbson.ObjectId -import kotlin.reflect.KClass -import kotlin.reflect.KMutableProperty1 -import kotlin.reflect.KProperty1 - -/** - * Serializer that will encode and decode realm objects to and from EJSON sent and received - * from the MongoClient APIs according to the schema definition of the realm objects. - * - * Serialization of links will only include primary key of the target and deserialization of - * responses from MongoClient will create target link instances where only the primary key is set - * (and all the other properties of the object will have default values). - * - * The target types of links in mixed fields cannot be derived from the schema definition of the - * realm objects. To be able to deserialize and create the correct instance, the serializer needs to - * know of all potential target types. - */ -public class MongoDBSerializer internal constructor( - clazz: KClass, - internal val schema: Map = emptyMap() -) : KSerializer { - override val descriptor: SerialDescriptor = BsonDocument.serializer().descriptor - @Suppress("invisible_reference", "invisible_member") - private val companion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(clazz) - - override fun deserialize(decoder: Decoder): BaseRealmObject { - return bsonToObject(companion, decoder.decodeSerializableValue(BsonDocument.serializer())) - } - - private fun bsonToObject(companion: RealmObjectCompanion, bsonDocument: BsonDocument): BaseRealmObject { - val instance = companion.io_realm_kotlin_newInstance() as BaseRealmObject - val fields: Map, KProperty1>> = - companion.io_realm_kotlin_fields - val schema = companion.io_realm_kotlin_schema() - bsonDocument.keys.forEach { key -> - val (kClass, accessor) = fields[key] - ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") - val type = schema[key]?.type - ?: throw SerializationException("Unknown field '$key' for type ${companion.io_realm_kotlin_className}") - val value = bsonValueToStorageType(type.collectionType, type.storageType, kClass, bsonDocument[key]) - (accessor as KMutableProperty1).set(instance, value) - } - return instance - } - - override fun serialize(encoder: Encoder, value: BaseRealmObject) { - encoder.encodeSerializableValue(BsonDocument.serializer(), objectToBson(companion, value)) - } - - private fun objectToBson( - companion: RealmObjectCompanion, - realmObject: BaseRealmObject, - ): BsonDocument { - val fields: Map, KProperty1>> = - companion.io_realm_kotlin_fields - val schema = companion.io_realm_kotlin_schema() - val document = BsonDocument() - fields.forEach { (fieldName, fieldDetails) -> - val (_, accessor) = fieldDetails - val type: RealmPropertyType = - schema[fieldName]?.type ?: sdkError("Schema does not contain property $fieldName") - storageTypeToBsonValue(type.collectionType, type.storageType, accessor.get(realmObject)).let { - document[fieldName] = it - } - } - return document - } - - @Suppress("LongMethod", "ComplexMethod") - private fun storageTypeToBsonValue( - collectionType: CollectionType, - elementType: RealmStorageType, - value: Any?, - ): BsonValue { - if (value == null) return BsonNull - return when (collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> { - when (elementType) { - RealmStorageType.BOOL -> BsonBoolean(value as Boolean) - RealmStorageType.INT -> when (value) { - is Byte -> BsonInt32(value.toInt()) - is Char -> BsonInt32(value.code) - is Short -> BsonInt32(value.toInt()) - is Int -> BsonInt32(value) - is Long -> BsonInt64(value) - is MutableRealmInt -> BsonInt64(value.toLong()) - else -> sdkError("Unexpected value of type ${value::class.simpleName} for field with storage type $elementType") - } - RealmStorageType.STRING -> BsonString(value as String) - RealmStorageType.BINARY -> BsonBinary(value as ByteArray) - RealmStorageType.OBJECT -> { - @Suppress("UNCHECKED_CAST") - val targetCompanion = - @Suppress("invisible_reference", "invisible_member") - io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(value::class as KClass) - @Suppress("UNCHECKED_CAST") - val primaryKeyProperty: KMutableProperty1? = - targetCompanion.io_realm_kotlin_primaryKey as KMutableProperty1? - when (primaryKeyProperty) { - // Embedded objects does not have a primary key, so serialize to full documents - null -> objectToBson(targetCompanion, value as BaseRealmObject) - else -> { - val targetStorageType = - targetCompanion.io_realm_kotlin_schema().primaryKey!!.type.storageType - val primaryKey = primaryKeyProperty.get(value as BaseRealmObject) - storageTypeToBsonValue(CollectionType.RLM_COLLECTION_TYPE_NONE, targetStorageType, primaryKey) - } - } - } - - RealmStorageType.FLOAT -> BsonDouble((value as Float).toDouble()) - RealmStorageType.DOUBLE -> BsonDouble(value as Double) - RealmStorageType.DECIMAL128 -> value as Decimal128 - RealmStorageType.TIMESTAMP -> (value as RealmInstant).asBsonDateTime() - RealmStorageType.OBJECT_ID -> value as ObjectId - RealmStorageType.UUID -> (value as RealmUUID).asBsonBinary() - RealmStorageType.ANY -> { realmAnyToBsonValue(value as RealmAny) } - } - } - CollectionType.RLM_COLLECTION_TYPE_LIST -> { - BsonArray( - (value as List<*>).map { - storageTypeToBsonValue( - CollectionType.RLM_COLLECTION_TYPE_NONE, - elementType, - it - ) - } - ) - } - CollectionType.RLM_COLLECTION_TYPE_SET -> { - BsonArray( - (value as Set<*>).map { - storageTypeToBsonValue( - CollectionType.RLM_COLLECTION_TYPE_NONE, - elementType, - it - ) - } - ) - } - CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { - @Suppress("UNCHECKED_CAST") - val map = value as Map - BsonDocument( - map.mapValues { (_, v) -> - storageTypeToBsonValue( - CollectionType.RLM_COLLECTION_TYPE_NONE, - elementType, - v - ) - } - ) - } - else -> sdkError("Unknown collection type: $collectionType") - } - } - - @Suppress("ComplexMethod") - private fun realmAnyToBsonValue(realmAny: RealmAny?): BsonValue = when (realmAny?.type) { - null -> BsonNull - RealmAny.Type.BOOL -> BsonBoolean(realmAny.asBoolean()) - RealmAny.Type.INT -> BsonInt64(realmAny.asLong()) - RealmAny.Type.STRING -> BsonString(realmAny.asString()) - RealmAny.Type.BINARY -> BsonBinary(realmAny.asByteArray()) - RealmAny.Type.TIMESTAMP -> realmAny.asRealmInstant().asBsonDateTime() - RealmAny.Type.FLOAT -> BsonDouble(realmAny.asFloat().toDouble()) - RealmAny.Type.DOUBLE -> BsonDouble(realmAny.asDouble()) - RealmAny.Type.DECIMAL128 -> realmAny.asDecimal128() - RealmAny.Type.OBJECT_ID -> realmAny.asObjectId() - RealmAny.Type.UUID -> realmAny.asRealmUUID().asBsonBinary() - RealmAny.Type.OBJECT -> { - // Objects in RealmAny cannot be EmbeddedObjects - val target = realmAny.asRealmObject(BaseRealmObject::class) - @Suppress("invisible_reference", "invisible_member") - val targetCompanion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(target::class) - val primaryKeySchemaProperty: RealmProperty = targetCompanion.io_realm_kotlin_schema().primaryKey ?: throw SerializationException( - "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" - ) - val (_, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] ?: throw SerializationException( - "Cannot serialize class without primary key: '${targetCompanion.io_realm_kotlin_className}'" - ) - val primaryKey: BsonValue = storageTypeToBsonValue( - CollectionType.RLM_COLLECTION_TYPE_NONE, - primaryKeySchemaProperty.type.storageType, - primaryKeyAccessor.get(target) - ) - BsonDocument( - "\$ref" to BsonString(targetCompanion.io_realm_kotlin_className), - "\$id" to primaryKey - ) - } - RealmAny.Type.LIST -> { - BsonArray(realmAny.asList().map { realmAnyToBsonValue(it) }) - } - RealmAny.Type.DICTIONARY -> { - BsonDocument(realmAny.asDictionary().mapValues { (_, v) -> realmAnyToBsonValue(v) }) - } - } - - @Suppress("LongMethod", "ComplexMethod") - internal fun bsonValueToStorageType( - collectionType: CollectionType, - elementType: RealmStorageType, - kClass: KClass<*>, - bsonValue: BsonValue?, - ): Any? { - if (bsonValue == null || bsonValue == BsonNull) return null - return when (collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> when (elementType) { - RealmStorageType.BOOL -> bsonValue.asBoolean().value - RealmStorageType.INT -> when (kClass) { - Byte::class -> bsonValue.asNumber().longValue().toByte() - Char::class -> bsonValue.asNumber().intValue().toChar() - Short::class -> bsonValue.asNumber().intValue().toShort() - Int::class -> bsonValue.asNumber().intValue() - Long::class -> bsonValue.asNumber().longValue() - MutableRealmInt::class -> MutableRealmInt.create( - bsonValue.asNumber().longValue() - ) - else -> sdkError("Unexpected KClass ('${kClass.simpleName}') for element with storage type '$elementType'") - } - RealmStorageType.STRING -> bsonValue.asString().value - RealmStorageType.BINARY -> bsonValue.asBinary().data - RealmStorageType.OBJECT -> { - @Suppress("invisible_reference", "invisible_member") - val targetCompanion = - io.realm.kotlin.internal.platform.realmObjectCompanionOrNull(kClass) - ?: sdkError("Unexpected kClass ('${kClass::simpleName}') without realm companion") - when (val primaryKeySchemaProperty = targetCompanion.io_realm_kotlin_schema().primaryKey) { - // Embedded objects does not have primary keys - null -> bsonToObject(targetCompanion, bsonValue.asDocument()) - else -> { - val (targetKClass, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] - ?: throw SerializationException( - "Target class does not have a primary key: '${targetCompanion.io_realm_kotlin_className}'" - ) - val targetInstance = - (targetCompanion.io_realm_kotlin_newInstance() as BaseRealmObject) - (primaryKeyAccessor as KMutableProperty1).set( - targetInstance, - bsonValueToStorageType( - CollectionType.RLM_COLLECTION_TYPE_NONE, - primaryKeySchemaProperty.type.storageType, - targetKClass, - bsonValue - ) - ) - targetInstance - } - } - } - - RealmStorageType.FLOAT -> bsonValue.asDouble().value.toFloat() - RealmStorageType.DOUBLE -> bsonValue.asDouble().value - RealmStorageType.DECIMAL128 -> bsonValue.asDecimal128() - RealmStorageType.TIMESTAMP -> bsonValue.asDateTime().asRealmInstant() - RealmStorageType.OBJECT_ID -> bsonValue.asObjectId() - RealmStorageType.UUID -> bsonValue.asBinary().asRealmUUID() - RealmStorageType.ANY -> bsonValueToRealmAny(bsonValue) - } - CollectionType.RLM_COLLECTION_TYPE_LIST -> { - ( - bsonValue.asArray().map { - bsonValueToStorageType( - CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it - ) - } - ).toRealmList() - } - CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> { - ( - bsonValue.asDocument().mapValues { - bsonValueToStorageType( - CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it.value - ) - } - ).toRealmDictionary() - } - - CollectionType.RLM_COLLECTION_TYPE_SET -> { - ( - bsonValue.asArray().map { - bsonValueToStorageType( - CollectionType.RLM_COLLECTION_TYPE_NONE, elementType, kClass, it - ) - } - ).toRealmSet() - } - else -> sdkError("Unknown collection type: $collectionType") - } - } - - @Suppress("ComplexMethod", "LongMethod") - private fun bsonValueToRealmAny( - bsonValue: BsonValue?, - ): RealmAny? { - return when (bsonValue?.bsonType) { - null, - BsonType.NULL -> null - // RealmAny.Type.FLOAT - // RealmAny.Type.DOUBLE - BsonType.DOUBLE -> RealmAny.create(bsonValue.asDouble().value) - // RealmAny.Type.STRING - BsonType.STRING -> RealmAny.create(bsonValue.asString().value) - // RealmAny.Type.INT - BsonType.INT32 -> RealmAny.create(bsonValue.asInt32().value) - BsonType.INT64 -> RealmAny.create(bsonValue.asInt64().value) - // RealmAny.Type.DECIMAL128 - BsonType.DECIMAL128 -> RealmAny.create(bsonValue.asDecimal128()) - // RealmAny.Type.BINARY - // RealmAny.Type.UUID handled as binary, we can't distinguish it - BsonType.BINARY -> RealmAny.create(bsonValue.asBinary().data) - // RealmAny.Type.OBJECT_ID - BsonType.OBJECT_ID -> RealmAny.Companion.create(bsonValue.asObjectId()) - // RealmAny.Type.BOOL - BsonType.BOOLEAN -> RealmAny.create(bsonValue.asBoolean().value) - // RealmAny.Type.TIMESTAMP - BsonType.DATE_TIME -> RealmAny.Companion.create(bsonValue.asDateTime().asRealmInstant()) - BsonType.DOCUMENT -> { - val document = bsonValue.asDocument() - val type: String? = document["\$ref"]?.asString()?.value - val targetCompanion = schema[type] - val primaryKey = document["\$id"] - if (targetCompanion != null && primaryKey != null) { - val primaryKeySchemaProperty = - targetCompanion.io_realm_kotlin_schema().primaryKey - ?: throw SerializationException( - "Target class does not have a primary key: '${"$"}ref=$type'" - ) - val (primaryKeyType, primaryKeyAccessor) = targetCompanion.io_realm_kotlin_fields[primaryKeySchemaProperty.name] - ?: throw SerializationException( - "Target class does not have a primary key: '${"$"}ref=$type'" - ) - val instance: RealmObject = - targetCompanion.io_realm_kotlin_newInstance() as RealmObject - (primaryKeyAccessor as KMutableProperty1).set( - instance, - bsonValueToStorageType( - CollectionType.RLM_COLLECTION_TYPE_NONE, - primaryKeySchemaProperty.type.storageType, - primaryKeyType, - primaryKey - ) - ) - RealmAny.create(instance) - } else { - RealmAny.create( - document.mapValues { (_, v) -> bsonValueToRealmAny(v) } - .toRealmDictionary() - ) - } - } - BsonType.ARRAY -> { - RealmAny.create(bsonValue.asArray().map { bsonValueToRealmAny(it) }.toRealmList()) - } - BsonType.TIMESTAMP, - BsonType.END_OF_DOCUMENT, - BsonType.UNDEFINED, - BsonType.REGULAR_EXPRESSION, - BsonType.DB_POINTER, - BsonType.JAVASCRIPT, - BsonType.SYMBOL, - BsonType.JAVASCRIPT_WITH_SCOPE, - BsonType.MIN_KEY, - BsonType.MAX_KEY - -> throw SerializationException("Deserializer does not support ${bsonValue.bsonType}") - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt deleted file mode 100644 index 49fc55b8e5..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MongoDatabaseImpl.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.mongodb.mongo.MongoDatabase -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -@PublishedApi -@OptIn(ExperimentalKBsonSerializerApi::class) -internal class MongoDatabaseImpl constructor( - @PublishedApi - internal val client: MongoClientImpl, - override val name: String, - val eJson: EJson, -) : MongoDatabase { - - override fun collection(collectionName: String): MongoCollection { - return MongoDatabaseCollection(this, collectionName, this.eJson) - } - - override fun collection(collectionName: String, eJson: EJson?): MongoCollection = - MongoDatabaseCollection(this, collectionName, eJson ?: this.eJson) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt deleted file mode 100644 index ac56ccd5f3..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/MutableSubscriptionSetImpl.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.internal.interop.RealmBaseSubscriptionSetPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmMutableSubscriptionSetPointer -import io.realm.kotlin.mongodb.sync.MutableSubscriptionSet -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmObject -import kotlin.reflect.KClass - -internal class MutableSubscriptionSetImpl( - realm: T, - nativePointer: RealmMutableSubscriptionSetPointer -) : BaseSubscriptionSetImpl(realm), MutableSubscriptionSet { - - override val nativePointer: RealmMutableSubscriptionSetPointer = nativePointer - - override fun getIteratorSafePointer(): RealmBaseSubscriptionSetPointer { - return nativePointer - } - - @Suppress("invisible_reference", "invisible_member") - override fun add(query: RealmQuery, name: String?, updateExisting: Boolean): Subscription { - // If an existing Subscription already exists, just return that one instead. - val existingSub: Subscription? = if (name != null) findByName(name) else findByQuery(query) - existingSub?.let { - // Depending on how descriptors are added to the Query, the amount of whitespace in the - // `description()` might vary from what is reported by the Subscription, so we need - // to trim both to ensure a consistent result. - if (name == existingSub.name && query.description().trim() == existingSub.queryDescription.trim()) { - return existingSub - } - } - val (ptr, inserted) = RealmInterop.realm_sync_subscriptionset_insert_or_assign( - nativePointer, - (query as io.realm.kotlin.internal.query.ObjectQuery).queryPointer, - name - ) - if (!updateExisting && !inserted) { - // This will also cancel the entire update - throw IllegalStateException( - // Only named queries will run into this, so it is safe to reference the name. - "Existing query '$name' was found and could not be updated as " + - "`updateExisting = false`" - ) - } - - return SubscriptionImpl(realm, nativePointer, ptr) - } - - override fun remove(subscription: Subscription): Boolean { - return RealmInterop.realm_sync_subscriptionset_erase_by_id(nativePointer, (subscription as SubscriptionImpl).nativePointer) - } - - override fun remove(name: String): Boolean { - return RealmInterop.realm_sync_subscriptionset_erase_by_name(nativePointer, name) - } - - override fun removeAll(objectType: String): Boolean { - if (realm.schema()[objectType] == null) { - throw IllegalArgumentException("'$objectType' is not part of the schema for this Realm: ${realm.configuration.path}") - } - val result: Boolean - filter { it.objectType == objectType } - .also { result = it.isNotEmpty() } - .forEach { sub: Subscription -> - remove(sub) - } - return result - } - - @Suppress("invisible_member", "invisible_reference") - override fun removeAll(type: KClass): Boolean { - var result = false - val objectType = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(type).`io_realm_kotlin_className` - if (realm.schema().get(objectType) == null) { - throw IllegalArgumentException("'$type' is not part of the schema for this Realm: ${realm.configuration.path}") - } - forEach { sub: Subscription -> - if (sub.objectType == objectType) { - result = remove(sub) || result - } - } - return result - } - - override fun removeAll(anonymousOnly: Boolean): Boolean { - if (anonymousOnly) { - var result: Boolean = false - filter { it.name == null } - .also { result = it.isNotEmpty() } - .forEach { - remove(it) - } - return result - } else { - return RealmInterop.realm_sync_subscriptionset_clear(nativePointer) - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt deleted file mode 100644 index 3887309801..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt +++ /dev/null @@ -1,64 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.SynchronizableObject - -// Register a system specific network listener (if supported) -internal expect fun registerSystemNetworkObserver() - -/** - * This class is responsible for keeping track of system events related to the network so it can - * delegate them to interested parties. - */ -internal object NetworkStateObserver { - - /** - * This interface is used in a thread-safe manner, i.e. implementers do not have to think - * about race conditions. - */ - internal fun interface ConnectionListener { - fun onChange(connectionAvailable: Boolean) - } - - private val mutex = SynchronizableObject() - private val listeners = mutableListOf() - - init { - registerSystemNetworkObserver() - } - - /** - * Called by each custom network implementation whenever a network change is detected. - */ - fun notifyConnectionChange(isOnline: Boolean) { - mutex.withLock { - listeners.forEach { - it.onChange(isOnline) - } - } - } - - /** - * Add a listener to be notified about any network changes. - * This method is thread safe. - * IMPORTANT: Not removing it again will result in leaks. - * @param listener the listener to add. - */ - fun addListener(listener: ConnectionListener) { - mutex.withLock { - listeners.add(listener) - } - } - - /** - * Removes a network listener. - * This method is thread safe. - * - * @param listener the listener to remove. - * @return `true` if the listener was removed. - */ - fun removeListener(listener: ConnectionListener): Boolean { - mutex.withLock { - return listeners.remove(listener) - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmQueryExtImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmQueryExtImpl.kt deleted file mode 100644 index 973e37834d..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmQueryExtImpl.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.Realm -import io.realm.kotlin.internal.RealmImpl -import io.realm.kotlin.internal.getRealm -import io.realm.kotlin.internal.query.ObjectQuery -import io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.WaitForSync -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.types.RealmObject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration - -internal suspend fun createSubscriptionFromQuery( - query: RealmQuery, - name: String?, - updateExisting: Boolean = false, - mode: WaitForSync, - timeout: Duration -): RealmResults { - - if (query !is ObjectQuery) { - throw IllegalStateException("Only queries on objects are supported. This was: ${query::class}") - } - if (query.realmReference.owner !is RealmImpl) { - throw IllegalStateException("Calling `subscribe()` inside a write transaction is not allowed.") - } - val realm: Realm = query.getRealm() - val subscriptions = realm.subscriptions - val appDispatcher: CoroutineDispatcher = ((realm.configuration as SyncConfiguration).user.app as AppImpl).appNetworkDispatcher.dispatcher - - return withTimeout(timeout) { - withContext(appDispatcher) { - val existingSubscription: Subscription? = findExistingQueryInSubscriptions(name, query, subscriptions) - if (existingSubscription == null || updateExisting) { - subscriptions.update { - add(query, name, updateExisting) - } - } - if ((mode == WaitForSync.FIRST_TIME || mode == WaitForSync.ALWAYS) && existingSubscription == null) { - subscriptions.waitForSynchronization() - } else if (mode == WaitForSync.ALWAYS) { - // The subscription should already exist, just make sure we downloaded all - // server data before continuing. - realm.syncSession.downloadAllServerChanges() - subscriptions.refresh() - subscriptions.errorMessage?.let { errorMessage: String -> - throw BadFlexibleSyncQueryException(errorMessage, isFatal = false) - } - } - // Rerun the query on the latest Realm version. - realm.query(query.clazz, query.description()).find() - } - } -} - -// A subscription only matches if name, type and query all matches -private fun findExistingQueryInSubscriptions( - name: String?, - query: ObjectQuery, - subscriptions: SubscriptionSet -): Subscription? { - return if (name != null) { - val sub: Subscription? = subscriptions.findByName(name) - val companion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(query.clazz) - val userTypeName = companion.io_realm_kotlin_className - if (sub?.queryDescription == query.description() && sub.objectType == userTypeName) { - sub - } else { - null - } - } else { - subscriptions.findByQuery(query) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt deleted file mode 100644 index a6f015b9fb..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmSyncUtils.kt +++ /dev/null @@ -1,365 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.AppCallback -import io.realm.kotlin.internal.interop.CoreError -import io.realm.kotlin.internal.interop.ErrorCategory -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.sync.AppError -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.exceptions.AuthException -import io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException -import io.realm.kotlin.mongodb.exceptions.BadRequestException -import io.realm.kotlin.mongodb.exceptions.CompensatingWriteException -import io.realm.kotlin.mongodb.exceptions.ConnectionException -import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException -import io.realm.kotlin.mongodb.exceptions.FunctionExecutionException -import io.realm.kotlin.mongodb.exceptions.InvalidCredentialsException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException -import io.realm.kotlin.mongodb.exceptions.UserAlreadyExistsException -import io.realm.kotlin.mongodb.exceptions.UserNotFoundException -import io.realm.kotlin.mongodb.exceptions.WrongSyncTypeException -import io.realm.kotlin.serializers.MutableRealmIntKSerializer -import io.realm.kotlin.serializers.RealmAnyKSerializer -import io.realm.kotlin.serializers.RealmInstantKSerializer -import io.realm.kotlin.serializers.RealmUUIDKSerializer -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmUUID -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ChannelResult -import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.serializer - -@PublishedApi -internal fun channelResultCallback( - channel: Channel>, - success: (T) -> R -): AppCallback { - return object : AppCallback { - override fun onSuccess(result: T) { - try { - val sendResult: ChannelResult = - channel.trySend(Result.success(success.invoke(result))) - if (!sendResult.isSuccess) { - throw sendResult.exceptionOrNull()!! - } - } catch (ex: Throwable) { - channel.trySend(Result.failure(ex)).let { - if (!it.isSuccess) { - throw it.exceptionOrNull()!! - } - } - } - } - - override fun onError(error: AppError) { - try { - val sendResult = channel.trySend(Result.failure(convertAppError(error))) - if (!sendResult.isSuccess) { - throw sendResult.exceptionOrNull()!! - } - } catch (ex: Throwable) { - channel.trySend(Result.failure(ex)).let { - if (!it.isSuccess) { - throw it.exceptionOrNull()!! - } - } - } - } - } -} - -internal fun convertSyncError(syncError: SyncError): SyncException { - val errorCode = syncError.errorCode - val message = createMessageFromSyncError(errorCode) - return when (errorCode.errorCode) { - ErrorCode.RLM_ERR_WRONG_SYNC_TYPE -> WrongSyncTypeException(message) - - ErrorCode.RLM_ERR_INVALID_SUBSCRIPTION_QUERY -> { - // Flexible Sync Query was rejected by the server - BadFlexibleSyncQueryException(message, syncError.isFatal) - } - - ErrorCode.RLM_ERR_SYNC_COMPENSATING_WRITE -> CompensatingWriteException( - message, - syncError.compensatingWrites, - syncError.isFatal - ) - - ErrorCode.RLM_ERR_SYNC_PROTOCOL_INVARIANT_FAILED, - ErrorCode.RLM_ERR_SYNC_PROTOCOL_NEGOTIATION_FAILED, - ErrorCode.RLM_ERR_SYNC_PERMISSION_DENIED, - -> { - // Permission denied errors should be unrecoverable according to Core, i.e. the - // client will disconnect sync and transition to the "inactive" state - @Suppress("DEPRECATION") io.realm.kotlin.mongodb.exceptions.UnrecoverableSyncException( - message - ) - } - - else -> { - // An error happened we are not sure how to handle. Just report as a generic - // SyncException. - when (syncError.isFatal) { - false -> SyncException(message, syncError.isFatal) - true -> @Suppress("DEPRECATION") io.realm.kotlin.mongodb.exceptions.UnrecoverableSyncException( - message - ) - } - } - } -} - -@Suppress("ComplexMethod", "MagicNumber", "LongMethod") -internal fun convertAppError(appError: AppError): Throwable { - val msg = createMessageFromAppError(appError) - return when { - ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR in appError -> { - // Custom errors are only being thrown when executing the network request on the - // platform side and it failed in a way that didn't produce a HTTP status code. - ConnectionException(msg) - } - ErrorCategory.RLM_ERR_CAT_HTTP_ERROR in appError -> { - // HTTP errors from network requests towards Atlas. Generally we should see - // errors in these ranges: - // 300-399: Redirect Codes. Indicate either a misconfiguration in a users network - // environement or on Atlas itself. Retrying should be acceptable. - // 400-499: Client error codes. These point to different error scenarios on the - // client and each should be considered individually. - // 500-599: Server error codes. We assume all of these are intermiddent and retrying - // should be safe. - val statusCode: Int = appError.code.nativeValue - when (statusCode) { - in 300..399 -> ConnectionException(msg) - 401 -> InvalidCredentialsException(msg) // Unauthorized - 408, // Request Timeout - 429, // Too Many Requests - in 500..599 -> ConnectionException(msg) - else -> ServiceException(msg) - } - } - ErrorCategory.RLM_ERR_CAT_JSON_ERROR in appError -> { - // The JSON response from Atlas could not be parsed as valid JSON. Errors of this kind - // would indicate a problem on Atlas that should be fixed with no action needed by the - // client. So retrying the action should generally be safe. Although it might take a - // while for the server to correct the behavior. - ConnectionException(msg) - } - ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR in appError -> { - // See https://github.com/realm/realm-core/blob/master/src/realm/object-store/sync/generic_network_transport.hpp#L34 - // - // `ClientErrorCode::user_not_logged in` is used when the client decides that a login - // is no longer valid, this normally happens if the refresh_token has expired. The - // user needs to log in again in that case. - // - // `ClientErrorCode::user_not_found` is mostly used as a proxy for an illegal argument, - // but since most of our API methods that throws this is on the `User` object itself, - // it is being converted to an `IllegalStateException` here. It is also used internally - // when refreshing the access token, but since this error never reaches the end user, - // we just ignore this case. - // - // `ClientErrorCode::app_deallocated` should never happen, so is just returned as an - // AppException. - when (appError.code) { - ErrorCode.RLM_ERR_CLIENT_USER_NOT_FOUND -> { - IllegalStateException(msg) - } - ErrorCode.RLM_ERR_CLIENT_USER_ALREADY_NAMED -> { - CredentialsCannotBeLinkedException(msg) - } - else -> { - AppException(msg) - } - } - } - ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR in appError -> { - // This category is response codes from the server, that for some reason didn't - // accept a request from the client. Most of the error codes in this category - // can (most likely) be fixed by the client and should have a more granular - // exception type, but until we understand the details, they will be reported as - // generic `ServiceException`'s. - when (appError.code) { - ErrorCode.RLM_ERR_INTERNAL_SERVER_ERROR -> { - ServiceException(msg) - } - ErrorCode.RLM_ERR_INVALID_SESSION -> { - ServiceException(msg) - } - ErrorCode.RLM_ERR_USER_DISABLED, - ErrorCode.RLM_ERR_AUTH_ERROR -> { - AuthException(msg) - } - ErrorCode.RLM_ERR_USER_NOT_FOUND -> { - UserNotFoundException(msg) - } - ErrorCode.RLM_ERR_ACCOUNT_NAME_IN_USE -> { - UserAlreadyExistsException(msg) - } - ErrorCode.RLM_ERR_USER_ALREADY_CONFIRMED -> { - UserAlreadyConfirmedException(msg) - } - ErrorCode.RLM_ERR_INVALID_PASSWORD -> { - InvalidCredentialsException(msg) - } - ErrorCode.RLM_ERR_BAD_REQUEST -> { - BadRequestException(msg) - } - ErrorCode.RLM_ERR_FUNCTION_NOT_FOUND, - ErrorCode.RLM_ERR_EXECUTION_TIME_LIMIT_EXCEEDED, - ErrorCode.RLM_ERR_FUNCTION_EXECUTION_ERROR -> { - FunctionExecutionException(msg) - } - else -> ServiceException(message = msg, errorCode = appError.code) - } - } - else -> AppException(msg) - } -} - -internal fun createMessageFromSyncError(error: CoreError): String { - val categoryDesc = error.categories.description - val errorCodeDesc: String? = error.errorCode?.description ?: if (ErrorCategory.RLM_ERR_CAT_SYSTEM_ERROR in error.categories) { - // We lack information about these kinds of errors, - // so rather than returning a potentially misleading - // name, just return nothing. - null - } else { - "Unknown" - } - - // Combine all the parts to form an error format that is human-readable. - // An example could be this: `[Connection][WrongProtocolVersion(104)] Wrong protocol version was used: 25` - val errorDesc: String = - if (errorCodeDesc == null) error.errorCodeNativeValue.toString() else "$errorCodeDesc(${error.errorCodeNativeValue})" - - // Make sure that messages are uniformly formatted, so it looks nice if we append the - // server log. - val msg = error.message?.let { message: String -> - " $message${if (!message.endsWith(".")) "." else ""}" - } ?: "" - - return "[$categoryDesc][$errorDesc]$msg" -} - -@Suppress("ComplexMethod", "MagicNumber", "LongMethod") -private fun createMessageFromAppError(error: AppError): String { - // If the category is "Http", errorCode and httpStatusCode is the same. - // if the category is "Custom", httpStatusCode is optional (i.e != 0), but - // the Kotlin SDK always sets it to 0 in this case. - // For all other categories, httpStatusCode is 0 (i.e not used). - // linkToServerLog is only present if the category is "Service". - val categoryDesc: String? = when { - ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR in error -> ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR - ErrorCategory.RLM_ERR_CAT_JSON_ERROR in error -> ErrorCategory.RLM_ERR_CAT_JSON_ERROR - ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR in error -> ErrorCategory.RLM_ERR_CAT_SERVICE_ERROR - ErrorCategory.RLM_ERR_CAT_HTTP_ERROR in error -> ErrorCategory.RLM_ERR_CAT_HTTP_ERROR - ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR in error -> ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR - else -> null - }?.description ?: error.categoryFlags.toString() - - val errorCodeDesc = error.code.description ?: when { - ErrorCategory.RLM_ERR_CAT_HTTP_ERROR in error -> { - // Source https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml - // Only codes in the 300-599 range is mapped to errors - when (error.code.nativeValue) { - 300 -> "MultipleChoices" - 301 -> "MovedPermanently" - 302 -> "Found" - 303 -> "SeeOther" - 304 -> "NotModified" - 305 -> "UseProxy" - 307 -> "TemporaryRedirect" - 308 -> "PermanentRedirect" - 400 -> "BadRequest" - 401 -> "Unauthorized" - 402 -> "PaymentRequired" - 403 -> "Forbidden" - 404 -> "NotFound" - 405 -> "MethodNotAllowed" - 406 -> "NotAcceptable" - 407 -> "ProxyAuthenticationRequired" - 408 -> "RequestTimeout" - 409 -> "Conflict" - 410 -> "Gone" - 411 -> "LengthRequired" - 412 -> "PreconditionFailed" - 413 -> "ContentTooLarge" - 414 -> "UriTooLong" - 415 -> "UnsupportedMediaType" - 416 -> "RangeNotSatisfiable" - 417 -> "ExpectationFailed" - 421 -> "MisdirectedRequest" - 422 -> "UnprocessableContent" - 423 -> "Locked" - 424 -> "FailedDependency" - 425 -> "TooEarly" - 426 -> "UpgradeRequired" - 428 -> "PreconditionRequired" - 429 -> "TooManyRequests" - 431 -> "RequestHeaderFieldsTooLarge" - 451 -> "UnavailableForLegalReasons" - 500 -> "InternalServerError" - 501 -> "NotImplemented" - 502 -> "BadGateway" - 503 -> "ServiceUnavailable" - 504 -> "GatewayTimeout" - 505 -> "HttpVersionNotSupported" - 506 -> "VariantAlsoNegotiates" - 507 -> "InsufficientStorage" - 508 -> "LoopDetected" - 510 -> "NotExtended" - 511 -> "NetworkAuthenticationRequired" - else -> "Unknown" - } - } - ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR in error -> { - when (error.code.nativeValue) { - KtorNetworkTransport.ERROR_IO -> "IO" - KtorNetworkTransport.ERROR_INTERRUPTED -> "Interrupted" - else -> "Unknown" - } - } - else -> "Unknown" - } - - // Make sure that messages are uniformly formatted, so it looks nice if we append the - // server log. - val msg = error.message?.let { message: String -> - if (message.endsWith(".")) { - message - } else { - " $message." - } - } ?: "" - - // Combine all the parts to form an error format that is human-readable. - // An example could be this: `[Service][UserNotFound(44)] No matching user was found. Server logs: http://link.to.logs` - val serverLogsLink = error.linkToServerLog?.let { link: String -> - " Server log entry: $link" - } ?: "" - - val errorDesc = "$errorCodeDesc(${error.code.nativeValue})" - return "[$categoryDesc][$errorDesc]$msg$serverLogsLink" -} - -@Suppress("UNCHECKED_CAST") -@PublishedApi -internal inline fun SerializersModule.serializerOrRealmBuiltInSerializer(): KSerializer = - when (T::class) { - /** - * Automatically resolves any Realm datatype serializer or defaults to the type built in. - * - * ReamLists, Sets and others cannot be resolved here as we don't have the type information - * required to instantiate them. They require to be instantiated by the user. - */ - MutableRealmInt::class -> MutableRealmIntKSerializer - RealmUUID::class -> RealmUUIDKSerializer - RealmInstant::class -> RealmInstantKSerializer - RealmAny::class -> RealmAnyKSerializer - else -> serializer() - } as KSerializer diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmWebSocketTransport.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmWebSocketTransport.kt deleted file mode 100644 index 2f30169e2e..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/RealmWebSocketTransport.kt +++ /dev/null @@ -1,123 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmWebsocketHandlerCallbackPointer -import io.realm.kotlin.internal.interop.sync.CancellableTimer -import io.realm.kotlin.internal.interop.sync.WebSocketClient -import io.realm.kotlin.internal.interop.sync.WebSocketObserver -import io.realm.kotlin.internal.interop.sync.WebSocketTransport -import io.realm.kotlin.internal.interop.sync.WebsocketEngine -import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.internal.util.DispatcherHolder -import kotlinx.atomicfu.AtomicRef -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -public class RealmWebSocketTransport( - public val timeoutMs: Long -) : WebSocketTransport { - // we need a single thread dispatcher to act like an event loop - private val dispatcherHolder: DispatcherHolder = CoroutineDispatcherFactory.managed("RealmWebsocketTransport").create() - public val scope: CoroutineScope = CoroutineScope(dispatcherHolder.dispatcher) - - public val engine: WebsocketEngine by lazy { websocketEngine(timeoutMs) } - - private val websocketClients = mutableListOf() - - override fun post(handlerCallback: RealmWebsocketHandlerCallbackPointer) { - scope.launch { - (this as Job).invokeOnCompletion { completionHandler: Throwable? -> - when (completionHandler) { - // Only run the callback successfully if it was not cancelled in the meantime - null -> runCallback(handlerCallback) - else -> runCallback( - handlerCallback, cancelled = true - ) - } - } - } - } - - override fun createTimer( - delayInMilliseconds: Long, - handlerCallback: RealmWebsocketHandlerCallbackPointer - ): CancellableTimer { - val atomicCallback: AtomicRef = - atomic(handlerCallback) - return CancellableTimer( - scope.launch { - delay(delayInMilliseconds) - atomicCallback.getAndSet(null)?.run { // this -> callback pointer - runCallback(this) - } - } - ) { - scope.launch { - atomicCallback.getAndSet(null)?.run { // this -> callback pointer - runCallback(this, cancelled = true) - } - } - } - } - - override fun connect( - observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - numProtocols: Long, - supportedSyncProtocols: String, - ): WebSocketClient = platformWebsocketClient( - observer, path, address, port, isSsl, supportedSyncProtocols, this - ).also { - websocketClients.add(it) - } - - override fun write( - webSocketClient: WebSocketClient, - data: ByteArray, - length: Long, - handlerCallback: RealmWebsocketHandlerCallbackPointer - ) { - webSocketClient.send(data, handlerCallback) - } - - override fun close() { - // Notify clients that the transport is tearing down - websocketClients.forEach { it.close() } - - // Shutdown Websocket Engine shared by same App - if (websocketClients.isNotEmpty()) { - // Shutdown is an event that should be posted to the event loop, otherwise - // premature closing of the websocket could occur. Even if the transport is tearing down - // we still want to close gracefully the connection. - scope.launch { - engine.shutdown() - } - } - - // Closing the coroutine dispatcher should also be done via the event loop, to avoid prematurely - // closing the thread executor of a running coroutine (this will throw a `InterruptedException`) - scope.launch { - dispatcherHolder.close() - cancel() - } - websocketClients.clear() - } -} - -public expect fun websocketEngine(timeoutMs: Long): WebsocketEngine -@Suppress("LongParameterList") -public expect fun platformWebsocketClient( - observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - supportedSyncProtocols: String, - transport: RealmWebSocketTransport -): WebSocketClient diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt deleted file mode 100644 index 8d8b9f609d..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionImpl.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.internal.RealmInstantImpl -import io.realm.kotlin.internal.interop.RealmBaseSubscriptionSetPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSubscriptionPointer -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmObject -import org.mongodb.kbson.ObjectId -import kotlin.reflect.KClass - -internal class SubscriptionImpl( - private val realm: BaseRealm, - private val parentNativePointer: RealmBaseSubscriptionSetPointer, - internal val nativePointer: RealmSubscriptionPointer -) : Subscription { - override val id: ObjectId = RealmInterop.realm_sync_subscription_id(nativePointer) - override val createdAt: RealmInstant = RealmInstantImpl(RealmInterop.realm_sync_subscription_created_at(nativePointer)) - override val updatedAt: RealmInstant = RealmInstantImpl(RealmInterop.realm_sync_subscription_updated_at(nativePointer)) - override val name: String? = RealmInterop.realm_sync_subscription_name(nativePointer) - override val objectType: String = RealmInterop.realm_sync_subscription_object_class_name(nativePointer) - // Trim the query to match the output of RealmQuery.description() - override val queryDescription: String = RealmInterop.realm_sync_subscription_query_string(nativePointer).trim() - - @Suppress("invisible_member", "invisible_reference") - override fun asQuery(type: KClass): RealmQuery { - // TODO Check for invalid combinations of Realm and type once we properly support - // DynamicRealm - return when (realm) { - is TypedRealm -> { - val companion = io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(type) - val userTypeName = companion.`io_realm_kotlin_className` - if (userTypeName != objectType) { - throw IllegalArgumentException( - "Wrong query type. This subscription is for " + - "objects of type: $objectType, but $userTypeName was provided as input." - ) - } - realm.query(type, queryDescription) - } - // is DynamicRealm -> { - // if (type != DynamicRealmObject::class) { - // throw IllegalArgumentException( - // "This subscription was fetched from a " + - // "DynamicRealm, so the type argument must be `DynamicRealmObject`." - // ) - // } - // realm.query(className = objectType, query = queryDescription) as RealmQuery - // } - else -> { - throw IllegalStateException("Unsupported Realm type: ${realm::class}") - } - } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as SubscriptionImpl - - val version = RealmInterop.realm_sync_subscriptionset_version(parentNativePointer) - val otherVersion = RealmInterop.realm_sync_subscriptionset_version(other.parentNativePointer) - if (version != otherVersion) return false - val id = RealmInterop.realm_sync_subscription_id(nativePointer) - val otherId = RealmInterop.realm_sync_subscription_id(nativePointer) - if (id != otherId) return false - - return true - } - - override fun hashCode(): Int { - val id = RealmInterop.realm_sync_subscription_id(nativePointer) - val version = RealmInterop.realm_sync_subscriptionset_version(parentNativePointer) - var result = id.hashCode() - result = 31 * result + version.toInt() - return result - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt deleted file mode 100644 index 75dfdd82fc..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SubscriptionSetImpl.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.internal.BaseRealmImpl -import io.realm.kotlin.internal.RealmImpl -import io.realm.kotlin.internal.interop.RealmBaseSubscriptionSetPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSubscriptionSetPointer -import io.realm.kotlin.internal.interop.SubscriptionSetCallback -import io.realm.kotlin.internal.interop.sync.CoreSubscriptionSetState -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException -import io.realm.kotlin.mongodb.sync.MutableSubscriptionSet -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import kotlinx.atomicfu.AtomicRef -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration - -internal class SubscriptionSetImpl( - realm: T, - nativePointer: RealmSubscriptionSetPointer -) : BaseSubscriptionSetImpl(realm), SubscriptionSet { - - private val _nativePointer: AtomicRef = atomic(nativePointer) - override val nativePointer: RealmSubscriptionSetPointer - get() = _nativePointer.value - - override fun getIteratorSafePointer(): RealmBaseSubscriptionSetPointer { - return if (realm.isClosed()) { - // If the Realm is closed, no further changes can happen to the SubscriptionSet, - // So just return the current set - nativePointer - } else { - RealmInterop.realm_sync_get_latest_subscriptionset( - (realm as BaseRealmImpl).realmReference.dbPointer - ) - } - } - - override fun close() { - nativePointer.release() - } - - override suspend fun update(block: MutableSubscriptionSet.(realm: T) -> Unit): SubscriptionSet { - checkClosed() - val ptr = RealmInterop.realm_sync_make_subscriptionset_mutable(nativePointer) - val mut = MutableSubscriptionSetImpl(realm, ptr) - try { - mut.block(realm) - _nativePointer.value = RealmInterop.realm_sync_subscriptionset_commit(ptr) - } finally { - // Manually release the MutableSubscriptionSetPointer as it holds on to DB resources - // that should not be controlled by the GC. - ptr.release() - } - return this - } - - override suspend fun waitForSynchronization(timeout: Duration): Boolean { - checkClosed() - Validation.require(timeout.isPositive()) { - "'timeout' must be > 0. It was: $timeout" - } - - // Channel to work around not being able to use `suspendCoroutine` to wrap the callback, as - // that results in the `Continuation` being frozen, which breaks it. - val channel = Channel(1) - try { - val result: Any = withTimeout(timeout) { - // TODO Assuming this is always a RealmImpl is probably dangerous. But should be safe until we introduce a public DynamicRealm. - withContext((realm as RealmImpl).notificationScheduler.dispatcher) { - val callback = SubscriptionSetCallback { state -> - when (state) { - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_COMPLETE -> { - channel.trySend(true) - } - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_ERROR -> { - channel.trySend(false) - } - else -> { - // Ignore all other states, wait for either complete or error. - } - } - } - RealmInterop.realm_sync_on_subscriptionset_state_change_async( - nativePointer, - CoreSubscriptionSetState.RLM_SYNC_SUBSCRIPTION_COMPLETE, - callback - ) - channel.receive() - } - } - refresh() - // Also refresh the Realm as the data has only been written on a background thread - // when this is called. So the user facing Realm might not see the data yet. - // - if (realm is RealmImpl) { - realm.refresh() - } else { - // Currently we only support accessing subscriptions through - // `Realm.subscriptions`. - TODO("Calling `waitForSynchronization` on this type of Realm is not supported: $realm") - } - - when (result) { - is Boolean -> { - if (result) { - return true - } else { - throw BadFlexibleSyncQueryException(errorMessage, isFatal = false) - } - } - else -> throw IllegalStateException("Unexpected value: $result") - } - } catch (ex: TimeoutCancellationException) { - // Don't throw if timeout is hit, instead just return false per the API contract. - return false - } finally { - channel.close() - } - } - - override fun refresh(): SubscriptionSet { - checkClosed() - RealmInterop.realm_sync_subscriptionset_refresh(nativePointer) - return this - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt deleted file mode 100644 index 012736d418..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncConfigurationImpl.kt +++ /dev/null @@ -1,495 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.FrozenRealmReference -import io.realm.kotlin.internal.InternalConfiguration -import io.realm.kotlin.internal.MutableLiveRealmImpl -import io.realm.kotlin.internal.RealmImpl -import io.realm.kotlin.internal.TypedFrozenRealmImpl -import io.realm.kotlin.internal.interop.AsyncOpenCallback -import io.realm.kotlin.internal.interop.FrozenRealmPointer -import io.realm.kotlin.internal.interop.LiveRealmPointer -import io.realm.kotlin.internal.interop.RealmAppPointer -import io.realm.kotlin.internal.interop.RealmAsyncOpenTaskPointer -import io.realm.kotlin.internal.interop.RealmConfigurationPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSyncConfigurationPointer -import io.realm.kotlin.internal.interop.RealmSyncSessionPointer -import io.realm.kotlin.internal.interop.SyncAfterClientResetHandler -import io.realm.kotlin.internal.interop.SyncBeforeClientResetHandler -import io.realm.kotlin.internal.interop.SyncErrorCallback -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.internal.interop.sync.SyncSessionResyncMode -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException -import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.DiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.InitialRemoteDataConfiguration -import io.realm.kotlin.mongodb.sync.InitialSubscriptionsConfiguration -import io.realm.kotlin.mongodb.sync.ManuallyRecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverOrDiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.SyncClientResetStrategy -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.mongodb.sync.SyncSession -import kotlinx.atomicfu.AtomicBoolean -import kotlinx.atomicfu.AtomicRef -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import org.mongodb.kbson.BsonValue - -@Suppress("LongParameterList") -internal class SyncConfigurationImpl( - private val configuration: InternalConfiguration, - internal val partitionValue: BsonValue?, - override val user: UserImpl, - override val errorHandler: SyncSession.ErrorHandler, - override val syncClientResetStrategy: SyncClientResetStrategy, - override val initialSubscriptions: InitialSubscriptionsConfiguration?, - override val initialRemoteData: InitialRemoteDataConfiguration? -) : InternalConfiguration by configuration, SyncConfiguration { - - override suspend fun openRealm(realm: RealmImpl): Pair { - // Partition-based Realms with `waitForInitialRemoteData` enabled will use - // async open first do download the server side Realm. This is much faster than - // creating the Realm locally first and then downloading (and integrating) changes into - // that. - // - // Flexible Sync Realms with `waitForInitialRemoteData` enabled will use async open - // in order to prevent overloading the server with schema updates. By itself, it isn't - // a big problem, but if many thousands of devices all connect at the same time it puts - // unnecessary pressure on the server. - val fileExists: Boolean = fileExists(configuration.path) - val asyncOpenCreatedRealmFile: AtomicBoolean = atomic(false) - if (initialRemoteData != null && !fileExists) { - // Channel to work around not being able to use `suspendCoroutine` to wrap the callback, as - // that results in the `Continuation` being frozen, which breaks it. - val channel = Channel(1) - val taskPointer: AtomicRef = atomic(null) - try { - val result: Any = withTimeout(initialRemoteData.timeout.inWholeMilliseconds) { - withContext(realm.notificationScheduler.dispatcher) { - val callback = AsyncOpenCallback { error: Throwable? -> - if (error != null) { - channel.trySend(error) - } else { - channel.trySend(true) - } - } - - val configPtr = createNativeConfiguration() - taskPointer.value = RealmInterop.realm_open_synchronized(configPtr) - RealmInterop.realm_async_open_task_start(taskPointer.value!!, callback) - channel.receive() - } - } - when (result) { - is Boolean -> { - // Track whether or not async open created the file. - asyncOpenCreatedRealmFile.value = true - } - is Throwable -> throw result - else -> throw IllegalStateException("Unexpected value: $result") - } - } catch (ex: TimeoutCancellationException) { - taskPointer.value?.let { ptr: RealmAsyncOpenTaskPointer -> - RealmInterop.realm_async_open_task_cancel(ptr) - } - throw DownloadingRealmTimeOutException(this) - } finally { - channel.close() - } - } - - // Open the local Realm file. This will include any data potentially downloaded - // by Async Open above. - // - // Core will track whether or not the file was created as part of opening for the first - // time, but that might conflicts with us potentially using async open before calling - // this method. - // - // So there are two possibilities for the file to be created: - // 1) .waitForInitialRemoteData caused async open to be used, which created the file. - // 2) The synced Realm was opened locally first (without async open), which then created the file. - val result: Pair = configuration.openRealm(realm) - return Pair(result.first, result.second || asyncOpenCreatedRealmFile.value) - } - - override suspend fun initializeRealmData(realm: RealmImpl, realmFileCreated: Boolean) { - // Create or update subscriptions for Flexible Sync realms as needed. - initialSubscriptions?.let { initialSubscriptionsConfig -> - if (initialSubscriptionsConfig.rerunOnOpen || realmFileCreated) { - realm.subscriptions.update { - with(initialSubscriptions.callback) { - write(realm) - } - } - } - } - - // Download subscription data if needed. Partition-base realms can only configure - // `waitForInitialRemoteData` which is being accounted for when calling `openRealm`, so that - // case is ignored here. - if (initialRemoteData != null && initialSubscriptions != null) { - val updateExistingFile = initialSubscriptions.rerunOnOpen && !realmFileCreated - if (realmFileCreated || updateExistingFile) { - val success: Boolean = - realm.subscriptions.waitForSynchronization(initialRemoteData.timeout) - if (!success) { - throw DownloadingRealmTimeOutException(this) - } - } - } - - // Last, run any local Realm initialization logic - configuration.initializeRealmData(realm, realmFileCreated) - } - - override fun createNativeConfiguration(): RealmConfigurationPointer { - val ptr: RealmConfigurationPointer = configuration.createNativeConfiguration() - return syncInitializer(ptr) - } - - private val syncInitializer: (RealmConfigurationPointer) -> RealmConfigurationPointer - - init { - // We need to freeze `errorHandler` reference on initial thread - val userErrorHandler = errorHandler - val resetStrategy = syncClientResetStrategy - val frozenAppPointer = user.app.nativePointer - - val initializerHelper = when (resetStrategy) { - is DiscardUnsyncedChangesStrategy -> - DiscardUnsyncedChangesHelper(resetStrategy, configuration) - is ManuallyRecoverUnsyncedChangesStrategy -> - ManuallyRecoverUnsyncedChangesHelper(resetStrategy) - is RecoverUnsyncedChangesStrategy -> - RecoverUnsyncedChangesHelper(resetStrategy, configuration) - is RecoverOrDiscardUnsyncedChangesStrategy -> - RecoverOrDiscardUnsyncedChangesHelper(resetStrategy, configuration) - else -> throw IllegalArgumentException("Unsupported client reset strategy: $resetStrategy") - } - - val errorCallback = - SyncErrorCallback { pointer: RealmSyncSessionPointer, error: SyncError -> - val session = SyncSessionImpl(pointer) - val syncError = convertSyncError(error) - if (error.isClientResetRequested) { - // If a Client Reset happened, we only get here if `onManualResetFallback` needs - // to be called. This means there is a high likelihood that users will want to - // call ClientResetRequiredException.executeClientReset() inside the callback. - // - // In order to do that, they will need to close the Realm first. - // - // On POSIX this will work fine, but on Windows this will fail as the - // C++ session still holds a DBPointer preventing the release of the file during - // the callback. - // - // So, in order to prevent errors on Windows, we are running the Kotlin callback - // on a separate worker thread. This will allow Core to finish its callback so - // when we close the Realm from the worker thread, the underlying - // session can also be fully freed. - // - // Given that we do not make any promises regarding which thread the callback - // is running on. This should be fine. - @OptIn(DelicateCoroutinesApi::class) - try { - GlobalScope.launch { - initializerHelper.onSyncError(session, frozenAppPointer, error) - } - } catch (ex: Exception) { - @Suppress("invisible_member", "invisible_reference") - configuration.logger.error("Error thrown and ignored in `onManualResetFallback`: $ex") - } - } else { - userErrorHandler.onError(session, syncError) - } - } - - syncInitializer = { nativeConfig: RealmConfigurationPointer -> - val nativeSyncConfig: RealmSyncConfigurationPointer = when (partitionValue) { - null -> RealmInterop.realm_flx_sync_config_new(user.nativePointer) - else -> RealmInterop.realm_sync_config_new( - user.nativePointer, - partitionValue.toJson() - ) - } - - RealmInterop.realm_sync_config_set_error_handler( - nativeSyncConfig, - errorCallback - ) - - // Do any initialization required for the strategies - initializerHelper.initialize(nativeSyncConfig) - - RealmInterop.realm_config_set_sync_config(nativeConfig, nativeSyncConfig) - - nativeConfig - } - } - - override val syncMode: SyncMode = - if (partitionValue == null) SyncMode.FLEXIBLE else SyncMode.PARTITION_BASED -} - -private interface ClientResetStrategyHelper { - fun initialize(nativeSyncConfig: RealmSyncConfigurationPointer) - fun onSyncError(session: SyncSession, appPointer: RealmAppPointer, error: SyncError) -} - -private abstract class OnBeforeOnAfterHelper constructor( - val strategy: T, - val configuration: InternalConfiguration -) : ClientResetStrategyHelper { - - abstract fun getResyncMode(): SyncSessionResyncMode - abstract fun getBefore(): SyncBeforeClientResetHandler - abstract fun getAfter(): SyncAfterClientResetHandler - - override fun initialize(nativeSyncConfig: RealmSyncConfigurationPointer) { - RealmInterop.realm_sync_config_set_resync_mode(nativeSyncConfig, getResyncMode()) - RealmInterop.realm_sync_config_set_before_client_reset_handler( - nativeSyncConfig, - getBefore() - ) - RealmInterop.realm_sync_config_set_after_client_reset_handler( - nativeSyncConfig, - getAfter() - ) - } -} - -private class RecoverOrDiscardUnsyncedChangesHelper constructor( - strategy: RecoverOrDiscardUnsyncedChangesStrategy, - configuration: InternalConfiguration -) : OnBeforeOnAfterHelper(strategy, configuration) { - - override fun getResyncMode(): SyncSessionResyncMode = - SyncSessionResyncMode.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER_OR_DISCARD - - override fun getBefore(): SyncBeforeClientResetHandler = - object : SyncBeforeClientResetHandler { - override fun onBeforeReset(realmBefore: FrozenRealmPointer) { - strategy.onBeforeReset(TypedFrozenRealmImpl(realmBefore, configuration)) - } - } - - override fun getAfter(): SyncAfterClientResetHandler = - object : SyncAfterClientResetHandler { - override fun onAfterReset( - realmBefore: FrozenRealmPointer, - realmAfter: LiveRealmPointer, - didRecover: Boolean - ) { - // Needed to allow writes on the Mutable after Realm - RealmInterop.realm_begin_write(realmAfter) - - @Suppress("TooGenericExceptionCaught") - try { - val before = TypedFrozenRealmImpl(realmBefore, configuration) - val after = MutableLiveRealmImpl(realmAfter, configuration) - if (didRecover) { - strategy.onAfterRecovery(before, after) - } else { - strategy.onAfterDiscard(before, after) - } - - // Callback completed successfully we can safely commit the changes - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_commit(realmAfter) - } - } catch (exception: Throwable) { - // Cancel the transaction - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_rollback(realmAfter) - } - // Rethrow so core can send it over again - throw exception - } - } - } - - override fun onSyncError( - session: SyncSession, - appPointer: RealmAppPointer, - error: SyncError - ) { - // If there is a user exception we appoint it as the cause of the client reset - strategy.onManualResetFallback( - session, - ClientResetRequiredException(appPointer, error) - ) - } -} - -private class RecoverUnsyncedChangesHelper constructor( - strategy: RecoverUnsyncedChangesStrategy, - configuration: InternalConfiguration -) : OnBeforeOnAfterHelper(strategy, configuration) { - - override fun getResyncMode(): SyncSessionResyncMode = - SyncSessionResyncMode.RLM_SYNC_SESSION_RESYNC_MODE_RECOVER - - override fun getBefore(): SyncBeforeClientResetHandler = - object : SyncBeforeClientResetHandler { - override fun onBeforeReset(realmBefore: FrozenRealmPointer) { - strategy.onBeforeReset(TypedFrozenRealmImpl(realmBefore, configuration)) - } - } - - override fun getAfter(): SyncAfterClientResetHandler = - object : SyncAfterClientResetHandler { - override fun onAfterReset( - realmBefore: FrozenRealmPointer, - realmAfter: LiveRealmPointer, - didRecover: Boolean - ) { - // Needed to allow writes on the Mutable after Realm - RealmInterop.realm_begin_write(realmAfter) - - @Suppress("TooGenericExceptionCaught") - try { - strategy.onAfterReset( - TypedFrozenRealmImpl(realmBefore, configuration), - MutableLiveRealmImpl(realmAfter, configuration) - ) - - // Callback completed successfully we can safely commit the changes - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_commit(realmAfter) - } - } catch (exception: Throwable) { - // Cancel the transaction - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_rollback(realmAfter) - } - // Rethrow so core can send it over again - throw exception - } - } - } - - override fun onSyncError( - session: SyncSession, - appPointer: RealmAppPointer, - error: SyncError - ) { - // If there is a user exception we appoint it as the cause of the client reset - strategy.onManualResetFallback( - session, - ClientResetRequiredException(appPointer, error) - ) - } -} - -private class DiscardUnsyncedChangesHelper constructor( - strategy: DiscardUnsyncedChangesStrategy, - configuration: InternalConfiguration -) : OnBeforeOnAfterHelper(strategy, configuration) { - - override fun getResyncMode(): SyncSessionResyncMode = - SyncSessionResyncMode.RLM_SYNC_SESSION_RESYNC_MODE_DISCARD_LOCAL - - override fun getBefore(): SyncBeforeClientResetHandler = - object : SyncBeforeClientResetHandler { - override fun onBeforeReset(realmBefore: FrozenRealmPointer) { - strategy.onBeforeReset(TypedFrozenRealmImpl(realmBefore, configuration)) - } - } - - override fun getAfter(): SyncAfterClientResetHandler = - object : SyncAfterClientResetHandler { - override fun onAfterReset( - realmBefore: FrozenRealmPointer, - realmAfter: LiveRealmPointer, - didRecover: Boolean - ) { - // Needed to allow writes on the Mutable after Realm - RealmInterop.realm_begin_write(realmAfter) - - @Suppress("TooGenericExceptionCaught") - try { - strategy.onAfterReset( - TypedFrozenRealmImpl(realmBefore, configuration), - MutableLiveRealmImpl(realmAfter, configuration) - ) - - // Callback completed successfully we can safely commit the changes - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_commit(realmAfter) - } - } catch (exception: Throwable) { - // Cancel the transaction - // user might have cancelled the transaction manually - if (RealmInterop.realm_is_in_transaction(realmAfter)) { - RealmInterop.realm_rollback(realmAfter) - } - // Rethrow so core can send it over again - throw exception - } - } - } - - override fun onSyncError( - session: SyncSession, - appPointer: RealmAppPointer, - error: SyncError - ) { - strategy.onManualResetFallback( - session, - ClientResetRequiredException(appPointer, error) - ) - } -} - -private class ManuallyRecoverUnsyncedChangesHelper( - val strategy: ManuallyRecoverUnsyncedChangesStrategy -) : ClientResetStrategyHelper { - - override fun initialize(nativeSyncConfig: RealmSyncConfigurationPointer) { - RealmInterop.realm_sync_config_set_resync_mode( - nativeSyncConfig, - SyncSessionResyncMode.RLM_SYNC_SESSION_RESYNC_MODE_MANUAL - ) - } - - override fun onSyncError( - session: SyncSession, - appPointer: RealmAppPointer, - error: SyncError - ) { - strategy.onClientReset( - session, - ClientResetRequiredException(appPointer, error) - ) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncImpl.kt deleted file mode 100644 index 41391cd869..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncImpl.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmAppPointer -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.mongodb.sync.Sync - -internal class SyncImpl(private val app: RealmAppPointer) : Sync { - - override val hasSyncSessions: Boolean - get() = RealmInterop.realm_app_sync_client_has_sessions(app) - - override fun reconnect() { - RealmInterop.realm_app_sync_client_reconnect(app) - } - - override fun waitForSessionsToTerminate() { - RealmInterop.realm_app_sync_client_wait_for_sessions_to_terminate(app) - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt deleted file mode 100644 index c7dd207ae4..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncSessionImpl.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.NotificationToken -import io.realm.kotlin.internal.RealmImpl -import io.realm.kotlin.internal.interop.CoreError -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmSyncSessionPointer -import io.realm.kotlin.internal.interop.SyncSessionTransferCompletionCallback -import io.realm.kotlin.internal.interop.sync.CoreConnectionState -import io.realm.kotlin.internal.interop.sync.CoreSyncSessionState -import io.realm.kotlin.internal.interop.sync.ProgressDirection -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.internal.util.Validation -import io.realm.kotlin.internal.util.trySendWithBufferOverflowCheck -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.ConnectionState -import io.realm.kotlin.mongodb.sync.ConnectionStateChange -import io.realm.kotlin.mongodb.sync.Direction -import io.realm.kotlin.mongodb.sync.Progress -import io.realm.kotlin.mongodb.sync.ProgressMode -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.notifications.internal.Cancellable -import io.realm.kotlin.notifications.internal.Cancellable.Companion.NO_OP_NOTIFICATION_TOKEN -import kotlinx.atomicfu.AtomicRef -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlin.time.Duration - -internal open class SyncSessionImpl( - initializerRealm: RealmImpl?, - internal val nativePointer: RealmSyncSessionPointer -) : SyncSession { - - // Constructor used when there is no Realm available, e.g. in the SyncSessionErrorHandler. - // Without a Realm reference, it is impossible to track shared state between the public - // Realm and the SyncSession. This impacts `downloadAllServerChanges()`. - // Since there probably isn't a use case where you ever is going to call - // `downloadAllServerChanges` inside the error handler, we are just going to disallow it by - // throwing an IllegalStateException. Mostly because that is by far the easiest with the - // current implementation. - constructor(ptr: RealmSyncSessionPointer) : this(null, ptr) - - private val _realm: RealmImpl? = initializerRealm - private val realm: RealmImpl - get() = _realm ?: throw IllegalStateException("Operation is not allowed inside a `SyncSession.ErrorHandler`.") - - override val configuration: SyncConfiguration - // TODO Get the sync config w/o ever throwing - get() = realm.configuration as SyncConfiguration - - override val user: User - get() = configuration.user - - override val state: SyncSession.State - get() { - val state = RealmInterop.realm_sync_session_state(nativePointer) - return SyncSessionImpl.stateFrom(state) - } - - override val connectionState: ConnectionState - get() = connectionStateFrom(RealmInterop.realm_sync_connection_state(nativePointer)) - - private enum class TransferDirection { - UPLOAD, DOWNLOAD - } - - override suspend fun downloadAllServerChanges(timeout: Duration): Boolean { - return waitForChanges(TransferDirection.DOWNLOAD, timeout) - } - - override suspend fun uploadAllLocalChanges(timeout: Duration): Boolean { - return waitForChanges(TransferDirection.UPLOAD, timeout) - } - - override fun pause() { - RealmInterop.realm_sync_session_pause(nativePointer) - } - - override fun resume() { - RealmInterop.realm_sync_session_resume(nativePointer) - } - - @Suppress("invisible_member", "invisible_reference") - override fun progressAsFlow( - direction: Direction, - progressMode: ProgressMode, - ): Flow { - return realm.scopedFlow { - callbackFlow { - val token: AtomicRef = - kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) - token.value = NotificationToken( - RealmInterop.realm_sync_session_register_progress_notifier( - nativePointer, - when (direction) { - Direction.DOWNLOAD -> ProgressDirection.RLM_SYNC_PROGRESS_DIRECTION_DOWNLOAD - Direction.UPLOAD -> ProgressDirection.RLM_SYNC_PROGRESS_DIRECTION_UPLOAD - }, - progressMode == ProgressMode.INDEFINITELY - ) { progressEstimate: Double -> - val progress = Progress(progressEstimate) - trySendWithBufferOverflowCheck(progress) - if (progressMode == ProgressMode.CURRENT_CHANGES && progress.isTransferComplete) { - close() - } - } - ) - awaitClose { - token.value.cancel() - } - } - } - } - - @Suppress("invisible_member", "invisible_reference") // To be able to use RealmImpl.scopedFlow from library-base - override fun connectionStateAsFlow(): Flow = realm.scopedFlow { - callbackFlow { - val token: AtomicRef = kotlinx.atomicfu.atomic(NO_OP_NOTIFICATION_TOKEN) - token.value = NotificationToken( - RealmInterop.realm_sync_session_register_connection_state_change_callback( - nativePointer - ) { oldState: CoreConnectionState, newState: CoreConnectionState -> - trySendWithBufferOverflowCheck( - ConnectionStateChange( - connectionStateFrom(oldState), - connectionStateFrom(newState) - ) - ) - } - ) - awaitClose { - token.value.cancel() - } - } - } - - /** - * Simulates a sync error. Internal visibility only for testing. - */ - internal fun simulateSyncError( - error: ErrorCode, - message: String = "Simulate Client Reset" - ) { - RealmInterop.realm_sync_session_handle_error_for_testing( - nativePointer, - error, - message, - false - ) - } - - /** - * Wrap Core callbacks that will not be invoked until data has been either fully uploaded - * or downloaded. - * - * When this method returns. The user facing Realm has been updated to the latest state. - * - * @param direction whether data is being uploaded or downloaded. - * @param timeout timeout parameter. - * @return `true` if the job completed before the timeout was hit, `false` otherwise. - */ - private suspend fun waitForChanges(direction: TransferDirection, timeout: Duration): Boolean { - Validation.require(timeout.isPositive()) { - "'timeout' must be > 0. It was: $timeout" - } - - // Channel to work around not being able to use `suspendCoroutine` to wrap the callback, as - // that results in the `Continuation` being frozen, which breaks it. - val channel = Channel(1) - try { - val result: Any = withTimeout(timeout) { - withContext(realm.notificationScheduler.dispatcher) { - val callback = object : SyncSessionTransferCompletionCallback { - override fun invoke(errorCode: CoreError?) { - if (errorCode != null) { - // Transform the errorCode into a dummy syncError so we can have a - // common path. - val syncError = SyncError(errorCode) - channel.trySend(convertSyncError(syncError)) - } else { - channel.trySend(true) - } - } - } - when (direction) { - TransferDirection.UPLOAD -> { - RealmInterop.realm_sync_session_wait_for_upload_completion( - nativePointer, - callback - ) - } - TransferDirection.DOWNLOAD -> { - RealmInterop.realm_sync_session_wait_for_download_completion( - nativePointer, - callback - ) - } - } - channel.receive() - } - } - // We need to refresh the public Realm when downloading to make the changes visible - // to users immediately, this include functionality like `Realm.writeCopyTo()` which - // require that all changes are uploaded. - realm.refresh() - when (result) { - is Boolean -> return result - is Throwable -> throw result - else -> throw IllegalStateException("Unexpected value: $result") - } - } catch (ex: TimeoutCancellationException) { - // Don't throw if timeout is hit, instead just return false per the API contract. - // However, since the download might have made progress and integrated some changesets, - // we should still refresh the public facing Realm, so it reflect however far - // Sync has gotten. - realm.refresh() - return false - } finally { - channel.close() - } - } - - fun close() { - nativePointer.release() - } - - internal companion object { - internal fun stateFrom(coreState: CoreSyncSessionState): SyncSession.State { - return when (coreState) { - CoreSyncSessionState.RLM_SYNC_SESSION_STATE_DYING -> SyncSession.State.DYING - CoreSyncSessionState.RLM_SYNC_SESSION_STATE_ACTIVE -> SyncSession.State.ACTIVE - CoreSyncSessionState.RLM_SYNC_SESSION_STATE_INACTIVE -> SyncSession.State.INACTIVE - CoreSyncSessionState.RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN -> SyncSession.State.WAITING_FOR_ACCESS_TOKEN - CoreSyncSessionState.RLM_SYNC_SESSION_STATE_PAUSED -> SyncSession.State.PAUSED - else -> throw IllegalStateException("Unsupported state: $coreState") - } - } - internal fun connectionStateFrom(coreConnectionState: CoreConnectionState): ConnectionState { - return when (coreConnectionState) { - CoreConnectionState.RLM_SYNC_CONNECTION_STATE_DISCONNECTED -> ConnectionState.DISCONNECTED - CoreConnectionState.RLM_SYNC_CONNECTION_STATE_CONNECTING -> ConnectionState.CONNECTING - CoreConnectionState.RLM_SYNC_CONNECTION_STATE_CONNECTED -> ConnectionState.CONNECTED - else -> throw IllegalStateException("Unsupported connection state: $coreConnectionState") - } - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncedRealmContext.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncedRealmContext.kt deleted file mode 100644 index 5cde72de64..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/SyncedRealmContext.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.internal.BaseRealmImpl -import io.realm.kotlin.internal.RealmImpl -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession - -/** - * Since extension functions has limited capabilities, like not allowing backing fields. This class - * contains all fields and functionality needed to expose public Sync API's. Meaning that the - * extension functions only need to verify that the call is valid and otherwise just delegate - * to this class. - * - * In order to work around the bootstrap problem, all public API entry points that access this - * class must do so through the [executeInSyncContext] closure. - */ -@OptIn(ExperimentalStdlibApi::class) -internal class SyncedRealmContext(realm: T) : AutoCloseable { - // TODO For now this can only be a RealmImpl, which is required by the SyncSessionImpl - // When we introduce a public DynamicRealm, this can also be a `DynamicRealmImpl` - // And we probably need to modify the SyncSessionImpl to take either of these two. - private val baseRealm = realm as RealmImpl - internal val config: SyncConfiguration = baseRealm.configuration as SyncConfiguration - // Note: Session and Subscriptions only need a valid dbPointer when being created, after that, they - // have their own lifecycle and can be cached. - private val sessionDelegate: Lazy = lazy { - SyncSessionImpl( - baseRealm, - RealmInterop.realm_sync_session_get(baseRealm.realmReference.dbPointer) - ) - } - internal val session: SyncSession by sessionDelegate - - private val subscriptionsDelegate: Lazy> = lazy { - SubscriptionSetImpl( - realm, - RealmInterop.realm_sync_get_latest_subscriptionset(baseRealm.realmReference.dbPointer) - ) - } - internal val subscriptions: SubscriptionSet by subscriptionsDelegate - - override fun close() { - if (sessionDelegate.isInitialized()) { - (session as SyncSessionImpl).close() - } - } -} - -/** - * Helper methods that can be used by public API entry points to grant safe access to the - * [SyncedRealmContext], or otherwise throw an appropriate exception. - */ -internal fun executeInSyncContext(realm: R, block: (context: SyncedRealmContext) -> T): T { - if (realm.isClosed()) { - throw IllegalStateException("This method is not available when the Realm has been closed.") - } - val config = realm.configuration - if (config is SyncConfiguration) { - if (realm is BaseRealmImpl) { - val context: SyncedRealmContext = initSyncContextIfNeeded(realm) - return block(context) - } else { - // Should never happen. Indicates a problem with our internal architecture. - throw IllegalStateException("This method is not available on objects of type: $realm") - } - } else { - // Public error - throw IllegalStateException("This method is only available on synchronized realms.") - } -} - -@OptIn(ExperimentalStdlibApi::class) -private fun initSyncContextIfNeeded(realm: T): SyncedRealmContext { - // INVARIANT: `syncContext` is only ever set once, and never to `null`. - // This code works around the fact that `Mutex`'s can only be locked inside suspend functions on - // Kotlin Native. - val syncContext = (realm as RealmImpl).syncContext - return if (syncContext.value != null) { - @Suppress("UNCHECKED_CAST") - syncContext.value!! as SyncedRealmContext - } else { - // Worst case, two SyncedRealmContext will be created and one of them will thrown - // away. As long as SyncedRealmContext is cheap to create, this should be fine. If, at - // some point, it start having too much state, we can consider making `lazy` properties - // inside the class to defer the construction cost. - syncContext.compareAndSet(null, SyncedRealmContext(realm)) - @Suppress("UNCHECKED_CAST") - syncContext.value!! as SyncedRealmContext - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt deleted file mode 100644 index 22bb208e00..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/internal/UserImpl.kt +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.RealmUserPointer -import io.realm.kotlin.internal.interop.sync.CoreUserState -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.Functions -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.UserIdentity -import io.realm.kotlin.mongodb.auth.ApiKeyAuth -import io.realm.kotlin.mongodb.mongo.MongoClient -import kotlinx.coroutines.channels.Channel -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -// TODO Public due to being a transitive dependency to SyncConfigurationImpl -public class UserImpl( - public val nativePointer: RealmUserPointer, - override val app: AppImpl -) : User { - override val apiKeyAuth: ApiKeyAuth by lazy { - ApiKeyAuthImpl(app, this) - } - override val state: User.State - get() = fromCoreState(RealmInterop.realm_user_get_state(nativePointer)) - - override val id: String - get() = RealmInterop.realm_user_get_identity(nativePointer) - override val loggedIn: Boolean - get() = RealmInterop.realm_user_is_logged_in(nativePointer) - - override val accessToken: String - get() = RealmInterop.realm_user_get_access_token(nativePointer) - override val refreshToken: String - get() = RealmInterop.realm_user_get_refresh_token(nativePointer) - override val deviceId: String - get() = RealmInterop.realm_user_get_device_id(nativePointer) - override val functions: Functions by lazy { FunctionsImpl(app, this) } - - @PublishedApi - internal fun profileInternal(block: (ejsonEncodedProfile: String) -> T): T = - block(RealmInterop.realm_user_get_profile(nativePointer)) - - @PublishedApi - internal fun customDataInternal(block: (ejsonEncodedCustomData: String) -> T?): T? = - RealmInterop.realm_user_get_custom_data(nativePointer)?.let(block) - - override suspend fun refreshCustomData() { - Channel>(1).use { channel -> - RealmInterop.realm_user_refresh_custom_data( - app = app.nativePointer, - user = nativePointer, - callback = channelResultCallback(channel) { - // No-op - } - ) - return channel.receive() - .getOrThrow() - } - } - - override val identities: List - get() = RealmInterop.realm_user_get_all_identities(nativePointer).map { - UserIdentity(it.id, AuthenticationProviderImpl.fromId(it.provider)) - } - - override suspend fun logOut() { - Channel>(1).use { channel -> - val reportLoggedOut = loggedIn - RealmInterop.realm_app_log_out( - app.nativePointer, - nativePointer, - channelResultCallback(channel) { - if (reportLoggedOut) { - User.State.LOGGED_OUT - } else { - null - } - } - ) - return@use channel.receive() - .getOrThrow().also { state: User.State? -> - if (state != null) { - app.reportAuthenticationChange(this, state) - } - } - } - } - - override suspend fun remove(): User { - Channel>(1).use { channel -> - val reportRemoved = loggedIn - RealmInterop.realm_app_remove_user( - app.nativePointer, - nativePointer, - channelResultCallback(channel) { - if (reportRemoved) { - User.State.REMOVED - } else { - null - } - } - ) - return@use channel.receive() - .getOrThrow().also { state: User.State? -> - if (state != null) { - app.reportAuthenticationChange(this, state) - } - } - } - return this - } - - override suspend fun delete() { - if (state != User.State.LOGGED_IN) { - throw IllegalStateException("User must be logged in, in order to be deleted.") - } - Channel>(1).use { channel -> - RealmInterop.realm_app_delete_user( - app.nativePointer, - nativePointer, - channelResultCallback(channel) { - User.State.REMOVED - } - ) - return@use channel.receive() - .getOrThrow().also { state: User.State -> - app.reportAuthenticationChange(this, state) - } - } - } - - override suspend fun linkCredentials(credentials: Credentials): User { - if (state != User.State.LOGGED_IN) { - throw IllegalStateException("User must be logged in, in order to link credentials to it.") - } - Channel>(1).use { channel -> - RealmInterop.realm_app_link_credentials( - app.nativePointer, - nativePointer, - (credentials as CredentialsImpl).nativePointer, - channelResultCallback(channel) { userPointer -> - UserImpl(userPointer, app) - } - ) - channel.receive().getOrThrow() - return this - } - } - - @ExperimentalKBsonSerializerApi - override fun mongoClient(serviceName: String, eJson: EJson?): MongoClient { - if (!loggedIn) throw IllegalStateException("Cannot obtain a MongoClient from a logged out user") - return MongoClientImpl(this, serviceName, eJson ?: app.configuration.ejson) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as UserImpl - if (id != (other.id)) return false - - return app.configuration == other.app.configuration - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + app.configuration.appId.hashCode() - return result - } - - public companion object { - /** - * Converts a Core state value to a library state value. - * - * For internal use only. - */ - public fun fromCoreState(coreState: CoreUserState): User.State = when (coreState) { - CoreUserState.RLM_USER_STATE_LOGGED_OUT -> User.State.LOGGED_OUT - CoreUserState.RLM_USER_STATE_LOGGED_IN -> User.State.LOGGED_IN - CoreUserState.RLM_USER_STATE_REMOVED -> User.State.REMOVED - else -> throw IllegalArgumentException("Invalid user state: ${coreState.name}") - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt deleted file mode 100644 index b54d11b151..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoClient.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.mongo - -import io.realm.kotlin.internal.RealmObjectCompanion -import io.realm.kotlin.mongodb.internal.MongoDBSerializer -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.RealmObject -import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.SerializersModule -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson -import kotlin.reflect.KClass - -/** - * A **Mongo client** is used to access an App Service's Data Source directly without Sync support. - * - * This API corresponds to the Atlas App Service "MongoDB API". Please consult the - * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) - * for a detailed description of methods and arguments. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * that supports the [Kotlin Serialization framework](https://github.com/Kotlin/kotlinx.serialization) - * and handles serialization to and from classes marked with [Serializable]. Serialization can be - * customized by customizing the [EJson]-serializer passed to the various [MongoClient], - * [MongoDatabase] and [MongoCollection]-factory methods. - * - * Object references (links) are serialized solely by their primary keys, so to serialize the - * MongoDB API requests and responses to and from realm objects ([RealmObject], - * [EmbeddedRealmObject] and [AsymmetricRealmObject]) the serialization framework must be - * configured with special serializers for those. This can be done with - * ``` - * val user = app.currentUser - * val client = user.mongoClient( - * "serviceName", - * EJson( - * serializersModule = realmSerializerModule( - * setOf( - * MongoDBCollectionDataType1::class, - * MongoDBCollectionDataType2::class - * ) - * ) - * ) - * ``` - * - * *NOTE* Since the MongoDB API responses only includes primary key information for links, - * serialization of responses into realm objects ([RealmObject], [EmbeddedRealmObject] and - * [AsymmetricRealmObject]) will create instances of the target objects with only the primary key - * property set. All other properties from the realm objects will have the default values specified - * in the class definition. - * - * *NOTE* The EJSON serializer requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]. - */ -public interface MongoClient { - - /** - * The name of the data source that the [MongoClient] is connecting to. - */ - public val serviceName: String - - /** - * Get a [MongoDatabase] object to access data from the remote collections of the data source. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. - * - * @param databaseName name of the database from the data source. - * @param eJson the EJson serializer that the [MongoDatabase] should use to convert objects and - * primary keys with. Will default to the client's [EJson] instance. For details on - * configuration of serialization see - * [MongoClient]. - */ - @ExperimentalKBsonSerializerApi - public fun database(databaseName: String, eJson: EJson? = null): MongoDatabase -} - -/** - * Creates [SerializersModule] with MongoDB API compliant serializers for all the realm objects - * ([RealmObject], [EmbeddedRealmObject] and [AsymmetricRealmObject]) in the given [schema]. - * - * The target types of links in mixed fields cannot be derived from the schema definition of the - * realm objects. To be able to deserialize and create the correct instance of links in mixed - * fields, all serializers needs to know of the full set of realm objects. This means that the - * serializer module must be constructed with knowledge of all references classes in the schema. - */ -public fun realmSerializerModule(schema: Set>): SerializersModule { - val companions: Map = - schema.associate { kClass -> - @Suppress("invisible_reference", "invisible_member") - io.realm.kotlin.internal.platform.realmObjectCompanionOrThrow(kClass).let { - it.io_realm_kotlin_className to it - } - } - val serializers: List, KSerializer<*>>> = schema.map { - it to MongoDBSerializer(it, companions) - } - - return SerializersModule { - serializers.forEach { - @Suppress("UNCHECKED_CAST") - contextual(it.first as KClass, it.second as KSerializer) - } - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt deleted file mode 100644 index da07302a66..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoCollection.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.mongo - -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -/** - * A __mongo collection__ provides access to retrieve and update data from the database's - * collection with specific typed serialization. - * - * This API corresponds to the Atlas App Service "MongoDB API". Please consult the - * [MongoDB API Reference](https://www.mongodb.com/docs/atlas/app-services/functions/mongodb/api/) - * for a detailed description of methods and arguments. - * - * Input arguments and responses to the App Service HTTP requests will be serialized from and to - * the type [T] using [Kotlin's Serialization framework](https://kotlinlang.org/docs/serialization.html) - * and can be customized by [Serializable]-annotations or customizing the [EJson]-serializer passed - * to the various [MongoClient], [MongoDatabase] and [MongoCollection]-factory methods. For details - * on configuring the serialization see [MongoClient]. - * - * All operations on a [MongoCollection] will throw an: - * - [ServiceException] if the underlying App Service HTTP requests fails - * - [SerializationException] if input arguments cannot be serialized to a valid EJson document - * or if the App Service response could not be deserialized to the return types. - * - * @param T the default type that remote entities of the collection will be serialized from and - * to. - */ -public interface MongoCollection { - - /** - * Name of the remote collection. - */ - public val name: String - - /** - * Get an instance of the same collection with a different set of default types serialization. - * - * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and - * primary keys with. Will default to the databases [EJson] instance. For details on - * configuration of serialization see [MongoClient]. - */ - @ExperimentalKBsonSerializerApi - public fun withDocumentClass(eJson: EJson? = null): MongoCollection -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt deleted file mode 100644 index f9dc739cde..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/mongo/MongoDatabase.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.mongo - -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -/** - * A handle to a remote **Atlas App Service database** that provides access to its [MongoCollection]s. - */ -public interface MongoDatabase { - - /** - * Name of the remote database. - */ - public val name: String - - /** - * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's - * collection. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. - * - * @param collectionName the name of the collection name that the [MongoCollection] will - * connect to. - * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [BsonDocument] values. - */ - public fun collection(collectionName: String): MongoCollection - - /** - * Get a [MongoCollection] that exposed methods to retrieve and update data from the database's - * collection with specific typed serialization. - * - * Serialization to and from EJSON is performed with [KBSON](https://github.com/mongodb/kbson) - * and requires to opt-in to the experimental [ExperimentalKBsonSerializerApi]-feature. - * - * @param collectionName the name of the collection name that the [MongoCollection] will - * connect to. - * @param eJson the EJson serializer that the [MongoCollection] should use to convert objects and - * primary keys with. Will default to the databases [EJson] instance. For details on - * configuration of serialization see [MongoClient]. - * @param T the default type that remote entities of the collection will be serialized from and - * to. - * @return a [MongoCollection] that will accept and return entities from the remote collection - * as [T] values. - */ - @ExperimentalKBsonSerializerApi - public fun collection(collectionName: String, eJson: EJson? = null): MongoCollection -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/BaseSubscriptionSet.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/BaseSubscriptionSet.kt deleted file mode 100644 index e6fbcd884e..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/BaseSubscriptionSet.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmObject - -/** - * Base interface for shared functionality between [SubscriptionSet] and [MutableSubscriptionSet]. - */ -public interface BaseSubscriptionSet : Iterable { - - /** - * The current state of the SubscriptionSet. See [SubscriptionSetState] for more - * details about each state. - */ - public val state: SubscriptionSetState - - /** - * If [state] returns [SubscriptionSetState.ERROR], this method will return the reason. - * Errors can be fixed by modifying the subscription accordingly and then call - * [SubscriptionSet.waitForSynchronization]. - * - * @return the underlying error if the subscription set is in the [SubscriptionSetState.ERROR] - * state. For all other states `null` will be returned. - */ - public val errorMessage: String? - - /** - * The number of subscriptions currently in this subscription set. - */ - public val size: Int - - /** - * Find the subscription with a given name. - * - * @param name name of subscription to search for. - * @return the matching subscription or `null` if no subscription with that name was found. - */ - public fun findByName(name: String): Subscription? - - /** - * Find the first subscription that contains the given query. It is possible for multiple - * named subscriptions to contain the same query. - * - * @param query query to search for. - * @return the first subscription containing the query or `null` if no match was found. - */ - public fun findByQuery(query: RealmQuery): Subscription? -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionState.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionState.kt deleted file mode 100644 index 360398968c..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionState.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * A **connection state** indicates the state of the underlying connection of the [SyncSession]. - */ -public enum class ConnectionState { - - /** - * Indicates that there is no connection to the server. No data is being transferred even if - * the session is [SyncSession.State.ACTIVE]. If the connection entered this state due to an - * error, the error is reported in the [SyncConfiguration.errorHandler]. - */ - DISCONNECTED, - - /** - * Indicates that a connection is currently in progress of being established. If successful the - * next state is [CONNECTED], otherwise it will be [DISCONNECTED]. - */ - CONNECTING, - - /** - * Indicates that a connection is successfully established to the server. If the SyncSession is - * [SyncSession.State.ACTIVE] then data will now be transferred between the device and the - * server. - */ - CONNECTED, -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionStateChange.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionStateChange.kt deleted file mode 100644 index 397884994d..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ConnectionStateChange.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * A **connection state change** indicates a change in the [SyncSession]'s underlying connection - * state. - * - * @property oldState the sync session's old connection state. - * @property newState the sync session's new connection state. - * - * @see SyncSession.connectionStateAsFlow - */ -public data class ConnectionStateChange(val oldState: ConnectionState, val newState: ConnectionState) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Direction.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Direction.kt deleted file mode 100644 index 9a425b71d7..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Direction.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * A **direction** indicates whether a given [Progress]-flow created with - * [SyncSession.progressAsFlow] is reporting changes when either uploading or downloading data. - */ -public enum class Direction { - /** - * Used to pass to [SyncSession.progressAsFlow] to create a flow that reports [Progress] - * when downloading data from the server. - */ - DOWNLOAD, - /** - * Used to pass to [SyncSession.progressAsFlow] to create a flow that reports [Progress] - * when uploading data from the device to the server. - */ - UPLOAD, -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/MutableSubscriptionSet.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/MutableSubscriptionSet.kt deleted file mode 100644 index 8a92c4c213..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/MutableSubscriptionSet.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmObject -import kotlin.reflect.KClass - -/** - * A mutable subscription set makes it possible to add, remove or modify a - * [SubscriptionSet]. It becomes available when calling [SubscriptionSet.update]. - * - * @see SubscriptionSet for more information about subscription sets and Flexible Sync. - */ -public interface MutableSubscriptionSet : BaseSubscriptionSet { - - /** - * Adds a new subscription to the subscription set. If an existing subscription exists - * that matches the [query] and the [name], this operation does nothing and the existing - * subscription will be returned. - * - * If an existing named subscription exists on a different query an [IllegalArgumentException] - * will be thrown unless [updateExisting] is set to `true`, in which case the existing - * subscription will be updated with the new query. - * - * @param query the query that will be subscribed to. Note, subscription queries have - * restrictions compared to normal queries. - * @param name the name of the subscription. If no name is provided, the subscription is - * considered to be anonymous. - * @param updateExisting determines the behaviour if an existing named subscription - * already exists. This does nothing for anonymous subscriptions. - * @return the newly added subscription. - * @throws IllegalStateException if a subscription matching the provided one already exists - * but on a different query and [updateExisting] was set to `false`. - */ - public fun add(query: RealmQuery, name: String? = null, updateExisting: Boolean = false): Subscription - - /** - * Creates an anonymous [Subscription] in the current [MutableSubscriptionSet] directly from - * a [RealmQuery]. - * - * @return the [Subscription] that was added. - */ - public fun RealmQuery.subscribe(): Subscription = add(this) - - /** - * Creates a named [Subscription] in the current [MutableSubscriptionSet] directly from a - * [RealmQuery]. - * - * @param name name of the subscription. - * @param updateExisting if a different query is already registered with the provided [name], - * then set this to `true` to update the subscription. If set to `false` an exception is - * thrown instead of updating the query. - * @return the [Subscription] that was added or updated. - * @throws IllegalArgumentException if [updateExisting] is false, and another query was already - * registered with the given [name]. - */ - public fun RealmQuery.subscribe(name: String, updateExisting: Boolean = false): Subscription = - add(this, name, updateExisting) - - /** - * Remove a subscription. - * - * @param subscription subscription to remove - * @return `true` if the subscription was removed, `false` if not. - */ - public fun remove(subscription: Subscription): Boolean - - /** - * Remove a named subscription. - * - * @param name name of the subscription to remove. - * @return `true` if a matching subscription was removed, `false` if [Subscription] could be - * found. - */ - public fun remove(name: String): Boolean - - /** - * Remove all subscriptions with queries on a given [Subscription.objectType]. - * - * @param objectType subscriptions on this object type will be removed. - * @return `true` if one or more subscriptions were removed, `false` if no - * subscriptions were removed. - * @throws IllegalArgumentException if [objectType] is not part of the Schema for this Realm. - */ - public fun removeAll(objectType: String): Boolean - - /** - * Remove all subscriptions with queries on a given model class. - * - * @param type subscriptions on this type will be removed. - * @return `true` if one or more subscriptions were removed, `false` if no - * subscriptions were removed. - * @throws IllegalArgumentException if [objectType] is not part of the Schema for this Realm. - */ - public fun removeAll(type: KClass): Boolean - - /** - * Remove all subscriptions in this subscription set. - * - * @param anonymousOnly whether or not to only remove anonymous subscriptions. If `true` only - * anonymous subscriptions will be removed. If `false` both anonymous and named subscriptions - * will be removed. - * @return `true` if one or more subscriptions were removed, `false` if the subscription set - * was empty. - */ - public fun removeAll(anonymousOnly: Boolean = false): Boolean -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Progress.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Progress.kt deleted file mode 100644 index 7894b2de76..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Progress.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * A **progress indicator** emitted by flows created from [SyncSession.progressAsFlow]. - */ -public data class Progress( - /** - * Transfer progress estimation ranged from 0.0 to 1.0. - */ - val estimate: Double, -) { - /** - * Property indicating if all pending bytes have been transferred. - * - * If the [Progress]-flow was created with [ProgressMode.CURRENT_CHANGES] then - * the flow will complete when this returns `true`. - * - * If the [Progress]-flow was created with [ProgressMode.INDEFINITELY] then the - * flow can continue to emit events with `isTransferComplete = false` for subsequent events - * after returning a progress indicator with `isTransferComplete = true`. - */ - public val isTransferComplete: Boolean = estimate >= 1.0 -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ProgressMode.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ProgressMode.kt deleted file mode 100644 index 41fcf1e625..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/ProgressMode.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * A **progress mode** is used to select which notifications are received from - * [SyncSession.progressAsFlow]. - */ -public enum class ProgressMode { - - /** - * Used to pass to [SyncSession.progressAsFlow] to create a flow that will record the size of the - * current outstanding changes not transferred, and will only continue - * to report progress updates until those changes have been either downloaded or uploaded. After - * that the progress listener will not report any further changes. - * - * Progress reported in this mode will only ever increase. - * - * **NOTE:** The [Flow] returned by [SyncSession.progressAsFlow] will complete when the transfer is - * done, i.e. after emitting a [Progress] with - * [Progress.isTransferComplete] being `true`. - */ - CURRENT_CHANGES, - - /** - * Used to pass to [SyncSession.progressAsFlow] to create a flow that reports the size of outstanding - * changes not yet transferred and will continue to report progress changes, even if changes - * are being added after the listener was registered. - * - * Progress reported in this mode can both increase and decrease, e.g. if large amounts of data is - * written after registering the listener. - */ - INDEFINITELY, -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Subscription.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Subscription.kt deleted file mode 100644 index c817633e5f..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Subscription.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmObject -import org.mongodb.kbson.ObjectId -import kotlin.reflect.KClass - -/** - * A subscription defines a specific server query and its metadata. The result of this query - * is continuously being synchronized with the device as long as the subscription is part of a - * [SubscriptionSet] with a state of [SubscriptionSetState.COMPLETE]. - * - * Subscriptions can be updated using [MutableSubscriptionSet.add] with `updateExisting = true`. - */ -public interface Subscription { - - /** - * An unique id, generated by Realm, that identifies this subscription. - */ - public val id: ObjectId - - /** - * The timestamp for when this subscription was created. - */ - public val createdAt: RealmInstant - - /** - * The timestamp for when a persisted subscription was updated. When the subscription - * is created, this field is equal to [createdAt]. - */ - public val updatedAt: RealmInstant - - /** - * The name of subscription or `null` if this is an anonymous subscription. - */ - public val name: String? - - /** - * The class name of the objects being queried. - */ - public val objectType: String - - /** - * The subscription query that is running on objects of type [objectType]. - */ - public val queryDescription: String - - /** - * Converts the [Subscription.queryDescription] back to a [RealmQuery] that can be executed - * against the local Realm. - * - * @param type a reference to the Kotlin model class that represents [objectType]. - * @throws IllegalArgumentException if [type] does not match the type of objects this query - * can return. - */ - public fun asQuery(type: KClass): RealmQuery -} - -/** - * Converts the [Subscription.queryDescription] back to a [RealmQuery] that can be executed - * against the local Realm. - * - * @param T a reference to the Kotlin model class that represents the [Subscription.objectType]. - * @throws IllegalArgumentException if [type] does not match the type of objects this query - * can return. - */ -public inline fun Subscription.asQuery(): RealmQuery = - asQuery(T::class) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSet.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSet.kt deleted file mode 100644 index 1334b8eced..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSet.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.BaseRealm -import io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException -import io.realm.kotlin.query.RealmQuery -import kotlin.time.Duration - -/** - * A subscription set is an immutable view of all current [Subscription]s for a given - * Realm that has been configured for Flexible Sync. - * - * Flexible Sync is a way of defining which data gets synchronized to and from the device using - * [RealmQuery]s. The query and its metadata are represented by a [Subscription]. - * - * A subscription set thus defines all the data that is available to the device and being - * synchronized with the server. If the subscription set encounters an error, e.g. by containing an - * invalid query, the entire subscription set will enter an [SubscriptionSetState.ERROR] - * state, and no synchronization will happen until the error has been fixed. - * - * If a subscription is removed, so is the corresponding data, but it is only removed from the - * device. It isn't deleted on the server. - * - * It is possible to modify a subscription set while offline, but a modification isn't - * accepted by the server before [BaseSubscriptionSet.state] returns - * [SubscriptionSetState.COMPLETE], which requires that the device has been online. - * - * It is possible to wait for the subscription set to be synchronized with the server by using - * [waitForSynchronization]. - */ -public interface SubscriptionSet : BaseSubscriptionSet { - - public fun close() - - /** - * Modify the subscription set. If an exception is thrown during the update, no changes will be - * applied. If the update succeeds, this subscription set is updated with the modified state. - * - * @param block the block that modifies the subscription set. - * @return this subscription set, that now has been updated. - */ - public suspend fun update(block: MutableSubscriptionSet.(realm: T) -> Unit): SubscriptionSet - - /** - * Wait for the subscription set to synchronize with the server. It will return when the - * server either accepts the set of queries and has downloaded data for them, or if an - * error has occurred. - * - * @param timeout how long to wait for the synchronization to either succeed or fail. - * @return `true` if all current subscriptions were accepted by the server and data has - * been downloaded, or `false` if the [timeout] was hit before all data could be downloaded. - * @throws BadFlexibleSyncQueryException if the server did not accept the set of queries. The - * exact reason is found in the exception message. The [SubscriptionSet] will also enter a - * [SubscriptionSetState.ERROR] state. - */ - public suspend fun waitForSynchronization(timeout: Duration = Duration.INFINITE): Boolean - - /** - * Refresh the [state] of the subscription set, so it reflect the latest underlying state of - * the subscriptions. - */ - public fun refresh(): SubscriptionSet -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSetState.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSetState.kt deleted file mode 100644 index c54247d1fc..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SubscriptionSetState.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -/** - * The possible states a [SubscriptionSet] or [MutableSubscriptionSet] can be in. - */ -public enum class SubscriptionSetState { - /** - * The initial state of subscriptions when opening a new Realm or when entering - * [SubscriptionSet.update]. - */ - UNCOMMITTED, - - /** - * A subscription set has been modified locally, but is still waiting to be sent to the - * server. - */ - PENDING, - - /** - * A subscription set was accepted by the server and initial data is being sent to the - * device. - */ - BOOTSTRAPPING, - - /** - * The subscription set is valid and active. Any new data will be synchronized in both directions - * between the server and the device. - */ - COMPLETE, - - /** - * An error occurred in the subscription set or one of the subscriptions. The cause is - * found in [BaseSubscriptionSet.errorMessage]. - */ - ERROR, - - /** - * Another subscription set was stored before this one, the changes made to this set - * are ignored by the server. Get the latest subscription set by calling - * [SubscriptionSet.refresh]. - */ - SUPERSEDED, - - /** - * The last bootstrap message containing the initial state for this subscription set has been received. The - * client is awaiting a mark message to mark this subscription as fully caught up to history. - */ - AWAITING_MARK; -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Sync.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Sync.kt deleted file mode 100644 index 51cbdcd537..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/Sync.kt +++ /dev/null @@ -1,45 +0,0 @@ -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.syncSession - -/** - * A _Device Sync_ manager responsible for controlling all sync sessions across all realms - * associated with a given [App] instance. For session functionality associated with a single - * realm, see [syncSession]. - * - * @see App.sync - * @see io.realm.kotlin.mongodb.syncSession - */ -public interface Sync { - - /** - * Returns whether or not any sync sessions are still active. - */ - public val hasSyncSessions: Boolean - - /** - * Realm will automatically detect when a device gets connectivity after being offline and - * resume syncing. However, as some of these checks are performed using incremental backoff, - * this will in some cases not happen immediately. - * - * In those cases it can be beneficial to call this method manually, which will force all - * sessions to attempt to reconnect immediately and reset any timers they are using for - * incremental backoff. - * - * Note, Realm has an internal default socket read timeout of 2 minutes. Calling this method - * within those two minutes will not trigger a reconnect. - */ - public fun reconnect() - - /** - * Calling this method will block until all sync sessions for a given [App] has terminated. - * - * Closing a Realm will terminate the sync session, but it is not synchronous as Realms - * communicate with their sync session using an asynchronous communication channel. This - * has the effect that trying to delete a Realm right after closing it will sometimes throw - * an [IllegalStateException]. Using this method can be a way to ensure it is safe to delete - * the file. - */ - public fun waitForSessionsToTerminate() -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncClientResetStrategy.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncClientResetStrategy.kt deleted file mode 100644 index 4b38204122..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncClientResetStrategy.kt +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.MutableRealm -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException - -/** - * Interface that defines a generic sync client reset strategy. It can be either - * [ManuallyRecoverUnsyncedChangesStrategy] or [DiscardUnsyncedChangesStrategy]. - */ -public sealed interface SyncClientResetStrategy - -/** - * Interface that defines an automatic sync client reset strategy, it could be either - * [DiscardUnsyncedChangesStrategy], [RecoverOrDiscardUnsyncedChangesStrategy] or - * [RecoverUnsyncedChangesStrategy]. - */ -public interface AutomaticClientResetStrategy : SyncClientResetStrategy { - - /** - * Callback that indicates a Client Reset is about to happen. It receives a frozen instance - * of the realm that will be reset. - * - * @param realm frozen [TypedRealm] in its state before the reset. - */ - public fun onBeforeReset(realm: TypedRealm) - - /** - * Callback that indicates the Client reset failed to complete. It should be handled - * as in [ManuallyRecoverUnsyncedChangesStrategy.onClientReset]. - * - * @param session [SyncSession] this error happened on. - * @param exception [ClientResetRequiredException] the specific Client Reset error. - */ - public fun onManualResetFallback(session: SyncSession, exception: ClientResetRequiredException) -} - -/** - * Strategy to manually resolve a Client Reset. - * - * A synced realm may need to be reset because the Device Sync encountered an error and had to be - * restored from a backup, or because it has been too long since the client connected to the server - * so the server has rotated the logs. - * - * The Client Reset thus occurs because the server does not have all the information required to - * bring the client fully up to date. - * - * The manual reset process is as follows: the local copy of the realm is copied into a recovery - * directory for safekeeping and then deleted from the original location. The next time the realm - * for that URL is opened it will automatically be re-downloaded from Atlas, and can be used as - * usual. - * - * Data written to the realm after the local copy of itself diverged from the backup remote copy - * will be present in the local recovery copy of the Realm file. The re-downloaded realm will - * initially contain only the data present at the time the realm was backed up on the server. - * - * The client reset process can be initiated in one of two ways: - * - * 1. Run [ClientResetRequiredException.executeClientReset] manually. All Realm instances must be - * closed before this method is called. - * - * 2. If Client Reset isn't executed manually, it will automatically be carried out the next time - * all Realm instances have been closed and re-opened. This will most likely be when the app is - * restarted. - * - * **WARNING:** - * Any writes to the Realm file between this callback and Client Reset has been executed, will not - * be synchronized to Atlas. Those changes will only be present in the backed up file. It is - * therefore recommended to close all open Realm instances as soon as possible. - */ -public interface ManuallyRecoverUnsyncedChangesStrategy : SyncClientResetStrategy { - /** - * Callback that indicates a Client Reset has happened. This should be handled as quickly as - * possible as any further changes to the realm will not be synchronized with the server and - * must be moved manually from the backup realm to the new one. - * - * @param session [SyncSession] during which this error happened. - * @param exception [ClientResetRequiredException] the specific Client Reset error. - */ - public fun onClientReset(session: SyncSession, exception: ClientResetRequiredException) -} - -/** - * Strategy that automatically resolves a Client Reset by discarding any unsynced local data but - * otherwise keeps the realm open. Any changes will be reported through the normal collection and - * object notifications. - * - * A synced realm may need to be reset because the Device Sync encountered an error and had to be - * restored from a backup, or because it has been too long since the client connected to the server - * so the server has rotated the logs. - * - * The Client Reset thus occurs because the server does not have all the information required to - * bring the client fully up to date. - * - * The reset process for unsynced changes is as follows: when a client reset is triggered the - * [onBeforeReset] callback is invoked, providing an instance of the realm before the reset. Once - * the reset has concluded, [onAfterReset] will be invoked with both the instance before (read only) - * an instance of the final realm (mutable). - * - * In the event that discarding the unsynced data is not enough to resolve the reset the [onManualResetFallback] - * callback will be invoked allowing to manually resolve the reset as it would be done in - * [ManuallyRecoverUnsyncedChangesStrategy.onClientReset]. - */ -public interface DiscardUnsyncedChangesStrategy : AutomaticClientResetStrategy { - /** - * Callback invoked once the Client Reset happens. It receives two Realm instances: a frozen one - * displaying the state before the reset and a regular one with the current state that can be - * used to recover objects from the reset. - * - * @param before frozen [TypedRealm] realm before the reset. - * @param after [MutableRealm] realm after the reset. - */ - public fun onAfterReset(before: TypedRealm, after: MutableRealm) -} - -/** - * Strategy that attempts to automatically recover any unsynced changes during a Client Reset. - * - * A synced realm may need to be reset because the MongoDB Realm Server encountered an error and had - * to be restored from a backup or because it has been too long since the client connected to the - * server so the server has rotated the logs. - * - * The Client Reset thus occurs because the server does not have all the information required to - * bring the client fully up to date. - * - * The recover unsynced changes process is as follows: when a Client Reset is received by the client - * the [onBeforeReset] callback is invoked, after which the client will be reset. Once the reset - * has concluded the [onAfterReset] callback will be invoked if the changes have been recovered - * successfully. - * - * In the event that the client reset could not discard the unsynced data [onManualResetFallback] - * will be invoked. This allows to manually resolve the reset as it would have been done by - * [ManuallyRecoverUnsyncedChangesStrategy.onClientReset]. - - */ -public interface RecoverUnsyncedChangesStrategy : AutomaticClientResetStrategy { - /** - * Callback invoked once the Client Reset happens. It receives two Realm instances: a frozen one - * displaying the state before the reset and a regular one with the current state that can be - * used to recover objects from the reset. - * - * @param before frozen [TypedRealm] realm before the reset. - * @param after [MutableRealm] realm after the reset. - */ - public fun onAfterReset(before: TypedRealm, after: MutableRealm) -} - -/** - * Strategy that attempts to automatically recover any unsynced changes during a Client Reset, if - * the recovery fails the changes would be discarded. - * - * A synced realm may need to be reset because the MongoDB Realm Server encountered an error and had - * to be restored from a backup or because it has been too long since the client connected to the - * server so the server has rotated the logs. - * - * The Client Reset thus occurs because the server does not have all the information required to - * bring the client fully up to date. - * - * The recover or discard unsynced changes process is as follows: when a Client Reset is received by - * the client the [onBeforeReset] callback is invoked, after which the client will be reset. Once - * the reset has concluded the [onAfterRecovery] callback will be invoked if the changes have been - * recovered successfully, otherwise the changes will be discarded and [onAfterDiscard] will be - * invoked. - * - * In the event that the client reset could not discard the unsynced data [onManualResetFallback] - * will be invoked. This allows to manually resolve the reset as it would have been done by - * [ManuallyRecoverUnsyncedChangesStrategy.onClientReset]. - */ -public interface RecoverOrDiscardUnsyncedChangesStrategy : AutomaticClientResetStrategy { - /** - * Callback invoked once the Client Reset has recovered the unsynced changes successfully. - * It provides two Realm instances, a frozen one displaying the state before the reset and a - * mutable Realm with the current state. - * - * @param before frozen [TypedRealm] realm before the reset. - * @param after [MutableRealm] realm after the reset. - */ - public fun onAfterRecovery(before: TypedRealm, after: MutableRealm) - - /** - * Callback invoked once the Client Reset has discarded the unsynced changes because it couldn't - * recover them. It provides two Realm instances, a frozen one displaying the state before the - * reset and a regular Realm displaying the current state that can be used to recover objects - * from the reset. - * - * @param before frozen [TypedRealm] realm before the reset. - * @param after [MutableRealm] realm after the reset. - */ - public fun onAfterDiscard(before: TypedRealm, after: MutableRealm) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt deleted file mode 100644 index b2c53632a1..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncConfiguration.kt +++ /dev/null @@ -1,667 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.Configuration -import io.realm.kotlin.MutableRealm -import io.realm.kotlin.Realm -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.internal.ConfigurationImpl -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.SchemaMode -import io.realm.kotlin.internal.platform.PATH_SEPARATOR -import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.internal.SyncConfigurationImpl -import io.realm.kotlin.mongodb.internal.UserImpl -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.RealmUUID -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBinarySubType -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonValue -import kotlin.reflect.KClass -import kotlin.time.Duration - -/** - * This enum determines how Realm sync data with the server. - * - * The server must be configured for the selected way, otherwise an error will be - * reported to [SyncConfiguration.errorHandler] when the Realm connects to the server - * for the first time. - */ -public enum class SyncMode { - /** - * Partition-based Sync. Data is selected for synchronization based on a _partition key_, - * which is a property that must be set on all objects. Server objects that - * match a given _partition value_ are then synchronized to the device. - * - * **See:** [Partitions](https://www.mongodb.com/docs/atlas/app-services/sync/data-access-patterns/partitions/) - */ - PARTITION_BASED, - - /** - * Flexible Sync. Data is selected for synchronization based on one or more queries which are - * stored in a [SubscriptionSet]. All server objects that match one or more queries are then - * synchronized to the device. - * - * **See:** [Flexible Sync](https://www.mongodb.com/docs/atlas/app-services/sync/data-access-patterns/flexible-sync/) - */ - FLEXIBLE -} - -/** - * Callback used to populate the initial [SubscriptionSet] when opening a Realm. - * - * This is configured through [SyncConfiguration.Builder.initialSubscriptions]. - */ -public fun interface InitialSubscriptionsCallback { - /** - * Closure for adding or modifying the initial [SubscriptionSet], with the - * [MutableSubscriptionSet] as the receiver. This mirrors the API when using - * [SubscriptionSet.update] and allows for the following pattern: - * - * ``` - * val user = loginUser() - * val config = SyncConfiguration.Builder(user, schema) - * .initialSubscriptions { realm: Realm -> // this: MutableSubscriptionSet - * add(realm.query()) - * } - * .waitForInitialRemoteData(timeout = 30.seconds) - * .build() - * val realm = Realm.open(config) - * ``` - */ - public fun MutableSubscriptionSet.write(realm: Realm) -} - -/** - * Configuration options if [SyncConfiguration.Builder.waitForInitialRemoteData] is - * enabled. - */ -public data class InitialRemoteDataConfiguration( - - /** - * The timeout used when downloading any initial data server the first time the - * Realm is opened. - * - * If the timeout is hit, opening a Realm will throw an - * [io.realm.mongodb.exceptions.DownloadingRealmTimeOutException]. - */ - val timeout: Duration = Duration.INFINITE -) - -/** - * Configuration options if [SyncConfiguration.Builder.initialSubscriptions] is - * enabled. - */ -public data class InitialSubscriptionsConfiguration( - - /** - * The callback that will be called in order to populate the initial - * [SubscriptionSet] for the realm. - */ - val callback: InitialSubscriptionsCallback, - - /** - * The default behavior is that [callback] is only invoked the first time - * the Realm is opened, but if [rerunOnOpen] is `true`, it will be invoked - * every time the realm is opened. - */ - val rerunOnOpen: Boolean -) - -/** - * A [SyncConfiguration] is used to setup a Realm Database that can be synchronized between - * devices using Atlas Device Sync. - * - * A valid [User] is required to create a [SyncConfiguration]. See - * [Credentials] and [App.login] for more information on how to get a user object. - * - * A minimal [SyncConfiguration] can be found below. - * ``` - * val app = App.create(appId) - * val user = app.login(Credentials.anonymous()) - * val config = SyncConfiguration.create(user, "partition-value", setOf(YourRealmObject::class)) - * val realm = Realm.open(config) - * ``` - */ -public interface SyncConfiguration : Configuration { - - public val user: User - - // FIXME Hide this for now, as we should should not expose an internal class like this. - // Currently this is only available from `SyncConfigurationImpl`. - // See https://github.com/realm/realm-kotlin/issues/815 - // public val partitionValue: PartitionValue - public val errorHandler: SyncSession.ErrorHandler - - /** - * Strategy used to handle client reset scenarios. - * - * The SDK reads and writes to a local realm file. When Device Sync is enabled, the local realm - * syncs with the application backend. Some conditions can cause the realm to be unable to sync - * with Atlas. When this occurs, a client reset error is issued by the server. - * - * There is one constraint users need to be aware of when defining customized strategies when - * creating a configuration. Flexible Sync applications can **only** work in conjunction - * with [ManuallyRecoverUnsyncedChangesStrategy] whereas partition-based applications can - * **only** work in conjunction with [DiscardUnsyncedChangesStrategy]. - * - * If not specified, default strategies defined in [Builder.build] will be used, depending on - * whether the application has Flexible Sync enabled. Setting this parameter manually will - * override the use of either default strategy. - * - * **See:** [Client Reset](https://www.mongodb.com/docs/realm/sdk/java/examples/reset-a-client-realm/) - */ - public val syncClientResetStrategy: SyncClientResetStrategy - - /** - * The mode of synchronization for this realm. - */ - public val syncMode: SyncMode - - /** - * Configuration options if initial subscriptions have been enabled for this - * realm. - * - * If this has not been enabled, `null` is returned. - * - * @see SyncConfiguration.Builder.initialSubscriptions - */ - public val initialSubscriptions: InitialSubscriptionsConfiguration? - - /** - * Configuration options if downloading initial data from the server has been - * enabled for this realm. - * - * If this has not been enabled, `null` is returned. - * - * @see SyncConfiguration.Builder.waitForInitialRemoteData - */ - public val initialRemoteData: InitialRemoteDataConfiguration? - - /** - * Used to create a [SyncConfiguration]. For common use cases, a [SyncConfiguration] can be - * created using the [SyncConfiguration.create] function. - */ - public class Builder private constructor( - private var user: User, - schema: Set>, - private var partitionValue: BsonValue?, - ) : Configuration.SharedBuilder(schema) { - - // Shouldn't default to 'default.realm' - Object Store will generate it according to which - // type of Sync is used - protected override var name: String? = null - - private var errorHandler: SyncSession.ErrorHandler? = null - private var syncClientResetStrategy: SyncClientResetStrategy? = null - private var initialSubscriptions: InitialSubscriptionsConfiguration? = null - private var waitForServerChanges: InitialRemoteDataConfiguration? = null - - /** - * Creates a [SyncConfiguration.Builder] for Flexible Sync. Flexible Sync must be enabled - * on the server for this to work. - * - * **See:** [Flexible Sync](https://www.mongodb.com/docs/realm/sync/data-access-patterns/flexible-sync/) - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - */ - public constructor( - user: User, - schema: Set> - ) : this(user, schema, null) - - /** - * Creates a [SyncConfiguration.Builder] for Partition-Based Sync. Partition-Based Sync - * must be enabled on the server for this to work. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param partitionValue the partition value to use data from. The server must have been - * configured with an [BsonObjectId](https://www.mongodb.com/docs/v4.4/reference/method/ObjectId/) partition key for this to work. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * **See:** [partition key](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - */ - public constructor( - user: User, - partitionValue: BsonObjectId?, - schema: Set> - ) : this(user, schema, partitionValue ?: BsonNull) - - /** - * Creates a [SyncConfiguration.Builder] for Partition-Based Sync. Partition-Based Sync - * must be enabled on the server for this to work. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param partitionValue the partition value to use data from. The server must have been - * configured with a [RealmUUID] partition key for this to work. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * **See:** [partition key](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - */ - public constructor( - user: User, - partitionValue: RealmUUID?, - schema: Set> - ) : this( - user, - schema, - partitionValue?.let { - BsonBinary( - type = BsonBinarySubType.UUID_STANDARD, - data = it.bytes - ) - } ?: BsonNull - ) - - /** - * Creates a [SyncConfiguration.Builder] for Partition-Based Sync. Partition-Based Sync - * must be enabled on the server for this to work. - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param partitionValue the partition value to use data from. The server must have been - * configured with a Int partition key for this to work. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - */ - public constructor( - user: User, - partitionValue: Int?, - schema: Set> - ) : this(user, schema, partitionValue?.let { BsonInt32(it) } ?: BsonNull) - - /** - * Creates a [SyncConfiguration.Builder] for Partition-Based Sync. Partition-Based Sync - * must be enabled on the server for this to work. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param partitionValue the partition value to use data from. The server must have been - * configured with a Long partition key for this to work. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - */ - public constructor( - user: User, - partitionValue: Long?, - schema: Set> - ) : this(user, schema, partitionValue?.let { BsonInt64(it) } ?: BsonNull) - - /** - * Creates a [SyncConfiguration.Builder] for Partition-Based Sync. Partition-Based Sync - * must be enabled on the server for this to work. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user user used to access server side data. This will define which data is - * available from the server. - * @param partitionValue the partition value to use data from. The server must have been - * configured with a String partition key for this to work. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - */ - public constructor( - user: User, - partitionValue: String?, - schema: Set> - ) : this(user, schema, partitionValue?.let { BsonString(it) } ?: BsonNull) - - init { - if (!user.loggedIn) { - throw IllegalArgumentException("A valid, logged in user is required.") - } - } - - /** - * Sets the error handler used by Synced Realms when reporting errors with their session. - * - * @param errorHandler lambda to handle the error. - */ - public fun errorHandler(errorHandler: SyncSession.ErrorHandler): Builder = - apply { this.errorHandler = errorHandler } - - /** - * Sets the strategy that would handle the client reset by this synced Realm. - * - * In case no strategy is defined the default one, - * [RecoverOrDiscardUnsyncedChangesStrategy], will be used. - * - * @param resetStrategy custom strategy to handle client reset. - */ - public fun syncClientResetStrategy(resetStrategy: SyncClientResetStrategy): Builder = - apply { - this.syncClientResetStrategy = resetStrategy - } - - /** - * Sets the filename of the realm file. - * - * If a [SyncConfiguration] is built without having provided a [name], Realm will - * generate a file name based on the provided [partitionValue] and [AppConfiguration.appId] - * which will have a `.realm` extension. - * - * @throws IllegalArgumentException if the name includes a path separator or if the name is - * `.realm`. - */ - override fun name(name: String): Builder = apply { - checkName(name) - this.name = name - } - - /** - * Setting this will cause the Realm to download all known changes from the server the - * first time a Realm is opened. The Realm will not open until all the data has been - * downloaded. This means that if a device is offline the Realm will not open. - * - * Since downloading all changes can be a lengthy operation that might block the UI - * thread, Realms with this setting enabled should only be opened on background threads. - * - * This check is only enforced the first time a Realm is created, except if - * [initialSubscriptions] has been configured with `rerunOnOpen = true`. In that case, - * server data is downloaded every time the Realm is opened. - * - * If it is conditional when server data should be downloaded, this can be controlled - * through [SyncSession.downloadAllServerChanges], e.g like this: - * - * ``` - * val user = loginUser() - * val config = SyncConfiguration.Builder(user, schema) - * .initialSubscriptions { realm -> - * add(realm.query()) - * } - * .build() - * val realm = Realm.open(config) - * if (downloadData) { - * realm.syncSession.downloadAllServerChanges(timeout = 30.seconds) - * } - * ``` - * - * @param timeout how long to wait for the download to complete before an - * [io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException] is thrown when opening - * the Realm. - */ - public fun waitForInitialRemoteData(timeout: Duration = Duration.INFINITE): Builder = - apply { - this.waitForServerChanges = InitialRemoteDataConfiguration(timeout) - } - - /** - * Define the initial [io.realm.mongodb.sync.SubscriptionSet] for the Realm. This will only - * be executed the first time the Realm file is opened (and the file created). - * - * If [waitForInitialRemoteData] is configured as well, the realm file isn't fully - * opened until all subscription data also has been downloaded. - * - * @param rerunOnOpen If `true` this closure will rerun every time the Realm is opened, - * this makes it possible to update subscription queries with e.g. new timestamp information - * or other query data that might change over time. If [waitForInitialRemoteData] is also - * set, the Realm will download the new subscription data every time the Realm is opened, - * rather than just the first time. - * @param initialSubscriptionBlock closure making it possible to modify the set of - * subscriptions. - */ - public fun initialSubscriptions( - rerunOnOpen: Boolean = false, - initialSubscriptionBlock: InitialSubscriptionsCallback - ): Builder = apply { - if (partitionValue != null) { - throw IllegalStateException( - "Defining initial subscriptions is only available if " + - "the configuration is for Flexible Sync." - ) - } - this.initialSubscriptions = InitialSubscriptionsConfiguration( - initialSubscriptionBlock, - rerunOnOpen - ) - } - - @Suppress("LongMethod") - override fun build(): SyncConfiguration { - val realmLogger = ContextLogger() - - // Set default error handler after setting config logging logic - if (this.errorHandler == null) { - this.errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - error.message?.let { - realmLogger.warn(it) - } - } - } - } - - // Don't forget: Flexible Sync only supports Manual Client Reset - if (syncClientResetStrategy == null) { - syncClientResetStrategy = object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - realmLogger.info("Client reset: attempting to automatically recover unsynced changes in Realm: ${realm.configuration.path}") - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - realmLogger.info("Client reset: successfully recovered all unsynced changes in Realm: ${after.configuration.path}") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - realmLogger.info("Client reset: couldn't recover successfully, all unsynced changes were discarded in Realm: ${after.configuration.path}") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - realmLogger.error("Client reset: manual reset required for Realm in '${exception.originalFilePath}'") - } - } - } - - // ObjectStore uses a different default value for Flexible Sync than we want, - // so inject our default name if no user provided name was found - if (partitionValue == null && name == null) { - name = Realm.DEFAULT_FILE_NAME - } - val fullPathToFile = getAbsolutePath(name) - val fileName = fullPathToFile.substringAfterLast(PATH_SEPARATOR) - val directory = fullPathToFile.removeSuffix("$PATH_SEPARATOR$fileName") - - val baseConfiguration = ConfigurationImpl( - directory, - fileName, - schema, - maxNumberOfActiveVersions, - if (notificationDispatcher != null) { - CoroutineDispatcherFactory.unmanaged(notificationDispatcher!!) - } else { - CoroutineDispatcherFactory.managed("notifier-$fileName") - }, - if (writeDispatcher != null) { - CoroutineDispatcherFactory.unmanaged(writeDispatcher!!) - } else { - CoroutineDispatcherFactory.managed("writer-$fileName") - }, - schemaVersion, - SchemaMode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED, - encryptionKey, - compactOnLaunchCallback, - null, // migration is not relevant for sync, - false, // automatic backlink handling is not relevant for sync - initialDataCallback, - partitionValue == null, - inMemory, - initialRealmFileConfiguration, - realmLogger - ) - - return SyncConfigurationImpl( - baseConfiguration, - partitionValue, - user as UserImpl, - errorHandler!!, // It will never be null: either default or user-provided - syncClientResetStrategy!!, // It will never be null: either default or user-provided - initialSubscriptions, - waitForServerChanges - ) - } - - private fun getAbsolutePath(name: String?): String { - // In order for us to generate the path we need to provide a full-fledged sync - // configuration which at this point we don't yet have, so we have to create a - // temporary one so that we can return the actual path to a sync Realm using the - // realm_app_sync_client_get_default_file_path_for_realm function below - val absolutePath: String = when (partitionValue == null) { - true -> RealmInterop.realm_flx_sync_config_new((user as UserImpl).nativePointer) - false -> RealmInterop.realm_sync_config_new( - (user as UserImpl).nativePointer, - partitionValue!!.toJson() - ) - }.let { auxSyncConfig -> - RealmInterop.realm_app_sync_client_get_default_file_path_for_realm( - auxSyncConfig, - name - ) - } - - return absolutePath - } - } - - public companion object { - - /** - * Creates a sync configuration for Flexible Sync with default values for all - * optional configuration parameters. - * - * Flexible Sync uses a concept called subscription sets to define which data gets - * uploaded and downloaded to the device. See [SubscriptionSet] for more information. - * - * **See:** [Flexible Sync](https://www.mongodb.com/docs/atlas/app-services/sync/data-access-patterns/flexible-sync/) - * - * @param user the [User] who controls the realm. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - */ - public fun create(user: User, schema: Set>): SyncConfiguration = - Builder(user, schema).build() - - /** - * Creates a sync configuration for Partition-based Sync with default values for all - * optional configuration parameters. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user the [User] who controls the realm. - * @param partitionValue the partition value that defines which data to sync to the realm. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - */ - public fun create( - user: User, - partitionValue: String?, - schema: Set> - ): SyncConfiguration = - Builder(user, partitionValue, schema).build() - - /** - * Creates a sync configuration for Partition-based Sync with default values for all - * optional configuration parameters. - * - * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user the [User] who controls the realm. - * @param partitionValue the partition value that defines which data to sync to the realm. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - */ - public fun create( - user: User, - partitionValue: Int?, - schema: Set> - ): SyncConfiguration = - Builder(user, partitionValue, schema).build() - - /** - * Creates a sync configuration for Partition-based Sync with default values for all - * optional configuration parameters. - * - * * **See:** [Partitions](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - * - * @param user the [User] who controls the realm. - * @param partitionValue the partition value that defines which data to sync to the realm. - * @param schema the classes of the schema. The elements of the set must be direct class - * literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - */ - public fun create( - user: User, - partitionValue: Long?, - schema: Set> - ): SyncConfiguration = - Builder(user, partitionValue, schema).build() - - /** - * Creates a sync configuration for Partition-based Sync with default values for all - * optional configuration parameters. - * - * @param user the [User] who controls the realm. - * @param partitionValue the partition value that defines which data to sync to the realm. - * @param schema the classes of the schema. The elements of the set must be direct class literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - * * **See:** [partition key](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - */ - public fun create(user: User, partitionValue: BsonObjectId?, schema: Set>): SyncConfiguration = - Builder(user, partitionValue, schema).build() - - /** - * Creates a sync configuration for Partition-based Sync with default values for all - * optional configuration parameters. - * - * @param user the [User] who controls the realm. - * @param partitionValue the partition value that defines which data to sync to the realm. - * @param schema the classes of the schema. The elements of the set must be direct class literals. - * @throws IllegalArgumentException if the user is not valid and logged in. - * * **See:** [partition key](https://www.mongodb.com/docs/realm/sync/data-access-patterns/partitions/) - */ - public fun create(user: User, partitionValue: RealmUUID?, schema: Set>): SyncConfiguration = - Builder(user, partitionValue, schema).build() - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncSession.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncSession.kt deleted file mode 100644 index fa612eb833..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncSession.kt +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.Realm -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.sync.SyncSession.ErrorHandler -import io.realm.kotlin.mongodb.sync.SyncSession.State.ACTIVE -import io.realm.kotlin.mongodb.sync.SyncSession.State.DYING -import kotlinx.coroutines.flow.Flow -import kotlin.time.Duration - -/** - * A session controls how data is synchronized between a single Realm on the device and MongoDB on - * the server. - * - * A `SyncSession` is created by opening a Realm instance using a [SyncConfiguration]. Once a - * session has been created, it will continue to exist until the app is closed or the [Realm] is - * closed. - * - * A session is controlled by Realm, but can provide additional information in case of errors. - * These errors are passed along in the [ErrorHandler]. - * - * When creating a session, Realm will establish a connection to the server. This connection is - * controlled by Realm and might be shared between multiple sessions. - * - * The session itself has a different lifecycle than the underlying connection. - * - * The [SyncSession] object is thread safe. - */ -public interface SyncSession { - - /** - * The [SyncConfiguration] responsible for controlling the session. - * - * @throws IllegalStateException if accessed from inside a [SyncSession.ErrorHandler] due to session errors. - */ - public val configuration: SyncConfiguration - - /** - * The [User] used to authenticate the session on Atlas App Services. - */ - public val user: User - - /** - * The current session state. See [State] for more details about each state. - */ - public val state: State - - /** - * The current [ConnectionState]. - */ - public val connectionState: ConnectionState - - /** - * Calling this method will block until all known remote changes have been downloaded and - * applied to the Realm or the specified timeout is hit. This will involve network access, so - * calling this method should only be done from a non-UI thread. - * - * @param timeout Maximum amount of time before this method should return. - * @return `true` if the data was downloaded. `false` if the download timed out before it - * could complete. The download will continue in the background, even after returning `false`. - * @throws IllegalArgumentException if `timeout` is <= 0. - * @throws IllegalStateException if called from inside a [SyncSession.ErrorHandler]. - * @throws SyncException if a problem was encountered with the connection during the download. - */ - public suspend fun downloadAllServerChanges(timeout: Duration = Duration.INFINITE): Boolean - - /** - * Calling this method will block until all known local changes have been uploaded to the server - * or the specified timeout is hit. This will involve network access, so calling this method - * should only be done from a non-UI thread. - * - * @param timeout Maximum amount of time before this method should return. - * @return `true` if the data was uploaded. `false` if the upload timed out before it - * could complete. The upload will continue in the background, even after returning `false`. - * @throws IllegalArgumentException if `timeout` is <= 0. - * @throws IllegalStateException if called from inside a [SyncSession.ErrorHandler]. - * @throws SyncException if a problem was encountered with the connection during the upload. - */ - public suspend fun uploadAllLocalChanges(timeout: Duration = Duration.INFINITE): Boolean - - /** - * Pauses synchronization with Atlas until the Realm is closed and re-opened again. - * - * Synchronization can also be re-activated by calling [resume]. - * - * If the session is already [State.INACTIVE], calling this method will do nothing. - */ - public fun pause() - - /** - * Attempts to resume the session and activate synchronization with Atlas. - * - * This happens automatically when opening the Realm, so doing it manually should only - * be needed if the session was paused using [pause]. - * - * If the session was already [State.ACTIVE], calling this method will do nothing. - * - * If the session state is [State.DYING], the session will be moved back to [State.ACTIVE]. - */ - public fun resume() - - /** - * Create a [Flow] of [Progress]-events that track either downloads or uploads done by the [SyncSession]. - * - * This is only an indicator of transferring of data and an [Progress]-event with - * [Progress.isTransferComplete] being `true` does not guarantee that the data is already - * visible in the realm. To wait for data being integrated and visible use - * [downloadAllServerChanges]/[uploadAllLocalChanges]. - * - * If the flow is created with [ProgressMode.CURRENT_CHANGES] the [Progress] will - * only ever increase and will complete once `Progress.isTransferComplete = true`. - * - * If the flow is created with [ProgressMode.INDEFINITELY] the [Progress] can both - * increase and decrease since more changes might be added while the flow is still active. This - * means that it is possible for one [Progress] instance to report - * `isTransferComplete = true` and subsequent instances to report `isTransferComplete = false`. - * - * The flow will be completed if the realm is closed. - * - * The flow has an internal buffer of [Channel.BUFFERED] but if the consumer fails to consume the - * elements in a timely manner the flow will be completed with an [IllegalStateException]. - */ - public fun progressAsFlow( - direction: Direction, - progressMode: ProgressMode, - ): Flow - - /** - * Create a [Flow] of [ConnectionStateChange]-events to receive notifications of updates to the - * session's connection state. - * - * The flow will be completed if the realm is closed. - * - * The flow has an internal buffer of [Channel.BUFFERED] but if the consumer fails to consume - * the elements in a timely manner the flow will be completed with an [IllegalStateException]. - */ - public fun connectionStateAsFlow(): Flow - - /** - * Interface used to report any session errors. - * - * @see SyncConfiguration.Builder.errorHandler - */ - public fun interface ErrorHandler { - /** - * Callback for errors on a session object. It is not recommended to throw an exception - * inside an error handler, as the exception will be caught, logged, and ignored by Realm. - * Instead, it is better to manually catch all exceptions and manually handle these - * exceptions. - * - * @param session the [SyncSession] in which this error happened. - * @param error the [SyncException] being reported by the server. - */ - public fun onError(session: SyncSession, error: SyncException) - } - - /** - * The possible states for [SyncSession] to be. - * - * A Realm will automatically synchronize data with the server if the session is either [ACTIVE] - * or [DYING] and is connected. - */ - public enum class State { - /** - * This is the initial state. The session is closed. No data is being synchronized. The session - * will automatically transition to [ACTIVE] when a Realm is opened. - */ - INACTIVE, - - /** - * The Realm is open and data will be synchronized between the device and the server - * if the underlying connection is connected. - * - * The session will remain in this state until the session is either paused, after - * which the session becomes [PAUSED] or the realm is closed, in which case it will - * become [DYING]. - */ - ACTIVE, - - /** - * The Realm is open and has a connection to the server, but no data is allowed to be - * transferred between the device and the server. Call [SyncSession.resume] to start - * transferring data again. The state will then become [ACTIVE]. - */ - PAUSED, - - /** - * The Realm was closed, but still contains data that needs to be synchronized to the server. - * The session will attempt to upload all local data before going [INACTIVE]. - */ - DYING, - - /** - * The user is attempting to synchronize data but needs a valid access token to do so. Realm - * will either use a cached token or automatically try to acquire one based on the current - * users login. This requires a network connection. - * - * Data cannot be synchronized in this state. - * - * Once a valid token is acquired, the session will transition to [ACTIVE]. - */ - WAITING_FOR_ACCESS_TOKEN - } -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptions.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptions.kt deleted file mode 100644 index e4236ca854..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptions.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.sync - -import kotlin.time.Duration - -/** - * The configured timeouts for various aspects of the sync connection between synchronized realms - * and App Services. - * - * @see [io.realm.kotlin.mongodb.AppConfiguration.Builder.syncTimeouts] - */ -public data class SyncTimeoutOptions( - - /** - * The maximum time to allow for a connection to become fully established. This includes - * the time to resolve the network address, the TCP connect operation, the SSL - * handshake, and the WebSocket handshake. - */ - val connectTimeout: Duration, - - /** - * If session multiplexing is enabled, how long to keep connections open while there are - * no active session. - */ - val connectionLingerTime: Duration, - - /** - * How long to wait between each ping message sent to the server. The client periodically - * sends ping messages to the server to check if the connection is still alive. Shorter - * periods make connection state change notifications more responsive at the cost of - * more traffic. - */ - val pingKeepAlivePeriod: Duration, - - /** - * How long to wait for the server to respond to a ping message. Shorter values make - * connection state change notifications more responsive, but increase the chance of - * spurious disconnections. - */ - val pongKeepAlivePeriod: Duration, - - /** - * When a client first connects to the server, it downloads all data from the server - * before it begins to upload local changes. This typically reduces the total amount - * of merging needed and gets the local client into a useful state faster. If a - * disconnect and reconnect happens within the time span of the fast reconnect limit, - * this is skipped and the session behaves as if it were continuously - * connected. - */ - val fastReconnectLimit: Duration -) diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptionsBuilder.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptionsBuilder.kt deleted file mode 100644 index 0255a37fd5..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/SyncTimeoutOptionsBuilder.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.internal.util.Validation -import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -/** - * Builder for configuring various timeouts related to the sync connection to the server. - * - * @see [io.realm.kotlin.mongodb.AppConfiguration.Builder.syncTimeouts] - */ -public class SyncTimeoutOptionsBuilder { - - /** - * The maximum time to allow for a connection to become fully established. This includes - * the time to resolve the network address, the TCP connect operation, the SSL - * handshake, and the WebSocket handshake. - * - * Only values >= 1 second are allowed. Default is 2 minutes. - * - * @throws IllegalArgumentException if the duration is outside the allowed range. - */ - public var connectTimeout: Duration = 2.minutes - set(value) { - Validation.require(value >= 1.seconds) { "connectTimeout only support durations >= 1 second. This was: $value" } - field = value - } - - /** - * If session multiplexing is enabled, how long to keep connections open while there are - * no active session. - * - * Only durations > 0 seconds are allowed. Default is 30 seconds. - * - * @throws IllegalArgumentException if the duration is outside the allowed range. - * @see io.realm.kotlin.mongodb.AppConfiguration.Builder.enableSessionMultiplexing - */ - public var connectionLingerTime: Duration = 30.seconds - set(value) { - Validation.require(value > 0.milliseconds) { "connectionLingerTime must be a positive duration > 0. This was: $value" } - field = value - } - - /** - * How long to wait between each ping message sent to the server. The client periodically - * sends ping messages to the server to check if the connection is still alive. Shorter - * periods make connection state change notifications more responsive at the cost of - * battery life (as the antenna will have to wake up more often). - * - * Only durations > 5 seconds are allowed. Default is 1 minute. - * - * @throws IllegalArgumentException if the duration is outside the allowed range. - */ - public var pingKeepAlivePeriod: Duration = 1.minutes - set(value) { - Validation.require(value > 5.seconds) { "pingKeepAlivePeriod must be a positive duration > 5 seconds. This was: $value" } - field = value - } - - /** - * How long to wait for the server to respond to a ping message. Shorter values make - * connection state change notifications more responsive, but increase the chance of - * spurious disconnections. - * - * Only durations > 5 seconds are allowed. Default is 2 minutes. - * - * @throws IllegalArgumentException if the duration is outside the allowed range. - */ - public var pongKeepAlivePeriod: Duration = 2.minutes - set(value) { - Validation.require(value > 5.seconds) { "pongKeepAlivePeriod must be a positive duration > 5 seconds. This was: $value" } - field = value - } - - /** - * When a client first connects to the server, it downloads all data from the server - * before it begins to upload local changes. This typically reduces the total amount - * of merging needed and gets the local client into a useful state faster. If a - * disconnect and reconnect happens within the time span of the fast reconnect limit, - * this is skipped and the session behaves as if it were continuously - * connected. - * - * Only durations > 1 second are allowed. Default is 1 minute. - * - * @throws IllegalArgumentException if the duration is outside the allowed range. - */ - public var fastReconnectLimit: Duration = 1.minutes - set(value) { - Validation.require(value > 1.seconds) { "fastReconnectLimit must be a positive duration > 1 second. This was: $value" } - field = value - } - - /** - * Construct the final [SyncTimeoutOptions] object. - */ - internal fun build() = SyncTimeoutOptions( - connectTimeout = this.connectTimeout, - connectionLingerTime = this.connectionLingerTime, - pingKeepAlivePeriod = this.pingKeepAlivePeriod, - pongKeepAlivePeriod = this.pongKeepAlivePeriod, - fastReconnectLimit = this.fastReconnectLimit - ) -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/WaitForSync.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/WaitForSync.kt deleted file mode 100644 index 1b1e252362..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/mongodb/sync/WaitForSync.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.mongodb.sync - -import io.realm.kotlin.mongodb.ext.subscribe -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.query.RealmResults - -/** - * Enum defining the behaviour of when [RealmQuery.subscribe] and [RealmResults.subscribe] will - * return a query result. - * - * When the [Subscription] is created for the first time, data needs to be downloaded from the - * server before it becomes available, so depending on whether you run the query against the local - * database before or after this has happened, you query results might not look correct. - * - * This enum thus defines the behaviour of when the query is run, so it possible to make the - * appropriate tradeoff between correctness and availability. - * - * @see io.realm.kotlin.mongodb.ext.subscribe - */ -public enum class WaitForSync { - /** - * This mode will wait for the server data the first time a subscription is created before - * running the local query. Later calls to [io.realm.kotlin.mongodb.ext.subscribe] will detect - * that the subscription already exist and run the query immediately. - * - * This is the default mode. - */ - FIRST_TIME, - /** - * With this mode enabled, Realm will always download the latest server state before running - * the local query. This means that your query result is always seeing the latest data, but - * it also requires the app to be online. - */ - ALWAYS, - /** - * With this mode enabled, Realm will always query the local database first while any server - * data is being downloaded in the background. This update is not binary, which means that if - * you register a flow on the query result, you might see multiple events being emitted as the - * database is being filled based on the subscription. - */ - NEVER -} diff --git a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/types/AsymmetricRealmObject.kt b/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/types/AsymmetricRealmObject.kt deleted file mode 100644 index bf72e085a7..0000000000 --- a/packages/library-sync/src/commonMain/kotlin/io/realm/kotlin/types/AsymmetricRealmObject.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.types - -/** - * Asymmetric Realm objects are "write-only" objects that are only supported in synchronized realms - * configured for [Flexible Sync](https://www.mongodb.com/docs/realm/sdk/kotlin/sync/#flexible-sync). - * - * They are useful in write-heavy scenarios like sending telemetry data. Once the data is - * sent to the server, it is also automatically deleted on the device. - * - * The benefit of using [AsymmetricRealmObject] is that the performance of each sync operation - * is much higher. The drawback is that an [AsymmetricRealmObject] is synced unidirectional, so it - * cannot be queried or manipulated once inserted. - * - * Asymmetric objects also has limits on the schema they can support. Asymmetric objects can - * only link to [EmbeddedRealmObject]s, not [RealmObject]s or other asymmetric objects. Neither - * [RealmObject]s nor [EmbeddedRealmObject]s can link to [AsymmetricRealmObject]s. - * - * It IS possible to combine asymmetric, embedded and standard realm objects in a single - * [io.realm.kotlin.mongodb.sync.SyncConfiguration] schema. - */ -public interface AsymmetricRealmObject : BaseRealmObject diff --git a/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt b/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt deleted file mode 100644 index 61e8a2b3d4..0000000000 --- a/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt +++ /dev/null @@ -1,32 +0,0 @@ -@file:JvmName("HttpClientCacheJVM") -package io.realm.kotlin.mongodb.internal - -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.okhttp.OkHttp -import io.ktor.client.plugins.logging.Logger - -/** - * Cache HttpClient on Android and JVM. - * https://github.com/realm/realm-kotlin/issues/480 only seem to be a problem on macOS. - */ -internal actual class HttpClientCache actual constructor(timeoutMs: Long, customLogger: Logger?) { - private val httpClient: HttpClient by lazy { createClient(timeoutMs, customLogger) } - actual fun getClient(): HttpClient { - return httpClient - } - actual fun close() { - httpClient.close() - } -} - -public actual fun createPlatformClient(block: HttpClientConfig<*>.() -> Unit): HttpClient { - return HttpClient(OkHttp) { - engine { - config { - retryOnConnectionFailure(true) - } - } - this.apply(block) - } -} diff --git a/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/OkHttpWebsocketClient.kt b/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/OkHttpWebsocketClient.kt deleted file mode 100644 index b6e49173fc..0000000000 --- a/packages/library-sync/src/jvm/kotlin/io/realm/kotlin/mongodb/internal/OkHttpWebsocketClient.kt +++ /dev/null @@ -1,274 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.internal.interop.RealmWebsocketHandlerCallbackPointer -import io.realm.kotlin.internal.interop.sync.WebSocketClient -import io.realm.kotlin.internal.interop.sync.WebSocketObserver -import io.realm.kotlin.internal.interop.sync.WebsocketCallbackResult -import io.realm.kotlin.internal.interop.sync.WebsocketEngine -import io.realm.kotlin.internal.interop.sync.WebsocketErrorCode -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.WebSocket -import okhttp3.WebSocketListener -import okio.ByteString -import okio.ByteString.Companion.toByteString -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean -import kotlin.random.Random - -@Suppress("LongParameterList") -public class OkHttpWebsocketClient( - private val observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - supportedSyncProtocols: String, - websocketEngine: WebsocketEngine, - /** - * We use this single threaded scope as an event loop to run all events on the same thread, - * in the order they were queued in. This include callback to Core via [observer] as well as calls - * from Core to send a Frame or close the Websocket. - */ - private val scope: CoroutineScope, - private val runCallback: ( - handlerCallback: RealmWebsocketHandlerCallbackPointer, - cancelled: Boolean, - status: WebsocketCallbackResult, - reason: String - ) -> Unit -) : WebSocketClient, WebSocketListener() { - - private val logger = ContextLogger("Websocket-${Random.nextInt()}") - - /** - * [WebsocketEngine] responsible of establishing the connection, sending and receiving websocket Frames. - */ - private val okHttpClient: OkHttpClient = websocketEngine.getInstance() - - private lateinit var webSocket: WebSocket - - /** - * Indicates that the websocket is in the process of being closed by Core. - * We can still send enqueued Frames like 'unbind' but we should not communicate back any incoming messages to - * Core via the [observer]. - */ - private val observerIsClosed: AtomicBoolean = AtomicBoolean(false) - - /** - * Indicates that the websocket is effectively closed. No message should be sent or received after this. - */ - private val isClosed: AtomicBoolean = AtomicBoolean(false) - - private val protocolSelectionHeader = "Sec-WebSocket-Protocol" - - init { - val websocketURL = "${if (isSsl) "wss" else "ws"}://$address:$port$path" - val request: Request = Request.Builder().url(websocketURL) - .addHeader(protocolSelectionHeader, supportedSyncProtocols) - .build() - - scope.launch { - okHttpClient.newWebSocket(request, this@OkHttpWebsocketClient) - } - logger.debug("init") - } - - override fun onOpen(webSocket: WebSocket, response: Response) { - super.onOpen(webSocket, response) - logger.debug("onOpen websocket ${webSocket.request().url}") - - this.webSocket = webSocket - - response.header(protocolSelectionHeader)?.let { selectedProtocol -> - runIfObserverNotClosed { - observer.onConnected(selectedProtocol) - } - } - } - - override fun onMessage(webSocket: WebSocket, bytes: ByteString) { - super.onMessage(webSocket, bytes) - logger.trace("onMessage: ${bytes.toByteArray().decodeToString()} isClosed = ${isClosed.get()} observerIsClosed = ${observerIsClosed.get()}") - - runIfObserverNotClosed { - val shouldClose: Boolean = observer.onNewMessage(bytes.toByteArray()) - if (shouldClose) { - webSocket.close( - WebsocketErrorCode.RLM_ERR_WEBSOCKET_OK.nativeValue, - "websocket should be closed after last message received" - ) - } - } - } - - override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - super.onClosing(webSocket, code, reason) - logger.debug("onClosing code = $code reason = $reason isClosed = ${isClosed.get()} observerIsClosed = ${observerIsClosed.get()}") - } - - override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - super.onClosed(webSocket, code, reason) - logger.debug("onClosed code = $code reason = $reason isClosed = ${isClosed.get()} observerIsClosed = ${observerIsClosed.get()}") - - isClosed.set(true) - - runIfObserverNotClosed { - // It's important to rely properly the error code from the server. - // The server will report auth errors (and a few other error types) - // as websocket application-level errors after establishing the socket, rather than failing at the HTTP layer. - // since the websocket spec does not allow the HTTP status code from the response to be - // passed back to the client from the websocket implementation (example instruct a refresh token - // via a 401 HTTP response is not possible) see https://jira.mongodb.org/browse/BAAS-10531. - // In order to provide a reasonable response that the Sync Client can react upon, the private range of websocket close status codes - // 4000-4999, can be used to return a more specific error. - WebsocketErrorCode.of(code)?.let { errorCode -> - observer.onClose( - true, errorCode, reason - ) - } ?: run { - observer.onClose( - true, - WebsocketErrorCode.RLM_ERR_WEBSOCKET_FATAL_ERROR, - "Unknown error code $code. original reason $reason" - ) - } - } - } - - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - super.onFailure(webSocket, t, response) - logger.debug("onFailure throwable '${t.message}' isClosed = ${isClosed.get()} observerIsClosed = ${observerIsClosed.get()}") - - runIfObserverNotClosed { - observer.onError() - } - } - - override fun send(message: ByteArray, handlerCallback: RealmWebsocketHandlerCallbackPointer) { - logger.trace("send: ${message.decodeToString()} isClosed = ${isClosed.get()} observerIsClosed = ${observerIsClosed.get()}") - - // send any queued Frames even if the Core observer is closed, but only if the websocket is still open, this can be a message like 'unbind' - // which instruct the Sync server to terminate the Sync Session (server will respond by 'unbound'). - if (!isClosed.get()) { - scope.launch { - try { - if (!isClosed.get()) { // double check that the websocket is still open before sending. - webSocket.send(message.toByteString()) - runCallback( - handlerCallback, - observerIsClosed.get(), // if the Core observer is closed we run this callback as cancelled (to free underlying resources) - WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_SUCCESS, - "" - ) - } else { - runCallback( - handlerCallback, - observerIsClosed.get(), // if the Core observer is closed we run this callback as cancelled (to free underlying resources) - WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED, - "Connection already closed" - ) - } - } catch (e: Exception) { - runCallback( - handlerCallback, - observerIsClosed.get(), // if the Core observer is closed we run this callback as cancelled (to free underlying resources) - WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_RUNTIME, - "Sending Frame exception: ${e.message}" - ) - } - } - } else { - scope.launch { - runCallback( - handlerCallback, - observerIsClosed.get(), // if the Core observer is closed we run this callback as cancelled (to free underlying resources) - WebsocketCallbackResult.RLM_ERR_SYNC_SOCKET_CONNECTION_CLOSED, - "Connection already closed" - ) - } - } - } - - override fun close() { - logger.debug("close") - observerIsClosed.set(true) - - if (::webSocket.isInitialized) { - scope.launch { - if (!isClosed.get()) { - webSocket.close( - WebsocketErrorCode.RLM_ERR_WEBSOCKET_OK.nativeValue, "client closed websocket" - ) - } - } - } - } - - /** - * Runs the [block] inside the transport [scope] only if Core didn't initiate the Websocket closure. - */ - private fun runIfObserverNotClosed(block: () -> Unit) { - if (!observerIsClosed.get()) { // if Core has already closed the websocket there's no point in scheduling this coroutine. - scope.launch { - // The session could have been paused/closed in the meantime which will cause the WebSocket to be destroyed, as well as the 'observer', - // so avoid invoking any Core observer callback on a deleted 'CAPIWebSocketObserver'. - if (!observerIsClosed.get()) { // only run if Core observer is still valid (i.e Core didn't close the websocket yet) - block() - } - } - } - } -} - -private class OkHttpEngine(timeoutMs: Long) : WebsocketEngine { - private var engine: OkHttpClient = - OkHttpClient.Builder() - .connectTimeout(timeoutMs, TimeUnit.MILLISECONDS) - .readTimeout(timeoutMs, TimeUnit.MILLISECONDS) - .callTimeout(timeoutMs, TimeUnit.MILLISECONDS) - .writeTimeout(timeoutMs, TimeUnit.MILLISECONDS) - .build() - - override fun shutdown() { - engine.dispatcher.executorService.shutdown() - } - - override fun getInstance(): T { - @Suppress("UNCHECKED_CAST") return engine as T - } -} - -public actual fun websocketEngine(timeoutMs: Long): WebsocketEngine { - return OkHttpEngine(timeoutMs) -} - -@Suppress("LongParameterList") -public actual fun platformWebsocketClient( - observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - supportedSyncProtocols: String, - transport: RealmWebSocketTransport -): WebSocketClient { - return OkHttpWebsocketClient( - observer, - path, - address, - port, - isSsl, - supportedSyncProtocols, - transport.engine, - transport.scope - ) { handlerCallback: RealmWebsocketHandlerCallbackPointer, cancelled: Boolean, status: WebsocketCallbackResult, reason: String -> - transport.scope.launch { - transport.runCallback(handlerCallback, cancelled, status, reason) - } - } -} diff --git a/packages/library-sync/src/jvmMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt b/packages/library-sync/src/jvmMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt deleted file mode 100644 index 4b8841194f..0000000000 --- a/packages/library-sync/src/jvmMain/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -internal actual fun registerSystemNetworkObserver() { - // Do nothing on JVM. - // There isn't a great way to detect network connectivity on this platform. -} diff --git a/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt b/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt deleted file mode 100644 index 1ff1d158e5..0000000000 --- a/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/HttpClientCache.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.engine.darwin.Darwin -import io.ktor.client.plugins.logging.Logger - -/** - * Cache HttpClient on iOS. - */ -internal actual class HttpClientCache actual constructor(timeoutMs: Long, customLogger: Logger?) { - private val client: HttpClient by lazy { createClient(timeoutMs, customLogger) } - actual fun getClient(): HttpClient { - return client - } - actual fun close() { - client.close() - } -} - -public actual fun createPlatformClient(block: HttpClientConfig<*>.() -> Unit): HttpClient { - return HttpClient(Darwin, block) -} diff --git a/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt b/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt deleted file mode 100644 index 48ac84215b..0000000000 --- a/packages/library-sync/src/nativeDarwin/kotlin/io/realm/kotlin/mongodb/internal/NetworkStateObserver.kt +++ /dev/null @@ -1,24 +0,0 @@ -package io.realm.kotlin.mongodb.internal - -import io.realm.kotlin.internal.interop.sync.WebSocketClient -import io.realm.kotlin.internal.interop.sync.WebSocketObserver -import io.realm.kotlin.internal.interop.sync.WebsocketEngine - -internal actual fun registerSystemNetworkObserver() { - // This is handled automatically by Realm Core which will also call `Sync.reconnect()` - // automatically. So on iOS/macOS we do not do anything. - // See https://github.com/realm/realm-core/blob/a678c36a85cf299f745f68f8b5ceff364d714181/src/realm/object-store/sync/impl/sync_client.hpp#L82C3-L82C3 - // for further details. -} - -public actual fun platformWebsocketClient( - observer: WebSocketObserver, - path: String, - address: String, - port: Long, - isSsl: Boolean, - supportedSyncProtocols: String, - transport: RealmWebSocketTransport -): WebSocketClient = TODO() - -public actual fun websocketEngine(timeoutMs: Long): WebsocketEngine = TODO() diff --git a/packages/plugin-compiler/build.gradle.kts b/packages/plugin-compiler/build.gradle.kts index e3059adea5..dfc2ae34ec 100644 --- a/packages/plugin-compiler/build.gradle.kts +++ b/packages/plugin-compiler/build.gradle.kts @@ -42,7 +42,6 @@ dependencies { // Have to be mentioned explicitly as it is not an api dependency of library implementation(project(":cinterop")) testImplementation(project(":library-base")) - testImplementation(project(":library-sync")) } tasks.withType { diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/AccessorModifierIrGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/AccessorModifierIrGeneration.kt index 9b0d917c01..510fca7430 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/AccessorModifierIrGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/AccessorModifierIrGeneration.kt @@ -18,7 +18,6 @@ package io.realm.kotlin.compiler -import io.realm.kotlin.compiler.ClassIds.ASYMMETRIC_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.EMBEDDED_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.IGNORE_ANNOTATION import io.realm.kotlin.compiler.ClassIds.KBSON_DECIMAL128 @@ -223,11 +222,6 @@ class AccessorModifierIrGeneration(private val pluginContext: IrPluginContext) { objectReferenceProperty = irClass.lookupProperty(OBJECT_REFERENCE) objectReferenceType = objectReferenceProperty.backingField!!.type - // Attempt to find the interface for asymmetric objects. - // The class will normally only be on the classpath for library-sync builds, not - // library-base builds. - val asymmetricRealmObjectInterface: IrClass? = pluginContext.referenceClass(ASYMMETRIC_OBJECT_INTERFACE)?.owner - irClass.transformChildrenVoid(object : IrElementTransformerVoid() { @Suppress("LongMethod") override fun visitProperty(declaration: IrProperty): IrStatement { @@ -646,19 +640,6 @@ class AccessorModifierIrGeneration(private val pluginContext: IrPluginContext) { toRealmValue = null ) } - asymmetricRealmObjectInterface != null && propertyType.isSubtypeOfClass(asymmetricRealmObjectInterface.symbol) -> { - // Asymmetric objects must be top-level objects, so any link to one - // should be illegal. This will be detected later when creating the - // schema methods. So for now, just add the field to the list of schema - // properties, but do not modify the accessor. - logDebug("Object property named ${declaration.name} is ${if (nullable) "" else "not "}nullable") - val schemaProperty = SchemaProperty( - propertyType = PropertyType.RLM_PROPERTY_TYPE_OBJECT, - declaration = declaration, - collectionType = CollectionType.NONE - ) - fields[name] = schemaProperty - } propertyType.isSubtypeOfClass(realmObjectInterface) -> { logDebug("Object property named ${declaration.name} is ${if (nullable) "" else "not "}nullable") val schemaProperty = SchemaProperty( diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt index b1db2c722e..97dabaa94d 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Identifiers.kt @@ -16,8 +16,6 @@ package io.realm.kotlin.compiler -import io.realm.kotlin.compiler.FqNames.PACKAGE_MONGODB -import io.realm.kotlin.compiler.FqNames.PACKAGE_MONGODB_INTERNAL import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName @@ -28,7 +26,6 @@ internal object Names { val REALM_OBJECT: Name = Name.identifier("RealmObject") val EMBEDDED_REALM_OBJECT: Name = Name.identifier("EmbeddedRealmObject") - val ASYMMETRIC_REALM_OBJECT: Name = Name.identifier("AsymmetricRealmObject") val REALM_OBJECT_COMPANION_CLASS_MEMBER: Name = Name.identifier("${REALM_SYNTHETIC_PROPERTY_PREFIX}class") @@ -101,8 +98,6 @@ internal object FqNames { val PACKAGE_REALM_INTEROP = FqName("io.realm.kotlin.internal.interop") val PACKAGE_REALM_INTERNAL = FqName("io.realm.kotlin.internal") val PACKAGE_MONGODB = FqName("io.realm.kotlin.mongodb") - val PACKAGE_MONGODB_INTERNAL = FqName("io.realm.kotlin.mongodb.internal") - val APP_CONFIGURATION_BUILDER = FqName("AppConfiguration.Builder") } object ClassIds { @@ -120,7 +115,6 @@ object ClassIds { val REALM_OBJECT_INTERFACE = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("RealmObject")) val TYPED_REALM_OBJECT_INTERFACE = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("TypedRealmObject")) val EMBEDDED_OBJECT_INTERFACE = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("EmbeddedRealmObject")) - val ASYMMETRIC_OBJECT_INTERFACE = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("AsymmetricRealmObject")) val CLASS_APP_CONFIGURATION = ClassId(FqNames.PACKAGE_MONGODB, Name.identifier("AppConfiguration")) @@ -161,11 +155,4 @@ object ClassIds { val REALM_UUID = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("RealmUUID")) val REALM_MUTABLE_INTEGER = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("MutableRealmInt")) val REALM_ANY = ClassId(FqNames.PACKAGE_TYPES, Name.identifier("RealmAny")) - - // Sync types - val APP = ClassId(PACKAGE_MONGODB, Name.identifier("App")) - val APP_IMPL = ClassId(PACKAGE_MONGODB_INTERNAL, Name.identifier("AppImpl")) - val APP_CONFIGURATION = ClassId(PACKAGE_MONGODB, Name.identifier("AppConfiguration")) - val APP_CONFIGURATION_IMPL = ClassId(PACKAGE_MONGODB_INTERNAL, Name.identifier("AppConfigurationImpl")) - val APP_CONFIGURATION_BUILDER = ClassId(PACKAGE_MONGODB, FqNames.APP_CONFIGURATION_BUILDER, false) } diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt index 8c8d6d617b..70d6c46f97 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/IrUtils.kt @@ -18,14 +18,12 @@ package io.realm.kotlin.compiler -import io.realm.kotlin.compiler.ClassIds.ASYMMETRIC_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.BASE_REALM_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.EMBEDDED_OBJECT_INTERFACE import io.realm.kotlin.compiler.ClassIds.KOTLIN_COLLECTIONS_LISTOF import io.realm.kotlin.compiler.ClassIds.PERSISTED_NAME_ANNOTATION import io.realm.kotlin.compiler.ClassIds.REALM_OBJECT_INTERFACE import io.realm.kotlin.compiler.FqNames.PACKAGE_TYPES -import io.realm.kotlin.compiler.Names.ASYMMETRIC_REALM_OBJECT import io.realm.kotlin.compiler.Names.EMBEDDED_REALM_OBJECT import io.realm.kotlin.compiler.Names.REALM_OBJECT import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext @@ -147,8 +145,7 @@ val ClassDescriptor.isRealmObjectCompanion val realmObjectInterfaceFqNames = setOf(REALM_OBJECT_INTERFACE) val realmEmbeddedObjectInterfaceFqNames = setOf(EMBEDDED_OBJECT_INTERFACE) -val realmAsymmetricObjectInterfaceFqNames = setOf(ASYMMETRIC_OBJECT_INTERFACE) -val anyRealmObjectInterfacesFqNames = realmObjectInterfaceFqNames + realmEmbeddedObjectInterfaceFqNames + realmAsymmetricObjectInterfaceFqNames +val anyRealmObjectInterfacesFqNames = realmObjectInterfaceFqNames + realmEmbeddedObjectInterfaceFqNames fun IrType.classIdOrFail(): ClassId = getClass()?.classId ?: error("Can't get classId of ${render()}") @@ -194,16 +191,15 @@ inline fun ClassDescriptor.hasInterfacePsi(interfaces: Set): Boolean { // `RealmObject` (Kotlin, interface). val realmObjectPsiNames = setOf("RealmObject", "io.realm.kotlin.types.RealmObject") val embeddedRealmObjectPsiNames = setOf("EmbeddedRealmObject", "io.realm.kotlin.types.EmbeddedRealmObject") -val asymmetricRealmObjectPsiNames = setOf("AsymmetricRealmObject", "io.realm.kotlin.types.AsymmetricRealmObject") val realmJavaObjectPsiNames = setOf("io.realm.RealmObject()", "RealmObject()") val ClassDescriptor.isRealmObject: Boolean get() = this.hasInterfacePsi(realmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames) val ClassDescriptor.isEmbeddedRealmObject: Boolean get() = this.hasInterfacePsi(embeddedRealmObjectPsiNames) val ClassDescriptor.isBaseRealmObject: Boolean - get() = this.hasInterfacePsi(realmObjectPsiNames + embeddedRealmObjectPsiNames + asymmetricRealmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames) + get() = this.hasInterfacePsi(realmObjectPsiNames + embeddedRealmObjectPsiNames) && !this.hasInterfacePsi(realmJavaObjectPsiNames) -val realmObjectTypes: Set = setOf(REALM_OBJECT, EMBEDDED_REALM_OBJECT, ASYMMETRIC_REALM_OBJECT) +val realmObjectTypes: Set = setOf(REALM_OBJECT, EMBEDDED_REALM_OBJECT) val realmObjectClassIds = realmObjectTypes.map { name -> ClassId(PACKAGE_TYPES, name) } // This is the K2 equivalent of our PSI hack to determine if a symbol has a RealmObject base class. @@ -251,18 +247,12 @@ val IrClass.isRealmObject val IrClass.isEmbeddedRealmObject: Boolean get() = superTypes.any { it.classId == EMBEDDED_OBJECT_INTERFACE } -val IrClass.isAsymmetricRealmObject: Boolean - get() = superTypes.any { it.classId == ASYMMETRIC_OBJECT_INTERFACE } - val IrType.classId: ClassId? get() = this.getClass()?.classId val IrType.isEmbeddedRealmObject: Boolean get() = superTypes().any { it.classId == EMBEDDED_OBJECT_INTERFACE } -val IrType.isAsymmetricRealmObject: Boolean - get() = superTypes().any { it.classId == ASYMMETRIC_OBJECT_INTERFACE } - internal fun IrFunctionBuilder.at(startOffset: Int, endOffset: Int) = also { this.startOffset = startOffset this.endOffset = endOffset diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmCommandLineProcessor.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmCommandLineProcessor.kt index 926eec3ed4..424874d275 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmCommandLineProcessor.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmCommandLineProcessor.kt @@ -24,10 +24,7 @@ import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.CompilerConfigurationKey -// Must match io.realm.kotlin.gradle.RealmCompilerSubplugin.bundleId -const val BUNDLE_ID_KEY = "bundleId" const val FEATURE_LIST_PATH_KEY = "featureListPath" -val bundleIdConfigurationKey: CompilerConfigurationKey = CompilerConfigurationKey("io.realm.kotlin.bundleId") val featureListPathConfigurationKey: CompilerConfigurationKey = CompilerConfigurationKey("io.realm.kotlin.featureListPath") @OptIn(ExperimentalCompilerApi::class) @@ -35,13 +32,6 @@ val featureListPathConfigurationKey: CompilerConfigurationKey = Compiler class RealmCommandLineProcessor : CommandLineProcessor { override val pluginId: String = "io.realm.kotlin" override val pluginOptions: Collection = listOf( - CliOption( - optionName = "bundleId", - description = "Anonymized Bundle Id", - valueDescription = "Anonymized Bundle Id", - required = false, - allowMultipleOccurrences = false - ), CliOption( optionName = "featureListPath", description = "Feature List Path", @@ -57,8 +47,6 @@ class RealmCommandLineProcessor : CommandLineProcessor { configuration: CompilerConfiguration ) { when (option.optionName) { - BUNDLE_ID_KEY -> - configuration.put(bundleIdConfigurationKey, value) FEATURE_LIST_PATH_KEY -> configuration.put(featureListPathConfigurationKey, value) else -> super.processOption(option, value, configuration) diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt index 0ce7399497..a851662757 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/RealmModelSyntheticPropertiesGeneration.kt @@ -437,7 +437,6 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi classKindType.defaultType ) { startOffset, endOffset -> val isEmbedded = clazz.isEmbeddedRealmObject - val isAsymmetric = clazz.isAsymmetricRealmObject IrGetEnumValueImpl( startOffset = startOffset, endOffset = endOffset, @@ -446,7 +445,6 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi // These names must match the values in io.realm.kotlin.schema.RealmClassKind it.name == when { isEmbedded -> Name.identifier("EMBEDDED") - isAsymmetric -> Name.identifier("ASYMMETRIC") else -> Name.identifier("STANDARD") } }.symbol @@ -477,8 +475,6 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi logError("Embedded object is not allowed to have a primary key", irClass.locationOf()) } - val asymmetric = irClass.isAsymmetricRealmObject - val primaryKey: String? = when (primaryKeyFields.size) { 0 -> null 1 -> primaryKeyFields.entries.first().value.persistedName @@ -507,7 +503,7 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi type = classInfoClass.defaultType, symbol = classInfoCreateMethod.symbol, typeArgumentsCount = 0, - valueArgumentsCount = 5 + valueArgumentsCount = 4 ).apply { dispatchReceiver = irGetObject(classInfoClass.companionObject()!!.symbol) var arg = 0 @@ -527,7 +523,6 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi // num properties putValueArgument(arg++, irLong(fields.size.toLong())) putValueArgument(arg++, irBoolean(embedded)) - putValueArgument(arg++, irBoolean(asymmetric)) } ) putValueArgument( @@ -624,38 +619,6 @@ class RealmModelSyntheticPropertiesGeneration(private val pluginContext: IrPlugi persistedAndPublicNameToLocation[publicName] = location } - // Validate asymmetric object constraints: - // - Asymmetric objects can only contain embedded objects. - // - RealmObject and EmbeddedObject cannot contain a Asymmetric object. - // I.e. Asymmetric objects are only allowed as top-level objects. - when (type) { - objectType -> { - // Collections of type RealmObject require the type parameter be retrieved from the generic argument - when (value.collectionType) { - CollectionType.NONE -> { - backingField.type - } - CollectionType.LIST, - CollectionType.SET, - CollectionType.DICTIONARY -> { - getCollectionElementType(backingField.type) - ?: error("Could not get collection type from ${backingField.type}") - } - } - } - else -> null - }?.let { linkedType: IrType -> - if (asymmetric) { - if (!linkedType.isEmbeddedRealmObject) { - logError("AsymmetricObjects can only reference EmbeddedRealmObject classes.", property.locationOf()) - } - } else { - if (linkedType.isAsymmetricRealmObject) { - logError("RealmObjects and EmbeddedRealmObjects cannot reference AsymmetricRealmObjects.", property.locationOf()) - } - } - } - // Define the Realm `PropertyType` enum value for this kind of // property. val realmPropertyType = IrGetEnumValueImpl( diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt index 0218a50d72..6afa3b4912 100644 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt +++ b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/Registrar.kt @@ -90,12 +90,6 @@ class Registrar : org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar { LoadingOrder.LAST, project ) - configuration.get(bundleIdConfigurationKey)?.let { bundleId -> - getExtensionPoint(IrGenerationExtension.extensionPointName).registerExtension( - SyncLoweringExtension(bundleId), - project - ) - } } } diff --git a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt b/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt deleted file mode 100644 index d5a11767f2..0000000000 --- a/packages/plugin-compiler/src/main/kotlin/io/realm/kotlin/compiler/SyncLoweringExtension.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -@file:OptIn(UnsafeDuringIrConstructionAPI::class) - -package io.realm.kotlin.compiler - -import io.realm.kotlin.compiler.ClassIds.APP_CONFIGURATION_BUILDER -import org.jetbrains.kotlin.backend.common.ClassLoweringPass -import org.jetbrains.kotlin.backend.common.CompilationException -import org.jetbrains.kotlin.backend.common.DeclarationContainerLoweringPass -import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension -import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext -import org.jetbrains.kotlin.backend.common.runOnFilePostfix -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrDeclarationContainer -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction -import org.jetbrains.kotlin.ir.expressions.IrCall -import org.jetbrains.kotlin.ir.expressions.IrExpression -import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl -import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol -import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI -import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl -import org.jetbrains.kotlin.ir.util.companionObject -import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid -import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid -import org.jetbrains.kotlin.utils.KotlinExceptionWithAttachments - -/** - * Lowering extension that injects the 'io.realm.kotlin.bundleId' compiler plugin option into - * 'AppConfiguration's by rewiring: - * - App.create(appId) -> AppImpl.create(appId, bundleId) - * - AppConfiguration.create(appId) -> AppConfigurationImpl.create(appID, bundleId) - * - AppConfiguration.Builder().build() -> AppConfigurationImpl.Builder.build(bundleId) - */ -class SyncLoweringExtension(private val bundleId: String) : IrGenerationExtension { - override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) { - // Safe guard that we don't process anything unless we can actually look up a library-sync - // symbol - val syncSymbol = pluginContext.referenceClass(ClassIds.APP)?.owner - if (syncSymbol != null) { - SyncLowering(pluginContext, bundleId).lowerFromModuleFragment(moduleFragment) - } - } -} - -private class SyncLowering(private val pluginContext: IrPluginContext, private val bundleId: String) : ClassLoweringPass, DeclarationContainerLoweringPass { - private val appImplCompanionSymbol = - pluginContext.lookupClassOrThrow(ClassIds.APP_IMPL).companionObject()!!.symbol - private val appConfigurationImplCompanionSymbol = - pluginContext.lookupClassOrThrow(ClassIds.APP_CONFIGURATION_IMPL).companionObject()!!.symbol - // App.create(appId) - private val appCreateAppId: IrSimpleFunction = - pluginContext.lookupClassOrThrow(ClassIds.APP).companionObject()!! - .lookupFunction(Names.APP_CREATE) { - it.valueParameters.size == 1 && it.valueParameters[0].type == pluginContext.irBuiltIns.stringType - } - // AppImpl.create(appId, bundleId) - private val appCreateAppIdBundleId: IrSimpleFunction = - pluginContext.lookupClassOrThrow(ClassIds.APP_IMPL).companionObject()!!.lookupFunction(Names.APP_CREATE) { - it.valueParameters.size == 2 - } - // AppConfiguration.create(appId) - private val appConfigurationCreateAppId: IrSimpleFunction = - pluginContext.lookupClassOrThrow(ClassIds.APP_CONFIGURATION).companionObject()!! - .lookupFunction(Names.APP_CONFIGURATION_CREATE) { - it.valueParameters.size == 1 && it.valueParameters[0].type == pluginContext.irBuiltIns.stringType - } - // AppConfigurationImpl.create(appId, bundleId) - private val appConfigurationImplCreateAppIdBungleId: IrSimpleFunction = - pluginContext.lookupClassOrThrow(ClassIds.APP_CONFIGURATION_IMPL).companionObject()!!.lookupFunction(Names.APP_CONFIGURATION_CREATE) { - it.valueParameters.size == 2 - } - private val appConfigurationBuilder: IrClass = - pluginContext.lookupClassOrThrow(APP_CONFIGURATION_BUILDER) - // AppConfiguration.Builder.build() - private val appBuilderBuildNoArg: IrSimpleFunction = - appConfigurationBuilder.lookupFunction(Names.APP_CONFIGURATION_BUILDER_BUILD) { - it.valueParameters.isEmpty() - } - // AppConfiguration.Builder.build(bundleId) - private val appBuilderBuildBundleId: IrSimpleFunction = - appConfigurationBuilder.lookupFunction(Names.APP_CONFIGURATION_BUILDER_BUILD) { - it.valueParameters.size == 1 - } - - // Maps from a given call into a new call along with the accompanying dispatch receiver - val replacements: Map IrExpression?>> = mapOf( - appCreateAppId.symbol to ( - appCreateAppIdBundleId to { expression: IrCall -> - IrGetObjectValueImpl( - startOffset = expression.startOffset, - endOffset = expression.endOffset, - type = IrSimpleTypeImpl(appImplCompanionSymbol, false, emptyList(), emptyList()), - symbol = appImplCompanionSymbol - ) - } - ), - appConfigurationCreateAppId.symbol to ( - appConfigurationImplCreateAppIdBungleId to { expression: IrCall -> - IrGetObjectValueImpl( - expression.startOffset, - expression.endOffset, - IrSimpleTypeImpl( - appConfigurationImplCompanionSymbol, - false, - emptyList(), - emptyList() - ), - appConfigurationImplCompanionSymbol - ) - } - ), - appBuilderBuildNoArg.symbol to (appBuilderBuildBundleId to { expression: IrCall -> expression.dispatchReceiver }) - ) - - val transformer = object : IrElementTransformerVoid() { - override fun visitCall(expression: IrCall): IrExpression { - replacements[expression.symbol]?.let { (target, dispatchReceiverFunction) -> - return IrCallImpl( - startOffset = expression.startOffset, - endOffset = expression.endOffset, - type = expression.type, - symbol = target.symbol, - typeArgumentsCount = 0, - valueArgumentsCount = target.valueParameters.size, - origin = null, - superQualifierSymbol = null - ).apply { - dispatchReceiver = dispatchReceiverFunction(expression) - val valueArguments = List(expression.valueArgumentsCount) { expression.getValueArgument(it) } - valueArguments.forEachIndexed { index, irExpression -> - putValueArgument(index, irExpression,) - } - putValueArgument( - expression.valueArgumentsCount, - IrConstImpl.string( - startOffset, - endOffset, - pluginContext.irBuiltIns.stringType, - bundleId - ) - ) - } - } - return super.visitCall(expression) - } - } - - // TODO 1.9-DEPRECATION Remove and rely on ClassLoweringPass.lower(IrModuleFragment) when leaving i - // 1.9 support - // Workaround that FileLoweringPass.lower(IrModuleFragment) is implemented as extension method - // in 1.9 but as proper interface method in 2.0. Implementation in both versions are more or - // less the same but this common implementation can loose some information as the IrElement is - // also not uniformly available on the CompilationException across versions. - fun lowerFromModuleFragment( - moduleFragment: IrModuleFragment - ) = moduleFragment.files.forEach { - try { - lower(it) - } catch (e: CompilationException) { - // Unfortunately we cannot access the IR element of e uniformly across 1.9 and 2.0 so - // leaving it as null. Hopefully the embedded cause will give the appropriate pointers - // to fix this. - throw CompilationException( - "Internal error in realm lowering : ${this::class.qualifiedName}: ${e.message}", - it, - null, - cause = e - ).apply { - stackTrace = e.stackTrace - } - } catch (e: KotlinExceptionWithAttachments) { - throw e - } catch (e: Throwable) { - throw CompilationException( - "Internal error in file lowering : ${this::class.qualifiedName}: ${e.message}", - it, - null, - cause = e - ).apply { - stackTrace = e.stackTrace - } - } - } - - override fun lower(irFile: IrFile) { - (this as DeclarationContainerLoweringPass).runOnFilePostfix(irFile) - (this as ClassLoweringPass).runOnFilePostfix(irFile) - } - - override fun lower(irDeclarationContainer: IrDeclarationContainer) { - irDeclarationContainer.transformChildrenVoid( - transformer - ) - } - - override fun lower(irClass: IrClass) { - irClass.transformChildrenVoid( - transformer - ) - } -} diff --git a/packages/plugin-compiler/src/test/kotlin/io/realm/kotlin/compiler/GenerationExtensionTest.kt b/packages/plugin-compiler/src/test/kotlin/io/realm/kotlin/compiler/GenerationExtensionTest.kt index 29ac0c46fc..c3e15ef0d2 100644 --- a/packages/plugin-compiler/src/test/kotlin/io/realm/kotlin/compiler/GenerationExtensionTest.kt +++ b/packages/plugin-compiler/src/test/kotlin/io/realm/kotlin/compiler/GenerationExtensionTest.kt @@ -92,11 +92,11 @@ class GenerationExtensionTest { ).joinToString(separator = File.separator) fun assertGeneratedIR() { - val outputFile = File("${outputDir()}/main/02_AFTER.ValidateIrBeforeLowering.ir") + val outputFile = File("${outputDir()}/main/02_AFTER.JvmValidateIrBeforeLowering.ir") stripInputPath(outputFile, fileMap) val expected = File("${expectedDir()}/02_AFTER.ValidateIrBeforeLowering.ir").readText() val actual = outputFile.readText() - assertEquals(expected, actual) + assertEquals(expected, actual, "Not equal") } } @@ -366,23 +366,6 @@ class GenerationExtensionTest { // assertEquals("Hello Zepp", nameProperty.call(sampleModel)) } - @Test - fun testBundleIdRewiring() { - val inputs = Files("/sync") - val result = compile( - inputs, - options = listOf( - PluginOption( - pluginId = "io.realm.kotlin", - optionName = "bundleId", - optionValue = "BUNDLE_ID" - ), - ) - ) - assertEquals(KotlinCompilation.ExitCode.OK, result.exitCode) - inputs.assertGeneratedIR() - } - @Suppress("deprecation") private fun compile( inputs: Files, @@ -397,7 +380,7 @@ class GenerationExtensionTest { kotlincArguments = listOf( "-Xjvm-default=all-compatibility", "-Xdump-directory=${inputs.outputDir()}", - "-Xphases-to-dump-after=ValidateIrBeforeLowering" + "-Xphases-to-dump-after=JvmValidateIrBeforeLowering" ) commandLineProcessors = listOf(RealmCommandLineProcessor()) pluginOptions = options diff --git a/packages/plugin-compiler/src/test/resources/sample/expected/02_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/sample/expected/02_AFTER.ValidateIrBeforeLowering.ir index 2110ada2d6..7d46bf519d 100644 --- a/packages/plugin-compiler/src/test/resources/sample/expected/02_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/sample/expected/02_AFTER.ValidateIrBeforeLowering.ir @@ -78,10 +78,10 @@ MODULE_FRAGMENT name:
RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in sample.input.Sample' CALL 'public final fun getValue (thisRef: kotlin.Any?, property: kotlin.reflect.KProperty<*>): T of kotlin.LazyKt.getValue [inline,operator] declared in kotlin.LazyKt' type=kotlin.String origin=null : kotlin.String - $receiver: GET_FIELD 'FIELD PROPERTY_DELEGATE name:ignoredDelegate$delegate type:kotlin.Lazy visibility:private [final]' type=kotlin.Lazy origin=null + $receiver: GET_FIELD 'FIELD PROPERTY_DELEGATE name:ignoredDelegate$delegate type:kotlin.Lazy visibility:private [final] declared in sample.input.Sample' type=kotlin.Lazy origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null thisRef: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null - property: PROPERTY_REFERENCE 'public final ignoredDelegate: kotlin.String [delegated,val]' field=null getter='public final fun (): kotlin.String declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1 origin=PROPERTY_REFERENCE_FOR_DELEGATE + property: PROPERTY_REFERENCE 'public final ignoredDelegate: kotlin.String [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.String declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1 origin=PROPERTY_REFERENCE_FOR_DELEGATE PROPERTY name:ignoredString visibility:public modality:FINAL [var] annotations: Ignore @@ -5772,51 +5772,51 @@ MODULE_FRAGMENT name:
CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample' type=sample.input.Sample origin=null - sourceProperty: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null + sourceProperty: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:linkingObjectsByList visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Sample BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByList$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByList$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Sample' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null reference: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null - targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE PROPERTY name:linkingObjectsBySet visibility:public modality:FINAL [delegated,val] FIELD PROPERTY_DELEGATE name:linkingObjectsBySet$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] EXPRESSION_BODY CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample' type=sample.input.Sample origin=null - sourceProperty: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null + sourceProperty: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:linkingObjectsBySet visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Sample BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsBySet$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsBySet$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Sample' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null reference: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null - targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE PROPERTY name:linkingObjectsByDictionary visibility:public modality:FINAL [delegated,val] FIELD PROPERTY_DELEGATE name:linkingObjectsByDictionary$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] EXPRESSION_BODY CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample' type=sample.input.Sample origin=null - sourceProperty: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null + sourceProperty: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:linkingObjectsByDictionary visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Sample BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByDictionary$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByDictionary$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Sample' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null reference: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null - targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE PROPERTY name:publicNameStringField visibility:public modality:FINAL [var] annotations: PersistedName(name = "persistedNameStringField") @@ -5929,17 +5929,17 @@ MODULE_FRAGMENT name:
CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample' type=sample.input.Sample origin=null - sourceProperty: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null + sourceProperty: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1> origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Sample) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:publicNameLinkingObjectsField visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Sample BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:publicNameLinkingObjectsField$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:publicNameLinkingObjectsField$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Sample' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null reference: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null - targetProperty: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE FUN name:dumpSchema visibility:public modality:FINAL <> ($this:sample.input.Sample) returnType:kotlin.String $this: VALUE_PARAMETER name: type:sample.input.Sample BLOCK_BODY @@ -5989,13 +5989,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in sample.input.Sample.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="Sample" primaryKey: CONST String type=kotlin.String value="id" numProperties: CONST Long type=kotlin.Long value=116 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -7306,7 +7305,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in sample.input.Sample.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -7319,7 +7318,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in sample.input.Sample.Companion' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -7336,7 +7335,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final id: kotlin.Long [var]' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final id: kotlin.Long [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7345,7 +7344,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final stringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final stringField: kotlin.String? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7354,7 +7353,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final byteField: kotlin.Byte? [var]' field=null getter='public final fun (): kotlin.Byte? declared in sample.input.Sample' setter='public final fun (: kotlin.Byte?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final byteField: kotlin.Byte? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Byte? declared in sample.input.Sample' setter='public final fun (: kotlin.Byte?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7363,7 +7362,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final charField: kotlin.Char? [var]' field=null getter='public final fun (): kotlin.Char? declared in sample.input.Sample' setter='public final fun (: kotlin.Char?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final charField: kotlin.Char? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Char? declared in sample.input.Sample' setter='public final fun (: kotlin.Char?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7372,7 +7371,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final shortField: kotlin.Short? [var]' field=null getter='public final fun (): kotlin.Short? declared in sample.input.Sample' setter='public final fun (: kotlin.Short?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final shortField: kotlin.Short? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Short? declared in sample.input.Sample' setter='public final fun (: kotlin.Short?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7381,7 +7380,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final intField: kotlin.Int? [var]' field=null getter='public final fun (): kotlin.Int? declared in sample.input.Sample' setter='public final fun (: kotlin.Int?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final intField: kotlin.Int? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Int? declared in sample.input.Sample' setter='public final fun (: kotlin.Int?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7390,7 +7389,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final longField: kotlin.Long? [var]' field=null getter='public final fun (): kotlin.Long? declared in sample.input.Sample' setter='public final fun (: kotlin.Long?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final longField: kotlin.Long? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Long? declared in sample.input.Sample' setter='public final fun (: kotlin.Long?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7399,7 +7398,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final booleanField: kotlin.Boolean? [var]' field=null getter='public final fun (): kotlin.Boolean? declared in sample.input.Sample' setter='public final fun (: kotlin.Boolean?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final booleanField: kotlin.Boolean? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Boolean? declared in sample.input.Sample' setter='public final fun (: kotlin.Boolean?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7408,7 +7407,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final floatField: kotlin.Float? [var]' field=null getter='public final fun (): kotlin.Float? declared in sample.input.Sample' setter='public final fun (: kotlin.Float?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final floatField: kotlin.Float? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Float? declared in sample.input.Sample' setter='public final fun (: kotlin.Float?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7417,7 +7416,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final doubleField: kotlin.Double? [var]' field=null getter='public final fun (): kotlin.Double? declared in sample.input.Sample' setter='public final fun (: kotlin.Double?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final doubleField: kotlin.Double? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Double? declared in sample.input.Sample' setter='public final fun (: kotlin.Double?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7426,7 +7425,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final decimal128Field: org.mongodb.kbson.BsonDecimal128? [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonDecimal128? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonDecimal128?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final decimal128Field: org.mongodb.kbson.BsonDecimal128? [var] declared in sample.input.Sample' field=null getter='public final fun (): org.mongodb.kbson.BsonDecimal128? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonDecimal128?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7435,7 +7434,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final timestampField: io.realm.kotlin.types.RealmInstant? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmInstant? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmInstant?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final timestampField: io.realm.kotlin.types.RealmInstant? [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmInstant? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmInstant?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7444,7 +7443,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final bsonObjectIdField: org.mongodb.kbson.BsonObjectId? [var]' field=null getter='public final fun (): org.mongodb.kbson.BsonObjectId? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final bsonObjectIdField: org.mongodb.kbson.BsonObjectId? [var] declared in sample.input.Sample' field=null getter='public final fun (): org.mongodb.kbson.BsonObjectId? declared in sample.input.Sample' setter='public final fun (: org.mongodb.kbson.BsonObjectId?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7453,7 +7452,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final uuidField: io.realm.kotlin.types.RealmUUID? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmUUID? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmUUID?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final uuidField: io.realm.kotlin.types.RealmUUID? [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmUUID? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmUUID?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7462,7 +7461,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final byteArrayField: kotlin.ByteArray? [var]' field=null getter='public final fun (): kotlin.ByteArray? declared in sample.input.Sample' setter='public final fun (: kotlin.ByteArray?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final byteArrayField: kotlin.ByteArray? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.ByteArray? declared in sample.input.Sample' setter='public final fun (: kotlin.ByteArray?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7471,7 +7470,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:MutableRealmInt modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable; kotlin.Number]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final mutableRealmInt: io.realm.kotlin.types.MutableRealmInt? [var]' field=null getter='public final fun (): io.realm.kotlin.types.MutableRealmInt? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.MutableRealmInt?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final mutableRealmInt: io.realm.kotlin.types.MutableRealmInt? [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.MutableRealmInt? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.MutableRealmInt?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7480,7 +7479,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Child modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var] declared in sample.input.Sample' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7489,7 +7488,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmAny modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableRealmAny: io.realm.kotlin.types.RealmAny? [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmAny? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmAny?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableRealmAny: io.realm.kotlin.types.RealmAny? [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmAny? declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmAny?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7498,7 +7497,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final stringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final stringListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7507,7 +7506,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final byteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final byteListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7516,7 +7515,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final charListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final charListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7525,7 +7524,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final shortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final shortListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7534,7 +7533,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final intListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final intListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7543,7 +7542,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final longListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final longListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7552,7 +7551,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final booleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final booleanListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7561,7 +7560,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final floatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final floatListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7570,7 +7569,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final doubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final doubleListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7579,7 +7578,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final timestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final timestampListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7588,7 +7587,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final bsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final bsonObjectIdListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7597,7 +7596,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final uuidListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final uuidListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7606,7 +7605,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final binaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final binaryListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7615,7 +7614,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final decimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final decimal128ListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7624,7 +7623,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final objectListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7633,7 +7632,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:EmbeddedChild modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.EmbeddedRealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final embeddedRealmObjectListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final embeddedRealmObjectListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7642,7 +7641,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableStringListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableStringListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7651,7 +7650,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableByteListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableByteListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7660,7 +7659,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableCharListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableCharListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7669,7 +7668,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableShortListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableShortListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7678,7 +7677,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableIntListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableIntListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7687,7 +7686,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableLongListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableLongListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7696,7 +7695,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBooleanListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBooleanListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7705,7 +7704,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableFloatListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableFloatListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7714,7 +7713,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDoubleListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDoubleListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7723,7 +7722,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableTimestampListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableTimestampListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7732,7 +7731,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7741,7 +7740,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableUUIDListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableUUIDListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7750,7 +7749,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBinaryListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBinaryListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7759,7 +7758,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDecimal128ListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDecimal128ListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7768,7 +7767,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmAny modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableRealmAnyListField: io.realm.kotlin.types.RealmList [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableRealmAnyListField: io.realm.kotlin.types.RealmList [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmList declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmList): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7777,7 +7776,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final stringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final stringSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7786,7 +7785,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final byteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final byteSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7795,7 +7794,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final charSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final charSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7804,7 +7803,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final shortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final shortSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7813,7 +7812,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final intSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final intSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7822,7 +7821,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final longSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final longSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7831,7 +7830,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final booleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final booleanSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7840,7 +7839,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final floatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final floatSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7849,7 +7848,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final doubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final doubleSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7858,7 +7857,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final timestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final timestampSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7867,7 +7866,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final bsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final bsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7876,7 +7875,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final uuidSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final uuidSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7885,7 +7884,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final binarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final binarySetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7894,7 +7893,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final decimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final decimal128SetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7903,7 +7902,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final objectSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7912,7 +7911,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableStringSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableStringSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7921,7 +7920,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableByteSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableByteSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7930,7 +7929,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableCharSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableCharSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7939,7 +7938,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableShortSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableShortSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7948,7 +7947,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableIntSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableIntSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7957,7 +7956,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableLongSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableLongSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7966,7 +7965,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBooleanSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBooleanSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7975,7 +7974,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableFloatSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableFloatSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7984,7 +7983,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDoubleSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDoubleSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -7993,7 +7992,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableTimestampSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableTimestampSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8002,7 +8001,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8011,7 +8010,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableUUIDSetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableUUIDSetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8020,7 +8019,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBinarySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBinarySetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8029,7 +8028,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDecimal128SetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDecimal128SetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8038,7 +8037,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmAny modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableRealmAnySetField: io.realm.kotlin.types.RealmSet [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableRealmAnySetField: io.realm.kotlin.types.RealmSet [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmSet declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmSet): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8047,7 +8046,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final stringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final stringDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8056,7 +8055,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final byteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final byteDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8065,7 +8064,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final charDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final charDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8074,7 +8073,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final shortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final shortDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8083,7 +8082,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final intDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final intDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8092,7 +8091,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final longDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final longDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8101,7 +8100,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final booleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final booleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8110,7 +8109,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final floatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final floatDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8119,7 +8118,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final doubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final doubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8128,7 +8127,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final timestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final timestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8137,7 +8136,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final bsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final bsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8146,7 +8145,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final uuidDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final uuidDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8155,7 +8154,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final binaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final binaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8164,7 +8163,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final decimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final decimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8173,7 +8172,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableStringDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableStringDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8182,7 +8181,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Byte modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableByteDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableByteDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8191,7 +8190,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Char modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableCharDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableCharDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8200,7 +8199,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Short modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableShortDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableShortDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8209,7 +8208,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Int modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableIntDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableIntDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8218,7 +8217,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Long modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableLongDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableLongDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8227,7 +8226,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Boolean modality:FINAL visibility:public superTypes:[kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBooleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBooleanDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8236,7 +8235,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Float modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableFloatDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableFloatDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8245,7 +8244,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:Double modality:FINAL visibility:public superTypes:[kotlin.Number; kotlin.Comparable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDoubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDoubleDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8254,7 +8253,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmInstant modality:ABSTRACT visibility:public superTypes:[kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableTimestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableTimestampDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8263,7 +8262,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonObjectId modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue; kotlin.Comparable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBsonObjectIdDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8272,7 +8271,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmUUID modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableUUIDDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableUUIDDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8281,7 +8280,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:ByteArray modality:FINAL visibility:public superTypes:[kotlin.Any; kotlin.Cloneable; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableBinaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableBinaryDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8290,7 +8289,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:BsonDecimal128 modality:FINAL visibility:public superTypes:[org.mongodb.kbson.BsonValue]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableDecimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableDecimal128DictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8299,7 +8298,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB INTERFACE name:RealmAny modality:ABSTRACT visibility:public superTypes:[kotlin.Any]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableRealmAnyDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableRealmAnyDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8308,7 +8307,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8317,7 +8316,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:EmbeddedChild modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.EmbeddedRealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final nullableEmbeddedObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var]' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final nullableEmbeddedObjectDictionaryField: io.realm.kotlin.types.RealmDictionary [var] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.types.RealmDictionary declared in sample.input.Sample' setter='public final fun (: io.realm.kotlin.types.RealmDictionary): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8326,7 +8325,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final linkingObjectsByList: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8335,7 +8334,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final linkingObjectsBySet: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8344,7 +8343,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final linkingObjectsByDictionary: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8353,7 +8352,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final publicNameStringField: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final publicNameStringField: kotlin.String? [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.String? declared in sample.input.Sample' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8362,7 +8361,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Child modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var] declared in sample.input.Sample' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8371,7 +8370,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final publicNameLinkingObjectsField: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Sample' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Sample' setter=null type=kotlin.reflect.KMutableProperty1 origin=null FUN name: visibility:public modality:FINAL <> ($this:sample.input.Sample.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: @@ -8379,12 +8378,12 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in sample.input.Sample.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private EXPRESSION_BODY - PROPERTY_REFERENCE 'public final id: kotlin.Long [var]' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + PROPERTY_REFERENCE 'public final id: kotlin.Long [var] declared in sample.input.Sample' field=null getter='public final fun (): kotlin.Long declared in sample.input.Sample' setter='public final fun (: kotlin.Long): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null FUN name: visibility:public modality:FINAL <> ($this:sample.input.Sample.Companion) returnType:kotlin.reflect.KMutableProperty1 correspondingProperty: PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] overridden: @@ -8392,7 +8391,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in sample.input.Sample.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -8405,7 +8404,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Sample.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in sample.input.Sample.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Sample.Companion declared in sample.input.Sample.Companion.' type=sample.input.Sample.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -8418,7 +8417,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.Sample' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.Sample' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null FUN name: visibility:public modality:OPEN <> ($this:sample.input.Sample, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -8427,7 +8426,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Sample VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.Sample' type=kotlin.Unit origin=null receiver: GET_VAR ': sample.input.Sample declared in sample.input.Sample.' type=sample.input.Sample origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.Sample.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null CLASS CLASS name:Child modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal] @@ -8491,17 +8490,17 @@ MODULE_FRAGMENT name:
CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Child declared in sample.input.Child' type=sample.input.Child origin=null - sourceProperty: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + sourceProperty: PROPERTY_REFERENCE 'public final child: sample.input.Child? [var] declared in sample.input.Sample' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Child) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:linkingObjectsByObject visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Child BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByObject$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:linkingObjectsByObject$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Child' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null reference: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null - targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Child' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE PROPERTY name:publicNameParent visibility:public modality:FINAL [delegated,val] annotations: PersistedName(name = "persistedNameParent") @@ -8510,17 +8509,17 @@ MODULE_FRAGMENT name:
CALL 'public final fun backlinks (sourceProperty: kotlin.reflect.KProperty1): io.realm.kotlin.types.BacklinksDelegate [inline] declared in io.realm.kotlin.ext.RealmObjectExtKt' type=io.realm.kotlin.types.BacklinksDelegate origin=null : sample.input.Sample $receiver: GET_VAR ': sample.input.Child declared in sample.input.Child' type=sample.input.Child origin=null - sourceProperty: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var]' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null + sourceProperty: PROPERTY_REFERENCE 'public final publicNameChildField: sample.input.Child? [var] declared in sample.input.Sample' field=null getter='public final fun (): sample.input.Child? declared in sample.input.Sample' setter='public final fun (: sample.input.Child?): kotlin.Unit declared in sample.input.Sample' type=kotlin.reflect.KMutableProperty1 origin=null FUN DELEGATED_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:sample.input.Child) returnType:io.realm.kotlin.query.RealmResults correspondingProperty: PROPERTY name:publicNameParent visibility:public modality:FINAL [delegated,val] $this: VALUE_PARAMETER name: type:sample.input.Child BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' CALL 'public abstract fun getValue (reference: io.realm.kotlin.types.RealmObject, targetProperty: kotlin.reflect.KProperty<*>): io.realm.kotlin.query.RealmResults [operator] declared in io.realm.kotlin.types.BacklinksDelegate' type=io.realm.kotlin.query.RealmResults origin=null - $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:publicNameParent$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final]' type=io.realm.kotlin.types.BacklinksDelegate origin=null + $this: GET_FIELD 'FIELD PROPERTY_DELEGATE name:publicNameParent$delegate type:io.realm.kotlin.types.BacklinksDelegate visibility:private [final] declared in sample.input.Child' type=io.realm.kotlin.types.BacklinksDelegate origin=null receiver: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null reference: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null - targetProperty: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE + targetProperty: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Child' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KProperty1> origin=PROPERTY_REFERENCE_FOR_DELEGATE FUN GENERATED[io.realm.kotlin.compiler.fir.RealmPluginGeneratorKey] name:hashCode visibility:public modality:OPEN <> ($this:sample.input.Child) returnType:kotlin.Int overridden: public open fun hashCode (): kotlin.Int [fake_override] declared in io.realm.kotlin.types.RealmObject @@ -8563,13 +8562,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in sample.input.Child.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="Child" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=3 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -8637,7 +8635,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in sample.input.Child.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in sample.input.Child.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -8650,7 +8648,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in sample.input.Child.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in sample.input.Child.Companion' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -8667,7 +8665,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.Child' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Child' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var] declared in sample.input.Child' field=null getter='public final fun (): kotlin.String? declared in sample.input.Child' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.Child' type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8676,7 +8674,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final linkingObjectsByObject: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Child' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null CONSTRUCTOR_CALL 'public constructor (first: A of kotlin.Pair, second: B of kotlin.Pair) [primary] declared in kotlin.Pair' type=kotlin.Pair> origin=null : kotlin.String : kotlin.Pair> @@ -8685,7 +8683,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:Sample modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val]' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final publicNameParent: io.realm.kotlin.query.RealmResults [delegated,val] declared in sample.input.Child' field=null getter='public final fun (): io.realm.kotlin.query.RealmResults declared in sample.input.Child' setter=null type=kotlin.reflect.KMutableProperty1 origin=null FUN name: visibility:public modality:FINAL <> ($this:sample.input.Child.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: @@ -8693,7 +8691,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.Child.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in sample.input.Child.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -8706,7 +8704,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in sample.input.Child.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in sample.input.Child.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -8719,7 +8717,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.Child.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in sample.input.Child.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.Child.Companion declared in sample.input.Child.Companion.' type=sample.input.Child.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -8732,7 +8730,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.Child' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.Child' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null FUN name: visibility:public modality:OPEN <> ($this:sample.input.Child, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -8741,7 +8739,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.Child VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.Child' type=kotlin.Unit origin=null receiver: GET_VAR ': sample.input.Child declared in sample.input.Child.' type=sample.input.Child origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.Child.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null CLASS CLASS name:EmbeddedParent modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal] @@ -8843,13 +8841,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in sample.input.EmbeddedParent.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="EmbeddedParent" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=1 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -8895,7 +8892,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in sample.input.EmbeddedParent.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -8908,7 +8905,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in sample.input.EmbeddedParent.Companion' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -8925,7 +8922,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS CLASS name:EmbeddedChild modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.EmbeddedRealmObject; io.realm.kotlin.internal.RealmObjectInternal]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final child: sample.input.EmbeddedChild? [var]' field=null getter='public final fun (): sample.input.EmbeddedChild? declared in sample.input.EmbeddedParent' setter='public final fun (: sample.input.EmbeddedChild?): kotlin.Unit declared in sample.input.EmbeddedParent' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final child: sample.input.EmbeddedChild? [var] declared in sample.input.EmbeddedParent' field=null getter='public final fun (): sample.input.EmbeddedChild? declared in sample.input.EmbeddedParent' setter='public final fun (: sample.input.EmbeddedChild?): kotlin.Unit declared in sample.input.EmbeddedParent' type=kotlin.reflect.KMutableProperty1 origin=null FUN name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedParent.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: @@ -8933,7 +8930,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in sample.input.EmbeddedParent.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -8946,7 +8943,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in sample.input.EmbeddedParent.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -8959,7 +8956,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedParent.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in sample.input.EmbeddedParent.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedParent.Companion declared in sample.input.EmbeddedParent.Companion.' type=sample.input.EmbeddedParent.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -8972,7 +8969,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.EmbeddedParent' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.EmbeddedParent' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.' type=sample.input.EmbeddedParent origin=null FUN name: visibility:public modality:OPEN <> ($this:sample.input.EmbeddedParent, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -8981,7 +8978,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedParent VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.EmbeddedParent' type=kotlin.Unit origin=null receiver: GET_VAR ': sample.input.EmbeddedParent declared in sample.input.EmbeddedParent.' type=sample.input.EmbeddedParent origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.EmbeddedParent.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null CLASS CLASS name:EmbeddedChild modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.EmbeddedRealmObject; io.realm.kotlin.internal.RealmObjectInternal] @@ -9081,13 +9078,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in sample.input.EmbeddedChild.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="EmbeddedChild" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=1 isEmbedded: CONST Boolean type=kotlin.Boolean value=true - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -9133,7 +9129,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in sample.input.EmbeddedChild.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -9146,7 +9142,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in sample.input.EmbeddedChild.Companion' type=kotlin.String origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -9163,7 +9159,7 @@ MODULE_FRAGMENT name:
: kotlin.reflect.KClass<*> : kotlin.reflect.KMutableProperty1 first: CLASS_REFERENCE 'CLASS IR_EXTERNAL_DECLARATION_STUB CLASS name:String modality:FINAL visibility:public superTypes:[kotlin.Comparable; kotlin.CharSequence; java.io.Serializable]' type=kotlin.reflect.KClass - second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var]' field=null getter='public final fun (): kotlin.String? declared in sample.input.EmbeddedChild' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.EmbeddedChild' type=kotlin.reflect.KMutableProperty1 origin=null + second: PROPERTY_REFERENCE 'public final name: kotlin.String? [var] declared in sample.input.EmbeddedChild' field=null getter='public final fun (): kotlin.String? declared in sample.input.EmbeddedChild' setter='public final fun (: kotlin.String?): kotlin.Unit declared in sample.input.EmbeddedChild' type=kotlin.reflect.KMutableProperty1 origin=null FUN name: visibility:public modality:FINAL <> ($this:sample.input.EmbeddedChild.Companion) returnType:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> correspondingProperty: PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] overridden: @@ -9171,7 +9167,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in sample.input.EmbeddedChild.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -9184,7 +9180,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in sample.input.EmbeddedChild.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -9197,7 +9193,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in sample.input.EmbeddedChild.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in sample.input.EmbeddedChild.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': sample.input.EmbeddedChild.Companion declared in sample.input.EmbeddedChild.Companion.' type=sample.input.EmbeddedChild.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -9210,7 +9206,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.EmbeddedChild' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.EmbeddedChild' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.' type=sample.input.EmbeddedChild origin=null FUN name: visibility:public modality:OPEN <> ($this:sample.input.EmbeddedChild, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -9219,6 +9215,6 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:sample.input.EmbeddedChild VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in sample.input.EmbeddedChild' type=kotlin.Unit origin=null receiver: GET_VAR ': sample.input.EmbeddedChild declared in sample.input.EmbeddedChild.' type=sample.input.EmbeddedChild origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in sample.input.EmbeddedChild.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null diff --git a/packages/plugin-compiler/src/test/resources/schema/expected/02_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/schema/expected/02_AFTER.ValidateIrBeforeLowering.ir index 64b831f136..470b6e6c17 100644 --- a/packages/plugin-compiler/src/test/resources/schema/expected/02_AFTER.ValidateIrBeforeLowering.ir +++ b/packages/plugin-compiler/src/test/resources/schema/expected/02_AFTER.ValidateIrBeforeLowering.ir @@ -49,13 +49,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in schema.input.A.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="A" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=0 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -90,7 +89,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in schema.input.A.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in schema.input.A.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -103,7 +102,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in schema.input.A.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in schema.input.A.Companion' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -119,7 +118,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.A.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in schema.input.A.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -132,7 +131,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in schema.input.A.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in schema.input.A.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -145,7 +144,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.A.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in schema.input.A.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.A.Companion declared in schema.input.A.Companion.' type=schema.input.A.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -158,7 +157,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.A' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.A' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': schema.input.A declared in schema.input.A.' type=schema.input.A origin=null FUN name: visibility:public modality:OPEN <> ($this:schema.input.A, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -167,7 +166,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.A VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.A' type=kotlin.Unit origin=null receiver: GET_VAR ': schema.input.A declared in schema.input.A.' type=schema.input.A origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.A.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null CLASS CLASS name:B modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal] @@ -218,13 +217,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in schema.input.B.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="B" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=0 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -259,7 +257,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in schema.input.B.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in schema.input.B.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -272,7 +270,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in schema.input.B.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in schema.input.B.Companion' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -288,7 +286,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.B.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in schema.input.B.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -301,7 +299,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in schema.input.B.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in schema.input.B.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -314,7 +312,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.B.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in schema.input.B.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.B.Companion declared in schema.input.B.Companion.' type=schema.input.B.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -327,7 +325,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.B' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.B' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': schema.input.B declared in schema.input.B.' type=schema.input.B origin=null FUN name: visibility:public modality:OPEN <> ($this:schema.input.B, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -336,7 +334,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.B VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.B' type=kotlin.Unit origin=null receiver: GET_VAR ': schema.input.B declared in schema.input.B.' type=schema.input.B origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.B.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null CLASS CLASS name:C modality:OPEN visibility:public superTypes:[io.realm.kotlin.types.RealmObject; io.realm.kotlin.internal.RealmObjectInternal] @@ -387,13 +385,12 @@ MODULE_FRAGMENT name:
BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun io_realm_kotlin_schema (): kotlin.Any declared in schema.input.C.Companion' CONSTRUCTOR_CALL 'public constructor (cinteropClass: io.realm.kotlin.internal.interop.ClassInfo, cinteropProperties: kotlin.collections.List) [primary] declared in io.realm.kotlin.internal.schema.RealmClassImpl' type=io.realm.kotlin.internal.schema.RealmClassImpl origin=null - cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean, isAsymmetric: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null + cinteropClass: CALL 'public final fun create (name: kotlin.String, primaryKey: kotlin.String?, numProperties: kotlin.Long, isEmbedded: kotlin.Boolean): io.realm.kotlin.internal.interop.ClassInfo declared in io.realm.kotlin.internal.interop.ClassInfo.Companion' type=io.realm.kotlin.internal.interop.ClassInfo origin=null $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:public [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.internal.interop.ClassInfo.Companion name: CONST String type=kotlin.String value="C" primaryKey: CONST Null type=kotlin.Nothing? value=null numProperties: CONST Long type=kotlin.Long value=0 isEmbedded: CONST Boolean type=kotlin.Boolean value=false - isAsymmetric: CONST Boolean type=kotlin.Boolean value=false cinteropProperties: CALL 'public final fun listOf (vararg elements: T of kotlin.collections.CollectionsKt.listOf): kotlin.collections.List declared in kotlin.collections.CollectionsKt' type=kotlin.collections.List origin=null : io.realm.kotlin.internal.interop.PropertyInfo elements: VARARG type=kotlin.Array varargElementType=kotlin.collections.List @@ -428,7 +425,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KClass declared in schema.input.C.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private' type=kotlin.reflect.KClass origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_class type:kotlin.reflect.KClass visibility:private declared in schema.input.C.Companion' type=kotlin.reflect.KClass origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_className visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private @@ -441,7 +438,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.String declared in schema.input.C.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private' type=kotlin.String origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_className type:kotlin.String visibility:private declared in schema.input.C.Companion' type=kotlin.String origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_fields visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private @@ -457,7 +454,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> declared in schema.input.C.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_fields type:kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> visibility:private declared in schema.input.C.Companion' type=kotlin.collections.Map, kotlin.reflect.KMutableProperty1>> origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_primaryKey visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private @@ -470,7 +467,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): kotlin.reflect.KMutableProperty1 declared in schema.input.C.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private' type=kotlin.reflect.KMutableProperty1 origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_primaryKey type:kotlin.reflect.KMutableProperty1 visibility:private declared in schema.input.C.Companion' type=kotlin.reflect.KMutableProperty1 origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_classKind visibility:public modality:FINAL [var] FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private @@ -483,7 +480,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C.Companion BLOCK_BODY RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.schema.RealmClassKind declared in schema.input.C.Companion' - GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private' type=io.realm.kotlin.schema.RealmClassKind origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_classKind type:io.realm.kotlin.schema.RealmClassKind visibility:private declared in schema.input.C.Companion' type=io.realm.kotlin.schema.RealmClassKind origin=null receiver: GET_VAR ': schema.input.C.Companion declared in schema.input.C.Companion.' type=schema.input.C.Companion origin=null PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private @@ -496,7 +493,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C BLOCK_BODY RETURN type=kotlin.Nothing from='public open fun (): io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.C' - GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=io.realm.kotlin.internal.RealmObjectReference? origin=null + GET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.C' type=io.realm.kotlin.internal.RealmObjectReference? origin=null receiver: GET_VAR ': schema.input.C declared in schema.input.C.' type=schema.input.C origin=null FUN name: visibility:public modality:OPEN <> ($this:schema.input.C, :io.realm.kotlin.internal.RealmObjectReference?) returnType:kotlin.Unit correspondingProperty: PROPERTY name:io_realm_kotlin_objectReference visibility:public modality:OPEN [var] @@ -505,7 +502,7 @@ MODULE_FRAGMENT name:
$this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:schema.input.C VALUE_PARAMETER name: index:0 type:io.realm.kotlin.internal.RealmObjectReference? BLOCK_BODY - SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private' type=kotlin.Unit origin=null + SET_FIELD 'FIELD name:io_realm_kotlin_objectReference type:io.realm.kotlin.internal.RealmObjectReference? visibility:private declared in schema.input.C' type=kotlin.Unit origin=null receiver: GET_VAR ': schema.input.C declared in schema.input.C.' type=schema.input.C origin=null value: GET_VAR ': io.realm.kotlin.internal.RealmObjectReference? declared in schema.input.C.' type=io.realm.kotlin.internal.RealmObjectReference? origin=null PROPERTY name:conf1 visibility:public modality:FINAL [val] diff --git a/packages/plugin-compiler/src/test/resources/sync/expected/02_AFTER.ValidateIrBeforeLowering.ir b/packages/plugin-compiler/src/test/resources/sync/expected/02_AFTER.ValidateIrBeforeLowering.ir deleted file mode 100644 index cd0608e789..0000000000 --- a/packages/plugin-compiler/src/test/resources/sync/expected/02_AFTER.ValidateIrBeforeLowering.ir +++ /dev/null @@ -1,100 +0,0 @@ -// --- IR for
after Validate IR before lowering -MODULE_FRAGMENT name:
- FILE fqName: fileName:input/App.kt - PROPERTY name:app1 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app1 type:io.realm.kotlin.mongodb.App visibility:private [final,static] - EXPRESSION_BODY - CALL 'internal final fun create (appId: kotlin.String, bundleId: kotlin.String): io.realm.kotlin.mongodb.App declared in io.realm.kotlin.mongodb.internal.AppImpl.Companion' type=io.realm.kotlin.mongodb.App origin=null - $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:internal [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.mongodb.internal.AppImpl.Companion - appId: CONST String type=kotlin.String value="app1" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:io.realm.kotlin.mongodb.App - correspondingProperty: PROPERTY name:app1 visibility:public modality:FINAL [val] - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.App declared in ' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app1 type:io.realm.kotlin.mongodb.App visibility:private [final,static]' type=io.realm.kotlin.mongodb.App origin=null - PROPERTY name:app2 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app2 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final,static] - EXPRESSION_BODY - CALL 'internal final fun create (appId: kotlin.String, bundleId: kotlin.String): io.realm.kotlin.mongodb.AppConfiguration declared in io.realm.kotlin.mongodb.internal.AppConfigurationImpl.Companion' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:internal [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.mongodb.internal.AppConfigurationImpl.Companion - appId: CONST String type=kotlin.String value="app2" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:io.realm.kotlin.mongodb.AppConfiguration - correspondingProperty: PROPERTY name:app2 visibility:public modality:FINAL [val] - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.AppConfiguration declared in ' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app2 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final,static]' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - PROPERTY name:app3 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app3 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final,static] - EXPRESSION_BODY - CALL 'public final fun build (bundleId: kotlin.String): io.realm.kotlin.mongodb.AppConfiguration declared in io.realm.kotlin.mongodb.AppConfiguration.Builder' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - $this: CONSTRUCTOR_CALL 'public constructor (appId: kotlin.String) [primary] declared in io.realm.kotlin.mongodb.AppConfiguration.Builder' type=io.realm.kotlin.mongodb.AppConfiguration.Builder origin=null - appId: CONST String type=kotlin.String value="app3" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> () returnType:io.realm.kotlin.mongodb.AppConfiguration - correspondingProperty: PROPERTY name:app3 visibility:public modality:FINAL [val] - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.AppConfiguration declared in ' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app3 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final,static]' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - CLASS CLASS name:A modality:FINAL visibility:public superTypes:[kotlin.Any] - $this: VALUE_PARAMETER INSTANCE_RECEIVER name: type:.A - CONSTRUCTOR visibility:public <> () returnType:.A [primary] - BLOCK_BODY - DELEGATING_CONSTRUCTOR_CALL 'public constructor () [primary] declared in kotlin.Any' - INSTANCE_INITIALIZER_CALL classDescriptor='CLASS CLASS name:A modality:FINAL visibility:public superTypes:[kotlin.Any]' - PROPERTY name:app4 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app4 type:io.realm.kotlin.mongodb.App visibility:private [final] - EXPRESSION_BODY - CALL 'internal final fun create (appId: kotlin.String, bundleId: kotlin.String): io.realm.kotlin.mongodb.App declared in io.realm.kotlin.mongodb.internal.AppImpl.Companion' type=io.realm.kotlin.mongodb.App origin=null - $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:internal [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.mongodb.internal.AppImpl.Companion - appId: CONST String type=kotlin.String value="app4" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:.A) returnType:io.realm.kotlin.mongodb.App - correspondingProperty: PROPERTY name:app4 visibility:public modality:FINAL [val] - $this: VALUE_PARAMETER name: type:.A - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.App declared in .A' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app4 type:io.realm.kotlin.mongodb.App visibility:private [final]' type=io.realm.kotlin.mongodb.App origin=null - receiver: GET_VAR ': .A declared in .A.' type=.A origin=null - PROPERTY name:app5 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app5 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final] - EXPRESSION_BODY - CALL 'internal final fun create (appId: kotlin.String, bundleId: kotlin.String): io.realm.kotlin.mongodb.AppConfiguration declared in io.realm.kotlin.mongodb.internal.AppConfigurationImpl.Companion' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - $this: GET_OBJECT 'CLASS IR_EXTERNAL_DECLARATION_STUB OBJECT name:Companion modality:FINAL visibility:internal [companion] superTypes:[kotlin.Any]' type=io.realm.kotlin.mongodb.internal.AppConfigurationImpl.Companion - appId: CONST String type=kotlin.String value="app5" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:.A) returnType:io.realm.kotlin.mongodb.AppConfiguration - correspondingProperty: PROPERTY name:app5 visibility:public modality:FINAL [val] - $this: VALUE_PARAMETER name: type:.A - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.AppConfiguration declared in .A' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app5 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final]' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - receiver: GET_VAR ': .A declared in .A.' type=.A origin=null - PROPERTY name:app6 visibility:public modality:FINAL [val] - FIELD PROPERTY_BACKING_FIELD name:app6 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final] - EXPRESSION_BODY - CALL 'public final fun build (bundleId: kotlin.String): io.realm.kotlin.mongodb.AppConfiguration declared in io.realm.kotlin.mongodb.AppConfiguration.Builder' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - $this: CONSTRUCTOR_CALL 'public constructor (appId: kotlin.String) [primary] declared in io.realm.kotlin.mongodb.AppConfiguration.Builder' type=io.realm.kotlin.mongodb.AppConfiguration.Builder origin=null - appId: CONST String type=kotlin.String value="app6" - bundleId: CONST String type=kotlin.String value="BUNDLE_ID" - FUN DEFAULT_PROPERTY_ACCESSOR name: visibility:public modality:FINAL <> ($this:.A) returnType:io.realm.kotlin.mongodb.AppConfiguration - correspondingProperty: PROPERTY name:app6 visibility:public modality:FINAL [val] - $this: VALUE_PARAMETER name: type:.A - BLOCK_BODY - RETURN type=kotlin.Nothing from='public final fun (): io.realm.kotlin.mongodb.AppConfiguration declared in .A' - GET_FIELD 'FIELD PROPERTY_BACKING_FIELD name:app6 type:io.realm.kotlin.mongodb.AppConfiguration visibility:private [final]' type=io.realm.kotlin.mongodb.AppConfiguration origin=null - receiver: GET_VAR ': .A declared in .A.' type=.A origin=null - FUN FAKE_OVERRIDE name:equals visibility:public modality:OPEN <> ($this:kotlin.Any, other:kotlin.Any?) returnType:kotlin.Boolean [fake_override,operator] - overridden: - public open fun equals (other: kotlin.Any?): kotlin.Boolean [operator] declared in kotlin.Any - $this: VALUE_PARAMETER name: type:kotlin.Any - VALUE_PARAMETER name:other index:0 type:kotlin.Any? - FUN FAKE_OVERRIDE name:hashCode visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.Int [fake_override] - overridden: - public open fun hashCode (): kotlin.Int declared in kotlin.Any - $this: VALUE_PARAMETER name: type:kotlin.Any - FUN FAKE_OVERRIDE name:toString visibility:public modality:OPEN <> ($this:kotlin.Any) returnType:kotlin.String [fake_override] - overridden: - public open fun toString (): kotlin.String declared in kotlin.Any - $this: VALUE_PARAMETER name: type:kotlin.Any diff --git a/packages/plugin-compiler/src/test/resources/sync/input/App.kt b/packages/plugin-compiler/src/test/resources/sync/input/App.kt deleted file mode 100644 index 3c1b8fae99..0000000000 --- a/packages/plugin-compiler/src/test/resources/sync/input/App.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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 io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.User - -val app1 = App.create("app1") -val app2 = AppConfiguration.create("app2") -val app3 = AppConfiguration.Builder("app3").build() - -class A { - val app4 = App.create("app4") - val app5 = AppConfiguration.create("app5") - val app6 = AppConfiguration.Builder("app6").build() -} diff --git a/packages/settings.gradle.kts b/packages/settings.gradle.kts index 6ea6605fde..0f87c7ba40 100644 --- a/packages/settings.gradle.kts +++ b/packages/settings.gradle.kts @@ -41,7 +41,6 @@ fun getPropertyValue(propertyName: String): String? { include(":plugin-compiler") include(":plugin-compiler-shaded") include(":library-base") - include(":library-sync") include(":cinterop") include(":jni-swig-stub") } @@ -49,6 +48,5 @@ fun getPropertyValue(propertyName: String): String? { (getPropertyValue("includeTestModules")?.let { it.toBoolean() } ?: true).let { if (it) { include(":test-base") - include(":test-sync") } } diff --git a/packages/test-base/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/android/PlatformInfoTest.kt b/packages/test-base/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/android/PlatformInfoTest.kt index 023cdf5ed7..d9acf1e217 100644 --- a/packages/test-base/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/android/PlatformInfoTest.kt +++ b/packages/test-base/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/android/PlatformInfoTest.kt @@ -17,16 +17,12 @@ package io.realm.kotlin.test.android import io.realm.kotlin.internal.platform.OS_NAME -import io.realm.kotlin.internal.platform.OS_VERSION -import io.realm.kotlin.internal.platform.RUNTIME import kotlin.test.Test import kotlin.test.assertEquals class PlatformInfoTest { @Test fun platformInfo() { - assertEquals("Android", RUNTIME.description) assertEquals("Android", OS_NAME) - assertEquals(android.os.Build.VERSION.RELEASE, OS_VERSION) } } diff --git a/packages/test-base/src/iosTest/kotlin/io/realm/kotlin/test/ios/PlatformInfoTest.kt b/packages/test-base/src/iosTest/kotlin/io/realm/kotlin/test/ios/PlatformInfoTest.kt index 00fda586ef..5fda0123a4 100644 --- a/packages/test-base/src/iosTest/kotlin/io/realm/kotlin/test/ios/PlatformInfoTest.kt +++ b/packages/test-base/src/iosTest/kotlin/io/realm/kotlin/test/ios/PlatformInfoTest.kt @@ -16,17 +16,12 @@ package io.realm.kotlin.test.ios import io.realm.kotlin.internal.platform.OS_NAME -import io.realm.kotlin.internal.platform.OS_VERSION -import io.realm.kotlin.internal.platform.RUNTIME import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue class PlatformInfoTest { @Test fun platformInfo() { - assertEquals("Native", RUNTIME.description) assertEquals("iOS", OS_NAME) - assertTrue(OS_VERSION.startsWith("Version ")) } } diff --git a/packages/test-base/src/jvmTest/kotlin/io/realm/kotlin/test/jvm/PlatformInfoTest.kt b/packages/test-base/src/jvmTest/kotlin/io/realm/kotlin/test/jvm/PlatformInfoTest.kt index a41009e2be..bd748d1b26 100644 --- a/packages/test-base/src/jvmTest/kotlin/io/realm/kotlin/test/jvm/PlatformInfoTest.kt +++ b/packages/test-base/src/jvmTest/kotlin/io/realm/kotlin/test/jvm/PlatformInfoTest.kt @@ -17,16 +17,12 @@ package io.realm.kotlin.test.jvm import io.realm.kotlin.internal.platform.OS_NAME -import io.realm.kotlin.internal.platform.OS_VERSION -import io.realm.kotlin.internal.platform.RUNTIME import kotlin.test.Test import kotlin.test.assertEquals class PlatformInfoTest { @Test fun platformInfo() { - assertEquals("JVM", RUNTIME.description) assertEquals(System.getProperty("os.name"), OS_NAME) - assertEquals(System.getProperty("os.version"), OS_VERSION) } } diff --git a/packages/test-base/src/macosTest/kotlin/io/realm/kotlin/test/macos/PlatformInfoTest.kt b/packages/test-base/src/macosTest/kotlin/io/realm/kotlin/test/macos/PlatformInfoTest.kt index e172fb4162..f72a1e3aee 100644 --- a/packages/test-base/src/macosTest/kotlin/io/realm/kotlin/test/macos/PlatformInfoTest.kt +++ b/packages/test-base/src/macosTest/kotlin/io/realm/kotlin/test/macos/PlatformInfoTest.kt @@ -16,17 +16,12 @@ package io.realm.kotlin.test.macos import io.realm.kotlin.internal.platform.OS_NAME -import io.realm.kotlin.internal.platform.OS_VERSION -import io.realm.kotlin.internal.platform.RUNTIME import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertTrue class PlatformInfoTest { @Test fun platformInfo() { - assertEquals("Native", RUNTIME.description) assertEquals("MacOS", OS_NAME) - assertTrue(OS_VERSION.startsWith("Version ")) } } diff --git a/packages/test-sync/build.gradle.kts b/packages/test-sync/build.gradle.kts deleted file mode 100644 index d8ad09ebe1..0000000000 --- a/packages/test-sync/build.gradle.kts +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright 2020 Realm Inc. - * - * 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 org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithSimulatorTests -import com.codingfeline.buildkonfig.compiler.FieldSpec.Type -import kotlin.math.min - -plugins { - id("org.jetbrains.kotlin.multiplatform") - id("com.android.application") - kotlin("plugin.serialization") version Versions.kotlin - // Test relies on the compiler plugin, but we cannot apply our full plugin from within the same - // gradle run, so we just apply the compiler plugin directly as a dependency below instead - // id("io.realm.kotlin") - id("com.codingfeline.buildkonfig") version Versions.buildkonfig -} - - -// Test relies on the compiler plugin, but we cannot apply our full plugin from within the same -// gradle run, so we just apply the compiler plugin directly -dependencies { - kotlinCompilerPluginClasspath("io.realm.kotlin:plugin-compiler:${Realm.version}") - kotlinNativeCompilerPluginClasspath("io.realm.kotlin:plugin-compiler-shaded:${Realm.version}") - kotlinCompilerClasspath("org.jetbrains.kotlin:kotlin-compiler-embeddable:${Versions.kotlin}") - kotlinCompilerClasspath("org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:${Versions.kotlin}") -} - -// Substitute maven coordinate dependencies of pattern 'io.realm.kotlin::${Realm.version}' -// with project dependency ':' if '' is configured as a subproject of the root project -configurations.all { - resolutionStrategy.dependencySubstitution { - rootProject.allprojects - .filter { it != project && it != rootProject } - .forEach { subproject: Project -> - substitute(module("io.realm.kotlin:${subproject.name}:${Realm.version}")).using( - project(":${subproject.name}") - ) - } - } - - // Ensure that androidUnitTest uses the Realm JVM variant rather than Android. - // This should cover both "debug" and "release" variants. - // - // WARNING: This does not work unless jvm artifacts has been published which also means - // that Android JVM tests will not pickup changes to the library unless they are manually - // published using `publishAllPublicationsToTestRepository`. - // - // See https://github.com/realm/realm-kotlin/issues/1404 for more details. - if (name.endsWith("UnitTestRuntimeClasspath")) { - resolutionStrategy.dependencySubstitution { - substitute(module("io.realm.kotlin:library-base:${Realm.version}")).using( - module("io.realm.kotlin:library-base-jvm:${Realm.version}") - ) - substitute(module("io.realm.kotlin:cinterop:${Realm.version}")).using( - module("io.realm.kotlin:cinterop-jvm:${Realm.version}") - ) - } - } -} - -// Common Kotlin configuration -@Suppress("UNUSED_VARIABLE") -kotlin { - sourceSets { - val commonMain by getting { - dependencies { - implementation(kotlin("stdlib-common")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}") - // FIXME AUTO-SETUP Removed automatic dependency injection to ensure observability of - // requirements for now - implementation(project(":test-base")) - // IDE Doesn't resolve library-base symbols if not adding it as an explicit - // dependency. Probably due to our own custom dependency substitution above, but - // shouldn't be an issue as it is already a transitive dependency of library-sync. - implementation("io.realm.kotlin:library-base:${Realm.version}") - implementation("io.realm.kotlin:library-sync:${Realm.version}") - // FIXME API-SCHEMA We currently have some tests that verified injection of - // interfaces, uses internal representation for property meta data, etc. Can - // probably be replaced when schema information is exposed in the public API - // Our current compiler plugin tests only runs on JVM, so makes sense to keep them - // for now, but ideally they should go to the compiler plugin tests. - implementation("io.realm.kotlin:cinterop:${Realm.version}") - implementation("org.jetbrains.kotlinx:atomicfu:${Versions.atomicfu}") - - // For server admin - implementation("io.ktor:ktor-client-core:${Versions.ktor}") - implementation("io.ktor:ktor-client-logging:${Versions.ktor}") - implementation("io.ktor:ktor-serialization-kotlinx-json:${Versions.ktor}") - implementation("io.ktor:ktor-client-content-negotiation:${Versions.ktor}") - - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${Versions.serialization}") - - implementation("com.squareup.okio:okio:${Versions.okio}") - } - } - - val commonTest by getting { - dependencies { - // TODO AtomicFu doesn't work on the test project due to - // https://github.com/Kotlin/kotlinx.atomicfu/issues/90#issuecomment-597872907 - implementation("co.touchlab:stately-concurrency:1.2.0") - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - implementation("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetime}") - } - } - } - - // All kotlin compilation tasks - tasks.withType> { - compilerOptions.freeCompilerArgs.addAll( - "-P", "plugin:io.realm.kotlin:bundleId=TEST_BUNDLE_ID", - "-opt-in=org.mongodb.kbson.ExperimentalKBsonSerializerApi" - ) - } -} - -// Android configuration -android { - namespace = "io.realm.sync.testapp" - compileSdk = Versions.Android.compileSdkVersion - buildToolsVersion = Versions.Android.buildToolsVersion - - testBuildType = (properties["testBuildType"] ?: "debug") as String - - defaultConfig { - minSdk = Versions.Android.minSdk - targetSdk = Versions.Android.targetSdk - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - multiDexEnabled = true - - sourceSets { - getByName("main") { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - } - } - ndk { - abiFilters += setOf("x86_64", "x86", "arm64-v8a", "armeabi-v7a") - } - } - - buildTypes { - create("debugMinified") { - initWith(getByName("debug")) - matchingFallbacks.add("debug") - isMinifyEnabled = true - isDebuggable = false - proguardFiles("proguard-rules-test.pro") - } - } - - compileOptions { - sourceCompatibility = Versions.sourceCompatibilityVersion - targetCompatibility = Versions.targetCompatibilityVersion - } - - // Remove overlapping resources after adding "org.jetbrains.kotlinx:kotlinx-coroutines-test" to - // avoid errors like "More than one file was found with OS independent path 'META-INF/AL2.0'." - packagingOptions { - resources.excludes.add("META-INF/AL2.0") - resources.excludes.add("META-INF/LGPL2.1") - } -} -@Suppress("UNUSED_VARIABLE") -kotlin { - androidTarget() - sourceSets { - val androidMain by getting { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}") - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - implementation("junit:junit:${Versions.junit}") - implementation("androidx.test.ext:junit:${Versions.androidxJunit}") - implementation("androidx.test:runner:${Versions.androidxTest}") - implementation("androidx.test:rules:${Versions.androidxTest}") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}") - - } - } - val androidUnitTest by getting { - dependencies { - // Realm dependencies must be converted to -jvm variants here. - // This is currently done using dependency substitution in `build.gradle`. - // See https://kotlinlang.slack.com/archives/C19FD9681/p1685089661499199 - } - } - val androidInstrumentedTest by getting { - // Instrumentation tests do not depend on commonTest by default: - // https://kotlinlang.org/docs/whatsnew18.html#the-relation-between-android-and-common-tests - // But adding support for this using `dependsOn(commonTest)` will prevent us - // from selectively running unit tests on device from the IDE as the files do not - // become visible in IntelliJ this way. - // - // In order to work around this limitation, the following strategy is used: - // - // 1. A symlink between all commonTest files and androidInstrumentedTest is created. - // This symlink is called `common` to mirror the package structure in commonTest. - // 2. We need to duplicate all test dependencies from `commonTest` into - // `androidInstrumentedTest`. - // - // This approach results in a minimum amount of code changes and satisfies both our - // IDE and CI requirements. But it also introduces the downside that we need to - // duplicate dependencies between `androidInstrumentedTest` and `commonTest` - // - // Improvements to this situation is tracked here: - // https://youtrack.jetbrains.com/issue/KT-46452/Allow-to-run-common-tests-as-Android-Instrumentation-tests - - // Copy of `commonTest` dependencies - dependencies { - // TODO AtomicFu doesn't work on the test project due to - // https://github.com/Kotlin/kotlinx.atomicfu/issues/90#issuecomment-597872907 - implementation("co.touchlab:stately-concurrency:1.2.0") - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - implementation("org.jetbrains.kotlinx:kotlinx-datetime:${Versions.datetime}") - } - } - } -} -@Suppress("UNUSED_VARIABLE") -kotlin { - jvm() - sourceSets { - val jvmMain by getting { - dependencies { - implementation("org.jetbrains.kotlin:kotlin-compiler-embeddable:${Versions.kotlin}") - implementation("io.realm.kotlin:plugin-compiler:${Realm.version}") - implementation("dev.zacsweers.kctfork:core:${Versions.kotlinCompileTesting}") - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test")) - implementation(kotlin("test-junit")) - implementation(kotlin("reflect")) - } - } - } -} -@Suppress("UNUSED_VARIABLE") -kotlin { - if (HOST_OS == OperatingSystem.MACOS_ARM64) { - iosSimulatorArm64("ios") - macosArm64("macos") - } else if (HOST_OS == OperatingSystem.MACOS_X64) { - iosX64("ios") - macosX64("macos") - } - targets.filterIsInstance().forEach { simulatorTargets -> - simulatorTargets.testRuns.forEach { testRun -> - testRun.deviceId = project.findProperty("iosDevice")?.toString() ?: "iPhone 14" - } - } - sourceSets { - val commonMain by getting - val commonTest by getting - if (HOST_OS.isMacOs()) { - val nativeDarwin by creating { - dependsOn(commonMain) - } - val nativeDarwinTest by creating { - dependsOn(commonTest) - // We cannot include this as it will generate duplicates - // e: java.lang.IllegalStateException: IrPropertyPublicSymbolImpl for io.realm.kotlin.test.mongodb.util/TEST_METHODS|-1310682179529671403[0] is already bound: PROPERTY name:TEST_METHODS visibility:public modality:FINAL [val] - // dependsOn(nativeDarwin) - } - val macosMain by getting { dependsOn(nativeDarwin) } - val macosTest by getting { dependsOn(nativeDarwinTest) } - val iosMain by getting { dependsOn(nativeDarwin) } - val iosTest by getting { dependsOn(nativeDarwinTest) } - } - } -} - -// The Device Sync server used by the tests are configured through Gradle properties defined -// in `/packages/gradle.properties` -// - 'syncTestUrl` defines the root URL for the App Services server. Default is `http://localhost:9090` -// - 'syncTestAppNamePrefix' is added a differentiator for all apps created by tests. This makes -// it possible for builds in parallel to run against the same test server. Default is `test-app`. -fun getPropertyValue(propertyName: String): String? { - if (project.hasProperty(propertyName)) { - return project.property(propertyName) as String - } - return System.getenv(propertyName) -} -buildkonfig { - packageName = "io.realm.kotlin.test.mongodb" - objectName = "SyncServerConfig" - defaultConfigs { - buildConfigField(Type.STRING, "url", getPropertyValue("syncTestUrl")) - buildConfigField(Type.STRING, "appPrefix", getPropertyValue("syncTestAppNamePrefix")) - if (project.hasProperty("syncTestLoginEmail") && project.hasProperty("syncTestLoginPassword")) { - buildConfigField(Type.STRING, "email", getPropertyValue("syncTestLoginEmail")) - buildConfigField(Type.STRING, "password", getPropertyValue("syncTestLoginPassword")) - } else { - buildConfigField(Type.STRING, "email", "") - buildConfigField(Type.STRING, "password", "") - } - if (project.hasProperty("syncTestLoginPublicApiKey") && project.hasProperty("syncTestLoginPrivateApiKey")) { - buildConfigField(Type.STRING, "publicApiKey", getPropertyValue("syncTestLoginPublicApiKey")) - buildConfigField(Type.STRING, "privateApiKey", getPropertyValue("syncTestLoginPrivateApiKey")) - } else { - buildConfigField(Type.STRING, "publicApiKey", "") - buildConfigField(Type.STRING, "privateApiKey", "") - } - buildConfigField(Type.STRING, "clusterName", getPropertyValue("syncTestClusterName") ?: "") - buildConfigField(Type.BOOLEAN, "usePlatformNetworking", getPropertyValue("syncUsePlatformNetworking") ?: "false") - } -} - -// Rules for getting Kotlin Native resource test files in place for locating it with the `assetFile` -// configuration. For JVM platforms the files are placed in -// `src/jvmTest/resources`(non-Android JVM) and `src/androidTest/assets` (Android). -kotlin { - targets.filterIsInstance>().forEach { simulatorTargets -> - val target = simulatorTargets.name - val testTaskName = "${target}Test" - val testTask = tasks.findByName(testTaskName) ?: error("Cannot locate test task: '$testTaskName") - val copyTask = tasks.register("${target}TestResources") { - from("src/${testTaskName}/resources") - val parent = testTask.inputs.files.first().parent - into(parent) - } - testTask.let { - it.dependsOn(copyTask) - } - } -} diff --git a/packages/test-sync/gradle.properties b/packages/test-sync/gradle.properties deleted file mode 100644 index 63de1180aa..0000000000 --- a/packages/test-sync/gradle.properties +++ /dev/null @@ -1,16 +0,0 @@ -# -# Copyright 2024 Realm Inc. -# -# 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. -# -kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/packages/test-sync/proguard-rules-test.pro b/packages/test-sync/proguard-rules-test.pro deleted file mode 100644 index cbdb79e7e5..0000000000 --- a/packages/test-sync/proguard-rules-test.pro +++ /dev/null @@ -1,43 +0,0 @@ -## We cannot discard unused symbols for the non-test configurations as it might all symbols not used --dontoptimize --dontshrink - -# io.realm.kotlin.test.mongodb.shared.FlexibleSyncConfigurationTests.toString_nonEmpty, -# SyncConfigTests.unsupportedSchemaTypesThrowException_flexibleSync and -# SyncConfigTests.unsupportedSchemaTypesThrowException_partitionBasedSync verifies exception -# messages with explicit class names in them --keep class io.realm.kotlin.mongodb.internal.SyncConfigurationImpl --keep class io.realm.kotlin.dynamic.DynamicRealmObject - -## Serialization related rules --keepclasseswithmembers class **.*$Companion { - kotlinx.serialization.KSerializer serializer(...); -} -# If a companion has the serializer function, keep the companion field on the original type so that -# the reflective lookup succeeds. --if class **.*$Companion { - kotlinx.serialization.KSerializer serializer(...); -} --keepclassmembers class <1>.<2> { - <1>.<2>$Companion Companion; -} - -## Required to make asserted messages in FunctionTests and BsonEncoder work --keep class io.realm.kotlin.types.MutableRealmInt --keep class io.realm.kotlin.types.RealmUUID --keep class io.realm.kotlin.types.RealmList --keep class org.mongodb.kbson.* { - *; -} - --keep class org.mongodb.kbson.serialization.* { - *; -} - --dontwarn androidx.annotation.experimental.Experimental$Level --dontwarn androidx.annotation.experimental.Experimental --dontwarn java.lang.management.ManagementFactory --dontwarn java.lang.management.RuntimeMXBean --dontwarn org.slf4j.impl.StaticLoggerBinder --dontwarn org.slf4j.impl.StaticMDCBinder --dontwarn org.slf4j.impl.StaticMarkerBinder diff --git a/packages/test-sync/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/mongodb/common b/packages/test-sync/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/mongodb/common deleted file mode 120000 index 754ff9fae7..0000000000 --- a/packages/test-sync/src/androidInstrumentedTest/kotlin/io/realm/kotlin/test/mongodb/common +++ /dev/null @@ -1 +0,0 @@ -./../../../../../../../commonTest/kotlin/io/realm/kotlin/test/mongodb/common \ No newline at end of file diff --git a/packages/test-sync/src/androidMain/AndroidManifest.xml b/packages/test-sync/src/androidMain/AndroidManifest.xml deleted file mode 100644 index e467cc8dad..0000000000 --- a/packages/test-sync/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - diff --git a/packages/test-sync/src/androidMain/assets/asset-fs.realm b/packages/test-sync/src/androidMain/assets/asset-fs.realm deleted file mode 100644 index 5805e4550e..0000000000 Binary files a/packages/test-sync/src/androidMain/assets/asset-fs.realm and /dev/null differ diff --git a/packages/test-sync/src/androidMain/assets/asset-local.realm b/packages/test-sync/src/androidMain/assets/asset-local.realm deleted file mode 100644 index bff506e02d..0000000000 Binary files a/packages/test-sync/src/androidMain/assets/asset-local.realm and /dev/null differ diff --git a/packages/test-sync/src/androidMain/assets/asset-pbs.realm b/packages/test-sync/src/androidMain/assets/asset-pbs.realm deleted file mode 100644 index 2b1e3ad94c..0000000000 Binary files a/packages/test-sync/src/androidMain/assets/asset-pbs.realm and /dev/null differ diff --git a/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt b/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt deleted file mode 100644 index 0f58453a8b..0000000000 --- a/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt +++ /dev/null @@ -1,11 +0,0 @@ -package io.realm.kotlin.test.mongodb - -import android.os.Bundle -import androidx.test.platform.app.InstrumentationRegistry - -actual fun baasTestUrl(): String { - val arguments: Bundle = InstrumentationRegistry.getArguments() - // if the test runner provided an argument for the BAAS URL use it - // Example: adb shell am instrument -w -e baas_url "http"//8.8.8.8:2134" -r io.realm.sync.testapp.test/androidx.test.runner.AndroidJUnitRunner - return arguments.getString("baas_url") ?: SyncServerConfig.url -} diff --git a/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt b/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt deleted file mode 100644 index f6147403d4..0000000000 --- a/packages/test-sync/src/androidMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.sync - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -actual fun getDispatcher(): CoroutineDispatcher = Dispatchers.IO diff --git a/packages/test-sync/src/androidMain/res/xml/network_security_config.xml b/packages/test-sync/src/androidMain/res/xml/network_security_config.xml deleted file mode 100644 index 6407747c60..0000000000 --- a/packages/test-sync/src/androidMain/res/xml/network_security_config.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - localhost - 127.0.0.1 - - diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/BinaryObject.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/BinaryObject.kt deleted file mode 100644 index 9d4271871f..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/BinaryObject.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import kotlin.random.Random - -// const val MAX_BINARY_SIZE = 0xFFFFF8 - 8 /*array header size*/ - -class BinaryObject : RealmObject { - @Suppress("VariableNaming") - @PrimaryKey var _id: String = Random.nextLong().toString() - @Suppress("MagicNumber") - var binary: ByteArray = Random.nextBytes(999999) // don't go crazy with the size or else we risk reaching Atlas' transmission limit -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ChildPk.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ChildPk.kt deleted file mode 100644 index 5196ec87c8..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ChildPk.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.ext.backlinks -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import kotlin.random.Random - -class ChildPk : RealmObject { - @Suppress("VariableNaming") - @PrimaryKey var _id: String = Random.nextLong().toString() - var name: String = "DEFAULT" - var age: Int = 0 - - var link: ChildPk? = null - val linkedFrom: RealmResults by backlinks(ChildPk::link) -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt deleted file mode 100644 index e61d104943..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/MongoDBClientSchemas.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.types.EmbeddedRealmObject -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import kotlinx.serialization.Serializable -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.ObjectId -import kotlin.random.Random - -@Serializable -@Suppress("ConstructorParameterNaming") -class CollectionDataType(var name: String = "Default", @PrimaryKey var _id: Int = Random.nextInt()) : - RealmObject { - constructor() : this("Default") - - // Supplying custom companion object to work around that multiple K2 FIR extension clashes if - // they both generate a Companion. - // https://youtrack.jetbrains.com/issue/KT-62194/K2-Two-compiler-plugins-interference-in-generated-companion-object - companion object -} - -class ParentCollectionDataType : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id: ObjectId = BsonObjectId() - var name: String = "PARENT-DEFAULT" - var child: ChildCollectionDataType? = null - var embeddedChild: EmbeddedChildCollectionDataType? = null - var any: RealmAny? = null -} - -class ChildCollectionDataType : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id: ObjectId = BsonObjectId() - var name: String = "CHILD-DEFAULT" -} - -class EmbeddedChildCollectionDataType : EmbeddedRealmObject { - @Suppress("VariableNaming") - var _id: ObjectId = BsonObjectId() - var name: String = "EMBEDDEDCHILD-DEFAULT" -} - -internal val COLLECTION_SCHEMAS = setOf( - CollectionDataType::class, - ParentCollectionDataType::class, - ChildCollectionDataType::class, - EmbeddedChildCollectionDataType::class, -) diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ObjectIdPk.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ObjectIdPk.kt deleted file mode 100644 index cfc487a424..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ObjectIdPk.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * 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. - * - */ -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.BsonObjectId - -class ObjectIdPk : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id: BsonObjectId = BsonObjectId() - var name: String = "" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ParentPk.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ParentPk.kt deleted file mode 100644 index 0b1bd85f11..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/ParentPk.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey - -class ParentPk : RealmObject { - @Suppress("VariableNaming") - @PrimaryKey var _id: String = "" - var name: String = "N.N." - var child: ChildPk? = null -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt deleted file mode 100644 index 1f4f65129f..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncObjectWithAllTypes.kt +++ /dev/null @@ -1,711 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.ext.realmDictionaryOf -import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.ext.realmSetOf -import io.realm.kotlin.schema.RealmStorageType -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmDictionary -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmSet -import io.realm.kotlin.types.RealmUUID -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.Decimal128 - -private typealias FieldDataFactory = (SyncObjectWithAllTypes) -> Unit -private typealias FieldValidator = (SyncObjectWithAllTypes) -> Unit - -@Suppress("MagicNumber") -class SyncObjectWithAllTypes : RealmObject { - @PrimaryKey - - @Suppress("VariableNaming") - var _id: String = "id-${BsonObjectId()}" - - // Non-nullable types - var stringField: String = "hello world" - var byteField: Byte = 0 - var charField: Char = 0.toChar() - var shortField: Short = 0 - var intField: Int = 0 - var longField: Long = 0 - var booleanField: Boolean = true - var doubleField: Double = 0.0 - var floatField: Float = 0.0.toFloat() - var decimal128Field: Decimal128 = Decimal128("0") - var realmInstantField: RealmInstant = RealmInstant.MIN - var objectIdField: BsonObjectId = BsonObjectId() - var realmUUIDField: RealmUUID = RealmUUID.random() - var binaryField: ByteArray = byteArrayOf(42) - var mutableRealmIntField: MutableRealmInt = MutableRealmInt.create(42) - var objectField: SyncObjectWithAllTypes? = null - - // Nullable types - var stringNullableField: String? = null - var byteNullableField: Byte? = null - var charNullableField: Char? = null - var shortNullableField: Short? = null - var intNullableField: Int? = null - var longNullableField: Long? = null - var booleanNullableField: Boolean? = null - var doubleNullableField: Double? = null - var floatNullableField: Float? = null - var decimal128NullableField: Decimal128? = null - var realmInstantNullableField: RealmInstant? = null - var objectIdNullableField: BsonObjectId? = null - var realmUUIDNullableField: RealmUUID? = null - var binaryNullableField: ByteArray? = null - var objectNullableField: SyncObjectWithAllTypes? = null - var mutableRealmIntNullableField: MutableRealmInt? = null - var nullableRealmAnyField: RealmAny? = null - var nullableRealmAnyForObjectField: RealmAny? = null - - // RealmLists - var stringRealmList: RealmList = realmListOf("hello world") - var byteRealmList: RealmList = realmListOf(0) - var charRealmList: RealmList = realmListOf(0.toChar()) - var shortRealmList: RealmList = realmListOf(0) - var intRealmList: RealmList = realmListOf(0) - var longRealmList: RealmList = realmListOf(0) - var booleanRealmList: RealmList = realmListOf(true) - var doubleRealmList: RealmList = realmListOf(0.0) - var floatRealmList: RealmList = realmListOf(0.0.toFloat()) - var decimal128RealmList: RealmList = realmListOf(Decimal128("0.0")) - var realmInstantRealmList: RealmList = realmListOf(RealmInstant.MIN) - var objectIdRealmList: RealmList = realmListOf(BsonObjectId()) - var realmUUIDRealmList: RealmList = realmListOf(RealmUUID.random()) - var binaryRealmList: RealmList = realmListOf(byteArrayOf(42)) - var objectRealmList: RealmList = realmListOf() - - var nullableRealmAnyRealmList: RealmList = realmListOf(RealmAny.create(42)) - - // Nullable RealmLists of primitive values, not currently supported by Sync - // Nullable Object lists, not currently supported by Core - - // RealmSets - var stringRealmSet: RealmSet = realmSetOf("hello world") - var byteRealmSet: RealmSet = realmSetOf(0) - var charRealmSet: RealmSet = realmSetOf(0.toChar()) - var shortRealmSet: RealmSet = realmSetOf(0) - var intRealmSet: RealmSet = realmSetOf(0) - var longRealmSet: RealmSet = realmSetOf(0) - var booleanRealmSet: RealmSet = realmSetOf(true) - var doubleRealmSet: RealmSet = realmSetOf(0.0) - var floatRealmSet: RealmSet = realmSetOf(0.0.toFloat()) - var decimal128RealmSet: RealmSet = realmSetOf(Decimal128("0.0")) - var realmInstantRealmSet: RealmSet = realmSetOf(RealmInstant.MIN) - var objectIdRealmSet: RealmSet = realmSetOf(BsonObjectId()) - var realmUUIDRealmSet: RealmSet = realmSetOf(RealmUUID.random()) - var binaryRealmSet: RealmSet = realmSetOf(byteArrayOf(42)) - var objectRealmSet: RealmSet = realmSetOf() - - var nullableRealmAnyRealmSet: RealmSet = realmSetOf(RealmAny.create(42)) - - // RealmSets of nullable primitive values, not currently supported by Sync - // RealmSets of nullable objects, not currently supported by Core - - // RealmDictionaries - var stringRealmDictionary: RealmDictionary = realmDictionaryOf("A" to "hello world") - var byteRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0) - var charRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0.toChar()) - var shortRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0) - var intRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0) - var longRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0) - var booleanRealmDictionary: RealmDictionary = realmDictionaryOf("A" to true) - var doubleRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0.0) - var floatRealmDictionary: RealmDictionary = realmDictionaryOf("A" to 0.0.toFloat()) - var decimal128RealmDictionary: RealmDictionary = realmDictionaryOf("A" to Decimal128("0.0")) - var realmInstantRealmDictionary: RealmDictionary = realmDictionaryOf("A" to RealmInstant.MIN) - var objectIdRealmDictionary: RealmDictionary = realmDictionaryOf("A" to BsonObjectId()) - var realmUUIDRealmDictionary: RealmDictionary = realmDictionaryOf("A" to RealmUUID.random()) - var binaryRealmDictionary: RealmDictionary = realmDictionaryOf("A" to byteArrayOf(42)) - - // RealmDictionaries of objects can only be nullable, both for Core and Sync - var nullableObjectRealmDictionary: RealmDictionary = realmDictionaryOf() - var nullableRealmAnyRealmDictionary: RealmDictionary = realmDictionaryOf("A" to RealmAny.create(42)) - - companion object { - - // Mapping between each Core Field type and functions that can insert data for that type - // and also verify the value. This can be used to test objects that has been roundtripped - // through Sync. - private val mapper: Map> = - mutableMapOf>() - .also { map -> - RealmStorageType.values().forEach { type: RealmStorageType -> - map[type] = when (type) { - RealmStorageType.INT -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.mutableRealmIntField = MutableRealmInt.create(42) - obj.mutableRealmIntNullableField = null - obj.intField = 42 - obj.intNullableField = 42 - obj.intRealmList = realmListOf(42) - obj.intRealmSet = realmSetOf(42) - obj.intRealmDictionary = realmDictionaryOf("A" to 42) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(MutableRealmInt.create(42), obj.mutableRealmIntField) - assertEquals(null, obj.mutableRealmIntNullableField) - assertEquals(42, obj.intField) - assertEquals(42, obj.intNullableField) - assertEquals(42, obj.intRealmList.first()) - assertSetContains(42, obj.intRealmSet) - assertEquals(42, obj.intRealmDictionary["A"]) - } - ) - } - RealmStorageType.BOOL -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.booleanField = true - obj.booleanNullableField = true - obj.booleanRealmList = realmListOf(true, false) - obj.booleanRealmSet = realmSetOf(true, false) - obj.booleanRealmDictionary = realmDictionaryOf("A" to true, "B" to false) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(true, obj.booleanField) - assertEquals(true, obj.booleanNullableField) - assertEquals(true, obj.booleanRealmList[0]) - assertEquals(false, obj.booleanRealmList[1]) - assertSetContains(true, obj.booleanRealmSet) - assertSetContains(false, obj.booleanRealmSet) - assertEquals(true, obj.booleanRealmDictionary["A"]) - assertEquals(false, obj.booleanRealmDictionary["B"]) - } - ) - } - RealmStorageType.STRING -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.stringField = "Foo" - obj.stringNullableField = "Bar" - obj.stringRealmList = realmListOf("Foo", "") - obj.stringRealmSet = realmSetOf("Foo", "") - obj.stringRealmDictionary = realmDictionaryOf("A" to "Foo", "B" to "") - }, - { obj: SyncObjectWithAllTypes -> - assertEquals("Foo", obj.stringField) - assertEquals("Bar", obj.stringNullableField) - assertEquals("Foo", obj.stringRealmList[0]) - assertEquals("", obj.stringRealmList[1]) - assertSetContains("Foo", obj.stringRealmSet) - assertSetContains("", obj.stringRealmSet) - assertEquals("Foo", obj.stringRealmDictionary["A"]) - assertEquals("", obj.stringRealmDictionary["B"]) - } - ) - } - RealmStorageType.OBJECT -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.objectField = SyncObjectWithAllTypes().apply { - stringField = "child1" - } - obj.objectNullableField = null - obj.objectRealmList = - realmListOf( - SyncObjectWithAllTypes().apply { - stringField = "child2" - } - ) - obj.objectRealmSet = - realmSetOf( - SyncObjectWithAllTypes().apply { - stringField = "child2" - } - ) - obj.nullableObjectRealmDictionary = - realmDictionaryOf( - "A" to SyncObjectWithAllTypes().apply { - stringField = "child2" - } - ) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals("child1", obj.objectField!!.stringField) - assertEquals(null, obj.objectNullableField) - assertEquals( - "child2", - obj.objectRealmList.first().stringField - ) - assertSetContainsObject("child2", obj.objectRealmSet) - assertEquals( - "child2", - obj.nullableObjectRealmDictionary["A"]?.stringField - ) - } - ) - } - RealmStorageType.FLOAT -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.floatField = 1.23F - obj.floatNullableField = 1.23F - obj.floatRealmList = - realmListOf(1.23F, Float.MIN_VALUE, Float.MAX_VALUE) - obj.floatRealmSet = - realmSetOf(1.23F, Float.MIN_VALUE, Float.MAX_VALUE) - obj.floatRealmDictionary = - realmDictionaryOf( - "A" to 1.23F, - "B" to Float.MIN_VALUE, - "C" to Float.MAX_VALUE - ) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(1.23F, obj.floatField) - assertEquals(1.23F, obj.floatNullableField) - assertEquals(1.23F, obj.floatRealmList[0]) - assertEquals(Float.MIN_VALUE, obj.floatRealmList[1]) - assertEquals(Float.MAX_VALUE, obj.floatRealmList[2]) - assertSetContains(1.23F, obj.floatRealmSet) - assertSetContains(Float.MIN_VALUE, obj.floatRealmSet) - assertSetContains(Float.MAX_VALUE, obj.floatRealmSet) - assertEquals(1.23F, obj.floatRealmDictionary["A"]) - assertEquals(Float.MIN_VALUE, obj.floatRealmDictionary["B"]) - assertEquals(Float.MAX_VALUE, obj.floatRealmDictionary["C"]) - } - ) - } - RealmStorageType.DOUBLE -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.doubleField = 1.234 - obj.doubleNullableField = 1.234 - obj.doubleRealmList = - realmListOf(1.234, Double.MIN_VALUE, Double.MAX_VALUE) - obj.doubleRealmSet = - realmSetOf(1.234, Double.MIN_VALUE, Double.MAX_VALUE) - obj.doubleRealmDictionary = - realmDictionaryOf( - "A" to 1.234, - "B" to Double.MIN_VALUE, - "C" to Double.MAX_VALUE - ) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(1.234, obj.doubleField) - assertEquals(1.234, obj.doubleNullableField) - assertEquals(1.234, obj.doubleRealmList[0]) - assertEquals(Double.MIN_VALUE, obj.doubleRealmList[1]) - assertEquals(Double.MAX_VALUE, obj.doubleRealmList[2]) - assertSetContains(1.234, obj.doubleRealmSet) - assertSetContains(Double.MIN_VALUE, obj.doubleRealmSet) - assertSetContains(Double.MAX_VALUE, obj.doubleRealmSet) - assertEquals(1.234, obj.doubleRealmDictionary["A"]) - assertEquals(Double.MIN_VALUE, obj.doubleRealmDictionary["B"]) - assertEquals(Double.MAX_VALUE, obj.doubleRealmDictionary["C"]) - }, - ) - } - RealmStorageType.DECIMAL128 -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.decimal128Field = Decimal128("1.234") - obj.decimal128NullableField = Decimal128("1.234") - obj.decimal128RealmList = - realmListOf( - Decimal128("1.234"), - Decimal128.NEGATIVE_INFINITY, - Decimal128.POSITIVE_INFINITY - ) - obj.decimal128RealmSet = - realmSetOf( - Decimal128("1.234"), - Decimal128.NEGATIVE_INFINITY, - Decimal128.POSITIVE_INFINITY - ) - obj.decimal128RealmDictionary = - realmDictionaryOf( - "A" to Decimal128("1.234"), - "B" to Decimal128.NEGATIVE_INFINITY, - "C" to Decimal128.POSITIVE_INFINITY - ) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(Decimal128("1.234"), obj.decimal128Field) - assertEquals(Decimal128("1.234"), obj.decimal128NullableField) - assertEquals(Decimal128("1.234"), obj.decimal128RealmList[0]) - assertEquals(Decimal128.NEGATIVE_INFINITY, obj.decimal128RealmList[1]) - assertEquals(Decimal128.POSITIVE_INFINITY, obj.decimal128RealmList[2]) - assertSetContains(Decimal128("1.234"), obj.decimal128RealmSet) - assertSetContains(Decimal128.NEGATIVE_INFINITY, obj.decimal128RealmSet) - assertSetContains(Decimal128.POSITIVE_INFINITY, obj.decimal128RealmSet) - assertEquals(Decimal128("1.234"), obj.decimal128RealmDictionary["A"]) - assertEquals(Decimal128.NEGATIVE_INFINITY, obj.decimal128RealmDictionary["B"]) - assertEquals(Decimal128.POSITIVE_INFINITY, obj.decimal128RealmDictionary["C"]) - }, - ) - } - RealmStorageType.TIMESTAMP -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.realmInstantField = RealmInstant.from(1, 1) - obj.realmInstantNullableField = - RealmInstant.from(-1, -1) - obj.realmInstantRealmList = - realmListOf(RealmInstant.MIN, RealmInstant.MAX) - obj.realmInstantRealmSet = - realmSetOf(RealmInstant.MIN, RealmInstant.MAX) - obj.realmInstantRealmDictionary = realmDictionaryOf( - "A" to RealmInstant.MIN, - "B" to RealmInstant.MAX - ) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals( - RealmInstant.from(1, 1), - obj.realmInstantField - ) - assertEquals( - RealmInstant.from(-1, -1), - obj.realmInstantNullableField - ) - assertEquals(RealmInstant.MIN, obj.realmInstantRealmList[0]) - assertEquals(RealmInstant.MAX, obj.realmInstantRealmList[1]) - assertSetContains( - RealmInstant.MIN, - obj.realmInstantRealmSet - ) - assertSetContains( - RealmInstant.MAX, - obj.realmInstantRealmSet - ) - assertEquals(RealmInstant.MIN, obj.realmInstantRealmDictionary["A"]) - assertEquals(RealmInstant.MAX, obj.realmInstantRealmDictionary["B"]) - }, - ) - } - RealmStorageType.OBJECT_ID -> { - val minObjId = BsonObjectId("000000000000000000000000") - val maxObjId = BsonObjectId("ffffffffffffffffffffffff") - val randomObjId = BsonObjectId("503f1f77bcf86cd793439011") - Pair( - { obj: SyncObjectWithAllTypes -> - obj.objectIdField = randomObjId - obj.objectIdNullableField = randomObjId - obj.objectIdRealmList = realmListOf(minObjId, maxObjId) - obj.objectIdRealmSet = realmSetOf(minObjId, maxObjId) - obj.objectIdRealmDictionary = - realmDictionaryOf("A" to minObjId, "B" to maxObjId) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(randomObjId, obj.objectIdField) - assertEquals(randomObjId, obj.objectIdNullableField) - assertEquals(minObjId, obj.objectIdRealmList[0]) - assertEquals(maxObjId, obj.objectIdRealmList[1]) - assertSetContains(minObjId, obj.objectIdRealmSet) - assertSetContains(maxObjId, obj.objectIdRealmSet) - assertEquals(minObjId, obj.objectIdRealmDictionary["A"]) - assertEquals(maxObjId, obj.objectIdRealmDictionary["B"]) - }, - ) - } - RealmStorageType.UUID -> { - val uuid1 = RealmUUID.random() - val uuid2 = RealmUUID.random() - val uuid3 = RealmUUID.random() - Pair( - { obj: SyncObjectWithAllTypes -> - obj.realmUUIDField = uuid1 - obj.realmUUIDNullableField = uuid1 - obj.realmUUIDRealmList = realmListOf(uuid2, uuid3) - obj.realmUUIDRealmSet = realmSetOf(uuid2, uuid3) - obj.realmUUIDRealmDictionary = - realmDictionaryOf("A" to uuid2, "B" to uuid3) - }, - { obj: SyncObjectWithAllTypes -> - assertEquals(uuid1, obj.realmUUIDField) - assertEquals(uuid1, obj.realmUUIDNullableField) - assertEquals(uuid2, obj.realmUUIDRealmList[0]) - assertEquals(uuid3, obj.realmUUIDRealmList[1]) - assertSetContains(uuid2, obj.realmUUIDRealmSet) - assertSetContains(uuid3, obj.realmUUIDRealmSet) - assertEquals(uuid2, obj.realmUUIDRealmDictionary["A"]) - assertEquals(uuid3, obj.realmUUIDRealmDictionary["B"]) - }, - ) - } - RealmStorageType.BINARY -> { - Pair( - { obj: SyncObjectWithAllTypes -> - obj.binaryField = byteArrayOf(22) - obj.binaryNullableField = byteArrayOf(22) - obj.binaryRealmList = realmListOf( - byteArrayOf(22), - byteArrayOf(44, 66), - byteArrayOf(11, 33) - ) - obj.binaryRealmSet = realmSetOf( - byteArrayOf(22), - byteArrayOf(44, 66), - byteArrayOf(11, 33) - ) - obj.binaryRealmDictionary = realmDictionaryOf( - "A" to byteArrayOf(22), - "B" to byteArrayOf(44, 66), - "C" to byteArrayOf(11, 33) - ) - }, - { obj: SyncObjectWithAllTypes -> - assertContentEquals( - byteArrayOf(22), - obj.binaryField - ) - assertContentEquals( - byteArrayOf(22), - obj.binaryNullableField - ) - assertContentEquals( - byteArrayOf(22), - obj.binaryRealmList[0] - ) - assertContentEquals( - byteArrayOf(44, 66), - obj.binaryRealmList[1] - ) - assertContentEquals( - byteArrayOf(11, 33), - obj.binaryRealmList[2] - ) - assertSetContainsBinary( - byteArrayOf(22), - obj.binaryRealmSet - ) - assertSetContainsBinary( - byteArrayOf(44, 66), - obj.binaryRealmSet - ) - assertSetContainsBinary( - byteArrayOf(11, 33), - obj.binaryRealmSet - ) - assertContentEquals( - byteArrayOf(22), - obj.binaryRealmDictionary["A"] - ) - assertContentEquals( - byteArrayOf(44, 66), - obj.binaryRealmDictionary["B"] - ) - assertContentEquals( - byteArrayOf(11, 33), - obj.binaryRealmDictionary["C"] - ) - }, - ) - } - RealmStorageType.ANY -> { - val realmAnyValues = listOf( - RealmAny.create(42), - RealmAny.create("hello"), - RealmAny.create( - SyncObjectWithAllTypes().apply { - stringField = "Custom" - } - ) - ) - // Don't reuse the same object in collections as we would be saving the same PK - val realmAnyListValues = listOf( - realmAnyValues[0], - realmAnyValues[1], - RealmAny.create( - SyncObjectWithAllTypes().apply { - stringField = "List_element" - } - ) - ) - val realmAnySetValues = listOf( - realmAnyValues[0], - realmAnyValues[1], - RealmAny.create( - SyncObjectWithAllTypes().apply { - stringField = "Set_element" - } - ) - ) - val realmAnyDictionaryValues = listOf( - realmAnyValues[0], - realmAnyValues[1], - RealmAny.create( - SyncObjectWithAllTypes().apply { - stringField = "Dictionary_element" - } - ) - ) - Pair( - { obj: SyncObjectWithAllTypes -> - obj.nullableRealmAnyField = realmAnyValues[0] - obj.nullableRealmAnyForObjectField = realmAnyValues[2] - obj.nullableRealmAnyRealmList = realmListOf( - realmAnyListValues[0], - realmAnyListValues[1], - realmAnyListValues[2], - null - ) - obj.nullableRealmAnyRealmSet = realmSetOf( - realmAnySetValues[0], - realmAnySetValues[1], - realmAnySetValues[2], - null - ) - obj.nullableRealmAnyRealmDictionary = realmDictionaryOf( - "A" to realmAnyDictionaryValues[0], - "B" to realmAnyDictionaryValues[1], - "C" to realmAnyDictionaryValues[2], - "D" to null - ) - }, - { obj: SyncObjectWithAllTypes -> - // Check RealmAny containing an object - assertEquals( - realmAnyValues[2].asRealmObject().stringField, - obj.nullableRealmAnyForObjectField?.asRealmObject()?.stringField - ) - - // Check RealmAny field containing a primitive - assertEquals(realmAnyValues[0], obj.nullableRealmAnyField) - - // Check list of RealmAny values - assertEquals(realmAnyListValues[0], obj.nullableRealmAnyRealmList[0]) - assertEquals(realmAnyListValues[1], obj.nullableRealmAnyRealmList[1]) - assertEquals( - realmAnyListValues[2].asRealmObject().stringField, - obj.nullableRealmAnyRealmList[2]?.asRealmObject()?.stringField - ) - assertEquals(null, obj.nullableRealmAnyRealmList[3]) - - // Check set of RealmAny values - assertSetContains(realmAnySetValues[0], obj.nullableRealmAnyRealmSet) - assertSetContains(realmAnySetValues[1], obj.nullableRealmAnyRealmSet) - assertSetContains(null, obj.nullableRealmAnyRealmSet) - - // Extremely irritating to check this since none of the helpers are useful - obj.nullableRealmAnyRealmSet - .first { - it?.type == RealmAny.Type.OBJECT - }.also { - val expected = realmAnySetValues[2].asRealmObject().stringField - val actual = it?.asRealmObject()?.stringField - assertEquals(expected, actual) - } - - // Check dictionary of RealmAny values - assertEquals(realmAnyDictionaryValues[0], obj.nullableRealmAnyRealmDictionary["A"]) - assertEquals(realmAnyDictionaryValues[1], obj.nullableRealmAnyRealmDictionary["B"]) - assertEquals( - realmAnyDictionaryValues[2].asRealmObject().stringField, - obj.nullableRealmAnyRealmDictionary["C"]?.asRealmObject()?.stringField - ) - assertEquals(null, obj.nullableRealmAnyRealmDictionary["D"]) - }, - ) - } - else -> TODO("Missing support for type: $type") - } - } - } - - private fun assertEquals(value: Any?, other: Any?) { - if (value != other) { - throw IllegalStateException("Values do not match: '$value' vs. '$other'") - } - } - - private fun assertSetContains(value: Any?, set: RealmSet<*>) { - if (!set.contains(value)) { - throw IllegalStateException("Set doesn't contain value $value") - } - } - - // Sets don't expose indices so we need to iterate them - private fun assertSetContainsObject(value: String, set: RealmSet) { - var found = false - val iterator = set.iterator() - while (iterator.hasNext()) { - val obj = iterator.next() - if (obj.stringField == value) { - found = true - } - } - if (!found) { - throw IllegalStateException("Set doesn't contain object with 'stringField' value '$value'") - } - } - - // Similarly we need to iterate over the set and see if the binary contests are the same - private fun assertSetContainsBinary(value: ByteArray, set: RealmSet) { - val iterator = set.iterator() - var found = false - while (iterator.hasNext()) { - val byteArray = iterator.next() - if (value.contentEquals(byteArray)) { - found = true - } - } - if (!found) { - throw IllegalStateException("Set does not contain ByteArray $value") - } - } - - private fun assertContentEquals(value: ByteArray?, other: ByteArray?) { - value?.forEachIndexed { index, byte -> - val actual = other?.get(index) - if (byte != actual) { - throw IllegalStateException("Values do not match: '$byte' vs. '$actual'") - } - } - } - - /** - * Create an object with sample data for all supported Core field types. - */ - fun createWithSampleData(primaryKey: String): SyncObjectWithAllTypes { - return SyncObjectWithAllTypes().also { obj -> - obj._id = primaryKey - RealmStorageType.values().forEach { type: RealmStorageType -> - val dataFactory: FieldDataFactory = mapper[type]!!.first - dataFactory(obj) - } - } - } - - /** - * Validate that the incoming object has all the expected sample data - * - * @return `true` if the object matches. - * @throws IllegalStateException if the comparison failed. - */ - fun compareAgainstSampleData(obj: SyncObjectWithAllTypes): Boolean { - RealmStorageType.values().forEach { type: RealmStorageType -> - val dataValidator: FieldValidator = mapper[type]!!.second - dataValidator(obj) - } - return true - } - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncPerson.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncPerson.kt deleted file mode 100644 index 9a55c28d4f..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncPerson.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.BsonObjectId - -class SyncPerson : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id: BsonObjectId? = BsonObjectId() - var age: Long = 0 - var firstName: String = "" - var lastName: String = "" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncRestaurant.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncRestaurant.kt deleted file mode 100644 index 2cbbf89418..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/SyncRestaurant.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.entities.sync - -import io.realm.kotlin.entities.Location -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.ObjectId - -class SyncRestaurant : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id = ObjectId() - var section: ObjectId? = null - var location: Location? = null -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/bogus/ChildPk.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/bogus/ChildPk.kt deleted file mode 100644 index fff0ce56ca..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/bogus/ChildPk.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync.bogus - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey - -/** - * Represents the same entity as [io.realm.kotlin.entities.sync.ChildPk] but with a different `name` in - * order to trigger an invalid schema error in Sync. - */ -class ChildPk : RealmObject { - @Suppress("VariableNaming") - @PrimaryKey var _id: String = "" - @Suppress("MagicNumber") - var name: Int = 42 // Triggers a sync error since this field is a String in the original entity -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexChildObject.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexChildObject.kt deleted file mode 100644 index 6d35ba2ad5..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexChildObject.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync.flx - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.BsonObjectId - -/** - * Object used when testing Flexible Sync. - */ -class FlexChildObject : RealmObject { - @PrimaryKey - @Suppress("VariableNaming") - var _id: BsonObjectId = BsonObjectId() - var section: Int = 0 - var name: String = "" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexEmbeddedObject.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexEmbeddedObject.kt deleted file mode 100644 index 56207defee..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexEmbeddedObject.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync.flx - -import io.realm.kotlin.types.EmbeddedRealmObject - -/** - * Embedded Object used when testing Flexible Sync. - */ -class FlexEmbeddedObject : EmbeddedRealmObject { - var embeddedName: String = "" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexParentObject.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexParentObject.kt deleted file mode 100644 index 93c390e32b..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/flx/FlexParentObject.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync.flx - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.ObjectId - -/** - * Object used when testing Flexible Sync. - */ -class FlexParentObject() : RealmObject { - constructor(section: Int) : this() { - this.section = section - } - @PrimaryKey - @Suppress("VariableNaming") - var _id: ObjectId = ObjectId() - var section: Int = 0 - var name: String = "" - @Suppress("MagicNumber") - var age: Int = 42 - var child: FlexChildObject? = null - var embedded: FlexEmbeddedObject? = null -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/subset/ChildPk.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/subset/ChildPk.kt deleted file mode 100644 index 576a5a06cf..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/entities/sync/subset/ChildPk.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.entities.sync.subset - -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PersistedName -import io.realm.kotlin.types.annotations.PrimaryKey -import kotlin.random.Random - -/** - * Copy of io.realm.kotlin.entities.sync.ChildPk, but it only contains a subset of its properties - */ -class ChildPk : RealmObject { - @PersistedName("_id") - @PrimaryKey var id: String = Random.nextLong().toString() - var name: String = "DEFAULT" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt deleted file mode 100644 index 079ab3b377..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/TestApp.kt +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -@file:Suppress("invisible_member", "invisible_reference") -@file:OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) - -package io.realm.kotlin.test.mongodb - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.interop.SynchronizableObject -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.platform.singleThreadDispatcher -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.test.mongodb.util.AppAdmin -import io.realm.kotlin.test.mongodb.util.AppAdminImpl -import io.realm.kotlin.test.mongodb.util.AppInitializer -import io.realm.kotlin.test.mongodb.util.AppServicesClient -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestHelper -import kotlinx.coroutines.CloseableCoroutineDispatcher -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson - -val TEST_APP_PARTITION = syncServerAppName("pbs") // With Partition-based Sync -val TEST_APP_FLEX = syncServerAppName("flx") // With Flexible Sync -val TEST_APP_CLUSTER_NAME = SyncServerConfig.clusterName - -val TEST_SERVER_BASE_URL = baasTestUrl() -const val DEFAULT_PASSWORD = "password1234" - -expect fun baasTestUrl(): String - -// Expose a try-with-resource pattern for Test Apps -inline fun App.use(action: (App) -> Unit) { - try { - action(this) - } finally { - this.close() - } -} - -/** - * This class merges the classes [App] and [AppAdmin] making it easier to create an App that can be - * used for testing. - * - * @param app gives access to the [App] class delegate for testing purposes - * @param debug enable trace of command server and rest api calls in the test app. - */ -open class TestApp private constructor( - private val dispatcher: CoroutineDispatcher, - pairAdminApp: Pair -) : App by pairAdminApp.first, AppAdmin by pairAdminApp.second { - - var mutex = SynchronizableObject() - var isClosed: Boolean = false - val app: App = pairAdminApp.first - - /** - * Creates an [App] with the given configuration parameters. - * - * @param logLevel log level used to prime the AppConfiguration.Builder. - * @param builder the builder used to build the final app. The builder is already primed with the - * default test app configuration, but can be used to override the defaults and add additional - * options. - * @param debug enable trace of command server and rest api calls in the test app. - **/ - @Suppress("LongParameterList") - @OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalCoroutinesApi::class) - constructor( - testId: String?, - appInitializer: AppInitializer, - dispatcher: CoroutineDispatcher = singleThreadDispatcher("$testId-dispatcher"), - builder: (AppConfiguration.Builder) -> AppConfiguration.Builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("${appInitializer.name}-$testId")) - }, - debug: Boolean = false, - networkTransport: NetworkTransport? = null, - ejson: EJson = EJson, - ) : this( - dispatcher, - build( - debug = debug, - appInitializer = appInitializer, - dispatcher = dispatcher, - builder = builder, - networkTransport = networkTransport, - ejson = ejson, - ) - ) - - fun createUserAndLogin(): User = runBlocking { - val (email, password) = TestHelper.randomEmail() to "password1234" - emailPasswordAuth.registerUser(email, password).run { - logIn(email, password) - } - } - - override fun close() { - mutex.withLock { - if (isClosed) { - return - } - - app.sync.waitForSessionsToTerminate() - - // This is needed to "properly reset" all sessions across tests since deleting users - // directly using the REST API doesn't do the trick - runBlocking { - try { - while (currentUser != null) { - (currentUser as User).logOut() - } - deleteAllUsers() - } catch (ex: Exception) { - // Some tests might render the server inaccessible, preventing us from - // deleting users. Assume those tests know what they are doing and - // ignore errors here. - (configuration as AppConfigurationImpl).logger.warn("Server side users could not be deleted: $ex") - } - } - - app.close() - - // Tearing down the SyncSession still relies on the the event loop (powered by the coroutines) of the platform networking - // to post Function Handler, so we need to close it after we close the App - @OptIn(ExperimentalCoroutinesApi::class) - if (dispatcher is CloseableCoroutineDispatcher) { - dispatcher.close() - } - - // Close network client resources - closeClient() - - // Make sure to clear cached apps before deleting files - RealmInterop.realm_clear_cached_apps() - - // Delete metadata Realm files - PlatformUtils.deleteTempDir("${configuration.syncRootDirectory}/mongodb-realm") - isClosed = true - } - } - - companion object { - - @Suppress("LongParameterList") - fun build( - debug: Boolean, - appInitializer: AppInitializer, - dispatcher: CoroutineDispatcher, - builder: (AppConfiguration.Builder) -> AppConfiguration.Builder, - networkTransport: NetworkTransport?, - ejson: EJson, - ): Pair { - val appAdmin: AppAdmin = runBlocking(dispatcher) { - AppServicesClient.build( - baseUrl = TEST_SERVER_BASE_URL, - debug = debug, - dispatcher = dispatcher - ).run { - val baasApp = getOrCreateApp(appInitializer) - - AppAdminImpl(this, baasApp) - } - } - - @Suppress("invisible_member", "invisible_reference") - val config = AppConfiguration.Builder(appAdmin.clientAppId) - .baseUrl(TEST_SERVER_BASE_URL) - .networkTransport(networkTransport) - .ejson(ejson) - .apply { - if (SyncServerConfig.usePlatformNetworking) { - usePlatformNetworking() - } - } - - val app = App.create( - builder(config) - .dispatcher(dispatcher) - .build() - ) - - return Pair(app, appAdmin) - } - } -} - -val App.asTestApp: TestApp - get() = this as TestApp - -suspend fun App.createUserAndLogIn( - email: String = TestHelper.randomEmail(), - password: String = DEFAULT_PASSWORD -): User { - return this.emailPasswordAuth.registerUser(email, password).run { - logIn(email, password) - } -} - -suspend fun App.logIn(email: String, password: String): User = - this.login(Credentials.emailPassword(email, password)) - -fun syncServerAppName(appNameSuffix: String): String { - return "${SyncServerConfig.appPrefix}-$appNameSuffix" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSchemas.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSchemas.kt deleted file mode 100644 index 236f77d9a6..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSchemas.kt +++ /dev/null @@ -1,63 +0,0 @@ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.types.AsymmetricRealmObject -import io.realm.kotlin.types.EmbeddedRealmObject -import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PersistedName -import io.realm.kotlin.types.annotations.PrimaryKey -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.ObjectId - -class DeviceParent : RealmObject { - @PersistedName("_id") - @PrimaryKey - var id: ObjectId = BsonObjectId() - var device: Device? = null -} - -class Measurement : AsymmetricRealmObject { - @PersistedName("_id") - @PrimaryKey - var id: ObjectId = BsonObjectId() - var type: String = "temperature" - var value: Float = 0.0f - var device: Device? = null - var backups: RealmList = realmListOf() -} - -class BackupDevice() : EmbeddedRealmObject { - constructor(name: String, serialNumber: String) : this() { - this.name = name - this.serialNumber = serialNumber - } - var name: String = "" - var serialNumber: String = "" -} - -class Device() : EmbeddedRealmObject { - constructor(name: String, serialNumber: String) : this() { - this.name = name - this.serialNumber = serialNumber - } - var name: String = "" - var serialNumber: String = "" - var backupDevice: BackupDevice? = null -} - -class AsymmetricA : AsymmetricRealmObject { - @PrimaryKey - var _id: ObjectId = ObjectId() - var child: EmbeddedB? = null -} - -class EmbeddedB : EmbeddedRealmObject { - var child: StandardC? = null -} - -class StandardC : RealmObject { - @PrimaryKey - var _id: ObjectId = ObjectId() - var name: String = "" -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/Schema.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/Schema.kt deleted file mode 100644 index d95a465feb..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/common/Schema.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.entities.JsonStyleRealmObject -import io.realm.kotlin.entities.Location -import io.realm.kotlin.entities.sync.BinaryObject -import io.realm.kotlin.entities.sync.COLLECTION_SCHEMAS -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ObjectIdPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.entities.sync.SyncPerson -import io.realm.kotlin.entities.sync.SyncRestaurant -import io.realm.kotlin.entities.sync.flx.FlexChildObject -import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject - -private val ASYMMETRIC_SCHEMAS = setOf( - AsymmetricA::class, - EmbeddedB::class, - StandardC::class, - Measurement::class, -) -private val DEFAULT_SCHEMAS = setOf( - BackupDevice::class, - BinaryObject::class, - ChildPk::class, - Device::class, - DeviceParent::class, - FlexChildObject::class, - FlexEmbeddedObject::class, - FlexParentObject::class, - ObjectIdPk::class, - ParentPk::class, - SyncObjectWithAllTypes::class, - SyncPerson::class, - SyncRestaurant::class, - Location::class, - JsonStyleRealmObject::class, -) - -val PARTITION_BASED_SCHEMA = DEFAULT_SCHEMAS -// Amount of schema classes that should be created on the server. EmbeddedRealmObjects and -// AsymmetricRealmObjects are not included in this count. -// Run FlexibleSyncSchemaTests.flexibleSyncSchemaCount for verification. -const val FLEXIBLE_SYNC_SCHEMA_COUNT = 15 -val FLEXIBLE_SYNC_SCHEMA = DEFAULT_SCHEMAS + ASYMMETRIC_SCHEMAS + COLLECTION_SCHEMAS diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt deleted file mode 100644 index a3a9054fa3..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.sync - -import kotlinx.coroutines.CoroutineDispatcher - -expect fun getDispatcher(): CoroutineDispatcher diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt deleted file mode 100644 index e15939520e..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppAdmin.kt +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.util - -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.mongodb.sync.SyncSession -import kotlinx.serialization.json.JsonObject - -/** - * Wrapper around App Services Server Admin functions needed for tests. - */ -interface AppAdmin { - val clientAppId: String - - /** - * Deletes all currently registered and pending users on the App Services Application . - * - * Warning: This will run using `runBlocking`. - */ - suspend fun deleteAllUsers() - - /** - * Deactivates Sync on the server. This will not cause existing sessions to fail, - * they will instead attempt to reconnect later. - */ - suspend fun pauseSync() - - /** - * Activates Sync on the server. - */ - suspend fun startSync() - - /** - * Trigger a client reset by deleting user-related files in the server. - */ - suspend fun triggerClientReset(userId: String) - suspend fun triggerClientReset( - syncMode: SyncMode, - session: SyncSession, - userId: String, - block: (suspend () -> Unit)? = null - ) - - /** - * Changes the permissions for sync. Receives a lambda block which with your test logic. - * It will safely revert to the original permissions even when an exception was thrown. - */ - suspend fun changeSyncPermissions(permissions: SyncPermissions, block: () -> Unit) - - /** - * Set whether or not automatic confirmation is enabled. - */ - suspend fun setAutomaticConfirmation(enabled: Boolean) - - /** - * Set whether or not custom confirmation is enabled. - */ - suspend fun setCustomConfirmation(enabled: Boolean) - - /** - * Set whether or not using a reset function is available. - */ - suspend fun setResetFunction(enabled: Boolean) - - /** - * Return the JSON configuration for the Email/Password auth provider. - */ - suspend fun getAuthConfigData(): String - - /** - * Insert a MongoDB document which will be eventually synced as RealmObject. - */ - suspend fun insertDocument(clazz: String, json: String): JsonObject? - - /** - * Query the specified database and collection. - */ - suspend fun queryDocument(clazz: String, query: String): JsonObject? - - /** - * Count the number of documents of a given type. - */ - suspend fun countDocuments(clazz: String): Int - - /** - * Delete documents of a given type. - */ - suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? - - fun closeClient() -} - -class AppAdminImpl( - private val baasClient: AppServicesClient, - val app: BaasApp -) : AppAdmin { - override val clientAppId: String - get() = app.clientAppId - - override suspend fun deleteAllUsers() { - baasClient.run { - app.deleteAllUsers() - } - } - - override suspend fun pauseSync() { - baasClient.run { - app.pauseSync() - } - } - - override suspend fun startSync() { - baasClient.run { - app.startSync() - } - } - - override suspend fun triggerClientReset(userId: String) { - baasClient.run { - app.triggerClientReset(userId) - } - } - - override suspend fun triggerClientReset( - syncMode: SyncMode, - session: SyncSession, - userId: String, - block: (suspend () -> Unit)? - ) { - baasClient.run { - try { - session.pause() - app.triggerClientReset(syncMode, userId) - block?.invoke() - } finally { - session.resume() - } - } - } - - override suspend fun changeSyncPermissions(permissions: SyncPermissions, block: () -> Unit) { - baasClient.run { - app.changeSyncPermissions(permissions, block) - } - } - - override suspend fun setAutomaticConfirmation(enabled: Boolean) { - baasClient.run { - app.setAutomaticConfirmation(enabled) - } - } - - override suspend fun setCustomConfirmation(enabled: Boolean) { - baasClient.run { - app.setCustomConfirmation(enabled) - } - } - - override suspend fun setResetFunction(enabled: Boolean) { - baasClient.run { - app.setResetFunction(enabled) - } - } - - override suspend fun getAuthConfigData(): String = - baasClient.run { - app.getAuthConfigData() - } - - override suspend fun insertDocument(clazz: String, json: String): JsonObject? = - baasClient.run { - app.insertDocument(clazz, json) - } - - override suspend fun queryDocument(clazz: String, query: String): JsonObject? = - baasClient.run { - app.queryDocument(clazz, query) - } - - override suspend fun countDocuments(clazz: String): Int = - baasClient.run { - app.countDocuments(clazz) - } - - override suspend fun deleteDocuments(database: String, clazz: String, query: String): JsonObject? = - baasClient.run { - app.deleteDocument(database, clazz, query) - } - - override fun closeClient() { - baasClient.closeClient() - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt deleted file mode 100644 index 8f151ab5a9..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/AppServicesClient.kt +++ /dev/null @@ -1,992 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.util - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.defaultRequest -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.client.request.HttpRequestBuilder -import io.ktor.client.request.delete -import io.ktor.client.request.headers -import io.ktor.client.request.request -import io.ktor.client.request.setBody -import io.ktor.client.statement.HttpResponse -import io.ktor.client.statement.bodyAsText -import io.ktor.http.ContentType -import io.ktor.http.HttpMethod -import io.ktor.http.HttpMethod.Companion.Get -import io.ktor.http.HttpMethod.Companion.Post -import io.ktor.http.contentType -import io.ktor.http.isSuccess -import io.ktor.serialization.kotlinx.json.json -import io.realm.kotlin.internal.interop.PropertyType -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.schema.RealmClassImpl -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.schema.RealmClassKind -import io.realm.kotlin.test.mongodb.SyncServerConfig -import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.types.BaseRealmObject -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.delay -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient -import kotlinx.serialization.builtins.ListSerializer -import kotlinx.serialization.json.ClassDiscriminatorMode -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonElement -import kotlinx.serialization.json.JsonNull -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.add -import kotlinx.serialization.json.boolean -import kotlinx.serialization.json.buildJsonArray -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.int -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.put -import kotlinx.serialization.serializer -import kotlin.reflect.KClass -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -private const val ADMIN_PATH = "/api/admin/v3.0" -private const val PRIVATE_PATH = "/api/private/v1.0" - -data class SyncPermissions( - val read: Boolean, - val write: Boolean -) - -@OptIn(ExperimentalSerializationApi::class) -private val json = Json { - classDiscriminatorMode = ClassDiscriminatorMode.NONE - encodeDefaults = true -} - -@Serializable -data class Schema( - val metadata: SchemaMetadata = SchemaMetadata( - database = "database", - collection = "title" - ), - val schema: SchemaData, - val relationships: Map = emptyMap(), -) { - constructor( - database: String, - schema: SchemaData, - relationships: Map, - ) : this( - metadata = SchemaMetadata( - database = database, - collection = schema.title - ), - schema = schema, - relationships = relationships - ) -} - -@Serializable -data class SchemaMetadata( - var database: String = "", - @SerialName("data_source") - var dataSource: String = "BackingDB", - var collection: String = "SyncDog", -) - -@Serializable -data class SchemaRelationship( - @SerialName("source_key") - val sourceKey: String, - @SerialName("foreign_key") - val foreignKey: String, - @SerialName("is_list") - val isList: Boolean, - val ref: String = "", -) { - constructor( - target: String, - database: String, - sourceKey: String, - foreignKey: String, - isList: Boolean, - ) : this( - sourceKey = sourceKey, - foreignKey = foreignKey, - isList = isList, - ref = "#/relationship/BackingDB/$database/$target" - ) -} - -@Serializable -sealed interface SchemaPropertyType { - @Transient val isRequired: Boolean -} - -@Serializable -class ObjectReferenceType( - @Transient val sourceKey: String = "", - @Transient val targetKey: String = "", - @Transient val target: String = "", - @Transient val isList: Boolean = false, - val bsonType: PrimitivePropertyType.Type, -) : SchemaPropertyType { - constructor(sourceKey: String, targetSchema: RealmClassImpl, isCollection: Boolean) : this( - sourceKey = sourceKey, - targetKey = targetSchema.cinteropClass.primaryKey, - target = targetSchema.name, - bsonType = targetSchema.cinteropProperties - .first { it.name == targetSchema.cinteropClass.primaryKey } - .type - .toSchemaType(), - isList = isCollection - ) - - @Transient - override val isRequired: Boolean = false -} - -@Serializable -data class SchemaData( - var title: String = "", - var properties: Map = mutableMapOf(), - val required: List = mutableListOf(), - @Transient val kind: RealmClassKind = RealmClassKind.STANDARD, - val type: PrimitivePropertyType.Type = PrimitivePropertyType.Type.OBJECT, -) : SchemaPropertyType { - @Transient - override val isRequired: Boolean = false -} - -@Serializable -data class CollectionPropertyType( - val items: SchemaPropertyType, - val uniqueItems: Boolean = false, -) : SchemaPropertyType { - val bsonType = PrimitivePropertyType.Type.ARRAY - @Transient - override val isRequired: Boolean = false -} - -@Serializable -data class MapPropertyType( - val additionalProperties: SchemaPropertyType, -) : SchemaPropertyType { - val bsonType = PrimitivePropertyType.Type.OBJECT - @Transient - override val isRequired: Boolean = false -} - -@Serializable -open class PrimitivePropertyType( - val bsonType: Type, - @Transient override val isRequired: Boolean = false, -) : SchemaPropertyType { - - enum class Type { - @SerialName("string") - STRING, - - @SerialName("object") - OBJECT, - - @SerialName("array") - ARRAY, - - @SerialName("objectId") - OBJECT_ID, - - @SerialName("boolean") - BOOLEAN, - - @SerialName("bool") - BOOL, - - @SerialName("null") - NULL, - - @SerialName("regex") - REGEX, - - @SerialName("date") - DATE, - - @SerialName("timestamp") - TIMESTAMP, - - @SerialName("int") - INT, - - @SerialName("long") - LONG, - - @SerialName("decimal") - DECIMAL, - - @SerialName("double") - DOUBLE, - - @SerialName("number") - NUMBER, - - @SerialName("binData") - BIN_DATA, - - @SerialName("uuid") - UUID, - - @SerialName("mixed") - MIXED, - - @SerialName("float") - FLOAT; - } -} - -fun PropertyType.toSchemaType() = - when (this) { - PropertyType.RLM_PROPERTY_TYPE_BOOL -> PrimitivePropertyType.Type.BOOL - PropertyType.RLM_PROPERTY_TYPE_INT -> PrimitivePropertyType.Type.INT - PropertyType.RLM_PROPERTY_TYPE_STRING -> PrimitivePropertyType.Type.STRING - PropertyType.RLM_PROPERTY_TYPE_BINARY -> PrimitivePropertyType.Type.BIN_DATA - PropertyType.RLM_PROPERTY_TYPE_OBJECT -> PrimitivePropertyType.Type.OBJECT - PropertyType.RLM_PROPERTY_TYPE_FLOAT -> PrimitivePropertyType.Type.FLOAT - PropertyType.RLM_PROPERTY_TYPE_DOUBLE -> PrimitivePropertyType.Type.DOUBLE - PropertyType.RLM_PROPERTY_TYPE_DECIMAL128 -> PrimitivePropertyType.Type.DECIMAL - PropertyType.RLM_PROPERTY_TYPE_TIMESTAMP -> PrimitivePropertyType.Type.DATE - PropertyType.RLM_PROPERTY_TYPE_OBJECT_ID -> PrimitivePropertyType.Type.OBJECT_ID - PropertyType.RLM_PROPERTY_TYPE_UUID -> PrimitivePropertyType.Type.UUID - PropertyType.RLM_PROPERTY_TYPE_MIXED -> PrimitivePropertyType.Type.MIXED - else -> throw IllegalArgumentException("Unsupported type") - } - -@Serializable -data class LoginResponse(val access_token: String) - -@Serializable -data class Profile(val roles: List) - -@Serializable -data class Role(val role_name: String, val group_id: String? = null) - -@Serializable -data class AuthProvider constructor( - val _id: String, - val type: String, - val disabled: Boolean = false, - @Transient val app: BaasApp? = null -) - -@Serializable -data class Service( - val _id: String, - val name: String, - val type: String, - @Transient val app: BaasApp? = null -) - -@Serializable -data class Function( - val _id: String? = null, - val name: String, - val source: String? = null, - @SerialName("run_as_system") val runAsSystem: Boolean? = null, - val private: Boolean? = null, - @SerialName("can_evaluate") val canEvaluate: JsonObject? = null -) - -@Serializable -data class BaasApp( - val _id: String, - @SerialName("client_app_id") val clientAppId: String, - val name: String, - @SerialName("domain_id") val domainId: String, - @SerialName("group_id") val groupId: String, - - @Transient private val _client: AppServicesClient? = null -) { - val client: AppServicesClient - get() = _client ?: TODO("App should be copy'ed with _client set to the AppServicesClient that retrieved it") - val url: String - get() = client.baseUrl + ADMIN_PATH + "/groups/${this.groupId}/apps/${this._id}" - val privateUrl: String - get() = client.baseUrl + PRIVATE_PATH + "/groups/${this.groupId}/apps/${this._id}" -} - -/** - * Client to interact with App Services Server. It allows to create Applications and tweak their - * configurations. - */ -class AppServicesClient( - val baseUrl: String, - private val groupId: String, - internal val httpClient: HttpClient, - val dispatcher: CoroutineDispatcher, -) { - - val groupUrl: String - get() = baseUrl + ADMIN_PATH + "/groups/$groupId" - - fun closeClient() { - httpClient.close() - } - - suspend fun getOrCreateApp(appInitializer: AppInitializer): BaasApp { - val app = getApp(appInitializer.name) - return app ?: createApp(appInitializer.name) { - appInitializer.initialize(this@AppServicesClient, this) - } - } - - private suspend fun getApp(appName: String): BaasApp? { - val withContext = withContext(dispatcher) { - httpClient.typedListRequest(Get, "$groupUrl/apps") - .firstOrNull { - it.name == appName - }?.copy(_client = this@AppServicesClient) - } - return withContext - } - - private suspend fun createApp( - appName: String, - initializer: suspend BaasApp.() -> Unit - ): BaasApp { - if (appName.length > 32) { - throw IllegalArgumentException("App names are restricted to 32 characters: $appName was ${appName.length}") - } - return withContext(dispatcher) { - httpClient.typedRequest(Post, "$groupUrl/apps") { - setBody(Json.parseToJsonElement("""{"name": $appName}""")) - contentType(ContentType.Application.Json) - }.copy(_client = this@AppServicesClient).apply { - initializer(this) - } - } - } - - suspend fun BaasApp.setSchema( - schema: Set>, - extraProperties: Map = emptyMap() - ) { - val schemas = SchemaProcessor.process( - databaseName = clientAppId, - classes = schema, - extraProperties = extraProperties - ) - - // First we create the schemas without the relationships - val ids: Map = schemas.entries - .associate { (name, schema: Schema) -> - name to addSchema(schema = schema.copy(relationships = emptyMap())) - } - - // then we update the schema to add the relationships - schemas.forEach { (name, schema) -> - updateSchema( - id = ids[name]!!, - schema = schema - ) - } - } - - suspend fun BaasApp.updateSchema( - id: String, - schema: Schema, - ): HttpResponse = - withContext(dispatcher) { - httpClient.request( - "$url/schemas/$id" - ) { - this.method = HttpMethod.Put - setBody(json.encodeToJsonElement(schema)) - contentType(ContentType.Application.Json) - } - } - - val BaasApp.url: String - get() = "$groupUrl/apps/${this._id}" - suspend fun BaasApp.toggleFeatures(features: Set, enable: Boolean) { - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$privateUrl/features" - ) { - setBody(Json.parseToJsonElement("""{ "action": "${if (enable) "enable" else "disable"}", "feature_flags": [ ${features.joinToString { "\"$it\"" }} ] }""")) - contentType(ContentType.Application.Json) - } - } - } - - suspend fun BaasApp.addFunction(function: Function): Function = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/functions" - ) { - setBody(function) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.addSchema(schema: Schema): String = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/schemas" - ) { - setBody(json.encodeToJsonElement(schema)) - contentType(ContentType.Application.Json) - } - }.let { jsonObject: JsonObject -> - jsonObject["_id"]!!.jsonPrimitive.content - } - - suspend fun BaasApp.addService(service: String): Service = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/services" - ) { - setBody(Json.parseToJsonElement(service)) - contentType(ContentType.Application.Json) - }.run { - copy(app = this@addService) - } - } - - suspend fun BaasApp.addAuthProvider(authProvider: String): JsonObject = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/auth_providers" - ) { - setBody(Json.parseToJsonElement(authProvider)) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.getAuthProvider(type: String): AuthProvider = - withContext(dispatcher) { - httpClient.typedListRequest( - Get, - "$url/auth_providers" - ) - }.first { - it.type == type - }.run { - copy(app = this@getAuthProvider) - } - - suspend fun BaasApp.setDevelopmentMode(developmentModeEnabled: Boolean) = - withContext(dispatcher) { - httpClient.request("$url/sync/config") { - this.method = HttpMethod.Put - setBody(Json.parseToJsonElement("""{"development_mode_enabled": $developmentModeEnabled}""")) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.setCustomUserData(userDataConfig: String): HttpResponse = - withContext(dispatcher) { - httpClient.request("$url/custom_user_data") { - this.method = HttpMethod.Patch - setBody(Json.parseToJsonElement(userDataConfig)) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.addEndpoint(endpoint: String) = - withContext(dispatcher) { - httpClient.request("$url/endpoints") { - this.method = HttpMethod.Post - setBody(Json.parseToJsonElement(endpoint)) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.addSecret(secret: String): JsonObject = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/secrets" - ) { - setBody(Json.parseToJsonElement(secret)) - contentType(ContentType.Application.Json) - } - } - - val AuthProvider.url: String - get() = "${app!!.url}/auth_providers/$_id" - - suspend fun AuthProvider.enable(enabled: Boolean) = - withContext(dispatcher) { - httpClient.request("$url/${if (enabled) "enable" else "disable"}") { - this.method = HttpMethod.Put - } - } - - suspend fun AuthProvider.updateConfig(block: MutableMap.() -> Unit) { - mutableMapOf().apply { - block() - httpClient.request(url) { - this.method = HttpMethod.Patch - setBody(JsonObject(mapOf("config" to JsonObject(this@apply)))) - contentType(ContentType.Application.Json) - } - } - } - - val Service.url: String - get() = "${app!!.url}/services/$_id" - - suspend fun Service.setSyncConfig(config: String) = - withContext(dispatcher) { - httpClient.request("$url/config") { - this.method = HttpMethod.Patch - setBody(Json.parseToJsonElement(config)) - contentType(ContentType.Application.Json) - } - } - - suspend fun Service.addDefaultRule(rule: String): JsonObject = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/default_rule" - ) { - setBody(Json.parseToJsonElement(rule)) - contentType(ContentType.Application.Json) - } - } - - suspend fun Service.addRule(rule: String): JsonObject = - withContext(dispatcher) { - httpClient.typedRequest( - Post, - "$url/rules" - ) { - setBody(Json.parseToJsonElement(rule)) - contentType(ContentType.Application.Json) - } - } - - /** - * Deletes all currently registered and pending users on the App Services Application. - */ - suspend fun BaasApp.deleteAllUsers() = withContext(dispatcher) { - deleteAllRegisteredUsers() - deleteAllPendingUsers() - } - - private suspend fun BaasApp.deleteAllPendingUsers() { - val pendingUsers = httpClient.typedRequest( - Get, - "$url/user_registrations/pending_users" - ) - for (pendingUser in pendingUsers) { - val loginTypes = pendingUser.jsonObject["login_ids"]!!.jsonArray - loginTypes - .filter { it.jsonObject["id_type"]!!.jsonPrimitive.content == "email" } - .map { - httpClient.delete( - "$url/user_registrations/by_email/${it.jsonObject["id"]!!.jsonPrimitive.content}" - ) - } - } - } - - private suspend fun BaasApp.deleteAllRegisteredUsers() { - val users = httpClient.typedRequest( - Get, - "$url/users" - ) - users.map { - httpClient.delete("$url/users/${it.jsonObject["_id"]!!.jsonPrimitive.content}") - } - } - - val BaasApp.mongodbService: Service - get() { - return runBlocking { - httpClient.typedListRequest(Get, "$url/services") - .first { - val type = if (TEST_APP_CLUSTER_NAME.isEmpty()) "mongodb" else "mongodb-atlas" - it.type == type - }.copy(app = this@mongodbService) - } - } - - private suspend fun BaasApp.controlSync( - serviceId: String, - enabled: Boolean, - permissions: SyncPermissions? = null - ) { - val url = "$url/services/$serviceId/config" - val syncEnabled = if (enabled) "enabled" else "disabled" - val jsonPartition = permissions?.let { - val permissionList = JsonObject( - mapOf( - "read" to JsonPrimitive(permissions.read), - "write" to JsonPrimitive(permissions.read) - ) - ) - JsonObject(mapOf("permissions" to permissionList, "key" to JsonPrimitive("realm_id"))) - } - - // Add permissions if present, otherwise just change state - val content = jsonPartition?.let { - mapOf( - "state" to JsonPrimitive(syncEnabled), - "partition" to jsonPartition - ) - } ?: mapOf("state" to JsonPrimitive(syncEnabled)) - - val configObj = JsonObject(mapOf("sync" to JsonObject(content))) - httpClient.request(url) { - this.method = HttpMethod.Patch - setBody(configObj) - contentType(ContentType.Application.Json) - } - } - - suspend fun BaasApp.pauseSync() = - withContext(dispatcher) { - val backingDbServiceId = mongodbService._id - controlSync(backingDbServiceId, false) - } - - suspend fun BaasApp.startSync() = - withContext(dispatcher) { - val backingDbServiceId = mongodbService._id - controlSync(backingDbServiceId, true) - } - - suspend fun BaasApp.triggerClientReset(userId: String) = - withContext(dispatcher) { - deleteDocument("__realm_sync", "clientfiles", """{"ownerId": "$userId"}""") - deleteDocument("__realm_sync_$_id", "clientfiles", """{"ownerId": "$userId"}""") - } - - suspend fun BaasApp.triggerClientReset(syncMode: SyncMode, userId: String) = - withContext(dispatcher) { - when (syncMode) { - SyncMode.PARTITION_BASED -> - deleteDocument("__realm_sync", "clientfiles", """{"ownerId": "$userId"}""") - SyncMode.FLEXIBLE -> - deleteDocument("__realm_sync_$_id", "clientfiles", """{"ownerId": "$userId"}""") - } - } - - suspend fun BaasApp.changeSyncPermissions(permissions: SyncPermissions, block: () -> Unit) = - withContext(dispatcher) { - val backingDbServiceId = mongodbService._id - - // Execute test logic - try { - controlSync(backingDbServiceId, true, permissions) - block.invoke() - } finally { - // Restore original permissions - controlSync(backingDbServiceId, true, SyncPermissions(read = true, write = true)) - } - } - - suspend fun BaasApp.getAuthConfigData(): String = - withContext(dispatcher) { - val providerId: String = getLocalUserPassProviderId() - val url = "$url/auth_providers/$providerId" - httpClient.typedRequest(Get, url).toString() - } - - suspend fun BaasApp.setAutomaticConfirmation(enabled: Boolean) = - withContext(dispatcher) { - getAuthProvider("local-userpass").updateConfig { - put("autoConfirm", JsonPrimitive(enabled)) - } - } - - suspend fun BaasApp.setCustomConfirmation(enabled: Boolean) = - withContext(dispatcher) { - getAuthProvider("local-userpass").updateConfig { - put("runConfirmationFunction", JsonPrimitive(enabled)) - } - } - - suspend fun BaasApp.setResetFunction(enabled: Boolean) = - withContext(dispatcher) { - getAuthProvider("local-userpass").updateConfig { - put("runResetFunction", JsonPrimitive(enabled)) - } - } - - suspend fun BaasApp.insertDocument(clazz: String, json: String): JsonObject? = - withContext(dispatcher) { - functionCall( - name = "insertDocument", - arguments = buildJsonArray { - add(mongodbService.name) - add(clientAppId) - add(clazz) - add(Json.decodeFromString(json)) - } - ) - } - - suspend fun BaasApp.queryDocument(clazz: String, query: String): JsonObject? = - withContext(dispatcher) { - functionCall( - name = "queryDocument", - arguments = buildJsonArray { - add(mongodbService.name) - add(clientAppId) - add(clazz) - add(query) - } - ) - } - - suspend fun BaasApp.countDocuments(clazz: String): Int { - val result: JsonObject? = withContext(dispatcher) { - functionCall( - name = "countDocuments", - arguments = buildJsonArray { - add(mongodbService.name) - add(clientAppId) - add(clazz) - } - ) - } - return result?.let { - it["value"]?.jsonObject?.get("\$numberLong")?.jsonPrimitive?.int - } ?: throw IllegalStateException("Unexpected result: $result") - } - - suspend fun BaasApp.deleteDocument( - db: String, - clazz: String, - query: String - ): JsonObject? = - withContext(dispatcher) { - functionCall( - name = "deleteDocument", - arguments = buildJsonArray { - add(mongodbService.name) - add(db) - add(clazz) - add(query) - } - ) - } - - suspend fun BaasApp.waitUntilInitialSyncCompletes() { - withTimeout(5.minutes) { - while (!initialSyncComplete()) { - delay(1.seconds) - } - } - } - - suspend fun BaasApp.initialSyncComplete(): Boolean { - return withContext(dispatcher) { - try { - httpClient.typedRequest( - Get, - "$url/sync/progress" - ).let { obj: JsonObject -> - obj["accepting_clients"]?.jsonPrimitive?.boolean ?: false - } - } catch (ex: IllegalStateException) { - if (ex.message!!.contains("there are no mongodb/atlas services with provided sync state")) { - // If the network returns this error, it means that Sync is not enabled for this app, - // in that case, just report success. - true - } else { - throw ex - } - } - } - } - - private suspend fun BaasApp.getLocalUserPassProviderId(): String = - withContext(dispatcher) { - httpClient.typedRequest( - Get, - "$url/auth_providers" - ).let { arr: JsonArray -> - arr.firstOrNull { el: JsonElement -> - el.jsonObject["name"]!!.jsonPrimitive.content == "local-userpass" - }?.let { el: JsonElement -> - el.jsonObject["_id"]?.jsonPrimitive?.content ?: throw IllegalStateException( - "Could not find '_id': $arr" - ) - } ?: throw IllegalStateException("Could not find local-userpass provider: $arr") - } - } - - private suspend fun BaasApp.functionCall( - name: String, - arguments: JsonArray - ): JsonObject? = - withContext(dispatcher) { - val functionCall = buildJsonObject { - put("name", name) - put("arguments", arguments) - } - - val url = - "$url/debug/execute_function?run_as_system=true" - httpClient.typedRequest(Post, url) { - setBody(functionCall) - contentType(ContentType.Application.Json) - }.jsonObject["result"]!!.let { - when (it) { - is JsonNull -> null - else -> it.jsonObject - } - } - } - companion object { - suspend fun build( - debug: Boolean, - baseUrl: String, - dispatcher: CoroutineDispatcher, - ): AppServicesClient { - val adminUrl = baseUrl + ADMIN_PATH - // Work around issues on Native with the Ktor client being created and used - // on different threads. - // Log in using unauthorized client - val unauthorizedClient = defaultClient("realm-baas-unauthorized", debug) - - var loginMethod: String = "local-userpass" - var json: Map = mapOf("username" to SyncServerConfig.email, "password" to SyncServerConfig.password) - if (SyncServerConfig.publicApiKey.isNotEmpty()) { - loginMethod = "mongodb-cloud" - json = mapOf("username" to SyncServerConfig.publicApiKey, "apiKey" to SyncServerConfig.privateApiKey) - } - val loginResponse = unauthorizedClient.typedRequest( - HttpMethod.Post, - "$adminUrl/auth/providers/$loginMethod/login" - ) { - contentType(ContentType.Application.Json) - setBody(json) - } - - // Setup authorized client for the rest of the requests - val accessToken = loginResponse.access_token - unauthorizedClient.close() - - val httpClient = defaultClient("realm-baas-authorized", debug) { - expectSuccess = true - - defaultRequest { - headers { - append("Authorization", "Bearer $accessToken") - } - } - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - isLenient = true - } - ) - } - install(Logging) { - // Set to LogLevel.ALL to debug Admin API requests. All relevant - // data for each request/response will be console or LogCat. - level = LogLevel.ALL - } - } - - // Collect app group id - val groupId = httpClient.typedRequest(Get, "$adminUrl/auth/profile") - .roles - .first() - .group_id ?: "null" - - return AppServicesClient( - baseUrl, - groupId, - httpClient, - dispatcher - ) - } - } -} - -// Default serializer fails with -// InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap -// on native. Have tried the various workarounds from -// https://github.com/Kotlin/kotlinx.serialization/issues/1450 -// but only one that works is manual invoking the deserializer -@OptIn(InternalSerializationApi::class) -private suspend inline fun HttpClient.typedRequest( - method: HttpMethod, - url: String, - crossinline block: HttpRequestBuilder.() -> Unit = {} -): T { - val response: HttpResponse = this@typedRequest.request(url) { - this.method = method - this.apply(block) - } - if (!response.status.isSuccess()) { - throw IllegalStateException("Http request failed: $url. ${response.status}: ${response.bodyAsText()}") - } - return response.bodyAsText() - .let { - Json { ignoreUnknownKeys = true }.decodeFromString( - T::class.serializer(), - it - ) - } -} - -// Default serializer fails with -// InvalidMutabilityException: mutation attempt of frozen kotlin.collections.HashMap -// on native. Have tried the various workarounds from -// https://github.com/Kotlin/kotlinx.serialization/issues/1450 -// but only one that works is manual invoking the deserializer -@OptIn(InternalSerializationApi::class) -private suspend inline fun HttpClient.typedListRequest( - method: HttpMethod, - url: String, - crossinline block: HttpRequestBuilder.() -> Unit = {} -): List { - return this@typedListRequest.request(url) { - this.method = method - this.apply(block) - }.bodyAsText() - .let { - Json { ignoreUnknownKeys = true }.decodeFromString( - ListSerializer(T::class.serializer()), - it - ) - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt deleted file mode 100644 index 61a9894bf2..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/HttpClient.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") -package io.realm.kotlin.test.mongodb.util - -import io.ktor.client.HttpClient -import io.ktor.client.HttpClientConfig -import io.ktor.client.plugins.HttpRedirect -import io.ktor.client.plugins.HttpTimeout -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.logging.Logger -import io.ktor.client.plugins.logging.Logging -import io.ktor.serialization.kotlinx.json.json -import io.realm.kotlin.internal.ContextLogger -import io.realm.kotlin.mongodb.internal.LogObfuscatorImpl -import io.realm.kotlin.mongodb.internal.createPlatformClient -import kotlinx.serialization.json.Json -import kotlin.time.Duration.Companion.seconds - -// TODO Consider moving it to util package? -fun defaultClient(name: String, debug: Boolean, block: HttpClientConfig<*>.() -> Unit = {}): HttpClient { - val timeout = 60.seconds.inWholeMilliseconds - return createPlatformClient { - // Charset defaults to UTF-8 (https://ktor.io/docs/http-plain-text.html#configuration) - install(HttpTimeout) { - connectTimeoutMillis = timeout - requestTimeoutMillis = timeout - socketTimeoutMillis = timeout - } - - install(ContentNegotiation) { - json( - Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true - } - ) - } - - if (debug) { - install(Logging) { - logger = object : Logger { - private val logger = ContextLogger(name) - override fun log(message: String) { - logger.debug(LogObfuscatorImpl.obfuscate(message)) - } - } - level = io.ktor.client.plugins.logging.LogLevel.ALL - } - } - - // We should allow redirects for all types, not just GET and HEAD - // See https://github.com/ktorio/ktor/issues/1793 - install(HttpRedirect) { - checkHttpMethod = false - } - - // TODO connectionPool? - this.apply(block) - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt deleted file mode 100644 index 9cc9b17bf6..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/SchemaProcessor.kt +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") - -package io.realm.kotlin.test.mongodb.util - -import io.realm.kotlin.internal.interop.CollectionType -import io.realm.kotlin.internal.interop.PropertyInfo -import io.realm.kotlin.internal.interop.PropertyType -import io.realm.kotlin.internal.realmObjectCompanionOrNull -import io.realm.kotlin.internal.schema.RealmClassImpl -import io.realm.kotlin.schema.RealmClassKind -import io.realm.kotlin.types.BaseRealmObject -import kotlin.reflect.KClass - -class SchemaProcessor private constructor( - classes: Set>, - private val databaseName: String, - private val extraProperties: Map, -) { - companion object { - - fun process( - databaseName: String, - classes: Set>, - extraProperties: Map = emptyMap(), - ): Map { - val processor = SchemaProcessor(classes, databaseName, extraProperties) - - return processor.processedSchemas - .entries - .filterNot { (_, schema) -> schema.kind == RealmClassKind.EMBEDDED } - .associate { (name, schema) -> - // add metadata - name to Schema( - databaseName, - schema, - processor.processedRelationships[name]!! - ) - } - } - } - - private val realmSchemas: Map = classes.associate { clazz -> - val companion = clazz.realmObjectCompanionOrNull()!! - val realmSchema = companion.io_realm_kotlin_schema() - realmSchema.cinteropClass.name to realmSchema - } - - val processedSchemas: MutableMap = mutableMapOf() - val processedRelationships: MutableMap> = mutableMapOf() - - init { - checkCycles() - generateSchemas() - generateRelationships() - } - - private fun checkCycles() { - realmSchemas.values.filter { - it.kind == RealmClassKind.EMBEDDED - }.forEach { schema -> - checkCycles(schema) - } - } - - private fun checkCycles(schema: RealmClassImpl, visited: Array = emptyArray()) { - if (visited.contains(schema.cinteropClass.name)) - throw IllegalStateException("Cycles in embedded object schemas are not supported") - - schema.cinteropProperties - .filter { targetSchema -> - targetSchema.type == PropertyType.RLM_PROPERTY_TYPE_OBJECT - } - .forEach { targetSchema -> - checkCycles(realmSchemas[targetSchema.linkTarget]!!, visited + schema.name) - } - } - - private fun generateRelationships() { - processedSchemas.values.forEach { schema -> - processedRelationships[schema.title] = - findRelationships(schema.properties).associateBy { it.sourceKey } - } - } - - private fun findRelationships( - properties: Map, - path: String = "", - ): List = - properties.entries - .filterNot { (_, value) -> - value is PrimitivePropertyType - } - .flatMap { (key, value: SchemaPropertyType) -> - value.toSchemaRelationships(key, path) - } - - private fun SchemaPropertyType.toSchemaRelationships( - key: String, - path: String = "", - ): List { - return when (this) { - is ObjectReferenceType -> listOf(toSchemaRelationship(path)) - is CollectionPropertyType -> items.toSchemaRelationships("$path$key.[]") - is MapPropertyType -> additionalProperties.toSchemaRelationships("$path$key.[]") - is SchemaData -> findRelationships(properties, "$path$key.") - else -> emptyList() - } - } - - private fun ObjectReferenceType.toSchemaRelationship(path: String = "") = - SchemaRelationship( - database = databaseName, - target = target, - sourceKey = "$path$sourceKey", - foreignKey = targetKey, - isList = isList - ) - - private fun generateSchemas() { - realmSchemas.forEach { entry -> - if (entry.key !in processedSchemas) - entry.value.toSchema() - } - } - - private fun RealmClassImpl.toSchema() { - val name = cinteropClass.name - - val properties: Map = cinteropProperties - .filterNot { - it.isComputed - } - .associate { property: PropertyInfo -> - property.name to property.toSchemaProperty() - } + when (kind) { - RealmClassKind.STANDARD -> - extraProperties.entries.associate { - it.key to PrimitivePropertyType( - bsonType = it.value, - isRequired = false, - ) - } - - RealmClassKind.EMBEDDED -> emptyMap() - RealmClassKind.ASYMMETRIC -> emptyMap() - } - - val required: List = properties.entries - .filter { (_, value) -> - value.isRequired - } - .map { (name, _) -> name } - - processedSchemas[name] = SchemaData( - title = name, - properties = properties, - required = required, - kind = kind - ) - } - - private fun PropertyInfo.toSchemaProperty(): SchemaPropertyType = - when (collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> propertyValueType() - CollectionType.RLM_COLLECTION_TYPE_LIST -> CollectionPropertyType( - items = propertyValueType(isCollection = true), - uniqueItems = false - ) - - CollectionType.RLM_COLLECTION_TYPE_SET -> CollectionPropertyType( - items = propertyValueType(isCollection = true), - uniqueItems = true - ) - - CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> MapPropertyType( - additionalProperties = propertyValueType(isCollection = true) - ) - - else -> throw IllegalStateException("Unsupported $collectionType") - } - - private fun PropertyInfo.propertyValueType(isCollection: Boolean = false): SchemaPropertyType = - if (type == PropertyType.RLM_PROPERTY_TYPE_OBJECT) - realmSchemas[linkTarget]!! - .let { targetSchema: RealmClassImpl -> - when (targetSchema.kind) { - RealmClassKind.STANDARD -> ObjectReferenceType( - name, - targetSchema, - isCollection - ) - - RealmClassKind.EMBEDDED -> getSchema(targetSchema.name) - RealmClassKind.ASYMMETRIC -> TODO() - } - } - else - PrimitivePropertyType( - bsonType = type.toSchemaType(), - isRequired = !isNullable - ) - - private fun getSchema(name: String): SchemaData { - if (name !in processedSchemas) - realmSchemas[name]!!.toSchema() - - return processedSchemas[name]!! - } -} diff --git a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt b/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt deleted file mode 100644 index 5647d6f32c..0000000000 --- a/packages/test-sync/src/commonMain/kotlin/io/realm/kotlin/test/mongodb/util/TestAppInitializer.kt +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.util - -import io.realm.kotlin.test.mongodb.TEST_APP_CLUSTER_NAME -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA -import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA -import kotlinx.serialization.json.Json - -interface AppInitializer { - val name: String - suspend fun initialize(client: AppServicesClient, app: BaasApp) -} - -open class BaseAppInitializer( - override val name: String, - val block: (suspend AppServicesClient.(app: BaasApp) -> Unit)? = null, -) : AppInitializer { - - override suspend fun initialize(client: AppServicesClient, app: BaasApp) { - with(client) { - with(app) { - addFunction(insertDocument) - addFunction(queryDocument) - addFunction(deleteDocument) - addFunction(countDocuments) - - val testAuthFuncId = addFunction(testAuthFunc)._id - addAuthProvider( - """ - { - "type": "custom-function", - "config": { - "authFunctionId": "$testAuthFuncId", - "authFunctionName": "${testAuthFunc.name}" - } - } - """.trimIndent() - ) - - addAuthProvider("""{"type": "anon-user"}""") - - // Enable 'API-KEY' by updating it. It exists by default in the server so we cannot add. - getAuthProvider("api-key").run { - enable(true) - } - - val (type: String, config: String) = if (TEST_APP_CLUSTER_NAME.isEmpty()) { - "mongodb" to """{ "uri": "mongodb://localhost:26000" }""" - } else { - "mongodb-atlas" to """{ "clusterName": "$TEST_APP_CLUSTER_NAME" }""" - } - addService( - """ - { - "name": "BackingDB", - "type": "$type", - "config": $config - } - """.trimIndent() - ).let { service: Service -> - val dbName = app.clientAppId - service.addDefaultRule( - """ - { - "roles": [{ - "name": "defaultRole", - "apply_when": {}, - "document_filters": { - "read": true, - "write": true - }, - "write": true, - "read": true, - "insert": true, - "delete": true - }] - } - """.trimIndent() - ) - - app.setCustomUserData( - """ - { - "mongo_service_id": ${service._id}, - "enabled": true, - "database_name": "$dbName", - "collection_name": "UserData", - "user_id_field": "user_id" - } - """.trimIndent() - ) - } - with(client) { - block?.invoke(this, app) - } - app.setDevelopmentMode(true) - } - } - } -} - -object DefaultPartitionBasedAppInitializer : - BaseAppInitializer( - TEST_APP_PARTITION, - { app -> - addEmailProvider(app) - initializePartitionSync(app) - } - ) - -object DefaultFlexibleSyncAppInitializer : - BaseAppInitializer( - TEST_APP_FLEX, - { app -> - addEmailProvider(app) - initializeFlexibleSync(app) - } - ) - -@Suppress("LongMethod") -suspend fun AppServicesClient.initializeFlexibleSync( - app: BaasApp, - recoveryDisabled: Boolean = false, // TODO -) { - val databaseName = app.clientAppId - - app.setSchema(FLEXIBLE_SYNC_SCHEMA) - - app.mongodbService.setSyncConfig( - """ - { - "flexible_sync": { - "state": "enabled", - "database_name": "$databaseName", - "is_recovery_mode_disabled": $recoveryDisabled, - "queryable_fields_names": [ - "name", - "section", - "stringField", - "location", - "selector" - ], - "asymmetric_tables": [ - "AsymmetricA", - "Measurement" - ] - } - } - """.trimIndent() - ) - app.waitUntilInitialSyncCompletes() -} - -@Suppress("LongMethod") -suspend fun AppServicesClient.initializePartitionSync( - app: BaasApp, - recoveryDisabled: Boolean = false, // TODO -) { - val databaseName = app.clientAppId - - app.addFunction(canReadPartition) - app.addFunction(canWritePartition) - - app.setSchema( - schema = PARTITION_BASED_SCHEMA, - extraProperties = mapOf( - "realm_id" to PrimitivePropertyType.Type.STRING - ) - ) - - app.mongodbService.setSyncConfig( - """ - { - "sync": { - "state": "enabled", - "database_name": "$databaseName", - "is_recovery_mode_disabled": $recoveryDisabled, - "partition": { - "key": "realm_id", - "type": "string", - "permissions": { - "read": { - "%%true": { - "%function": { - "arguments": [ - "%%partition" - ], - "name": "canReadPartition" - } - } - }, - "write": { - "%%true": { - "%function": { - "arguments": [ - "%%partition" - ], - "name": "canWritePartition" - } - } - } - } - } - } - } - """.trimIndent() - ) - - app.waitUntilInitialSyncCompletes() -} - -suspend fun AppServicesClient.addEmailProvider( - app: BaasApp, - autoConfirm: Boolean = true, - runConfirmationFunction: Boolean = false, -) = with(app) { - val confirmFuncId = addFunction(confirmFunc)._id - val resetFuncId = addFunction(resetFunc)._id - - addAuthProvider( - """ - { - "type": "local-userpass", - "config": { - "autoConfirm": $autoConfirm, - "confirmationFunctionId": "$confirmFuncId", - "confirmationFunctionName": "${confirmFunc.name}", - "emailConfirmationUrl": "http://realm.io/confirm-user", - "resetFunctionId": "$resetFuncId", - "resetFunctionName": "${resetFunc.name}", - "resetPasswordSubject": "Reset Password", - "resetPasswordUrl": "http://realm.io/reset-password", - "runConfirmationFunction": $runConfirmationFunction, - "runResetFunction": false - } - } - """.trimIndent() - ) -} - -private val insertDocument = Function( - name = "insertDocument", - source = - """ - exports = function (service, db, collection, document) { - const mongodb = context.services.get(service); - const result = mongodb - .db(db) - .collection(collection) - .insertOne(document); - - return result; - } - - """.trimIndent() -) - -private val deleteDocument = Function( - name = "deleteDocument", - source = - """ - exports = function (service, db, collection, query) { - const mongodb = context.services.get(service); - const result = mongodb - .db(db) - .collection(collection) - .deleteMany(EJSON.parse(query)); - - return result; - } - - """.trimIndent() -) - -private val queryDocument = Function( - name = "queryDocument", - source = - """ - exports = function (service, db, collection, query) { - const mongodb = context.services.get(service); - const result = mongodb - .db(db) - .collection(collection) - .findOne(EJSON.parse(query)); - - return result; - } - """.trimIndent() -) - -private val countDocuments = Function( - name = "countDocuments", - source = - """ - exports = function (service, db, collection) { - const mongodb = context.services.get(service); - const result = mongodb - .db(db) - .collection(collection) - .count(); - return { value: result }; - } - - """.trimIndent() -) - -private val testAuthFunc = Function( - name = "testAuthFunc", - source = - """ - exports = ({mail, id}) => { - // Auth function will fail for emails with a domain different to @10gen.com - // or with id lower than 666 - if (!new RegExp("@10gen.com${'$'}").test(mail) || id < 666) { - throw new Error(`Authentication failed`); - } else { - // Use the users email as UID - return mail; - } - } - """.trimIndent() -) - -private val confirmFunc = Function( - name = "confirmFunc", - source = - """ - exports = async ({ token, tokenId, username }) => { - // process the confirm token, tokenId and username - - if (username.includes("realm_verify")) { - // Automatically confirm users with `realm_verify` in their email. - return { status: 'success' } - } else if (username.includes("realm_pending")) { - // Emails with `realm_pending` in their email will be placed in Pending - // the first time they register and will then be fully confirmed when - // they retry their confirmation logic. - const mdb = context.services.get("BackingDB"); - const collection = mdb.db("custom-auth").collection("users"); - const existing = await collection.findOne({ username: username }); - if (existing) { - return { status: 'success' }; - } - await collection.insertOne({ username: username }); - return { status: 'pending' } - } else if (username.endsWith("@10gen.com")) { - return { status: 'success' } - } else { - // All other emails should fail to confirm outright. - return { status: 'fail' }; - } - } - """.trimIndent() -) - -private val resetFunc = Function( - name = "resetFunc", - source = - """ - exports = ({ token, tokenId, username, password }, customParam1, customParam2) => { - if (customParam1 != "say-the-magic-word" || customParam2 != 42) { - return { status: 'fail' }; - } else { - return { status: 'success' }; - } - } - """.trimIndent() -) - -private val canReadPartition = Function( - name = "canReadPartition", - source = - """ - /** - * Users with an email that contains `_noread_` do not have read access, - * all others do. - */ - exports = async (partition) => { - const email = context.user.data.email; - if (email != undefined) { - return !email.includes("_noread_"); - } else { - return true; - } - } - """.trimIndent() -) - -private val canWritePartition = - Function( - name = "canWritePartition", - source = - """ - /** - * Users with an email that contains `_nowrite_` do not have write access, - * all others do. - */ - exports = async (partition) => { - const email = context.user.data.email; - if (email != undefined) { - return(!email.includes("_nowrite_")); - } else { - return true; - } - } - """.trimIndent() - ) - -val FIRST_ARG_FUNCTION = Function( - name = "firstArg", - source = - """ - exports = function(arg){ - // Returns first argument - return arg - }; - """.trimIndent() -) - -val SUM_FUNCTION = Function( - name = "sum", - source = - """ - exports = function(...args) { - return parseInt(args.reduce((a,b) => a + b, 0)); - }; - """.trimIndent() -) - -val NULL_FUNCTION = Function( - name = "null", - source = - """ - exports = function(arg){ - return null; - }; - """.trimIndent() -) - -val ERROR_FUNCTION = Function( - name = "error", - source = - """ - exports = function(arg){ - return unknown; - }; - """.trimIndent() -) - -val VOID_FUNCTION = Function( - name = "void", - source = - """ - exports = function(arg){ - return void(0); - }; - """.trimIndent() -) - -val AUTHORIZED_ONLY_FUNCTION = Function( - name = "authorizedOnly", - source = - """ - exports = function(arg){ - /* - Accessing application's values: - var x = context.values.get("value_name"); - - Accessing a mongodb service: - var collection = context.services.get("mongodb-atlas").db("dbname").collection("coll_name"); - var doc = collection.findOne({owner_id: context.user.id}); - - To call other named functions: - var result = context.functions.execute("function_name", arg1, arg2); - - Try running in the console below. - */ - return {arg: context.user}; - }; - """.trimIndent(), - canEvaluate = Json.decodeFromString( - """ - { - "%%user.data.email": { - "%in": [ - "authorizeduser@example.org" - ] - } - } - """.trimIndent() - ) -) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt deleted file mode 100644 index 0c6a6e30d7..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/ApiKeyAuthTests.kt +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.auth.ApiKeyAuth -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper -import org.mongodb.kbson.BsonObjectId -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class ApiKeyAuthTests { - private lateinit var app: TestApp - private lateinit var user: User - private lateinit var provider: ApiKeyAuth - - private enum class Method { - CREATE, - FETCH_SINGLE, - FETCH_ALL, - DELETE, - ENABLE, - DISABLE - } - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - user = app.createUserAndLogin() - provider = user.apiKeyAuth - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun create() = runBlocking { - val name = TestHelper.randomString("key-") - val key = provider.create(name) - assertEquals(name, key.name) - assertNotNull(key.value) - assertNotNull(key.id) - assertTrue(key.enabled) - } - - @Test - fun create_throwsWithInvalidName() { - assertFailsWithMessage("[Service][InvalidParameter(4305)] can only contain ASCII letters, numbers, underscores, and hyphens.") { - runBlocking { - provider.create("%s") - } - } - } - - @Test - fun create_throwsWithNoName() { - assertFailsWithMessage("[Service][Unknown(4351)] 'name' is a required string.") { - runBlocking { - provider.create("") - } - } - } - - @Test - fun fetch() = runBlocking { - val key1 = provider.create("foo") - val key2 = provider.fetch(key1.id) - assertEquals(key1.id, key2!!.id) - assertEquals(key1.name, key2.name) - assertNull(key2.value) - assertEquals(key1.enabled, key2.enabled) - } - - @Test - fun fetch_nonExistingKeyThrows() = runBlocking { - assertNull(provider.fetch(BsonObjectId())) - } - - @Test - fun fetchAll() = runBlocking { - val key1 = provider.create(TestHelper.randomString("key-")) - val key2 = provider.create(TestHelper.randomString("key-")) - val keys = provider.fetchAll() - assertEquals(2, keys.size) - assertTrue(keys.any { it.id == key1.id }) - assertTrue(keys.any { it.id == key2.id }) - } - - @Test - fun fetchAll_noExistingKeysGiveEmptyList() = runBlocking { - val keys = provider.fetchAll() - assertEquals(0, keys.size) - } - - @Test - fun delete() = runBlocking { - val key1 = provider.create(TestHelper.randomString("key-")) - assertNotNull(provider.fetch(key1.id)) - provider.delete(key1.id) - assertNull(provider.fetch(key1.id)) - } - - @Test - fun delete_nonExisitingKeyNoOps(): Unit = runBlocking { - provider.create(TestHelper.randomString("key-")) - val keys = provider.fetchAll() - assertEquals(1, keys.size) - provider.delete(BsonObjectId()) - val keysAfterInvalidDelete = provider.fetchAll() - assertEquals(1, keysAfterInvalidDelete.size) - } - - @Test - fun enable(): Unit = runBlocking { - val key = provider.create(TestHelper.randomString("key-")) - provider.disable(key.id) - assertFalse(provider.fetch(key.id)!!.enabled) - provider.enable(key.id) - assertTrue(provider.fetch(key.id)!!.enabled) - } - - @Test - fun enable_alreadyEnabled() = runBlocking { - val key = provider.create(TestHelper.randomString("key-")) - provider.disable(key.id) - assertFalse(provider.fetch(key.id)!!.enabled) - provider.enable(key.id) - assertTrue(provider.fetch(key.id)!!.enabled) - provider.enable(key.id) - assertTrue(provider.fetch(key.id)!!.enabled) - } - - @Test - fun enable_nonExistingKeyThrows() { - assertFailsWithMessage("[Service][ApiKeyNotFound(4334)] API key not found.") { - runBlocking { - provider.enable(BsonObjectId()) - } - } - } - - @Test - fun disable() = runBlocking { - val key = provider.create(TestHelper.randomString("key-")) - provider.disable(key.id) - assertFalse(provider.fetch(key.id)!!.enabled) - } - - @Test - fun disable_alreadyDisabled() = runBlocking { - val key = provider.create(TestHelper.randomString("key-")) - provider.disable(key.id) - assertFalse(provider.fetch(key.id)!!.enabled) - provider.disable(key.id) - assertFalse(provider.fetch(key.id)!!.enabled) - } - - @Test - fun disable_nonExistingKeyThrows() { - assertFailsWithMessage("[Service][ApiKeyNotFound(4334)] API key not found.") { - runBlocking { - provider.disable(BsonObjectId()) - } - } - } - - @Test - fun callMethodWithLoggedOutUser() { - runBlocking { - user.logOut() - } - for (method in Method.values()) { - assertFailsWithMessage("[Service][Unknown(4351)] expected Authorization header with JWT (Bearer schema).") { - runBlocking { - when (method) { - Method.CREATE -> provider.create(TestHelper.randomString("key-")) - Method.FETCH_SINGLE -> provider.fetch(BsonObjectId()) - Method.FETCH_ALL -> provider.fetchAll() - Method.DELETE -> provider.delete(BsonObjectId()) - Method.ENABLE -> provider.enable(BsonObjectId()) - Method.DISABLE -> provider.disable(BsonObjectId()) - } - } - } - } - } - - @Test - @Ignore // TODO wait for https://jira.mongodb.org/browse/BAAS-17508 to complete before implementing - fun callMethodsWithApiKeysDisabled() { - } - - @Test - fun getUser() { - assertEquals(app.currentUser, provider.user) - } - - @Test - fun getApp() { - // Testapp is a pair of , that's why further delving needs to be done to - // get the actual app - assertEquals(app.app, provider.app) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt deleted file mode 100644 index 0400b24695..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppConfigurationTests.kt +++ /dev/null @@ -1,559 +0,0 @@ -@file:Suppress("invisible_member", "invisible_reference") // Needed to call session.simulateError() -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.internal.platform.appFilesDirectory -import io.realm.kotlin.internal.platform.isWindows -import io.realm.kotlin.internal.platform.pathOf -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogCategory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.internal.AppConfigurationImpl -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.trySendOrFail -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNotSame -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -private const val CUSTOM_HEADER_NAME = "Foo" -private const val CUSTOM_HEADER_VALUE = "bar" -private const val AUTH_HEADER_NAME = "RealmAuth" - -private const val APP_ID = "app-id" - -class AppConfigurationTests { - - @Test - fun authorizationHeaderName() { - val config1 = AppConfiguration.Builder(APP_ID).build() - assertEquals("Authorization", config1.authorizationHeaderName) - - val config2 = AppConfiguration.Builder(APP_ID) - .authorizationHeaderName("CustomAuth") - .build() - assertEquals("CustomAuth", config2.authorizationHeaderName) - - val builder = AppConfiguration.Builder(APP_ID) - - assertFailsWithMessage("Non-empty 'name' required.") { - builder.authorizationHeaderName("") - } - } - - @Test - fun addCustomRequestHeader() { - val config = AppConfiguration.Builder(APP_ID) - .customRequestHeaders { - putAll( - mapOf( - "h0" to "v0", - "h1" to "v1", - ) - ) - put("h2", "v2") - } - .build() - - config.customRequestHeaders.let { headers -> - assertEquals(3, headers.size) - repeat(3) { index -> - assertTrue(headers.any { it.key == "h$index" && it.value == "v$index" }) - } - } - - // Accept empty values - AppConfiguration.Builder(APP_ID).apply { - customRequestHeaders { - put("header1", "") - putAll(mapOf("header1" to "")) - } - } - - // Fail if empty header name - AppConfiguration.Builder(APP_ID).apply { - assertFailsWithMessage("Non-empty custom header name required.") { - customRequestHeaders { - put("", "value") - } - } - } - - AppConfiguration.Builder(APP_ID).apply { - assertFailsWithMessage("Non-empty custom header name required.") { - customRequestHeaders { - putAll(mapOf("" to "value")) - } - } - } - } - - @Test - fun create() { - val config = AppConfiguration.create(APP_ID) - assertIs(config) - assertEquals(APP_ID, config.appId) - } - - @Test - fun syncRootDirectory_default() { - val config = AppConfiguration.Builder(APP_ID).build() - val expectedDefaultRoot = appFilesDirectory() - assertEquals(expectedDefaultRoot, config.syncRootDirectory) - } - - @Test - fun syncRootDirectory() { - val builder: AppConfiguration.Builder = AppConfiguration.Builder(APP_ID) - val expectedRoot = pathOf(appFilesDirectory(), "myCustomDir") - val config = builder - .syncRootDirectory(expectedRoot) - .build() - assertEquals(expectedRoot, config.syncRootDirectory) - } - - @Test - fun syncRootDirectory_writeProtectedDir() { - // Creating a read-only directory throws UnsupportedOperationException on Windows, so ignore - // for now. - if (!isWindows()) { - val builder: AppConfiguration.Builder = AppConfiguration.Builder(APP_ID) - val dir = PlatformUtils.createTempDir(readOnly = true) - assertFailsWith { builder.syncRootDirectory(dir) } - } - } - - // When creating the full path for a synced Realm, we will always append `/mongodb-realm` to - // the configured `AppConfiguration.syncRootDir` - @Test - fun syncRootDirectory_appendDirectoryToPath() = runBlocking { - val expectedRoot = pathOf(appFilesDirectory(), "myCustomDir") - TestApp( - "syncRootDirectory_appendDirectoryToPath", - DefaultPartitionBasedAppInitializer, - builder = { it.syncRootDirectory(expectedRoot) } - ).use { app -> - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = app.createUserAndLogIn(email, password) - assertEquals(expectedRoot, app.configuration.syncRootDirectory) - // When creating the full path for a synced Realm, we will always append `/mongodb-realm` to - // the configured `AppConfiguration.syncRootDir` - val partitionValue = TestHelper.randomPartitionValue() - val suffix = - pathOf("", "myCustomDir", "mongodb-realm", user.app.configuration.appId, user.id, "s_$partitionValue.realm") - val config = SyncConfiguration.Builder(user, partitionValue, schema = setOf()).build() - assertTrue(config.path.endsWith(suffix), "Failed: ${config.path} vs. $suffix") - } - } - - // @Test // TODO we need an IO framework to test this properly, see https://github.com/realm/realm-kotlin/issues/699 -// fun syncRootDirectory_dirIsAFile() { -// val builder: AppConfiguration.Builder = AppConfiguration.Builder(APP_ID) -// val file = File(tempFolder.newFolder(), "dummyfile") -// assertTrue(file.createNewFile()) -// assertFailsWith { builder.syncRootDirectory(file) } -// } -// - @Test - fun appName() { - val config = AppConfiguration.Builder(APP_ID) - .appName("app-name") - .build() - assertEquals("app-name", config.appName) - } - - @Test - fun appName_defaultValue() { - val config = AppConfiguration.Builder(APP_ID).build() - assertEquals(null, config.appName) - } - - @Test - fun appName_invalidValuesThrows() { - val builder = AppConfiguration.Builder(APP_ID) - assertFailsWith { builder.appName("") } - } - - @Test - fun appVersion() { - val config = AppConfiguration.Builder(APP_ID) - .appVersion("app-version") - .build() - assertEquals("app-version", config.appVersion) - } - - @Test - fun appVersion_defaultValue() { - val config = AppConfiguration.Builder(APP_ID).build() - assertEquals(null, config.appVersion) - } - - @Test - fun appVersion_invalidValuesThrows() { - val builder = AppConfiguration.Builder(APP_ID) - assertFailsWith { builder.appVersion("") } - } - - @Test - fun baseUrl() { - val url = "http://myurl.com" - val config = AppConfiguration.Builder("foo").baseUrl(url).build() - assertEquals(url, config.baseUrl) - } - - @Test - fun baseUrl_defaultValue() { - val url = "https://services.cloud.mongodb.com" - val config = AppConfiguration.Builder("foo").build() - assertEquals(url, config.baseUrl) - } - -// @Test -// fun baseUrl_invalidValuesThrows() { -// val configBuilder = AppConfiguration.Builder("foo") -// assertFailsWith { configBuilder.baseUrl("") } -// assertFailsWith { configBuilder.baseUrl(TestHelper.getNull()) } -// assertFailsWith { configBuilder.baseUrl("invalid-url") } -// } -// -// @Test -// fun defaultSyncErrorHandler() { -// val errorHandler = SyncSession.ErrorHandler { _, _ -> } -// -// val config = AppConfiguration.Builder(APP_ID) -// .defaultSyncErrorHandler(errorHandler) -// .build() -// assertEquals(config.defaultErrorHandler, errorHandler) -// } -// -// @Test -// fun defaultSyncErrorHandler_invalidValuesThrows() { -// assertFailsWith { -// AppConfiguration.Builder(APP_ID) -// .defaultSyncErrorHandler(TestHelper.getNull()) -// } -// -// } -// -// @Test -// fun defaultClientResetHandler() { -// val handler = SyncSession.ClientResetHandler { _, _ -> } -// -// val config = AppConfiguration.Builder(APP_ID) -// .defaultClientResetHandler(handler) -// .build() -// assertEquals(config.defaultClientResetHandler, handler) -// } -// -// @Test -// fun defaultClientResetHandler_invalidValuesThrows() { -// val builder = AppConfiguration.Builder(APP_ID) -// assertFailsWith { -// builder.defaultClientResetHandler(TestHelper.getNull()) -// } -// } -// - - @Test - fun encryptionKey() { - val key = TestHelper.getRandomKey() - val config = AppConfiguration.Builder(APP_ID) - .encryptionKey(key) - .build() - - assertContentEquals(key, config.encryptionKey) - } - - @Test - fun encryptionKey_isCopy() { - val key = TestHelper.getRandomKey() - val config = AppConfiguration.Builder(APP_ID) - .encryptionKey(key) - .build() - - assertNotSame(key, config.encryptionKey) - } - - @Test - fun encryptionKey_illegalValueThrows() { - val builder = AppConfiguration.Builder(APP_ID) - - val tooShortKey = ByteArray(1) - assertFailsWithMessage("The provided key must be") { - builder.encryptionKey(tooShortKey) - } - - val tooLongKey = ByteArray(65) - assertFailsWithMessage("The provided key must be") { - builder.encryptionKey(tooLongKey) - } - } - - @Test - fun httpLogObfuscator_null() { - val config = AppConfiguration.Builder(APP_ID) - .httpLogObfuscator(null) - .build() - assertNull(config.httpLogObfuscator) - } - - @Suppress("invisible_reference", "invisible_member") - @Test - fun defaultLoginInfoObfuscator() { - val config = AppConfiguration.Builder(APP_ID).build() - assertNotNull(config.httpLogObfuscator) - assertTrue(config.httpLogObfuscator is io.realm.kotlin.mongodb.internal.LogObfuscatorImpl) - } - - // -// @Test -// fun requestTimeout() { -// val config = AppConfiguration.Builder(APP_ID) -// .requestTimeout(1, TimeUnit.SECONDS) -// .build() -// assertEquals(1000L, config.requestTimeoutMs) -// } -// -// @Test -// fun requestTimeout_invalidValuesThrows() { -// val builder = AppConfiguration.Builder(APP_ID) -// -// assertFailsWith { builder.requestTimeout(-1, TimeUnit.MILLISECONDS) } -// assertFailsWith { builder.requestTimeout(1, TestHelper.getNull()) } -// } -// -// @Test -// fun codecRegistry_null() { -// val builder: AppConfiguration.Builder = AppConfiguration.Builder(APP_ID) -// assertFailsWith { -// builder.codecRegistry(TestHelper.getNull()) -// } -// } -// -// @Test -// fun defaultFunctionsCodecRegistry() { -// val config: AppConfiguration = AppConfiguration.Builder(APP_ID).build() -// assertEquals(AppConfiguration.DEFAULT_BSON_CODEC_REGISTRY, config.defaultCodecRegistry) -// } -// -// @Test -// fun customCodecRegistry() { -// val configCodecRegistry = CodecRegistries.fromCodecs(StringCodec()) -// val config: AppConfiguration = AppConfiguration.Builder(APP_ID) -// .codecRegistry(configCodecRegistry) -// .build() -// assertEquals(configCodecRegistry, config.defaultCodecRegistry) -// } -// - // Check that custom headers and auth header renames are correctly used for HTTP requests. - @Test - fun customHeadersTest() = runBlocking { - TestApp( - "customHeadersTest", - DefaultPartitionBasedAppInitializer, - debug = true, - builder = { builder -> - builder.customRequestHeaders { - put(CUSTOM_HEADER_NAME, CUSTOM_HEADER_VALUE) - }.authorizationHeaderName(AUTH_HEADER_NAME) - } - ).use { app -> - doCustomHeaderTest(app) - } - } - - private suspend fun doCustomHeaderTest(app: App) { - val originalLevel = RealmLog.getLevel() - RealmLog.setLevel(LogLevel.ALL) - val channel = TestChannel() - - val logger = object : RealmLogger { - override fun log( - category: LogCategory, - level: LogLevel, - throwable: Throwable?, - message: String?, - vararg args: Any?, - ) { - if (level == LogLevel.DEBUG && message!!.contains("-> $CUSTOM_HEADER_NAME: $CUSTOM_HEADER_VALUE") && message.contains( - "$AUTH_HEADER_NAME: " - ) - ) { - channel.trySendOrFail(true) - } - } - } - - try { - // Setup custom logger - RealmLog.add(logger) - - // Perform a network related operation - // It will fail because the server does not expect the modified auth header name - assertFailsWith { - app.createUserAndLogIn() - } - - // Receive the results. - assertTrue(channel.receiveOrFail()) - } finally { - // Restore log status - RealmLog.remove(logger) - RealmLog.setLevel(originalLevel) - } - } - - @Test - fun injectedBundleId() { - val app = App.create(APP_ID) - val config1 = app.configuration - assertIs(config1) - @Suppress("invisible_member") - assertEquals("TEST_BUNDLE_ID", config1.bundleId) - - val config2 = AppConfiguration.create(APP_ID) - assertIs(config2) - @Suppress("invisible_member") - assertEquals("TEST_BUNDLE_ID", config2.bundleId) - - val config3 = AppConfiguration.Builder(APP_ID).build() - assertIs(config3) - @Suppress("invisible_member") - assertEquals("TEST_BUNDLE_ID", config3.bundleId) - } - - fun equals_same() { - val appId = "foo" - val url = "http://myurl.com" - val config = AppConfiguration.Builder(appId) - .baseUrl(url) - .build() - val otherConfig = AppConfiguration.Builder(appId) - .baseUrl(url) - .build() - assertEquals(config, otherConfig) - } - - fun equals_different() { - val config = AppConfiguration.Builder("foo") - .baseUrl("http://myurl.com") - .build() - val otherConfig = AppConfiguration.Builder("fooooo") - .baseUrl("http://www.mongodb.com") - .build() - assertNotEquals(config, otherConfig) - } - - @Test - @Ignore // TODO - fun dispatcher() { - } - - @Test - fun multiplexing_default() { - val config = AppConfiguration.Builder("foo").build() - assertFalse(config.enableSessionMultiplexing) - } - - @Test - fun multiplexing() { - val config = AppConfiguration.Builder("foo") - .enableSessionMultiplexing(true) - .build() - assertTrue(config.enableSessionMultiplexing) - } - - @Test - fun syncTimeOutOptions_default() { - val config = AppConfiguration.Builder("foo").build() - with(config.syncTimeoutOptions) { - assertEquals(2.minutes, connectTimeout) - assertEquals(30.seconds, connectionLingerTime) - assertEquals(1.minutes, pingKeepAlivePeriod) - assertEquals(2.minutes, pongKeepAlivePeriod) - assertEquals(1.minutes, fastReconnectLimit) - } - } - - @Test - fun syncTimeOutOptions() { - val config = AppConfiguration.Builder("foo") - .syncTimeouts { - connectTimeout = 10.seconds - connectionLingerTime = 10.seconds - pingKeepAlivePeriod = 10.seconds - pongKeepAlivePeriod = 10.seconds - fastReconnectLimit = 10.seconds - } - .build() - with(config.syncTimeoutOptions) { - assertEquals(10.seconds, connectTimeout) - assertEquals(10.seconds, connectionLingerTime) - assertEquals(10.seconds, pingKeepAlivePeriod) - assertEquals(10.seconds, pongKeepAlivePeriod) - assertEquals(10.seconds, fastReconnectLimit) - } - } - - @Test - fun syncTimeOutOptions_throwsIfValuesAreOutOfRange() { - AppConfiguration.Builder("foo") - .syncTimeouts { - assertFailsWith { - connectTimeout = 999.milliseconds - } - assertFailsWith { - connectionLingerTime = 0.seconds - } - assertFailsWith { - pingKeepAlivePeriod = 5.seconds - } - assertFailsWith { - pongKeepAlivePeriod = 5.seconds - } - assertFailsWith { - pongKeepAlivePeriod = 1.seconds - } - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt deleted file mode 100644 index 9c47e77df8..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AppTests.kt +++ /dev/null @@ -1,587 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.RealmConfiguration -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AppConfiguration -import io.realm.kotlin.mongodb.AuthenticationChange -import io.realm.kotlin.mongodb.AuthenticationProvider -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.LoggedIn -import io.realm.kotlin.mongodb.LoggedOut -import io.realm.kotlin.mongodb.Removed -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.annotations.ExperimentalEdgeServerApi -import io.realm.kotlin.mongodb.exceptions.AuthException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.SyncServerConfig -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.TestHelper.randomEmail -import io.realm.kotlin.test.util.receiveOrFail -import kotlinx.coroutines.async -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertSame -import kotlin.test.assertTrue -import kotlin.test.fail - -class AppTests { - - private lateinit var app: App - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun defaultApp() { - App.create("foo").use { defaultApp -> - assertEquals("foo", defaultApp.configuration.appId) - assertEquals(AppConfiguration.DEFAULT_BASE_URL, defaultApp.configuration.baseUrl) - } - } - - @Test - fun defaultApp_emptyIdThrows() { - assertFailsWith { - App.create("") - } - } - - // TODO Minimal subset of login tests. Migrate AppTest from realm-java, when full API is in - // place - // TODO Exhaustive test on io.realm.kotlin.mongodb.internal.Provider - @Test - fun login_Anonymous() { - runBlocking { - app.login(Credentials.anonymous()) - } - } - - @Test - fun login_NonCredentialImplThrows() { - runBlocking { - assertFailsWith { - app.login(object : Credentials { - override val authenticationProvider: AuthenticationProvider = - AuthenticationProvider.ANONYMOUS - }) - } - } - } - - // Check that all auth providers throw the same exception for when invalid credentials are - // presented. - @Suppress("LoopWithTooManyJumpStatements") - @Test - fun login_invalidCredentialsThrows() = runBlocking { - for (provider in AuthenticationProvider.entries) { - when (provider) { - AuthenticationProvider.ANONYMOUS -> { - // No user input, so invalid credentials are not possible. - null - } - AuthenticationProvider.API_KEY -> Credentials.apiKey("foo") - AuthenticationProvider.EMAIL_PASSWORD -> - Credentials.emailPassword("foo@bar.com", "123456") - AuthenticationProvider.JWT -> { - // There doesn't seem to be easy way to test this. - null - } - AuthenticationProvider.APPLE, - AuthenticationProvider.FACEBOOK, - AuthenticationProvider.GOOGLE -> { - // There doesn't seem to be a reliable way to throw "InvalidCredentials" for these. - null - } - AuthenticationProvider.CUSTOM_FUNCTION -> { - // There is no way to capture custom authentication credentials exceptions. - null - } - }?.let { credentials: Credentials -> - assertFailsWith { - app.login(credentials) - } - } - } - } - - @Test - fun currentUser() = runBlocking { - assertNull(app.currentUser) - val user: User = app.login(Credentials.anonymous()) - assertEquals(user, app.currentUser) - user.logOut() - assertNull(app.currentUser) - } - - @Test - fun allUsers() = runBlocking { - assertEquals(0, app.allUsers().size) - - val user1 = app.login(Credentials.anonymous()) - var allUsers = app.allUsers() - assertEquals(1, allUsers.size) - assertTrue(allUsers.contains(user1)) - - // Only 1 anonymous user exists, so logging in again just returns the old one - val user2 = app.login(Credentials.anonymous()) - allUsers = app.allUsers() - assertEquals(1, allUsers.size) - assertTrue(allUsers.contains(user2)) - - val user3: User = app.asTestApp.createUserAndLogIn(TestHelper.randomEmail(), "123456") - allUsers = app.allUsers() - assertEquals(2, allUsers.size) - assertTrue(allUsers.contains(user3)) - - // Logging out users that registered with email/password will just put them in LOGGED_OUT state - user3.logOut() - allUsers = app.allUsers() - assertEquals(2, allUsers.size) - assertTrue(allUsers.contains(user3)) - assertEquals(User.State.LOGGED_OUT, user3.state) - - // Logging out anonymous users will remove them completely - user1.logOut() - allUsers = app.allUsers() - assertEquals(1, allUsers.size) - assertTrue(allUsers.contains(user3)) - assertFalse(allUsers.contains(user2)) - assertFalse(allUsers.contains(user1)) - } - - @Test - fun allUsers_retrieveRemovedUser() = runBlocking { - val user1: User = app.login(Credentials.anonymous()) - val allUsers = app.allUsers() - assertEquals(1, allUsers.size) - user1.logOut() - assertEquals(1, allUsers.size) - val userCopy: User = allUsers.first() - assertEquals(user1, userCopy) - assertEquals(User.State.REMOVED, userCopy.state) - assertTrue(app.allUsers().isEmpty()) - } - - @Test - fun switchUser() = runBlocking { - val user1: User = app.login(Credentials.anonymous()) - assertEquals(user1, app.currentUser) - val user2: User = app.login(Credentials.anonymous()) - assertEquals(user2, app.currentUser) - - app.switchUser(user1) - assertEquals(user1, app.currentUser) - } - - @Test - fun switchUser_throwIfUserNotLoggedIn() = runBlocking { - val user1 = app.login(Credentials.anonymous()) - val user2 = app.login(Credentials.anonymous()) - assertEquals(user2, app.currentUser) - - user1.logOut() - try { - app.switchUser(user1) - fail() - } catch (ignore: IllegalStateException) { - } - } - - @Test - fun currentUser_FallbackToNextValidUser() = runBlocking { - assertNull(app.currentUser) - - val user1 = app.createUserAndLogIn(randomEmail(), "123456") - assertEquals(user1, app.currentUser) - - val user2 = app.createUserAndLogIn(randomEmail(), "123456") - assertEquals(user2, app.currentUser) - - user2.logOut() - assertEquals(user1, app.currentUser) - - user1.logOut() - assertNull(app.currentUser) - } - - @Test - @Ignore // Waiting for https://github.com/realm/realm-core/issues/6514 - fun currentUser_clearedAfterUserIsRemoved() = runBlocking { - assertNull(app.currentUser) - val user1 = app.login(Credentials.anonymous()) - assertEquals(user1, app.currentUser) - user1.remove() - assertNull(app.currentUser) - } - - @Test - fun authenticationChangeAsFlow() = runBlocking { - val c = TestChannel() - val job = async { - app.authenticationChangeAsFlow().collect { - c.send(it) - } - } - - val user1 = app.login(Credentials.anonymous()) - val loggedInEvent = c.receiveOrFail() - assertTrue(loggedInEvent is LoggedIn) - assertSame(user1, loggedInEvent.user) - - user1.logOut() - val loggedOutEvent = c.receiveOrFail() - assertTrue(loggedOutEvent is LoggedOut) - assertSame(user1, loggedOutEvent.user) - - // Repeating logout does not trigger a new event - user1.logOut() - val user2 = app.login(Credentials.anonymous()) - val reloginEvent = c.receiveOrFail() - assertEquals(user2, reloginEvent.user) - assertTrue(reloginEvent is LoggedIn) - - job.cancel() - c.close() - } - - @Test - fun authenticationChangeAsFlow_removeUser() = runBlocking { - val c = TestChannel() - val job = async { - app.authenticationChangeAsFlow().collect { - c.send(it) - } - } - val user1 = app.login(Credentials.anonymous(reuseExisting = true)) - val loggedInEvent = c.receiveOrFail() - assertTrue(loggedInEvent is LoggedIn) - - user1.remove() - val loggedOutEvent = c.receiveOrFail() - assertTrue(loggedOutEvent is Removed) - assertSame(user1, loggedOutEvent.user) - - job.cancel() - c.close() - - // Work-around for https://github.com/realm/realm-core/issues/6514 - // By logging the user back in, the TestApp teardown can correctly remove it. - app.login(Credentials.anonymous(reuseExisting = true)).logOut() - } - - @Test - fun authenticationChangeAsFlow_deleteUser() = runBlocking { - val c = TestChannel() - val job = async { - app.authenticationChangeAsFlow().collect { - c.send(it) - } - } - val user = app.login(Credentials.anonymous(reuseExisting = true)) - val loggedInEvent = c.receiveOrFail() - assertTrue(loggedInEvent is LoggedIn) - - user.delete() - val loggedOutEvent = c.receiveOrFail() - assertTrue(loggedOutEvent is Removed) - assertSame(user, loggedOutEvent.user) - - job.cancel() - c.close() - } - - @Test - fun authenticationChangeAsFlow_throwsWhenExceedCapacity() = runBlocking { - val latch = Mutex(locked = true) - val job = async { - app.authenticationChangeAsFlow().collect { - // Block `flow` from collecting any more events beside the first. - latch.withLock { - // Allow flow to continue - } - } - } - // Logging in 9 users should hit the capacity of the flow, causing the next - // login to fail. - repeat(9) { - app.createUserAndLogIn() - } - assertFailsWith { - app.createUserAndLogIn() - } - job.cancel() - } - - @Test - fun encryptedMetadataRealm() { - val tempDir = PlatformUtils.createTempDir() - - // Create new test app with a random encryption key - val key = TestHelper.getRandomKey() - TestApp( - "encryptedMetadataRealm", - DefaultFlexibleSyncAppInitializer, - builder = { - it - .encryptionKey(key) - .syncRootDirectory("$tempDir/foo") - } - ).use { app -> - // Create Realm in order to create the sync metadata Realm - val user = app.asTestApp.createUserAndLogin() - val syncConfig = SyncConfiguration - .Builder(user, FLEXIBLE_SYNC_SCHEMA) - .build() - Realm.open(syncConfig).close() - - // Create a configuration pointing to the metadata Realm for that app - val lastSetSchemaVersion = 7L - val metadataDir = "${app.configuration.syncRootDirectory}/mongodb-realm/${app.configuration.appId}/server-utility/metadata/" - val config = RealmConfiguration - .Builder(setOf()) - .name("sync_metadata.realm") - .directory(metadataDir) - .schemaVersion(lastSetSchemaVersion) - .encryptionKey(key) - .build() - assertTrue(fileExists(config.path)) - - // Should be possible to open the encrypted metadata realm file with the encryption key - Realm.open(config).close() - } - } - - @Test - fun encryptedMetadataRealm_openWithWrongKeyThrows() { - val tempDir = PlatformUtils.createTempDir() - - // Create new test app with a random encryption key - val correctKey = TestHelper.getRandomKey() - TestApp( - "encryptedMetadataRealm_openWithWrongKeyThrows", - DefaultFlexibleSyncAppInitializer, - builder = { - it - .encryptionKey(correctKey) - .syncRootDirectory("$tempDir/foo") - } - ).use { app -> - // Create Realm in order to create the sync metadata Realm - val user = app.asTestApp.createUserAndLogin() - val syncConfig = SyncConfiguration - .Builder(user, FLEXIBLE_SYNC_SCHEMA) - .build() - Realm.open(syncConfig).close() - - // Create a configuration pointing to the metadata Realm for that app - val metadataDir = "${app.configuration.syncRootDirectory}/mongodb-realm/${app.configuration.appId}/server-utility/metadata/" - - // Workaround for https://github.com/realm/realm-core/issues/7876 - // We cannot validate if the test app metadata realm is encrypted directly, as it is cached - // and subsequent access wont validate the encryption key. Copying the Realm allows to bypass - // the cache. - PlatformUtils.copyFile(metadataDir + "sync_metadata.realm", metadataDir + "copy_sync_metadata.realm") - - val wrongKey = TestHelper.getRandomKey() - val config = RealmConfiguration - .Builder(setOf()) - .name("copy_sync_metadata.realm") - .directory(metadataDir) - .encryptionKey(wrongKey) - .schemaVersion(7) - .build() - assertTrue(fileExists(config.path)) - - // Open the metadata realm file with an invalid encryption key - assertNotEquals(correctKey, wrongKey) - assertFailsWithMessage("Failed to open Realm file at path") { - Realm.open(config) - } - } - } - - @Test - fun encryptedMetadataRealm_openWithoutKeyThrows() { - val tempDir = PlatformUtils.createTempDir() - - // Create new test app with a random encryption key - TestApp( - "encryptedMetadataRealm_openWithoutKeyThrows", - DefaultFlexibleSyncAppInitializer, - builder = { - it - .encryptionKey(TestHelper.getRandomKey()) - .syncRootDirectory("$tempDir/foo") - } - ).use { app -> - // Create Realm in order to create the sync metadata Realm - val user = app.asTestApp.createUserAndLogin() - val syncConfig = SyncConfiguration - .Builder(user, FLEXIBLE_SYNC_SCHEMA) - .build() - Realm.open(syncConfig).close() - - // Create a configuration pointing to the metadata Realm for that app - val metadataDir = "${app.configuration.syncRootDirectory}/mongodb-realm/${app.configuration.appId}/server-utility/metadata/" - - // Workaround for https://github.com/realm/realm-core/issues/7876 - // We cannot validate if the test app metadata realm is encrypted directly, as it is cached - // and subsequent access wont validate the encryption key. Copying the Realm allows to bypass - // the cache. - PlatformUtils.copyFile(metadataDir + "sync_metadata.realm", metadataDir + "copy_sync_metadata.realm") - - val config = RealmConfiguration - .Builder(setOf()) - .name("copy_sync_metadata.realm") - .directory(metadataDir) - .schemaVersion(7) - .build() - assertTrue(fileExists(config.path)) - - // Open the metadata realm file without a valid encryption key - assertFailsWithMessage("Failed to open Realm file at path") { - Realm.open(config) - } - } - } - - /** - * The app id must exist on the new base url, it is validated and an exception would be thrown. - * - * This test case circumvents this issue by initializing an app to a url that does not contain the - * app, as it is not validated on initialization. And then updating the base url the the test server. - */ - @Test - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl() { - TestApp( - testId = "changeBaseUrl", - DefaultPartitionBasedAppInitializer, - builder = { builder -> - // We create a test app that points to the default base url - // this app is not going to be validated yet. - builder.baseUrl(AppConfiguration.DEFAULT_BASE_URL) - } - ).use { testApp -> - assertEquals(AppConfiguration.DEFAULT_BASE_URL, testApp.baseUrl) - - runBlocking { - // Update the base url, this method will validate the app - // if the app id is not available it would fail. - testApp.updateBaseUrl(app.configuration.baseUrl) - } - assertEquals(app.configuration.baseUrl, testApp.baseUrl) - } - } - - @Test - // We don't have a way to test this on CI, so for now just verify manually that the - // request towards the server after setting the URL to null is using the default URL. - @Ignore - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl_null() { - TestApp( - testId = "changeBaseUrl", - DefaultPartitionBasedAppInitializer, - ).use { testApp -> - assertEquals(SyncServerConfig.url, testApp.baseUrl) - - RealmLog.setLevel(LogLevel.ALL) - runBlocking { - testApp.updateBaseUrl(null) - } - } - } - - @Test - @Ignore // See https://github.com/realm/realm-kotlin/issues/1734 - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl_trailing_slashes_trimmed() { - assertFailsWithMessage("cannot find app using Client App ID") { - runBlocking { - app.updateBaseUrl(AppConfiguration.DEFAULT_BASE_URL + "///") - } - } - } - - @Test - @Ignore // see https://github.com/realm/realm-kotlin/issues/1734 - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl_empty() { - assertFailsWithMessage("cannot find app using Client App ID") { - runBlocking { - app.updateBaseUrl("") - } - } - } - - @Test - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl_invalidUrl() { - assertFailsWithMessage("URL missing scheme") { - runBlocking { - app.updateBaseUrl("hello world") - } - } - } - - @Test - @Ignore // see https://github.com/realm/realm-kotlin/issues/1734 - @OptIn(ExperimentalEdgeServerApi::class) - fun changeBaseUrl_nonAppServicesUrl() { - assertFailsWithMessage("http error code considered fatal") { - runBlocking { - app.updateBaseUrl("https://www.google.com/") - } - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt deleted file mode 100644 index 4c1bf09f0e..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/AsymmetricSyncTests.kt +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -@file:Suppress("invisible_member", "invisible_reference") -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.dynamic.DynamicMutableRealm -import io.realm.kotlin.dynamic.DynamicMutableRealmObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.InternalConfiguration -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.annotations.ExperimentalAsymmetricSyncApi -import io.realm.kotlin.mongodb.ext.insert -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.schema.RealmClassKind -import io.realm.kotlin.test.StandaloneDynamicMutableRealm -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.delay -import org.mongodb.kbson.ObjectId -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -@OptIn(ExperimentalAsymmetricSyncApi::class) -class AsymmetricSyncTests { - - private lateinit var app: TestApp - private lateinit var realm: Realm - private lateinit var config: SyncConfiguration - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - config = SyncConfiguration.Builder( - user, - schema = FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { - it.query().subscribe() - }.build() - realm = Realm.open(config) - } - - @AfterTest - fun tearDown() { - if (this::realm.isInitialized) { - realm.close() - } - runBlocking { - app.deleteDocuments(app.clientAppId, Measurement::class.simpleName!!, "{}") - } - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun insert() = runBlocking { - val initialServerDocuments = app.countDocuments("Measurement") - val newDocuments = 10 - realm.write { - repeat(newDocuments) { no -> - insert( - Measurement().apply { - value = 42.0f + no.toFloat() - device = Device(name = "living room", serialNumber = "TMP432142") - } - ) - } - } - - realm.syncSession.uploadAllLocalChangesOrFail() - verifyDocuments(clazz = "Measurement", expectedCount = newDocuments, initialCount = initialServerDocuments) - } - - @Test - fun insert_samePrimaryKey_throws() { - val generatedId = ObjectId() - realm.writeBlocking { - insert( - Measurement().apply { - id = generatedId - } - ) - assertFailsWith("foo") { - insert( - Measurement().apply { - id = generatedId - } - ) - } - } - } - - @Test - fun realmClassSchema_isAsymmetric() { - assertEquals(RealmClassKind.ASYMMETRIC, realm.schema()[Measurement::class.simpleName!!]!!.kind) - } - - @Test - fun findLatestThrows() { - realm.writeBlocking { - // There is no way to get a managed Measurement, but calling `findLatest` on an - // unmanaged AsymmetricObjet should still fail. - assertFailsWith { - findLatest(Measurement()) - } - } - } - - // If you have A -> B, where A is an asymmetric object and B is embedded it is still possible - // to query for B. However, no objects belong to asymmetric objects will be found. - @Test - fun nestedEmbeddedHierarchyIsQueryable() = runBlocking { - realm.syncSession.pause() - realm.write { - repeat(10) { no -> - insert( - Measurement().apply { - value = 42.0f + no.toFloat() - device = Device(name = "living room", serialNumber = "TMP432142").apply { - backupDevice = BackupDevice(name = "kitchen", serialNumber = "TMP14090") - } - } - ) - } - repeat(10) { - copyToRealm( - DeviceParent().apply { - device = Device(name = "living room", serialNumber = "TMP432142") - } - ) - } - } - // Will only find the embedded objects belong to standard realm objects - assertEquals(10, realm.query().count().find()) - - // Nested embedded objects are not searchable either if the top-level is asymmetric. - assertEquals(0, realm.query().count().find()) - } - - @Test - fun deleteAll_doNotDeleteAsymmetricObjects() = runBlocking { - val initialServerDocuments = app.countDocuments("Measurement") - val newDocuments = 10 - realm.syncSession.pause() - realm.write { - repeat(newDocuments) { no -> - insert( - Measurement().apply { - value = 42.0f + no.toFloat() - device = Device(name = "living room", serialNumber = "TMP432142") - } - ) - } - } - - // Deleting everything should not delete the asymmetric objects - realm.write { - deleteAll() - } - - // Re-enable the sync session and verify that all objects made it to the server. - realm.syncSession.run { - resume() - uploadAllLocalChanges(30.seconds) - } - verifyDocuments(clazz = "Measurement", expectedCount = newDocuments, initialCount = initialServerDocuments) - } - - @Test - fun mutableDynamicRealm_insert_unsuportedType() { - useDynamicRealm { dynamicRealm: DynamicMutableRealm -> - val realmObject = DynamicMutableRealmObject.create(DeviceParent::class.simpleName!!) - assertFailsWith { - dynamicRealm.insert(realmObject) - } - val embeddedRealmObject = DynamicMutableRealmObject.create(BackupDevice::class.simpleName!!) - assertFailsWith { - dynamicRealm.insert(embeddedRealmObject) - } - } - } - - @Test - fun mutableDynamicRealm_copyToRealm_throws() { - useDynamicRealm { dynamicRealm: DynamicMutableRealm -> - val asymmetricObject = DynamicMutableRealmObject.create(Measurement::class.simpleName!!) - assertFailsWith { - dynamicRealm.copyToRealm(asymmetricObject) - } - } - } - - @Test - fun mutableDynamicRealm_query_throws() { - useDynamicRealm { dynamicRealm: DynamicMutableRealm -> - assertFailsWith { - dynamicRealm.query(Measurement::class.simpleName!!) - } - } - } - - @Test - fun mutableDynamicRealm_delete_throws() { - useDynamicRealm { dynamicRealm: DynamicMutableRealm -> - assertFailsWith { - dynamicRealm.delete(Measurement::class.simpleName!!) - } - } - } - - // Verify that a schema of Asymmetric -> Embedded -> RealmObject work. - @Test - @Ignore - // See https://github.com/realm/realm-kotlin/issues/1823 - fun asymmetricSchema() = runBlocking { - config = SyncConfiguration.Builder( - app.login(Credentials.anonymous()), - schema = FLEXIBLE_SYNC_SCHEMA - ).build() - val initialServerDocuments = app.countDocuments("AsymmetricA") - Realm.open(config).use { - it.write { - insert( - AsymmetricA().apply { - child = EmbeddedB().apply { - StandardC() - } - } - ) - } - it.syncSession.uploadAllLocalChangesOrFail() - verifyDocuments("AsymmetricA", 1, initialServerDocuments) - } - } - - private suspend fun verifyDocuments(clazz: String, expectedCount: Int, initialCount: Int) { - // Variable shared across while must be atomic - // https://youtrack.jetbrains.com/issue/KT-64139/Native-Bug-with-while-loop-coroutine-which-is-started-and-stopped-on-the-same-thread - var documents = atomic(0) - var found = false - var attempt = 60 // Wait 1 minute - // The translator might be slow to incorporate changes into MongoDB, so we retry for a bit - // before giving up. - while (!found && attempt > 0) { - documents.value = app.countDocuments(clazz) - initialCount - if (documents.value == expectedCount) { - found = true - } else { - attempt -= 1 - delay(1.seconds) - } - } - assertTrue(found, "Number of documents was: ${documents.value} [initialCount: $initialCount, expectedCount: $expectedCount]") - } - - private fun useDynamicRealm(function: (DynamicMutableRealm) -> Unit) { - val dynamicMutableRealm = StandaloneDynamicMutableRealm(realm.configuration as InternalConfiguration) - dynamicMutableRealm.beginTransaction() - try { - function(dynamicMutableRealm) - } finally { - dynamicMutableRealm.close() - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt deleted file mode 100644 index 75e1e3f828..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/CredentialsTests.kt +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") -@file:OptIn(ExperimentalRealmSerializerApi::class) - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.AuthenticationProvider -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.GoogleAuthType -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.auth.ApiKey -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.exceptions.AuthException -import io.realm.kotlin.mongodb.internal.AppImpl -import io.realm.kotlin.mongodb.internal.CredentialsImpl -import io.realm.kotlin.mongodb.internal.CustomEJsonCredentialsImpl -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper -import kotlinx.serialization.Serializable -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.test.fail - -@Serializable -data class CustomCredentialsPayload( - val id: Int, - val mail: String -) - -@Suppress("ForbiddenComment") -class CredentialsTests { - - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun allCredentials() { - AuthenticationProvider.entries.flatMap { - when (it) { - AuthenticationProvider.ANONYMOUS -> listOf(it to anonymous()) - AuthenticationProvider.EMAIL_PASSWORD -> listOf(it to emailPassword()) - AuthenticationProvider.API_KEY -> listOf(it to apiKey()) - AuthenticationProvider.APPLE -> listOf(it to apple()) - AuthenticationProvider.FACEBOOK -> listOf(it to facebook()) - AuthenticationProvider.GOOGLE -> listOf() // Ignore, see below - AuthenticationProvider.JWT -> listOf(it to jwt()) - AuthenticationProvider.CUSTOM_FUNCTION -> listOf( - it to customFunction(), - it to customFunctionExperimental(), - it to customFunctionExperimentalWithSerializer(), - ) - } - }.forEach { (authenticationProvider, credentials) -> - assertEquals(authenticationProvider, credentials.authenticationProvider) - } - - // Special case for Anonymous having 'reuseExisting' - val nonReusableAnonymous = anonymous(false) - assertEquals(AuthenticationProvider.ANONYMOUS, nonReusableAnonymous.authenticationProvider) - - // Special case for Google Auth having two types - val googleIdToken = google_idToken() - assertEquals(AuthenticationProvider.GOOGLE, googleIdToken.authenticationProvider) - google_authCode() - } - - @Test - fun allCredentials_emptyInputThrows() { - for (value in AuthenticationProvider.entries) { - assertFailsWith("$value failed") { // No arguments should be allow - when (value) { - AuthenticationProvider.ANONYMOUS -> throw IllegalArgumentException("Do nothing, no arguments") - AuthenticationProvider.API_KEY -> Credentials.apiKey("") - AuthenticationProvider.APPLE -> Credentials.apple("") - AuthenticationProvider.EMAIL_PASSWORD -> throw IllegalArgumentException("Test below as a special case") - AuthenticationProvider.FACEBOOK -> Credentials.facebook("") - AuthenticationProvider.GOOGLE -> throw IllegalArgumentException("Test below as a special case") - AuthenticationProvider.JWT -> Credentials.jwt("") - AuthenticationProvider.CUSTOM_FUNCTION -> throw IllegalArgumentException("No check required") - } - } - } - - // Test Email/Password as a special case, due to it having two arguments - assertFailsWith { Credentials.emailPassword("", "password") } - assertFailsWith { Credentials.emailPassword("foo@bar.com", "") } - - // Test Google as a special case as two types of Google login exists - assertFailsWith { - Credentials.google("", GoogleAuthType.AUTH_CODE) - } - assertFailsWith { - Credentials.google("", GoogleAuthType.ID_TOKEN) - } - } - - @Suppress("invisible_reference", "invisible_member") - private fun anonymous(reuseExisting: Boolean = true): Credentials { - val creds: Credentials = Credentials.anonymous(reuseExisting) - val credsImpl = creds as CredentialsImpl - assertTrue(credsImpl.asJson().contains("anon-user")) // Treat the JSON as an opaque value. - return creds - } - - @Suppress("invisible_reference", "invisible_member") - private fun apiKey(): Credentials { - val creds: Credentials = Credentials.apiKey("token") - val credsImpl = creds as CredentialsImpl - assertTrue(credsImpl.asJson().contains("token")) // Treat the JSON as an opaque value. - return creds - } - - private fun apple(): Credentials { - val creds = Credentials.apple("apple-token") - assertJsonContains(creds, "apple-token") - return creds - } - - private fun customFunction(): Credentials { - val mail = TestHelper.randomEmail() - val id = 700 - val credentials = Credentials.customFunction( - payload = mapOf("mail" to mail, "id" to id) - ) - - assertEquals(AuthenticationProvider.CUSTOM_FUNCTION, credentials.authenticationProvider) - assertJsonContains(credentials, mail) - assertJsonContains(credentials, id.toString()) - return credentials - } - - private fun customFunctionExperimental(): Credentials { - val mail = TestHelper.randomEmail() - val id = 700 - - val credentials = Credentials.customFunction( - payload = CustomCredentialsPayload( - id = id, - mail = mail, - ) - ) - - assertEquals(AuthenticationProvider.CUSTOM_FUNCTION, credentials.authenticationProvider) - assertJsonContains(credentials, mail) - assertJsonContains(credentials, id.toString()) - return credentials - } - - private fun customFunctionExperimentalWithSerializer(): Credentials { - val mail = TestHelper.randomEmail() - val id = 700 - - val credentials = Credentials.customFunction( - payload = CustomCredentialsPayload( - id = id, - mail = mail, - ), - serializer = CustomCredentialsPayload.serializer() - ) - - assertEquals(AuthenticationProvider.CUSTOM_FUNCTION, credentials.authenticationProvider) - assertJsonContains(credentials, mail) - assertJsonContains(credentials, id.toString()) - return credentials - } - - private fun emailPassword(): Credentials { - val creds = Credentials.emailPassword("foo@bar.com", "secret") - assertJsonContains(creds, "foo@bar.com") - assertJsonContains(creds, "secret") - return creds - } - - private fun facebook(): Credentials { - val creds = Credentials.facebook("fb-token") - assertEquals(AuthenticationProvider.FACEBOOK, creds.authenticationProvider) - assertJsonContains(creds, "fb-token") - return creds - } - - private fun google_authCode() { - val creds = Credentials.google("google-token", GoogleAuthType.AUTH_CODE) - assertEquals(AuthenticationProvider.GOOGLE, creds.authenticationProvider) - assertJsonContains(creds, "google-token") - assertJsonContains(creds, "authCode") - } - - @Suppress("invisible_reference", "invisible_member") - private fun google_idToken(): Credentials { - val creds = Credentials.google("google-token", GoogleAuthType.ID_TOKEN) - assertEquals(AuthenticationProvider.GOOGLE, creds.authenticationProvider) - assertJsonContains(creds, "google-token") - assertJsonContains(creds, "id_token") - return creds - } - - @Suppress("invisible_reference", "invisible_member") - private fun jwt(): Credentials { - val creds = Credentials.jwt("jwt-token") - assertEquals(AuthenticationProvider.JWT, creds.authenticationProvider) - assertJsonContains(creds, "jwt-token") - return creds - } - - // Since integration tests of Credentials are very hard to setup, we instead just fake it - // by checking that the JSON payload we send to the server seems to be correct. If that is - // the case, we assume the server does the right thing (and has tests for it). - private fun assertJsonContains(creds: Credentials, subString: String) { - val jsonEncodedCredentials = when (creds) { - is CredentialsImpl -> { - creds.asJson() - } - is CustomEJsonCredentialsImpl -> { - creds.asJson(app.app as AppImpl) - } - else -> error("Invalid crendentials type ${creds::class.simpleName}") - } - - // Treat the JSON as a largely opaque value. - assertTrue( - jsonEncodedCredentials.contains(subString), - "[$jsonEncodedCredentials] does not contain [$subString]" - ) - } - - @Test - fun anonymousLogin() { - runBlocking { - val firstUser = app.login(Credentials.anonymous()) - assertNotNull(firstUser) - val reusedUser = app.login(Credentials.anonymous()) - assertNotNull(reusedUser) - assertEquals(firstUser, reusedUser) - - val newAnonymousUser1 = app.login(Credentials.anonymous(false)) - assertNotNull(newAnonymousUser1) - assertNotEquals(firstUser, newAnonymousUser1) - - val newAnonymousUser2 = app.login(Credentials.anonymous(false)) - assertNotNull(newAnonymousUser2) - assertNotEquals(newAnonymousUser1, newAnonymousUser2) - } - } - - @Test - fun loginUsingCredentials() { - runBlocking { - AuthenticationProvider.entries.forEach { provider -> - when (provider) { - AuthenticationProvider.ANONYMOUS -> { - val reusableUser = app.login(Credentials.anonymous()) - assertNotNull(reusableUser) - val nonReusableUser = app.login(Credentials.anonymous(false)) - assertNotNull(nonReusableUser) - assertNotEquals(reusableUser, nonReusableUser) - } - AuthenticationProvider.API_KEY -> { - // Log in, create an API key, log out, log in with the key, compare users - val user: User = - app.createUserAndLogIn(TestHelper.randomEmail(), "password1234") - val key: ApiKey = user.apiKeyAuth.create("my-key") - user.logOut() - val apiKeyUser = app.login(Credentials.apiKey(key.value!!)) - assertEquals(user.id, apiKeyUser.id) - } - AuthenticationProvider.CUSTOM_FUNCTION -> { - val credentials = Credentials.customFunction( - payload = mapOf("mail" to TestHelper.randomEmail(), "id" to 700) - ) - - // We are not testing the authentication function itself, but rather that the - // credentials work - val functionUser = app.login(credentials) - assertNotNull(functionUser) - - // Test customFunction with kserializer - setOf( - Credentials.customFunction( - payload = CustomCredentialsPayload( - mail = TestHelper.randomEmail(), - id = 700 - ) - ), - Credentials.customFunction( - payload = CustomCredentialsPayload( - mail = TestHelper.randomEmail(), - id = 700 - ), - serializer = CustomCredentialsPayload.serializer() - ) - ).forEach { credentials: Credentials -> - // We are not testing the authentication function itself, but rather that the - // credentials work - val functionUserExperimental = app.login(credentials) - assertNotNull(functionUserExperimental) - } - } - AuthenticationProvider.EMAIL_PASSWORD -> { - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = app.createUserAndLogIn(email, password) - assertNotNull(user) - } - // These providers are hard to test for real since they depend on a 3rd party - // login service. Instead we attempt to login and verify that a proper exception - // is thrown. At least that should verify that correctly formatted JSON is being - // sent across the wire. - AuthenticationProvider.FACEBOOK -> - expectInvalidSession(app, Credentials.facebook("facebook-token")) - AuthenticationProvider.APPLE -> - expectInvalidSession(app, Credentials.apple("apple-token")) - AuthenticationProvider.GOOGLE -> { - expectInvalidSession( - app, - Credentials.google("google-token", GoogleAuthType.AUTH_CODE) - ) - expectInvalidSession( - app, - Credentials.google("google-token", GoogleAuthType.ID_TOKEN) - ) - } - AuthenticationProvider.JWT -> - expectInvalidSession(app, Credentials.jwt("jwt-token")) - else -> error("Untested provider: $provider") - } - } - } - } - - @Test - fun customFunction_authExceptionThrownOnError() { - val credentials = Credentials.customFunction( - payload = mapOf("mail" to TestHelper.randomEmail(), "id" to 0) - ) - - assertFailsWithMessage("Authentication failed") { - runBlocking { - app.login(credentials) - } - } - } - - private fun expectInvalidSession(app: App, credentials: Credentials) { - try { - runBlocking { - app.login(credentials) - } - fail() - } catch (error: AppException) { - assertTrue(error.message!!.contains("unauthorized"), error.message) - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt deleted file mode 100644 index 39f123121c..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/EmailPasswordAuthTests.kt +++ /dev/null @@ -1,360 +0,0 @@ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.auth.EmailPasswordAuth -import io.realm.kotlin.mongodb.exceptions.AppException -import io.realm.kotlin.mongodb.exceptions.AuthException -import io.realm.kotlin.mongodb.exceptions.BadRequestException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.exceptions.UserAlreadyConfirmedException -import io.realm.kotlin.mongodb.exceptions.UserAlreadyExistsException -import io.realm.kotlin.mongodb.exceptions.UserNotFoundException -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.syncServerAppName -import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.BaseAppInitializer -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.mongodb.util.addEmailProvider -import io.realm.kotlin.test.util.TestHelper -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.test.fail - -class EmailPasswordAuthWithAutoConfirmTests { - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun registerUser() = runBlocking { - val (email, password) = TestHelper.randomEmail() to "password1234" - app.emailPasswordAuth.registerUser(email, password) - val user = app.login(Credentials.emailPassword(email, password)) - assertNotNull(user) - Unit - } - - @Test - fun registerUser_sameUserThrows() = runBlocking { - val (email, password) = TestHelper.randomEmail() to "password1234" - app.emailPasswordAuth.registerUser(email, password) - assertFailsWith { - app.emailPasswordAuth.registerUser(email, password) - } - Unit - } - - @Test - fun registerUser_invalidServerArgsThrows_invalidUser() = runBlocking { - // Invalid mail and too short password - val (email, password) = "invalid-email" to "1234" - // TODO do exhaustive exception assertion once we have all AppException fields in place - assertFailsWith { - app.emailPasswordAuth.registerUser(email, password) - } - Unit - } - - @Test - fun registerUser_invalidServerArgsThrows_invalidPassword() { - runBlocking { - // Valid mail but too short password - val (email, password) = TestHelper.randomEmail() to "1234" - // TODO do exhaustive exception assertion once we have all AppException fields in place - assertFailsWith { - app.emailPasswordAuth.registerUser(email, password) - } - } - } - - @Ignore - @Test - fun confirmUser() { - TODO("Figure out how to manually test this") - } - - @Ignore - @Test - fun confirmUser_alreadyConfirmedThrows() { - TODO("Figure out how to manually test this") - } - - @Test - fun confirmUser_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth - runBlocking { - // TODO Do better validation when AppException is done - // assertEquals(ErrorCode.BAD_REQUEST, ex.errorCode) - assertFailsWith { - provider.confirmUser("invalid-token", "invalid-token-id") - } - } - } - - @Test - fun confirmUser_invalidArgumentsThrows() { - val provider = app.emailPasswordAuth - runBlocking { - assertFailsWith { provider.confirmUser("", "token-id") } - assertFailsWith { provider.confirmUser("token", "") } - } - } - - @Test - fun resendConfirmationEmail_userAlreadyConfirmedThrows() = runBlocking { - val email = TestHelper.randomEmail() - val provider = app.emailPasswordAuth - provider.registerUser(email, "123456") - assertFailsWith { provider.resendConfirmationEmail(email) } - Unit - } - - @Test - fun resendConfirmationEmail_invalidArgumentsThrows() = runBlocking { - val provider: EmailPasswordAuth = app.emailPasswordAuth - assertFailsWith { provider.resendConfirmationEmail("") } - Unit - } - - @Test - fun sendResetPasswordEmail() = runBlocking { - val provider = app.emailPasswordAuth - val email = TestHelper.randomEmail() - provider.registerUser(email, "123456") - provider.sendResetPasswordEmail(email) - } - - @Test - fun sendResetPasswordEmail_noUserThrows() = runBlocking { - val provider = app.emailPasswordAuth - val error = assertFailsWith { provider.sendResetPasswordEmail("unknown@10gen.com") } - assertTrue(error.message!!.contains("user not found"), error.message) - } - - @Test - fun sendResetPasswordEmail_invalidArgumentsThrows() = runBlocking { - val provider = app.emailPasswordAuth - assertFailsWith { provider.sendResetPasswordEmail("") } - Unit - } - - @Test - fun callResetPasswordFunction() { - val provider = app.emailPasswordAuth - val adminApi = app.asTestApp - runBlocking { - adminApi.setResetFunction(enabled = true) - val email = TestHelper.randomEmail() - provider.registerUser(email, "123456") - try { - provider.callResetPasswordFunction(email, "new-password", "say-the-magic-word", 42) - val user = app.login(Credentials.emailPassword(email, "new-password")) - user.logOut() - } finally { - adminApi.setResetFunction(enabled = false) - } - } - } - - @Test - fun callResetPasswordFunction_invalidServerArgsThrows() { - val provider = app.emailPasswordAuth - val adminApi = app.asTestApp - runBlocking { - adminApi.setResetFunction(enabled = true) - val email = TestHelper.randomEmail() - provider.registerUser(email, "123456") - try { - provider.callResetPasswordFunction(email, "new-password", "wrong-magic-word") - } catch (error: ServiceException) { - assertTrue(error.message!!.contains("failed to reset password for user \"$email\""), error.message) - } finally { - adminApi.setResetFunction(enabled = false) - } - } - } - - @Test - fun callResetPasswordFunction_invalidArgumentsThrows() { - val provider = app.emailPasswordAuth - val adminApi = app.asTestApp - runBlocking { - adminApi.setResetFunction(enabled = true) - val email = TestHelper.randomEmail() - assertFailsWith { provider.callResetPasswordFunction("", "password") } - assertFailsWith { provider.callResetPasswordFunction(email, "") } - assertFailsWith { provider.callResetPasswordFunction(email, "new-password2", object {}) } - } - } - - @Ignore - @Test - fun resetPassword() { - TODO("Find a way to test this.") - } - - @Test - fun resetPassword_wrongArgumentTypesThrows() = runBlocking { - val provider = app.emailPasswordAuth - try { - provider.resetPassword("invalid-token", "invalid-token-id", "new-password") - } catch (error: BadRequestException) { - assertTrue(error.message!!.contains("invalid token data"), error.message) - } - } - - @Ignore - @Test - fun resetPassword_noUserFoundThrows() { - // If the token data is valid but the user no longer exists, a different - // error is thrown: https://github.com/10gen/baas/blob/master/authprovider/providers/local/password_store_test.go - // Find a way to test this. - } - - @Test - fun resetPassword_invalidArgumentsThrows() = runBlocking { - val provider = app.emailPasswordAuth - assertFailsWith { provider.resetPassword("", "token-id", "password") } - assertFailsWith { provider.resetPassword("token", "", "password") } - assertFailsWith { provider.resetPassword("token", "token-id", "") } - Unit - } -} - -class EmailPasswordAuthWithEmailConfirmTests { - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp( - this::class.simpleName, - object : BaseAppInitializer( - syncServerAppName("em-cnfrm"), - { app: BaasApp -> addEmailProvider(app, autoConfirm = false) } - ) {} - ) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun resendConfirmationEmail() = runBlocking { - // We only test that the server successfully accepts the request. We have no way of knowing - // if the Email was actually sent. - // TODO Figure out a way to check if this actually happened. Perhaps a custom SMTP server? - val email = TestHelper.randomEmail() - val provider = app.emailPasswordAuth - provider.registerUser(email, "123456") - provider.resendConfirmationEmail(email) - } - - @Test - fun resendConfirmationEmail_noUserThrows() = runBlocking { - val email = TestHelper.randomEmail() - val provider = app.emailPasswordAuth - provider.registerUser(email, "123456") - val error = assertFailsWith { provider.resendConfirmationEmail("foo") } - assertTrue(error.message!!.contains("user not found"), error.message) - } -} - -class EmailPasswordAuthWithCustomFunctionTests { - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp( - this::class.simpleName, - object : BaseAppInitializer( - syncServerAppName("em-cstm"), - { app -> - addEmailProvider(app, autoConfirm = false, runConfirmationFunction = true) - } - ) {} - ) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun retryCustomConfirmation() = runBlocking { - val (email, password) = "realm_pending_${TestHelper.randomEmail()}" to "123456" - val provider = app.emailPasswordAuth - provider.registerUser(email, password) // Will move to "pending" - assertFailsWith { - app.login(Credentials.emailPassword(email, password)) - } - provider.retryCustomConfirmation(email) // Will properly "confirm" - app.login(Credentials.emailPassword(email, password)) - Unit - } - - @Test - fun retryCustomConfirmation_failConfirmation() = runBlocking { - // Only emails containing realm_tests_do_autoverify will be confirmed - val email = "do_not_confirm_${TestHelper.randomEmail()}" - val provider = app.emailPasswordAuth - val exception = assertFailsWith { - provider.retryCustomConfirmation(email) - } - assertTrue(exception.message!!.contains("user not found"), exception.message) - } - - @Test - fun retryCustomConfirmation_noUserThrows() = runBlocking { - val email = "realm_pending_${TestHelper.randomEmail()}" - val provider = app.emailPasswordAuth - provider.registerUser(email, "123456") - try { - provider.retryCustomConfirmation("foo@gen.com") - fail() - } catch (error: UserNotFoundException) { - assertTrue(error.message!!.contains("user not found"), error.message) - } - } - - @Test - fun retryCustomConfirmation_alreadyConfirmedThrows() = runBlocking { - val email = "realm_verify_${TestHelper.randomEmail()}" - val provider = app.emailPasswordAuth - provider.registerUser(email, "123456") - assertFailsWith { - provider.retryCustomConfirmation(email) - } - Unit - } - - @Test - fun retryCustomConfirmation_invalidArgumentsThrows() = runBlocking { - val provider: EmailPasswordAuth = app.emailPasswordAuth - assertFailsWith { provider.retryCustomConfirmation("") } - Unit - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt deleted file mode 100644 index 1ff2992425..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FLXProgressListenerTests.kt +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.Direction -import io.realm.kotlin.mongodb.sync.Progress -import io.realm.kotlin.mongodb.sync.ProgressMode -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.buffer -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.last -import kotlinx.coroutines.flow.scan -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withTimeout -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -class FLXProgressListenerTests { - - private val TEST_SIZE = 10 - private val TIMEOUT = 30.seconds - - private lateinit var app: TestApp - private lateinit var partitionValue: String - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - partitionValue = org.mongodb.kbson.ObjectId().toString() - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun downloadProgressListener_changesOnly() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { uploadRealm -> - // Verify that we: - // - get a "transferComplete" event - // - complete the flow, and - // - that all objects are available afterwards - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - // Ensure that we can do consecutive CURRENT_CHANGES registrations - for (i in 0 until 3) { - val transferCompleteJob = async { - // Postpone the progress listener flow so that it is started after the - // following downloadAllServerChanges. This should ensure that we are - // actually downloading stuff. - realm.syncSession.progressAsFlow( - Direction.DOWNLOAD, - ProgressMode.CURRENT_CHANGES - ).run { - withTimeout(TIMEOUT) { - last().let { progress: Progress -> - assertTrue(progress.isTransferComplete) - assertEquals(1.0, progress.estimate) - } - } - } - } - uploadRealm.writeSampleData( - TEST_SIZE, - timeout = TIMEOUT - ) - transferCompleteJob.await() - - // Progress.isTransferComplete does not guarantee that changes are integrated and - // visible in the realm - realm.syncSession.downloadAllServerChanges(TIMEOUT) - assertEquals( - TEST_SIZE * (i + 1), - realm.query().find().size - ) - } - } - } - } - - @Test - fun downloadProgressListener_indefinitely() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { uploadRealm -> - uploadRealm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession - .progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - .completionCounter() - - withTimeout(TIMEOUT) { - flow.takeWhile { completed -> completed < 3 } - .collect { _ -> - uploadRealm.writeSampleData( - TEST_SIZE, - timeout = TIMEOUT - ) - } - } - } - } - } - - @Test - fun uploadProgressListener_changesOnly() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - for (i in 0..3) { - val task = async { - realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.CURRENT_CHANGES) - .run { - last().let { - assertTrue(it.isTransferComplete) - assertEquals(1.0, it.estimate) - } - } - } - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - withTimeout(TIMEOUT) { - task.await() - } - } - } - } - - @Test - fun uploadProgressListener_indefinitely() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - .completionCounter() - - withTimeout(TIMEOUT) { - flow.takeWhile { completed -> completed < 3 } - .collect { _ -> - realm.writeSampleData(TEST_SIZE) - } - } - } - } - - @Test - fun worksAfterExceptions() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - } - - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - assertFailsWith { - flow.collect { - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("Crashing progress flow") - } - } - - withTimeout(TIMEOUT) { - flow.first { it.isTransferComplete } - } - } - } - - @Test - fun worksAfterCancel() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { writerRealm -> - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - // Setup a flow that we are just going to cancel - val flow = realm.syncSession.progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - - supervisorScope { - val mutex = Mutex(true) - val task = async { - flow.collect { - mutex.unlock() - } - } - // Await the flow actually being active, this requires actual data transfer as - // we arent guaranteed any initial events. - writerRealm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - mutex.lock() - task.cancel() - } - - // Verify that progress listeners still work - withTimeout(TIMEOUT) { - val task = async { flow.first { it.isTransferComplete } } - // Trigger data transfer to ensure we get an event at some point - writerRealm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - task.await() - } - } - } - } - - @Test - fun completesOnClose() = runBlocking { - val channel = TestChannel(capacity = 5, onBufferOverflow = BufferOverflow.DROP_OLDEST, failIfBufferIsEmptyOnCancel = false) - TestApp("completesOnClose", DefaultFlexibleSyncAppInitializer).use { app -> - val user = app.createUserAndLogIn() - val realm = Realm.open(createSyncConfig(user)) - try { - val flow = realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - val job = async { - withTimeout(30.seconds) { - flow.collect { - channel.trySend(true) - } - } - } - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - // Wait for Flow to start, so we do not close the Realm before - // `flow.collect()` can be called. - channel.receiveOrFail() - realm.close() - job.await() - } finally { - channel.close() - if (!realm.isClosed()) { - realm.close() - } - } - } - } - - private suspend fun Realm.writeSampleData(count: Int, timeout: Duration? = null) { - repeat(count) { - write { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = getTestPartitionValue() - binaryField = Random.nextBytes(100) - } - ) - } - } - timeout?.let { - assertTrue { syncSession.uploadAllLocalChanges(timeout) } - } - } - - // Operator that will return a flow that emits an increasing integer on each completion event - private fun Flow.completionCounter(): Flow = - filter { it.isTransferComplete } - .buffer(5, onBufferOverflow = BufferOverflow.DROP_OLDEST) - .scan(0) { accumulator, _ -> - accumulator + 1 - } - - private fun createSyncConfig( - user: User, - ): SyncConfiguration { - return SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query("stringField = $0", getTestPartitionValue())) - } - .build() - } - - private fun getTestPartitionValue(): String { - if (!this::partitionValue.isInitialized) { - fail("Test not setup correctly. Partition value is missing") - } - return partitionValue - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt deleted file mode 100644 index 337a520fae..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncConfigurationTests.kt +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.internal.platform.PATH_SEPARATOR -import io.realm.kotlin.internal.platform.pathOf -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.InitialSubscriptionsCallback -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import kotlinx.atomicfu.atomic -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class FlexibleSyncConfigurationTests { - - private lateinit var app: App - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun with() { - val user: User = app.asTestApp.createUserAndLogin() - val config = SyncConfiguration.create(user, setOf()) - assertEquals(SyncMode.FLEXIBLE, config.syncMode) - assertNull(config.initialRemoteData) - assertNull(config.initialSubscriptions) - } - - @Test - fun equals() { - val user: User = app.asTestApp.createUserAndLogin() - val config: SyncConfiguration = SyncConfiguration.create(user, setOf()) - assertEquals(config, config) - } - - @Test - fun equals_same() { - val user: User = app.asTestApp.createUserAndLogin() - val config1: SyncConfiguration = SyncConfiguration.Builder(user, setOf()).build() - val config2: SyncConfiguration = SyncConfiguration.Builder(user, setOf()).build() - // TODO Currently we use default implementation for equals. This is different - // compared to Realm Java, but it is unclear if this is something that is worth - // implementing? - assertNotEquals(config1, config2) - } - - @Test - fun equals_not() { - val user1: User = app.asTestApp.createUserAndLogin() - val user2: User = app.asTestApp.createUserAndLogin() - val config1: SyncConfiguration = SyncConfiguration.Builder(user1, setOf()).build() - val config2: SyncConfiguration = SyncConfiguration.Builder(user2, setOf()).build() - assertNotEquals(config1, config2) - } - - @Test - fun hashCode_equal() { - val user: User = app.asTestApp.createUserAndLogin() - val config: SyncConfiguration = SyncConfiguration.create(user, setOf()) - assertEquals(config.hashCode(), config.hashCode()) - } - - @Test - fun hashCode_notEquals() { - val user1: User = app.asTestApp.createUserAndLogin() - val user2: User = app.asTestApp.createUserAndLogin() - val config1: SyncConfiguration = SyncConfiguration.create(user1, setOf()) - val config2: SyncConfiguration = SyncConfiguration.create(user2, setOf()) - assertNotEquals(config1.hashCode(), config2.hashCode()) - } - - @Test - fun toString_nonEmpty() { - val user: User = app.asTestApp.createUserAndLogin() - val config: SyncConfiguration = SyncConfiguration.create(user, setOf()) - // TODO Currently we use default implementation for `toString()`. This is - // different compared to Realm Java, but it is unclear if this is something - // that is worth implementing? - assertTrue(config.toString().startsWith("io.realm.kotlin.mongodb.internal.SyncConfigurationImpl@"), config.toString()) - } - - // @Test - // fun getPartitionValueThrows() { - // val user: User = createTestUser(app) - // val config: SyncConfiguration = SyncConfiguration.defaultConfig(user) - // assertFailsWith { config.partitionValue } - // } - @Test - fun defaultPath() { - val user: User = app.asTestApp.createUserAndLogin() - val config: SyncConfiguration = SyncConfiguration.create(user, setOf()) - assertTrue(config.path.endsWith("${PATH_SEPARATOR}default.realm"), "Path is: ${config.path}") - } - - @Test - fun initialSubscriptions_throwsOnPartitionBasedConfig() { - val user: User = app.asTestApp.createUserAndLogin() - val partitionValue = TestHelper.randomPartitionValue() - val configBuilder = SyncConfiguration.Builder(user, partitionValue, setOf()) - assertFailsWith { - configBuilder.initialSubscriptions { /* Do nothing */ } - } - } - - @Test - fun initialSubscriptions() { - val user: User = app.asTestApp.createUserAndLogin() - val handler = InitialSubscriptionsCallback { /* Do nothing */ } - val config: SyncConfiguration = SyncConfiguration.Builder(user, setOf()) - .initialSubscriptions(rerunOnOpen = true, handler) - .build() - assertEquals(handler, config.initialSubscriptions!!.callback) - assertTrue(config.initialSubscriptions!!.rerunOnOpen) - } - - @Test - fun durability() { - val user: User = app.asTestApp.createUserAndLogin() - val config = SyncConfiguration.Builder(user, schema = setOf()) - .build() - val inMemoryConfig = SyncConfiguration.Builder(user, schema = setOf()) - .inMemory() - .build() - assertFalse(config.inMemory) - assertTrue(inMemoryConfig.inMemory) - } - - @Test - fun initialSubscriptions_rerunOnOpen_false() { - val user: User = app.asTestApp.createUserAndLogin() - val counter = atomic(0) - val config: SyncConfiguration = SyncConfiguration.Builder(user, setOf()) - .initialSubscriptions(rerunOnOpen = false) { - counter.incrementAndGet() - } - .build() - - Realm.open(config).close() - assertEquals(1, counter.value) - Realm.open(config).close() - assertEquals(1, counter.value) - } - - @Test - fun initialSubscriptions_rerunOnOpen_true() { - val user: User = app.asTestApp.createUserAndLogin() - val counter = atomic(0) - val config: SyncConfiguration = SyncConfiguration.Builder(user, setOf()) - .initialSubscriptions(rerunOnOpen = true) { - counter.incrementAndGet() - } - .build() - - Realm.open(config).close() - assertEquals(1, counter.value) - Realm.open(config).close() - assertEquals(2, counter.value) - } - - @Test - @Ignore - fun initialSubscriptions_failures_shouldDeleteRealm() { - // See https://github.com/realm/realm-core/issues/5364 - // See https://github.com/realm/realm-kotlin/issues/851 - TODO() - } - - // @Test - // fun defaultClientResetStrategy() { - // val user: User = createTestUser(app) - // val handler = SyncConfiguration.InitialFlexibleSyncSubscriptions { realm, subscriptions -> - // // Do nothing - // } - // val config: SyncConfiguration = SyncConfiguration.defaultConfig(user) - // assertTrue(config.syncClientResetStrategy is ManuallyRecoverUnsyncedChangesStrategy) - // } - - @Test - fun overrideDefaultPath() { - val user: User = app.asTestApp.createUserAndLogin() - val config: SyncConfiguration = SyncConfiguration.Builder(user, setOf()) - .name("custom.realm") - .build() - assertTrue(config.path.endsWith(pathOf(app.configuration.appId, user.id, "custom.realm")), "Path is: ${config.path}") - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt deleted file mode 100644 index 6951cab536..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FlexibleSyncIntegrationTests.kt +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.flx.FlexChildObject -import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.exceptions.CompensatingWriteException -import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.common.utils.waitForSynchronizationOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.use -import kotlinx.atomicfu.atomic -import org.mongodb.kbson.BsonObjectId -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.nanoseconds - -/** - * Integration smoke tests for Flexible Sync. This is not intended to cover all cases, but just - * test common scenarios. - */ -class FlexibleSyncIntegrationTests { - - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - runBlocking { - app.createUserAndLogIn(email, password) - } - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun downloadInitialData() = runBlocking { - val randomSection = Random.nextInt() // Generate random name to allow replays of unit tests - - // Upload data from user 1 - val user1 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config1 = SyncConfiguration.create(user1, FLEXIBLE_SYNC_SCHEMA) - Realm.open(config1).use { realm1 -> - val subs = realm1.subscriptions.update { - add(realm1.query("section = $0", randomSection)) - } - subs.waitForSynchronizationOrFail() - realm1.write { - copyToRealm(FlexParentObject(randomSection).apply { name = "red" }) - copyToRealm(FlexParentObject(randomSection).apply { name = "blue" }) - } - realm1.syncSession.uploadAllLocalChangesOrFail() - } - - // Download data from user 2 - val user2 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config2 = SyncConfiguration.Builder(user2, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { realm -> - add( - realm.query( - "section = $0 AND name = $1", - randomSection, - "blue" - ) - ) - } - .waitForInitialRemoteData(timeout = 1.minutes) - .build() - - Realm.open(config2).use { realm2 -> - assertEquals(1, realm2.query().count().find()) - } - } - - @Test - fun writeFailsIfNoSubscription() = runBlocking { - val user = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config = SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .build() - - Realm.open(config).use { realm -> - realm.writeBlocking { - assertFailsWith { - // This doesn't trigger a client reset event, it is caught by Core instead - copyToRealm(FlexParentObject().apply { name = "red" }) - } - } - } - } - - @Test - fun dataIsDeletedWhenSubscriptionIsRemoved() = runBlocking { - val randomSection = Random.nextInt() // Generate random section to allow replays of unit tests - - val user = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config = SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA).build() - Realm.open(config).use { realm -> - realm.subscriptions.update { - val query = realm.query() - .query("section = $0", randomSection) - .query("(name = 'red' OR name = 'blue')") - add(query, "sub") - } - realm.subscriptions.waitForSynchronizationOrFail() - realm.write { - copyToRealm(FlexParentObject(randomSection).apply { name = "red" }) - copyToRealm(FlexParentObject(randomSection).apply { name = "blue" }) - } - assertEquals(2, realm.query().count().find()) - realm.subscriptions.update { - val query = realm.query("section = $0 AND name = 'red'", randomSection) - add(query, "sub", updateExisting = true) - } - realm.subscriptions.waitForSynchronizationOrFail() - assertEquals(1, realm.query().count().find()) - } - } - - @Test - fun initialSubscriptions_timeOut() { - val config = SyncConfiguration.Builder(app.currentUser!!, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { realm -> - repeat(10) { - add(realm.query("section = $0", it)) - } - } - .waitForInitialRemoteData(1.nanoseconds) - .build() - assertFailsWith { - Realm.open(config).use { - fail("Realm should not have opened in time.") - } - } - } - - // Make sure that if `rerunOnOpen` and `waitForInitialRemoteData` is set, we don't - // open the Realm until all new subscription data is downloaded. - @Test - fun rerunningInitialSubscriptionsAndWaitForInitialRemoteData() = runBlocking { - val randomSection = Random.nextInt() // Generate random name to allow replays of unit tests - - // Prepare some user data - val user1 = app.createUserAndLogin() - val config1 = SyncConfiguration.create(user1, FLEXIBLE_SYNC_SCHEMA) - Realm.open(config1).use { realm -> - assertTrue( - realm.subscriptions.update { - add(realm.query("section = $0", randomSection)) - }.waitForSynchronization(4.minutes), - "Failed to update subscriptions in time" - ) - - realm.write { - repeat(10) { counter -> - copyToRealm( - FlexParentObject().apply { - section = randomSection - name = "Name-$counter" - } - ) - } - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - - // User 2 opens a Realm twice - val counter = atomic(0) - val user2 = app.createUserAndLogin() - val config2 = SyncConfiguration.Builder(user2, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions(rerunOnOpen = true) { realm -> - add( - realm.query( - "section = $0 AND name = $1", - randomSection, - "Name-${counter.getAndIncrement()}" - ) - ) - } - .waitForInitialRemoteData(2.minutes) - .build() - - Realm.open(config2).use { realm -> - assertEquals(1, realm.query().count().find()) - } - Realm.open(config2).use { realm -> - assertEquals(2, realm.query().count().find()) - } - } - - @Suppress("LongMethod") - @Test - fun roundTripLinkedAndEmbeddedObjects() = runBlocking { - val randomSection = Random.nextInt() // Generate random name to allow replays of unit tests - - // Upload data from user 1 - val user1 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config1 = SyncConfiguration.create(user1, FLEXIBLE_SYNC_SCHEMA) - Realm.open(config1).use { realm1 -> - val subs = realm1.subscriptions.update { - add(realm1.query("section = $0", randomSection)) - add(realm1.query("section = $0", randomSection)) - } - subs.waitForSynchronizationOrFail() - realm1.write { - copyToRealm( - FlexParentObject(randomSection).apply { - name = "red" - child = FlexChildObject().apply { - section = randomSection - name = "redChild" - } - embedded = FlexEmbeddedObject().apply { - embeddedName = "redEmbedded" - } - } - ) - copyToRealm( - FlexParentObject(randomSection).apply { - name = "blue" - child = FlexChildObject().apply { - section = randomSection - name = "blueChild" - } - embedded = FlexEmbeddedObject().apply { - embeddedName = "blueEmbedded" - } - } - ) - } - realm1.syncSession.uploadAllLocalChangesOrFail() - } - - // Download data from user 2 - val user2 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val config2 = SyncConfiguration.Builder(user2, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { realm -> - add( - realm.query( - "section = $0 AND name = $1", - randomSection, - "blue" - ) - ) - add(realm.query("section = $0", randomSection)) - } - .waitForInitialRemoteData(timeout = 1.minutes) - .build() - - Realm.open(config2).use { realm2 -> - assertEquals(1, realm2.query().count().find()) - assertEquals(2, realm2.query().count().find()) - // Embedded objects are pulled down as part of their parents - assertEquals(1, realm2.query().count().find()) - val obj = realm2.query().first().find()!! - assertEquals("blueChild", obj.child!!.name) - assertEquals("blueEmbedded", obj.embedded!!.embeddedName) - } - } - - @Test - fun compensationWrite_writeOutsideOfSubscriptionsGetsReveredByServer() { - val user1 = app.createUserAndLogin() - - val channel = TestChannel() - - val config1 = SyncConfiguration.Builder(user1, FLEXIBLE_SYNC_SCHEMA) - .errorHandler { _: SyncSession, syncException: SyncException -> - runBlocking { - channel.send(syncException as CompensatingWriteException) - } - } - .build() - - runBlocking { - val expectedPrimaryKey = BsonObjectId() - - Realm.open(config1).use { realm -> - val objectId = BsonObjectId() - - realm.subscriptions.update { - add(realm.query("_id = $0", objectId)) - }.waitForSynchronizationOrFail() - - assertNotEquals(expectedPrimaryKey, objectId) - - realm.write { - copyToRealm(FlexParentObject().apply { _id = expectedPrimaryKey }) - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - - val exception: CompensatingWriteException = channel.receiveOrFail() - - assertTrue(exception.message!!.startsWith("[Sync][CompensatingWrite(1033)] Client attempted a write that is not allowed; it has been reverted Logs:"), exception.message) - assertEquals(1, exception.writes.size) - - exception.writes[0].run { - assertContains(reason, "object is outside of the current query view") - assertEquals("FlexParentObject", objectType) - assertEquals(expectedPrimaryKey, primaryKey?.asObjectId()) - } - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt deleted file mode 100644 index 39fcab3ce7..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/FunctionsTests.kt +++ /dev/null @@ -1,1134 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") -@file:OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.ext.toRealmDictionary -import io.realm.kotlin.ext.toRealmList -import io.realm.kotlin.ext.toRealmSet -import io.realm.kotlin.internal.asBsonDateTime -import io.realm.kotlin.internal.interop.CollectionType -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.restrictToMillisPrecision -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.Functions -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.FunctionExecutionException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.ext.CallBuilder -import io.realm.kotlin.mongodb.ext.call -import io.realm.kotlin.serializers.RealmDictionaryKSerializer -import io.realm.kotlin.serializers.RealmListKSerializer -import io.realm.kotlin.serializers.RealmSetKSerializer -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.syncServerAppName -import io.realm.kotlin.test.mongodb.util.AUTHORIZED_ONLY_FUNCTION -import io.realm.kotlin.test.mongodb.util.BaseAppInitializer -import io.realm.kotlin.test.mongodb.util.ERROR_FUNCTION -import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION -import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION -import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION -import io.realm.kotlin.test.mongodb.util.VOID_FUNCTION -import io.realm.kotlin.test.mongodb.util.addEmailProvider -import io.realm.kotlin.test.util.TypeDescriptor -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmDictionary -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmList -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmSet -import io.realm.kotlin.types.RealmUUID -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.builtins.nullable -import kotlinx.serialization.builtins.serializer -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.polymorphic -import kotlinx.serialization.modules.subclass -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBinarySubType -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDBPointer -import org.mongodb.kbson.BsonDecimal128 -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonDouble -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonJavaScript -import org.mongodb.kbson.BsonJavaScriptWithScope -import org.mongodb.kbson.BsonMaxKey -import org.mongodb.kbson.BsonMinKey -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.BsonRegularExpression -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonSymbol -import org.mongodb.kbson.BsonTimestamp -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonUndefined -import org.mongodb.kbson.Decimal128 -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson -import org.mongodb.kbson.serialization.encodeToBsonValue -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -const val STRING_VALUE = "Hello world" -const val BYTE_VALUE = Byte.MAX_VALUE -const val SHORT_VALUE = Short.MAX_VALUE -const val INT_VALUE = Int.MAX_VALUE -const val LONG_VALUE = Long.MAX_VALUE -const val CHAR_VALUE = 'a' -const val FLOAT_VALUE = 1.4f -const val DOUBLE_VALUE = 1.4 -val REALM_INSTANT_VALUE = RealmInstant.now().restrictToMillisPrecision() -val REALM_UUID_VALUE = RealmUUID.random() -val BYTE_ARRAY_VALUE = byteArrayOf(0, 1, 0) -val MUTABLE_REALM_INT_VALUE = MutableRealmInt.create(50) -val REALM_OBJECT_VALUE = SerializablePerson() -val LIST_VALUE = listOf("hello", "world", null) -val SET_VALUE = LIST_VALUE.toSet() -val REALM_LIST_VALUE = LIST_VALUE.toRealmList() -val REALM_SET_VALUE = SET_VALUE.toRealmSet() -val BSON_ARRAY_VALUE = BsonArray( - LIST_VALUE.map { value -> - value?.let { stringValue: String -> BsonString(stringValue) } ?: BsonNull - } -) - -val MAP_VALUE: Map = LIST_VALUE.mapIndexed { index, stringValue -> - "$index" to stringValue -}.toMap() - -val REALM_MAP_VALUE = MAP_VALUE.toRealmDictionary() - -val BSON_DOCUMENT_VALUE = BsonDocument( - MAP_VALUE.map { entry -> - entry.key to (entry.value?.let { BsonString(it) } ?: BsonNull) - }.toMap() -) - -@Serializable -class SerializablePerson : RealmObject { - var firstName: String = "FIRST NAME" - var lastName: String = "LAST NAME" - - // Supplying custom companion object to work around that multiple K2 FIR extension clashes if - // they both generate a Companion. - // https://youtrack.jetbrains.com/issue/KT-62194/K2-Two-compiler-plugins-interference-in-generated-companion-object - companion object -} - -class FunctionsTests { - private data class Dog(var name: String? = null) - -// private val looperThread = BlockingLooperThread() - - private lateinit var app: TestApp - private lateinit var functions: Functions - private lateinit var anonUser: User - -// private lateinit var admin: ServerAdmin - -// // Custom registry with support for encoding/decoding Dogs -// private val pojoRegistry by lazy { -// CodecRegistries.fromRegistries( -// app.configuration.defaultCodecRegistry, -// CodecRegistries.fromProviders( -// PojoCodecProvider.builder() -// .register(Dog::class.java) -// .build() -// ) -// ) -// } - -// // Custom string decoder returning hardcoded value -// private class CustomStringDecoder(val value: String) : Decoder { -// override fun decode(reader: BsonReader, decoderContext: DecoderContext): String { -// reader.readString() -// return value -// } -// } -// -// // Custom codec that throws an exception when encoding/decoding integers -// private val faultyIntegerCodec = object : Codec { -// override fun decode(reader: BsonReader, decoderContext: DecoderContext): Integer { -// throw RuntimeException("Simulated error") -// } -// -// override fun getEncoderClass(): Class { -// return Integer::class.java -// } -// -// override fun encode(writer: BsonWriter?, value: Integer?, encoderContext: EncoderContext?) { -// throw RuntimeException("Simulated error") -// } -// } -// -// // Custom registry that throws an exception when encoding/decoding integers -// private val faultyIntegerRegistry = CodecRegistries.fromRegistries( -// CodecRegistries.fromProviders(IterableCodecProvider()), -// CodecRegistries.fromCodecs(StringCodec(), faultyIntegerCodec) -// ) - - @BeforeTest - fun setup() { - app = TestApp( - FunctionsTests::class.simpleName, - object : BaseAppInitializer( - syncServerAppName("funcs"), - { app -> - addEmailProvider(app) - app.addFunction(FIRST_ARG_FUNCTION) - app.addFunction(NULL_FUNCTION) - app.addFunction(SUM_FUNCTION) - app.addFunction(ERROR_FUNCTION) - app.addFunction(VOID_FUNCTION) - app.addFunction(AUTHORIZED_ONLY_FUNCTION) - } - ) {}, - ejson = EJson( - serializersModule = SerializersModule { - polymorphic(RealmObject::class) { - subclass(SerializablePerson::class) - } - } - ) - ) - anonUser = runBlocking { - app.login(Credentials.anonymous()) - } - functions = anonUser.functions - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun exhaustiveElementClassifiersTest() { - runBlocking { - TypeDescriptor.elementClassifiers - .filterNot { classifier -> - classifier in listOf( - Decimal128::class, - BsonObjectId::class // - ) - } - .forEach { classifier -> - when (classifier) { - String::class -> testFunctionCall_String() - Char::class -> testFunctionCall_Char() - Byte::class -> testFunctionCall_Byte() - Short::class -> testFunctionCall_Short() - Int::class -> testFunctionCall_Int() - Long::class -> testFunctionCall_Long() - Float::class -> testFunctionCall_Float() - Double::class -> testFunctionCall_Double() - Boolean::class -> testFunctionCall_Boolean() - RealmInstant::class -> testFunctionCall_RealmInstant() - RealmUUID::class -> testFunctionCall_RealmUUID() - ByteArray::class -> testFunctionCall_ByteArray() - MutableRealmInt::class -> testFunctionCall_MutableRealmInt() - RealmObject::class -> testFunctionCall_RealmObject() - RealmAny::class -> testFunctionCall_RealmAny() - else -> error("Untested classifier $classifier") - } - } - } - } - - @Test - fun exhaustiveCollectionTest() { - runBlocking { - CollectionType.values() - .forEach { collectionType -> - when (collectionType) { - CollectionType.RLM_COLLECTION_TYPE_NONE -> {} - CollectionType.RLM_COLLECTION_TYPE_LIST -> testFunctionCall_List() - CollectionType.RLM_COLLECTION_TYPE_SET -> testFunctionCall_Set() - CollectionType.RLM_COLLECTION_TYPE_DICTIONARY -> testFunctionCall_Dictionary() - else -> error("Untested collection type $collectionType") - } - } - } - } - - @Test - fun exhaustiveBsonTypesTest() { - runBlocking { - BsonType.values() - .forEach { - when (it) { - BsonType.END_OF_DOCUMENT -> {} // it is not an actual BsonType - BsonType.DOUBLE -> testFunctionCall_BsonDouble() - BsonType.STRING -> testFunctionCall_BsonString() - BsonType.DOCUMENT -> testFunctionCall_BsonDocument() - BsonType.ARRAY -> testFunctionCall_BsonArray() - BsonType.BINARY -> testFunctionCall_BsonBinary() - BsonType.UNDEFINED -> testFunctionCall_BsonUndefined() - BsonType.OBJECT_ID -> testFunctionCall_BsonObjectId() - BsonType.BOOLEAN -> testFunctionCall_BsonBoolean() - BsonType.DATE_TIME -> testFunctionCall_BsonDateTime() - BsonType.NULL -> testFunctionCall_BsonNull() - BsonType.REGULAR_EXPRESSION -> testFunctionCall_BsonRegularExpresion() - BsonType.DB_POINTER -> testFunctionCall_BsonDBPointer() - BsonType.JAVASCRIPT -> testFunctionCall_BsonJavaScript() - BsonType.SYMBOL -> testFunctionCall_BsonSymbol() - BsonType.JAVASCRIPT_WITH_SCOPE -> testFunctionCall_BsonJavaScriptWithScope() - BsonType.INT32 -> testFunctionCall_BsonInt32() - BsonType.TIMESTAMP -> testFunctionCall_BsonTimestamp() - BsonType.INT64 -> testFunctionCall_BsonInt64() - BsonType.DECIMAL128 -> testFunctionCall_BsonDecimal128() - BsonType.MIN_KEY -> testFunctionCall_BsonMinKey() - BsonType.MAX_KEY -> testFunctionCall_BsonMaxKey() - } - } - } - } - - private fun testFunctionCall_BsonMaxKey() { - functionCallRoundTrip(BsonMaxKey, BsonMaxKey) - } - - private fun testFunctionCall_BsonMinKey() { - functionCallRoundTrip(BsonMinKey, BsonMinKey) - } - - private fun testFunctionCall_BsonDecimal128() { - BsonDecimal128(LONG_VALUE.toString()).let { decimal128 -> - functionCallRoundTrip(decimal128, decimal128) - } - } - - private fun testFunctionCall_BsonInt64() { - functionCallRoundTrip(BsonInt64(LONG_VALUE), LONG_VALUE) - functionCallRoundTrip(BsonInt64(LONG_VALUE), LONG_VALUE.toLong()) - functionCallRoundTrip(BsonInt64(LONG_VALUE), LONG_VALUE.toFloat()) - functionCallRoundTrip(BsonInt64(LONG_VALUE), LONG_VALUE.toDouble()) - } - - private fun testFunctionCall_BsonTimestamp() { - BsonTimestamp().let { timestamp -> - functionCallRoundTrip(timestamp, timestamp) - } - } - - private fun testFunctionCall_BsonInt32() { - functionCallRoundTrip(BsonInt32(BYTE_VALUE.toInt()), BYTE_VALUE) - functionCallRoundTrip(BsonInt32(SHORT_VALUE.toInt()), SHORT_VALUE) - functionCallRoundTrip(BsonInt32(INT_VALUE), INT_VALUE) - functionCallRoundTrip(BsonInt32(INT_VALUE), BsonInt64(INT_VALUE.toLong())) - functionCallRoundTrip(BsonInt32(INT_VALUE), INT_VALUE.toLong()) - functionCallRoundTrip(BsonInt32(INT_VALUE), INT_VALUE.toFloat()) - functionCallRoundTrip(BsonInt32(INT_VALUE), INT_VALUE.toDouble()) - } - - private fun testFunctionCall_BsonJavaScriptWithScope() { - BsonJavaScriptWithScope("", BsonDocument()).let { javaScriptWithScope -> - functionCallRoundTrip(javaScriptWithScope, javaScriptWithScope) - } - } - - private fun testFunctionCall_BsonSymbol() { - BsonSymbol("").let { bsonSymbol -> - functionCallRoundTrip(bsonSymbol, bsonSymbol) - } - } - - private fun testFunctionCall_BsonJavaScript() { - BsonJavaScript("").let { bsonJavaScript -> - functionCallRoundTrip(bsonJavaScript, bsonJavaScript) - } - } - - private fun testFunctionCall_BsonDBPointer() { - BsonDBPointer( - namespace = "namespace", - id = BsonObjectId() - ).let { bsonDBPointer -> - functionCallRoundTrip(bsonDBPointer, bsonDBPointer) - } - } - - private fun testFunctionCall_BsonRegularExpresion() { - BsonRegularExpression("").let { bsonRegularExpression -> - functionCallRoundTrip(bsonRegularExpression, bsonRegularExpression) - } - } - - private fun testFunctionCall_BsonNull() { - functionCallRoundTrip(BsonNull, BsonNull) - functionCallRoundTrip(BsonNull, null as String?) - } - - private fun testFunctionCall_BsonDateTime() { - REALM_INSTANT_VALUE.asBsonDateTime().let { bsonDateTimeValue -> - functionCallRoundTrip( - bsonDateTimeValue, - bsonDateTimeValue - ) - - functionCallRoundTrip(bsonDateTimeValue, REALM_INSTANT_VALUE) - } - } - - private fun testFunctionCall_BsonBoolean() { - functionCallRoundTrip(BsonBoolean(true), BsonBoolean(true)) - functionCallRoundTrip(BsonBoolean(true), true) - } - - private fun testFunctionCall_BsonObjectId() { - BsonObjectId().let { objectId -> - functionCallRoundTrip(objectId, objectId) - } - } - - private fun testFunctionCall_BsonUndefined() { - functionCallRoundTrip(BsonUndefined, BsonUndefined) - } - - private fun testFunctionCall_BsonBinary() { - functionCallRoundTrip( - argument = BsonBinary(BYTE_ARRAY_VALUE), - expectedResult = BsonBinary(BYTE_ARRAY_VALUE) - ) - functionCallRoundTrip( - argument = BsonBinary(BYTE_ARRAY_VALUE), - expectedResult = BYTE_ARRAY_VALUE - ) - } - - private fun testFunctionCall_BsonString() { - functionCallRoundTrip( - BsonString(STRING_VALUE), - BsonString(STRING_VALUE) - ) - functionCallRoundTrip(BsonString(STRING_VALUE), STRING_VALUE) - } - - private fun testFunctionCall_BsonDocument() { - functionCallRoundTrip(BSON_DOCUMENT_VALUE, BSON_DOCUMENT_VALUE) - - assertKSerializerFunctionCall( - BSON_DOCUMENT_VALUE, - MAP_VALUE - ) - assertKSerializerFunctionCall( - argument = BSON_DOCUMENT_VALUE, - expectedResult = REALM_MAP_VALUE - ) { arg: BsonDocument -> - add(arg) - returnValueSerializer = RealmDictionaryKSerializer(String.serializer().nullable) - } - } - - private fun testFunctionCall_BsonArray() { - functionCallRoundTrip(BSON_ARRAY_VALUE, BSON_ARRAY_VALUE) - - // only kserializer can deserialize RealmList and lists - assertKSerializerFunctionCall(BSON_ARRAY_VALUE, LIST_VALUE) - assertKSerializerFunctionCall( - argument = BSON_ARRAY_VALUE, - expectedResult = REALM_LIST_VALUE - ) { arg: BsonArray -> - add(arg) - returnValueSerializer = RealmListKSerializer(String.serializer().nullable) - } - - // only kserializer can deserialize RealmSet and sets - assertKSerializerFunctionCall(BSON_ARRAY_VALUE, SET_VALUE) - assertKSerializerFunctionCall( - argument = BSON_ARRAY_VALUE, - expectedResult = REALM_SET_VALUE - ) { arg: BsonArray -> - add(arg) - returnValueSerializer = RealmSetKSerializer(String.serializer().nullable) - } - } - - private fun testFunctionCall_BsonDouble() { - functionCallRoundTrip( - BsonDouble(DOUBLE_VALUE), - BsonDouble(DOUBLE_VALUE) - ) - functionCallRoundTrip(BsonDouble(DOUBLE_VALUE), DOUBLE_VALUE) - } - - private fun testFunctionCall_RealmObject(): BsonDocument { - // The "stable" serializer does not support RealmObject serialization - - assertKSerializerFunctionCall( - REALM_OBJECT_VALUE, - REALM_OBJECT_VALUE - ) - return assertKSerializerFunctionCall( - argument = REALM_OBJECT_VALUE, - expectedResult = EJson.encodeToBsonValue( - REALM_OBJECT_VALUE - ).asDocument() - ) - } - - private fun testFunctionCall_RealmAny() { - functionCallRoundTrip( - RealmAny.create(STRING_VALUE), - RealmAny.create(STRING_VALUE) - ) - functionCallRoundTrip( - RealmAny.create(INT_VALUE), - RealmAny.create(INT_VALUE) - ) - assertFailsWithMessage("Polymorphic values are not supported.") { - assertKSerializerFunctionCall( - RealmAny.create(REALM_OBJECT_VALUE), - RealmAny.create(REALM_OBJECT_VALUE) - ) - } - } - - private fun testFunctionCall_MutableRealmInt() { - assertStableSerializerFunctionCall( - argument = MUTABLE_REALM_INT_VALUE, - expectedResult = MUTABLE_REALM_INT_VALUE, - ) - - assertKSerializerFunctionCall( - argument = MUTABLE_REALM_INT_VALUE, - expectedResult = MUTABLE_REALM_INT_VALUE - ) - } - - private fun testFunctionCall_ByteArray() { - functionCallRoundTrip( - argument = BYTE_ARRAY_VALUE, - expectedResult = BYTE_ARRAY_VALUE - ) - - functionCallRoundTrip( - argument = BYTE_ARRAY_VALUE, - expectedResult = BsonBinary( - type = BsonBinarySubType.BINARY, - data = BYTE_ARRAY_VALUE - ) - ) - } - - private fun testFunctionCall_RealmUUID() { - functionCallRoundTrip(REALM_UUID_VALUE, REALM_UUID_VALUE) - functionCallRoundTrip( - argument = REALM_UUID_VALUE, - expectedResult = BsonBinary( - type = BsonBinarySubType.UUID_STANDARD, - data = REALM_UUID_VALUE.bytes - ) - ) - } - - private fun testFunctionCall_RealmInstant() { - functionCallRoundTrip(REALM_INSTANT_VALUE, REALM_INSTANT_VALUE) - functionCallRoundTrip(REALM_INSTANT_VALUE, REALM_INSTANT_VALUE.asBsonDateTime()) - } - - private fun testFunctionCall_Boolean() { - functionCallRoundTrip( - argument = true, - expectedResult = true - ) - functionCallRoundTrip( - argument = true, - expectedResult = BsonBoolean(true) - ) - } - - private fun testFunctionCall_Double() { - functionCallRoundTrip(DOUBLE_VALUE, DOUBLE_VALUE) - functionCallRoundTrip(DOUBLE_VALUE, BsonDouble(DOUBLE_VALUE)) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Float() { - functionCallRoundTrip(FLOAT_VALUE, FLOAT_VALUE) - functionCallRoundTrip(FLOAT_VALUE, BsonDouble(FLOAT_VALUE.toDouble())) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Long() { - functionCallRoundTrip(LONG_VALUE, LONG_VALUE) - functionCallRoundTrip(LONG_VALUE, BsonInt64(LONG_VALUE)) - functionCallRoundTrip(LONG_VALUE, LONG_VALUE.toFloat()) - functionCallRoundTrip(LONG_VALUE, LONG_VALUE.toDouble()) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Int() { - functionCallRoundTrip(INT_VALUE, INT_VALUE) - functionCallRoundTrip(INT_VALUE, BsonInt32(INT_VALUE)) - functionCallRoundTrip(INT_VALUE, BsonInt64(INT_VALUE.toLong())) - functionCallRoundTrip(INT_VALUE, INT_VALUE.toLong()) - functionCallRoundTrip(INT_VALUE, INT_VALUE.toFloat()) - functionCallRoundTrip(INT_VALUE, INT_VALUE.toDouble()) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Short() { - functionCallRoundTrip(SHORT_VALUE, SHORT_VALUE) - functionCallRoundTrip(SHORT_VALUE, BsonInt32(SHORT_VALUE.toInt())) - functionCallRoundTrip(SHORT_VALUE, BsonInt64(SHORT_VALUE.toLong())) - functionCallRoundTrip(SHORT_VALUE, SHORT_VALUE.toInt()) - functionCallRoundTrip(SHORT_VALUE, SHORT_VALUE.toLong()) - functionCallRoundTrip(SHORT_VALUE, SHORT_VALUE.toFloat()) - functionCallRoundTrip(SHORT_VALUE, SHORT_VALUE.toDouble()) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Byte() { - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE) - functionCallRoundTrip(BYTE_VALUE, BsonInt32(BYTE_VALUE.toInt())) - functionCallRoundTrip(BYTE_VALUE, BsonInt64(BYTE_VALUE.toLong())) - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE.toShort()) - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE.toInt()) - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE.toLong()) - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE.toFloat()) - functionCallRoundTrip(BYTE_VALUE, BYTE_VALUE.toDouble()) - // TODO coercion with Decimal128 - } - - private fun testFunctionCall_Char() { - functionCallRoundTrip(CHAR_VALUE, CHAR_VALUE) - } - - private fun testFunctionCall_String() { - functionCallRoundTrip(STRING_VALUE, STRING_VALUE) - functionCallRoundTrip(STRING_VALUE, BsonString(STRING_VALUE)) - } - - private fun testFunctionCall_List() { - // common roundtrips - functionCallRoundTrip(LIST_VALUE, BSON_ARRAY_VALUE) - - val serializer = RealmListKSerializer(String.serializer().nullable) - - // only kserializer can deserialize RealmList and lists - assertKSerializerFunctionCall(LIST_VALUE, LIST_VALUE) - assertKSerializerFunctionCall( - argument = LIST_VALUE, - expectedResult = REALM_LIST_VALUE - ) { arg: List -> - add(arg) - returnValueSerializer = serializer - } - - assertKSerializerFunctionCall( - argument = REALM_LIST_VALUE, - expectedResult = LIST_VALUE - ) { arg: RealmList -> - add(arg, serializer) - } - - assertKSerializerFunctionCall( - argument = REALM_LIST_VALUE, - expectedResult = REALM_LIST_VALUE - ) { arg: RealmList -> - add(arg, serializer) - returnValueSerializer = serializer - } - } - - private fun testFunctionCall_Set() { - // common roundtrips - functionCallRoundTrip(SET_VALUE, BSON_ARRAY_VALUE) - - // these roundtrips are only possible with the kserializer function call - val serializer = RealmSetKSerializer(String.serializer().nullable) - - assertKSerializerFunctionCall( - SET_VALUE, - SET_VALUE - ) - assertKSerializerFunctionCall( - argument = SET_VALUE, - expectedResult = REALM_SET_VALUE - ) { arg: Set -> - add(arg) - returnValueSerializer = serializer - } - - assertKSerializerFunctionCall( - argument = REALM_SET_VALUE, - expectedResult = SET_VALUE - ) { arg: RealmSet -> - add(arg, serializer) - } - - assertKSerializerFunctionCall( - argument = REALM_SET_VALUE, - expectedResult = REALM_SET_VALUE - ) { arg: RealmSet -> - add(arg, serializer) - returnValueSerializer = serializer - } - } - - private fun testFunctionCall_Dictionary() { - // common roundtrips - functionCallRoundTrip(MAP_VALUE, BSON_DOCUMENT_VALUE) - - // these roundtrips are only possible with the kserializer function call - val serializer = RealmDictionaryKSerializer(String.serializer().nullable) - - assertKSerializerFunctionCall(MAP_VALUE, MAP_VALUE) - assertKSerializerFunctionCall( - argument = MAP_VALUE, - expectedResult = REALM_MAP_VALUE - ) { arg: Map -> - add(arg) - returnValueSerializer = serializer - } - - assertKSerializerFunctionCall( - argument = REALM_MAP_VALUE, - expectedResult = MAP_VALUE - ) { arg: RealmDictionary -> - add(arg, serializer) - } - - assertKSerializerFunctionCall( - argument = REALM_MAP_VALUE, - expectedResult = REALM_MAP_VALUE - ) { arg: RealmDictionary -> - add(arg, serializer) - returnValueSerializer = serializer - } - } - - private inline fun functionCallRoundTrip( - argument: A, - expectedResult: R - ) { - assertStableSerializerFunctionCall( - argument = argument, - expectedResult = expectedResult - ) - assertKSerializerFunctionCall( - argument = argument, - expectedResult = expectedResult - ) - } - - // Invokes [functions.call] with a given argument and validates that the result matches a given - // expected result. - private inline fun assertKSerializerFunctionCall( - argument: A, - expectedResult: R, - crossinline callBuilderBlock: CallBuilder.(arg: A) -> Unit = { arg -> - add(arg) - } - ) = runBlocking { - functions.call(FIRST_ARG_FUNCTION.name) { - this.callBuilderBlock(argument) - } - }.also { returnValue: R -> - assertValueEquals(expectedResult, returnValue) - } - - private fun assertValueEquals(expected: T, actual: T) { - when (expected) { - is SerializablePerson -> { - actual as SerializablePerson - - assertEquals(expected.firstName, actual.firstName) - assertEquals(expected.lastName, actual.lastName) - } - is BsonBinary -> { - actual as BsonBinary - assertContentEquals(expected.data, actual.data) - } - is ByteArray -> { - actual as ByteArray - assertContentEquals(expected, actual) - } - is RealmUUID -> { - actual as RealmUUID - assertEquals(expected, actual) - } - is Iterable<*> -> { - actual as Iterable<*> - assertContentEquals(expected, actual) - } - else -> assertEquals(expected, actual) - } - } - - // Invokes [functions.call] (kserializer version) with a given argument and validates that the - // result matches a given expected result. - private inline fun assertStableSerializerFunctionCall( - argument: A, - expectedResult: R - ) = runBlocking { functions.call(FIRST_ARG_FUNCTION.name, argument) } - .also { returnValue: R -> - assertValueEquals(expectedResult, returnValue) - } - - // Facilitates debugging by executing the functions on its own block. - private inline fun Functions.callBlocking( - name: String, - vararg args: Any?, - ): T = runBlocking { - call(name, *args) - } - - @Test - fun unsupportedArgumentTypeThrows() { - assertFailsWithMessage("Failed to convert arguments, type 'Dog' not supported. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, Array, Collection, Map and primitives are valid arguments types.") { - functions.callBlocking(FIRST_ARG_FUNCTION.name, Dog()) - } - } - - @Test - fun unsupportedReturnTypeThrows() { - assertFailsWithMessage("Unsupported type 'RealmList'. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, and primitives are valid decoding types.") { - functions.callBlocking>(FIRST_ARG_FUNCTION.name, "hello world") - } - } - -// @Test -// fun asyncCallFunction() = looperThread.runBlocking { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), Integer::class.java) { result -> -// try { -// assertEquals(32, result.orThrow.toInt()) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// -// @Test -// fun codecArgumentFailure() { -// assertFailsWithErrorCode(ErrorCode.BSON_CODEC_NOT_FOUND) { -// functions.callFunction(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Dog::class.java) -// } -// } -// -// @Test -// fun asyncCodecArgumentFailure() = looperThread.runBlocking { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> -// try { -// assertEquals(ErrorCode.BSON_CODEC_NOT_FOUND, result.error.errorCode) -// assertTrue(result.error.exception is CodecConfigurationException) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun codecResponseFailure() { -// assertFailsWithErrorCode(ErrorCode.BSON_CODEC_NOT_FOUND) { -// functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), Dog::class.java) -// } -// } -// -// @Test -// fun asyncCodecResponseFailure() = looperThread.runBlocking { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(Dog("PojoFido")), Integer::class.java) { result -> -// try { -// assertEquals(ErrorCode.BSON_CODEC_NOT_FOUND, result.error.errorCode) -// assertTrue(result.error.exception is CodecConfigurationException) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun codecBsonEncodingFailure() { -// assertFailsWithErrorCode(ErrorCode.BSON_ENCODING) { -// functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), String::class.java, faultyIntegerRegistry) -// } -// } -// -// @Test -// fun asyncCodecBsonEncodingFailure() = looperThread.runBlocking { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), String::class.java, faultyIntegerRegistry) { result -> -// try { -// assertEquals(ErrorCode.BSON_ENCODING, result.error.errorCode) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun codecBsonDecodingFailure() { -// assertFailsWithErrorCode(ErrorCode.BSON_DECODING) { -// functions.callFunction(FIRST_ARG_FUNCTION, listOf(32), String::class.java) -// } -// } -// -// @Test -// fun asyncCodecBsonDecodingFailure() = looperThread.runBlocking { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), String::class.java) { result -> -// try { -// assertEquals(ErrorCode.BSON_DECODING, result.error.errorCode) -// assertTrue(result.error.exception is BSONException) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun localCodecRegistry() { -// val input = Dog("PojoFido") -// assertEquals(input, functions.callFunction(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java, pojoRegistry)) -// } -// -// @Test -// fun asyncLocalCodecRegistry() = looperThread.runBlocking { -// val input = Dog("PojoFido") -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java, pojoRegistry) { result -> -// try { -// assertEquals(input, result.orThrow) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun instanceCodecRegistry() { -// val input = Dog("PojoFido") -// val functionsWithCodecRegistry = anonUser.getFunctions(pojoRegistry) -// assertEquals(input, functionsWithCodecRegistry.callFunction(FIRST_ARG_FUNCTION, listOf(input), Dog::class.java)) -// } -// -// @Test -// fun resultDecoder() { -// val input = "Realm" -// val output = "Custom Realm" -// assertEquals(output, functions.callFunction(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output))) -// } -// -// @Test -// fun asyncResultDecoder() = looperThread.runBlocking { -// val input = "Realm" -// val output = "Custom Realm" -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(input), CustomStringDecoder(output), App.Callback { result -> -// try { -// assertEquals(output, result.orThrow) -// } finally { -// looperThread.testComplete() -// } -// }) -// } - - @Test - fun unknownFunction() { - assertFailsWithMessage("function not found") { - runBlocking { - functions.call("unknown", 32) - } - } - } - -// @Test -// fun asyncUnknownFunction() = looperThread.runBlocking { -// val input = Dog("PojoFido") -// functions.callFunctionAsync("unknown", listOf(input), Dog::class.java, pojoRegistry) { result -> -// try { -// assertEquals(ErrorCode.FUNCTION_NOT_FOUND, result.error.errorCode) -// } finally { -// looperThread.testComplete() -// } -// } -// } -// -// @Test -// fun asyncNonLoopers() { -// assertFailsWith { -// functions.callFunctionAsync(FIRST_ARG_FUNCTION, listOf(32), Integer::class.java, pojoRegistry) { result -> -// fail() -// } -// } -// } - - @Test - fun callFunction_sum() { - runBlocking { - assertEquals(10, functions.call(SUM_FUNCTION.name, 1, 2, 3, 4)) - } - } - - @Test - fun callFunction_remoteError() { - assertFailsWithMessage("ReferenceError: 'unknown' is not defined") { - runBlocking { - functions.call(ERROR_FUNCTION.name) - } - } - } - - @Test - fun callFunction_null() { - runBlocking { - assertTrue(functions.call(NULL_FUNCTION.name, emptyList()).isNull()) - } - } - - @Test - fun callFunction_void() { - runBlocking { - assertEquals( - BsonType.UNDEFINED, - functions.call(VOID_FUNCTION.name).bsonType - ) - } - } - - @Test - fun callFunction_afterLogout() { - runBlocking { - anonUser.logOut() - } - assertFailsWithMessage("unauthorized") { - runBlocking { - functions.call(FIRST_ARG_FUNCTION.name, 1, 2, 3) - } - } - } - - // Tests that functions that should not execute based on "canevalute"-expression fails. - @Test - fun callFunction_authorizedOnly() { - // Not allow for anonymous user - assertFailsWithMessage("[Service][FunctionExecutionError(4313)] rule not matched for function \"authorizedOnly\"") { - runBlocking { - functions.call(AUTHORIZED_ONLY_FUNCTION.name, 1, 2, 3) - } - } - - runBlocking { - // User email must match "canevaluate" section of servers "functions/authorizedOnly/config.json" - val authorizedUser = app.createUserAndLogIn( - email = "authorizeduser@example.org", - password = "asdfasdf" - ) - assertNotNull( - authorizedUser.functions.call( - AUTHORIZED_ONLY_FUNCTION.name, - 1, - 2, - 3 - ) - ) - } - } - - @Test - fun getApp() { - assertEquals(app.app, functions.app) - } - - @Test - fun getUser() { - assertEquals(anonUser, functions.user) - } -// -// @Test -// fun defaultCodecRegistry() { -// // TODO Maybe we should test that setting configuration specific would propagate all the way -// // to here, but we do not have infrastructure to easily override TestApp configuration, -// // and actual configuration is verified in AppConfigurationTests -// assertEquals(app.configuration.defaultCodecRegistry, functions.defaultCodecRegistry) -// } -// -// @Test -// fun customCodecRegistry() { -// val configCodecRegistry = CodecRegistries.fromCodecs(StringCodec()) -// val customCodecRegistryFunctions = anonUser.getFunctions(configCodecRegistry) -// assertEquals(configCodecRegistry, customCodecRegistryFunctions.defaultCodecRegistry) -// } -// -// @Test -// fun illegalBsonArgument() { -// // Coded that will generate non-BsonArray from list -// val faultyListCodec = object : Codec> { -// override fun getEncoderClass(): Class> { -// return Iterable::class.java -// } -// -// override fun encode(writer: BsonWriter, value: Iterable<*>, encoderContext: EncoderContext) { -// writer.writeString("Not an array") -// } -// -// override fun decode(reader: BsonReader?, decoderContext: DecoderContext?): ArrayList<*> { -// TODO("Not yet implemented") -// } -// } -// // Codec registry that will use the above faulty codec for lists -// val faultyCodecRegistry = CodecRegistries.fromProviders( -// object : CodecProvider { -// override fun get(clazz: Class?, registry: CodecRegistry?): Codec { -// @Suppress("UNCHECKED_CAST") -// return faultyListCodec as Codec -// } -// } -// ) -// assertFailsWith { -// functions.callFunction(FIRST_ARG_FUNCTION, listOf("Realm"), String::class.java, faultyCodecRegistry) -// } -// } -// -// // Test cases previously failing due to C++ parsing -// @Test -// fun roundtrip_arrayOfBinary() { -// val value = byteArrayOf(1, 2, 3) -// val listOf = listOf(value) -// val actual = functions.callFunction(FIRST_ARG_FUNCTION, listOf, ByteArray::class.java) -// assertEquals(value.toList(), actual.toList()) -// } -// -// @Test -// fun roundtrip_arrayOfDocuments() { -// val map = mapOf("foo" to 5, "bar" to 7) -// assertEquals(map, functions.callFunction(FIRST_ARG_FUNCTION, listOf(map), Map::class.java)) -// } -// -// @Test -// @Ignore("C++ parser does not support binary subtypes yet") -// fun roundtrip_binaryUuid() { -// // arg = "{"value": {"$binary": {"base64": "JmS8oQitTny4IPS2tyjmdA==", "subType": "04"}}}" -// // response = "{"value":{"$binary":{"base64":"JmS8oQitTny4IPS2tyjmdA==","subType":"00"}}}" -// assertTypeOfFirstArgFunction(BsonBinary(UUID.randomUUID()), BsonBinary::class.java) -// } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt deleted file mode 100644 index 58c2943aab..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/GeoSpatialTests.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.annotations.ExperimentalGeoSpatialApi -import io.realm.kotlin.entities.Location -import io.realm.kotlin.entities.sync.SyncRestaurant -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use -import io.realm.kotlin.types.geo.Distance -import io.realm.kotlin.types.geo.GeoBox -import io.realm.kotlin.types.geo.GeoCircle -import io.realm.kotlin.types.geo.GeoPoint -import io.realm.kotlin.types.geo.GeoPolygon -import org.mongodb.kbson.ObjectId -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.time.Duration.Companion.seconds - -@OptIn(ExperimentalGeoSpatialApi::class) -class GeoSpatialTests { - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - private suspend fun createRandomUser(): User = - app.createUserAndLogIn( - email = TestHelper.randomEmail(), - password = "password1234" - ) - - @Test - fun write() { - runBlocking { - val user = createRandomUser() - - val config = - SyncConfiguration.Builder( - user = user, - schema = FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { - add(it.query()) - }.build() - - Realm.open(config).use { realm -> - realm.write { - copyToRealm(SyncRestaurant()) - } - } - } - } - - @Test - fun write_outsideSubscriptionsFail() { - runBlocking { - val user = createRandomUser() - - val config = - SyncConfiguration.Builder( - user = user, - schema = FLEXIBLE_SYNC_SCHEMA - ).build() - - Realm.open(config).use { realm -> - realm.write { - assertFailsWith( - message = "[RLM_ERR_NO_SUBSCRIPTION_FOR_WRITE]: Cannot write to class SyncRestaurant when no flexible sync subscription has been created." - ) { - copyToRealm(SyncRestaurant()) - } - } - } - } - } - - @Test - fun geoBox_tests() { - runBlocking { - generic_geo_test( - bounds = GeoBox.create( - top = 1.0, left = 1.0, - bottom = -1.0, right = -1.0, - ), - validLocation = Location(0.0, 0.0), - invalidLocation = Location(40.0, 40.0), - ) - } - } - - @Test - fun geoCircle_tests() { - runBlocking { - generic_geo_test( - bounds = GeoCircle.create( - GeoPoint.create(0.0, 0.0), Distance.fromKilometers(.01) - ), - validLocation = Location(0.0, 0.0), - invalidLocation = Location(40.0, 40.0), - ) - } - } - - @Test - fun geoPolygon_tests() { - runBlocking { - generic_geo_test( - bounds = GeoPolygon.create( - outerRing = listOf( - GeoPoint.create(-5.0, -5.0), - GeoPoint.create(5.0, -5.0), - GeoPoint.create(5.0, 5.0), - GeoPoint.create(-5.0, 5.0), - GeoPoint.create(-5.0, -5.0) - ), - holes = arrayOf( - listOf( - GeoPoint.create(-4.0, -4.0), - GeoPoint.create(4.0, -4.0), - GeoPoint.create(4.0, 4.0), - GeoPoint.create(-4.0, 4.0), - GeoPoint.create(-4.0, -4.0) - ) - ) - ), - validLocation = Location(4.5, 4.5), // Outside the hole and withing the ring - invalidLocation = Location(0.0, 0.0), // Inside the hole - ) - } - } - - private suspend fun generic_geo_test( - bounds: Any, - validLocation: Location, - invalidLocation: Location, - ) { - val section = ObjectId() - - // User #1 will try to write some data and assert some failure conditions. - val user1 = createRandomUser() - val config = - SyncConfiguration.Builder( - user = user1, - schema = FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { - add( - it.query( - "section = $0 AND location GEOWITHIN $1", - section, - bounds - ) - ) - }.waitForInitialRemoteData().build() - - Realm.open(config).use { realm -> - val restaurant = realm.write { - // Fail: write outside subscription bounds, compensating write - copyToRealm( - SyncRestaurant().apply { - this.section = section - this.location = invalidLocation - } - ) - - // Ok: Write within subscription bounds - copyToRealm( - SyncRestaurant().apply { - this.section = section - this.location = validLocation - } - ) - - // Ok: Write within subscription bounds, this one will be moved outside of bounds in the next step. - copyToRealm( - SyncRestaurant().apply { - this.section = section - this.location = validLocation - } - ) - } - - realm.syncSession.uploadAllLocalChanges(timeout = 30.seconds) - - realm.write { - // Ok. The object will be updated and moved outside of its view. - // It is a valid operation, it would be removed from the local Realm but still be - // accessible at the mongo instance. - findLatest(restaurant)!!.location = invalidLocation - } - - realm.syncSession.uploadAllLocalChanges(timeout = 30.seconds) - } - - // Download data on user #2 - val user2 = createRandomUser() - val config2 = - SyncConfiguration.Builder( - user = user2, - schema = FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { - add( - it.query( - "section = $0 AND location GEOWITHIN $1", - section, - bounds - ) - ) - }.waitForInitialRemoteData( - timeout = 30.seconds - ).build() - - Realm.open(config2).use { realm -> - assertEquals(1, realm.query().count().find()) - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt deleted file mode 100644 index bb8cffc67d..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/HttpLogObfuscatorTests.kt +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogCategory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.GoogleAuthType -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.ext.call -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.CustomLogCollector -import io.realm.kotlin.test.mongodb.syncServerAppName -import io.realm.kotlin.test.mongodb.util.BaseAppInitializer -import io.realm.kotlin.test.mongodb.util.FIRST_ARG_FUNCTION -import io.realm.kotlin.test.mongodb.util.NULL_FUNCTION -import io.realm.kotlin.test.mongodb.util.SUM_FUNCTION -import io.realm.kotlin.test.mongodb.util.addEmailProvider -import io.realm.kotlin.test.util.receiveOrFail -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope -import org.mongodb.kbson.BsonNull -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFails -import kotlin.test.fail - -private const val password: String = "password1234" - -class HttpLogObfuscatorTests { - - private lateinit var app: TestApp - private lateinit var channel: Channel - private lateinit var user: User - - private class ObfuscatorLoggerInspector( - private val channel: Channel - ) : RealmLogger { - override fun log( - category: LogCategory, - level: LogLevel, - throwable: Throwable?, - message: String?, - vararg args: Any? - ) { - message?.also { - if (it.contains(""""password":"***"""")) { - channel.trySend(Operation.OBFUSCATED_PASSWORD) - } else if (it.contains(""""access_token":"***","refresh_token":"***"""")) { - channel.trySend(Operation.OBFUSCATED_ACCESS_AND_REFRESH_TOKENS) - } else if (it.contains(""""key":"***"""")) { - channel.trySend(Operation.OBFUSCATED_API_KEY) - } else if (it.contains(""""id_token":"***"""")) { - channel.trySend(Operation.OBFUSCATED_APPLE_OR_GOOGLE_ID_TOKEN) - } else if (it.contains(""""accessToken":"***"""")) { - channel.trySend(Operation.OBFUSCATED_FACEBOOK) - } else if (it.contains(""""authCode":"***"""")) { - channel.trySend(Operation.OBFUSCATED_GOOGLE_AUTH_CODE) - } else if (it.contains(""""token":"***"""")) { - channel.trySend(Operation.OBFUSCATED_JWT) - } else if ( - it.contains(""""arguments":[***]""") || - it.contains("BODY START\n***\nBODY END") - ) { - channel.trySend(Operation.OBFUSCATED_CUSTOM_FUNCTION) - } else if (it.contains(""""password":"$password"""")) { - channel.cancel(CancellationException("Password was not obfuscated: $message")) - } else if (it.contains(""""(("access_token"):(".+?")),(("refresh_token"):(".+?"))""".toRegex())) { - channel.cancel( - CancellationException( - """Access and refresh tokens were not obfuscated: $message""".trimMargin() - ) - ) - } else if (it.contains(""""(("key"):(".+?"))""".toRegex())) { - channel.cancel( - CancellationException( - """API key was not obfuscated: $message""".trimMargin() - ) - ) - } - } - } - } - - private enum class Operation { - OBFUSCATED_PASSWORD, - OBFUSCATED_ACCESS_AND_REFRESH_TOKENS, - OBFUSCATED_API_KEY, - OBFUSCATED_APPLE_OR_GOOGLE_ID_TOKEN, - OBFUSCATED_FACEBOOK, - OBFUSCATED_GOOGLE_AUTH_CODE, - OBFUSCATED_JWT, - OBFUSCATED_CUSTOM_FUNCTION - } - - @BeforeTest - fun setUp() { - RealmLog.setLevel(LogLevel.DEBUG) - channel = Channel(1) - RealmLog.add(ObfuscatorLoggerInspector(channel)) - } - - private fun initApp(): TestApp { - return TestApp( - this::class.simpleName, - object : BaseAppInitializer(syncServerAppName("obfsctr"), { app -> - addEmailProvider(app) - app.addFunction(FIRST_ARG_FUNCTION) - app.addFunction(SUM_FUNCTION) - app.addFunction(NULL_FUNCTION) - }) {} - ) - } - - @AfterTest - fun tearDown() { - channel.close() - RealmLog.reset() - - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun nullObfuscator() = runBlocking { - val logger = CustomLogCollector() - RealmLog.add(logger) - app = TestApp( - "nullObfuscator", - object : BaseAppInitializer( - syncServerAppName("null-obf"), - { app -> - addEmailProvider(app) - app.addFunction(FIRST_ARG_FUNCTION) - app.addFunction(SUM_FUNCTION) - app.addFunction(NULL_FUNCTION) - } - ) {}, - builder = { it.httpLogObfuscator(null) }, - ) - - // Create user and log in - user = app.createUserAndLogin() - - // Create API key - val key = user.apiKeyAuth.create("foo") - app.login(Credentials.apiKey(key.value!!)) - - // Login with Apple credentials fails as it normally does - assertFails { - app.login(Credentials.apple("apple")) - } - - // Login with Facebook credentials fails as it normally does - assertFails { - app.login(Credentials.facebook("facebook")) - } - - // Login with Google credentials fails as it normally does - assertFails { - app.login(Credentials.google("google-auth-code", GoogleAuthType.AUTH_CODE)) - } - assertFails { - app.login(Credentials.google("google-id-token", GoogleAuthType.ID_TOKEN)) - } - - // Login with JWT fails as it normally does - assertFails { - app.login(Credentials.jwt("jwt")) - } - - // Calling functions with arguments results in these not being obfuscated - with(user.functions) { - call(FIRST_ARG_FUNCTION.name, 42.0) - call(SUM_FUNCTION.name, 42.0, 1.0) - call(NULL_FUNCTION.name) - } - - // Verify that none of the logs are obfuscated - logger.logs.forEach { message -> - message.also { - if (it.contains(""""password":"***"""")) { - fail("Password was obfuscated: $it") - } else if (it.contains(""""access_token":"***","refresh_token":"***"""")) { - fail("Access/refresh tokens were obfuscated: $it") - } else if (it.contains(""""key":"***"""")) { - fail("API key was obfuscated: $it") - } else if (it.contains(""""id_token":"***"""")) { - fail("Apple/Google ID tokens were obfuscated: $it") - } else if (it.contains(""""accessToken":"***"""")) { - fail("Facebook token was obfuscated: $it") - } else if (it.contains(""""authCode":"***"""")) { - fail("Google Auth Code was obfuscated: $it") - } else if (it.contains(""""token":"***"""")) { - fail("JWT key was obfuscated: $it") - } else if ( - it.contains(""""arguments":[***]""") || - it.contains("BODY START\n***\nBODY END") - ) { - fail("Custom function arguments were obfuscated: $it") - } - } - } - } - - @Test - fun emailPassword_registerAndLogin() = runBlocking { - app = initApp() - createUserAndLoginAssertions() - } - - @Test - fun apiKey_createAndLogin() = runBlocking { - app = initApp() - createUserAndLoginAssertions() - - async { - val key = user.apiKeyAuth.create("foo") - app.login(Credentials.apiKey(key.value!!)) - } - - // Create API KEY - response (obfuscate API key) - assertEquals(Operation.OBFUSCATED_API_KEY, channel.receiveOrFail()) - // Login API KEY - request (obfuscate API key) - assertEquals(Operation.OBFUSCATED_API_KEY, channel.receiveOrFail()) - // Login API KEY - response (obfuscate access and refresh tokens) - assertEquals(Operation.OBFUSCATED_ACCESS_AND_REFRESH_TOKENS, channel.receiveOrFail()) - } - - @Test - fun apple_login() = runBlocking { - app = initApp() - - async { - // Testing this requires a valid token so let's just test we obfuscate the request - assertFails { - app.login(Credentials.apple("apple")) - } - } - // Login Apple - request (obfuscate token) - assertEquals(Operation.OBFUSCATED_APPLE_OR_GOOGLE_ID_TOKEN, channel.receiveOrFail()) - } - - @Test - fun facebook_login() = runBlocking { - app = initApp() - - async { - // Testing this requires a valid token so let's just test we obfuscate the request - assertFails { - app.login(Credentials.facebook("facebook")) - } - } - // Login Facebook - request (obfuscate token) - assertEquals(Operation.OBFUSCATED_FACEBOOK, channel.receiveOrFail()) - } - - @Test - fun googleAuthToken_login() = runBlocking { - app = initApp() - - async { - // Testing these two requires a valid token so let's just test we obfuscate the request - assertFails { - app.login(Credentials.google("google-auth-code", GoogleAuthType.AUTH_CODE)) - } - assertFails { - app.login(Credentials.google("google-id-token", GoogleAuthType.ID_TOKEN)) - } - } - // Login Google auth token - request (obfuscate token) - assertEquals(Operation.OBFUSCATED_GOOGLE_AUTH_CODE, channel.receiveOrFail()) - // Login Google ID token - request (obfuscate token) - assertEquals(Operation.OBFUSCATED_APPLE_OR_GOOGLE_ID_TOKEN, channel.receiveOrFail()) - } - - @Test - fun jwtToken_login() = runBlocking { - app = initApp() - createUserAndLoginAssertions() - - async { - // Testing this requires a valid token so let's just test we obfuscate the request - assertFails { - app.login(Credentials.jwt("jwt")) - } - } - // Login JWT - request (obfuscate token) - assertEquals(Operation.OBFUSCATED_JWT, channel.receiveOrFail()) - } - - @Test - fun customFunction() = runBlocking { - app = initApp() - createUserAndLoginAssertions() - - async { - with(user.functions) { - call(FIRST_ARG_FUNCTION.name, 42.0) - call(SUM_FUNCTION.name, 42.0, 1.0) - call(NULL_FUNCTION.name) - } - } - // 1st custom function call - request (obfuscate arguments) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - // 1st custom function call - response (obfuscate result) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - - // 2nd custom function call - request (obfuscate arguments) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - // 2nd custom function call - response (obfuscate result) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - - // 3rd custom function call - request (obfuscate arguments) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - // 3rd custom function call - response (obfuscate result) - assertEquals(Operation.OBFUSCATED_CUSTOM_FUNCTION, channel.receiveOrFail()) - } - - private suspend fun createUserAndLoginAssertions() { - coroutineScope { - val deferred = async { user = app.createUserAndLogin() } - assertEquals(Operation.OBFUSCATED_PASSWORD, channel.receiveOrFail()) - assertEquals(Operation.OBFUSCATED_PASSWORD, channel.receiveOrFail()) - assertEquals(Operation.OBFUSCATED_ACCESS_AND_REFRESH_TOKENS, channel.receiveOrFail()) - deferred.cancel() - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt deleted file mode 100644 index 9e5f9524a0..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/MutableSubscriptionSetTests.kt +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.flx.FlexChildObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SubscriptionSetState -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.toRealmInstant -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.delay -import kotlinx.datetime.Clock -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue - -/** - * Class wrapping tests for modifying a subscription set. - */ -class MutableSubscriptionSetTests { - - private lateinit var app: TestApp - private lateinit var realm: Realm - private lateinit var config: SyncConfiguration - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - config = SyncConfiguration.Builder( - user, - schema = FLEXIBLE_SYNC_SCHEMA - ) - .build() - realm = Realm.open(config) - } - - @AfterTest - fun tearDown() { - realm.close() - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun initialSubscriptions() = runBlocking { - realm.subscriptions.update { - assertEquals(0, size) - assertEquals(SubscriptionSetState.UNCOMMITTED, state) - } - Unit - } - - @Test - fun addNamedSubscription() = runBlocking { - val now = Clock.System.now().toRealmInstant() - // On macOS, Core and Kotlin apparently doesn't agree on the exact timing, sometimes - // resulting in Core setting an earlier timestamp than "now". To prevent flaky tests - // we thus wait a little before letting Core write the timestamp. - // See https://github.com/realm/realm-kotlin/issues/846 - delay(1000) - val updatedSubs = realm.subscriptions.update { realmRef: Realm -> - add(realmRef.query(), "test") - } - assertEquals(1, updatedSubs.size) - assertEquals(SubscriptionSetState.PENDING, updatedSubs.state) - val sub: Subscription = updatedSubs.first() - assertEquals("test", sub.name) - assertEquals("TRUEPREDICATE", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - assertTrue(now <= sub.createdAt, "Was: $now <= ${sub.createdAt}") - assertEquals(sub.updatedAt, sub.createdAt) - } - - @Test - fun addAnonymousSubscription() = runBlocking { - val now = Clock.System.now().toRealmInstant() - // on macOS Core and Kotlin apparently doesn't agree on the exact timing, sometimes - // resulting in Core setting an earlier timestamp than "now". To prevent flaky tests - // we thus wait a little before letting Core write the timestamp. - delay(1000) - val updatedSubs = realm.subscriptions.update { realmRef: Realm -> - add(realmRef.query()) - } - assertEquals(1, updatedSubs.size) - assertEquals(SubscriptionSetState.PENDING, updatedSubs.state) - val sub: Subscription = updatedSubs.first() - assertNull(sub.name) - assertEquals("TRUEPREDICATE", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - assertTrue(now <= sub.createdAt, "Was: $now <= ${sub.createdAt}") - assertEquals(sub.updatedAt, sub.createdAt) - } - - @Test - fun add_multiple_anonymous() = runBlocking { - realm.subscriptions.update { realmRef: Realm -> - assertEquals(0, size) - add(realmRef.query()) - add(realmRef.query("section = $0", 10L)) - add(realmRef.query("section = $0 ", 5L)) - add(realmRef.query("section = $0", 1L)) - assertEquals(4, size) - } - Unit - } - - @Test - fun addExistingAnonymous_returnsAlreadyPersisted() = runBlocking { - realm.subscriptions.update { realmRef: Realm -> - val sub1 = add(realmRef.query()) - val sub2 = add(realmRef.query()) - assertEquals(sub1, sub2) - } - Unit - } - - @Test - fun addExistingNamed_returnsAlreadyPersisted() = runBlocking { - realm.subscriptions.update { realmRef: Realm -> - val sub1 = add(realmRef.query(), "sub1") - val sub2 = add(realmRef.query(), "sub1") - assertEquals(sub1, sub2) - } - Unit - } - - @Test - fun add_conflictingNamesThrows() = runBlocking { - realm.subscriptions.update { realmRef: Realm -> - add(realmRef.query(), "sub1") - assertFailsWith { - add(realmRef.query("name = $0", "foo"), "sub1") - } - } - Unit - } - - @Test - fun update() = runBlocking { - val subs = realm.subscriptions - subs.update { realmRef: Realm -> - realmRef.query().subscribe("sub1") - } - val createdAt = subs.first().createdAt - subs.update { realmRef: Realm -> - realmRef.query("name = $0", "red").subscribe("sub1", updateExisting = true) - } - val sub = subs.first() - assertEquals("sub1", sub.name) - assertEquals("FlexParentObject", sub.objectType) - assertEquals("name == \"red\"", sub.queryDescription) - assertTrue(sub.createdAt < sub.updatedAt) - assertEquals(createdAt, sub.createdAt) - } - - @Test - fun removeNamed() = runBlocking { - var updatedSubs = realm.subscriptions.update { realmRef: Realm -> - realmRef.query().subscribe("test") - } - assertEquals(1, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(remove("test")) - assertEquals(0, size) - } - assertEquals(0, updatedSubs.size) - } - - @Test - fun removeNamed_fails() = runBlocking { - realm.subscriptions.update { - assertFalse(remove("dont-exists")) - } - Unit - } - - @Test - fun removeSubscription_success() = runBlocking { - var updatedSubs = realm.subscriptions.update { realmRef: Realm -> - realmRef.query().subscribe("test") - } - assertEquals(1, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(remove(first())) - assertEquals(0, size) - } - assertEquals(0, updatedSubs.size) - } - - @Test - fun removeSubscription_fails() = runBlocking { - realm.subscriptions.update { realmRef: Realm -> - val managedSub = add(realmRef.query()) - assertTrue(remove(managedSub)) - assertFalse(remove(managedSub)) - } - Unit - } - - @Test - fun removeAllStringTyped() = runBlocking { - var updatedSubs: SubscriptionSet = realm.subscriptions.update { realmRef: Realm -> - add(realmRef.query()) - realmRef.query().subscribe(name = "parents") - add(realmRef.query()) - realmRef.query().subscribe(name = "children") - removeAll("FlexParentObject") - } - assertEquals(2, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(removeAll("FlexChildObject")) - } - assertEquals(0, updatedSubs.size) - } - - @Test - fun removeAllStringTyped_fails() = runBlocking { - // Not part of schema - realm.subscriptions.update { - assertFailsWith { - removeAll("DontExists") - } - } - - // part of schema - realm.subscriptions.update { - assertFalse(removeAll("FlexParentObject")) - } - Unit - } - - @Test - fun removeAllClassTyped() = runBlocking { - var updatedSubs = realm.subscriptions.update { realmRef: Realm -> - add(realmRef.query()) - } - assertEquals(1, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(removeAll(FlexParentObject::class)) - assertEquals(0, size) - } - assertEquals(0, updatedSubs.size) - } - - @Test - fun removeAllClassTyped_fails() = runBlocking { - // Not part of schema - realm.subscriptions.update { - assertFailsWith { - removeAll(io.realm.kotlin.entities.Sample::class) - } - } - - // part of schema - realm.subscriptions.update { - assertFalse(removeAll(FlexParentObject::class)) - } - Unit - } - - @Test - fun removeAll() = runBlocking { - var updatedSubs = realm.subscriptions.update { realmRef: Realm -> - realmRef.query().subscribe("test") - realmRef.query().subscribe("test2") - } - assertEquals(2, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(removeAll()) - assertEquals(0, size) - } - assertEquals(0, updatedSubs.size) - } - - @Test - fun removeAll_anonymouslyOnly() = runBlocking { - var updatedSubs = realm.subscriptions.update { realmRef: Realm -> - realmRef.query().subscribe("test") - realmRef.query().subscribe() - } - assertEquals(2, updatedSubs.size) - updatedSubs = updatedSubs.update { - assertTrue(removeAll(anonymousOnly = true)) - assertEquals(1, size) - } - assertEquals(1, updatedSubs.size) - } - - @Test - fun removeAll_fails() = runBlocking { - realm.subscriptions.update { - assertFalse(removeAll()) - } - Unit - } - - // Ensure that all resources are correctly torn down when an error happens inside a - // MutableSubscriptionSet - @Ignore // Require support for deleting synchronized Realms. See https://github.com/realm/realm-kotlin/issues/1425 - @Test - @Suppress("TooGenericExceptionThrown") - fun deleteFile_exceptionInsideMutableRealm() = runBlocking { - try { - realm.subscriptions.update { - throw RuntimeException("Boom!") - } - } catch (ex: RuntimeException) { - if (ex.message == "Boom!") { - realm.close() - Realm.deleteRealm(config) - } - } - Unit - } - - @Test - fun iterator_duringWrite() = runBlocking { - realm.subscriptions.update { - assertFalse(iterator().hasNext()) - add(realm.query(), name = "sub") - var iterator = iterator() - assertTrue(iterator.hasNext()) - val sub = iterator.next() - assertEquals("sub", sub.name) - assertFalse(iterator.hasNext()) - removeAll() - iterator = iterator() - assertFalse(iterator.hasNext()) - } - Unit - } - - private suspend fun uploadServerData(sectionId: Int, noOfObjects: Int) { - val user = app.createUserAndLogin() - val config = SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - it.query().subscribe() - } - .waitForInitialRemoteData() - .build() - - Realm.open(config).use { realm -> - realm.writeBlocking { - repeat(noOfObjects) { - copyToRealm(FlexParentObject(sectionId)) - } - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt deleted file mode 100644 index 3b65a396e3..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/PBSProgressListenerTests.kt +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.Direction -import io.realm.kotlin.mongodb.sync.Progress -import io.realm.kotlin.mongodb.sync.ProgressMode -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.last -import kotlinx.coroutines.flow.scan -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.withTimeout -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -class PBSProgressListenerTests { - private val TEST_SIZE = 10 - private val TIMEOUT = 30.seconds - - private lateinit var app: TestApp - private lateinit var partitionValue: String - - @BeforeTest - fun setup() { - RealmLog.setLevel(LogLevel.INFO) - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - partitionValue = org.mongodb.kbson.ObjectId().toString() - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - @Ignore // https://github.com/realm/realm-core/issues/7627 - fun downloadProgressListener_changesOnly() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { uploadRealm -> - // Verify that we: - // - get a "transferComplete" event - // - complete the flow, and - // - that all objects are available afterwards - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - // Ensure that we can do consecutive CURRENT_CHANGES registrations - repeat(3) { iteration -> - val transferCompleteJob = async { - realm.syncSession.progressAsFlow( - Direction.DOWNLOAD, - ProgressMode.CURRENT_CHANGES - ).run { - withTimeout(TIMEOUT) { - last().let { progress: Progress -> - assertTrue(progress.isTransferComplete) - assertEquals(1.0, progress.estimate) - } - } - } - } - realm.syncSession.runWhilePaused { - uploadRealm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - } - - transferCompleteJob.await() - - // Progress.isTransferComplete does not guarantee that changes are integrated and - // visible in the realm - realm.syncSession.downloadAllServerChanges(TIMEOUT) - assertEquals( - TEST_SIZE * (iteration + 1), - realm.query().find().size - ) - } - } - } - } - - @Test - fun downloadProgressListener_indefinitely() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { uploadRealm -> - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession - .progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - .completionCounter() - - withTimeout(TIMEOUT) { - flow.takeWhile { completed -> completed < 3 } - .collect { completed -> - realm.syncSession.runWhilePaused { - uploadRealm.writeSampleData( - TEST_SIZE, - timeout = TIMEOUT - ) - } - } - } - } - } - } - - @Test - fun uploadProgressListener_changesOnly() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - repeat(3) { - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.CURRENT_CHANGES) - .run { - withTimeout(TIMEOUT) { - last().let { - assertTrue(it.isTransferComplete) - assertEquals(1.0, it.estimate) - } - } - } - } - } - } - - @Test - fun uploadProgressListener_indefinitely() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession - .progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - .completionCounter() - - withTimeout(TIMEOUT) { - flow.takeWhile { completed -> completed < 3 } - .collect { _ -> - realm.syncSession.runWhilePaused { - realm.writeSampleData(TEST_SIZE) - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } - } - } - - @Test - @Ignore // https://github.com/realm/realm-core/issues/7627 - fun worksAfterExceptions() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - } - - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - val flow = realm.syncSession - .progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - - assertFailsWith { - flow.collect { - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("Crashing progress flow") - } - } - - withTimeout(TIMEOUT) { - flow.first { it.isTransferComplete } - } - } - } - - @Test - @Ignore // https://github.com/realm/realm-core/issues/7627 - fun worksAfterCancel() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - realm.writeSampleData(TEST_SIZE, timeout = TIMEOUT) - } - - Realm.open(createSyncConfig(app.createUserAndLogin())).use { realm -> - // Setup a flow that we are just going to cancel - val flow = - realm.syncSession - .progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - - supervisorScope { - val mutex = Mutex(true) - val task = async { - flow.collect { - mutex.unlock() - } - } - // Await the flow actually being active - mutex.lock() - task.cancel() - } - - // Verify that progress listeners still work - withTimeout(TIMEOUT) { - flow.first { it.isTransferComplete } - } - } - } - - @Test - @Ignore // https://github.com/realm/realm-core/issues/7627 - fun triggerImmediatelyWhenRegistered() = runBlocking { - Realm.open(createSyncConfig(app.createUserAndLogIn())).use { realm -> - withTimeout(10.seconds) { - // Ensure that all data is already synced - realm.syncSession.uploadAllLocalChangesOrFail() - assertTrue { realm.syncSession.downloadAllServerChanges() } - // Ensure that progress listeners are triggered at least one time even though there - // is no data - realm.syncSession.progressAsFlow(Direction.DOWNLOAD, ProgressMode.CURRENT_CHANGES) - .first() - realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.CURRENT_CHANGES) - .first() - realm.syncSession.progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - .first() - realm.syncSession.progressAsFlow(Direction.UPLOAD, ProgressMode.INDEFINITELY) - .first() - } - } - } - - @Test - fun completesOnClose() = runBlocking { - val channel = - TestChannel(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - TestApp("completesOnClose", DefaultPartitionBasedAppInitializer).use { app -> - val user = app.createUserAndLogIn() - val realm = Realm.open(createSyncConfig(user)) - try { - val flow = - realm.syncSession.progressAsFlow(Direction.DOWNLOAD, ProgressMode.INDEFINITELY) - val job = async { - withTimeout(10.seconds) { - flow.collect { - channel.send(true) - } - } - } - // Wait for Flow to start, so we do not close the Realm before - // `flow.collect()` can be called. - channel.receiveOrFail() - realm.close() - job.await() - } finally { - channel.close() - if (!realm.isClosed()) { - realm.close() - } - } - } - } - - private suspend fun Realm.writeSampleData(count: Int, timeout: Duration? = null) { - repeat(count) { - write { - copyToRealm( - SyncObjectWithAllTypes() - .apply { - binaryField = Random.nextBytes(100) - } - ) - } - } - - timeout?.let { - assertTrue { syncSession.uploadAllLocalChanges(timeout) } - } - } - - // Operator that will return a flow that emits an increasing integer on each completion event - private fun Flow.completionCounter(): Flow = - filter { it.isTransferComplete } - .scan(0) { accumulator, _ -> - accumulator + 1 - } - - private fun createSyncConfig( - user: User, - partitionValue: String = getTestPartitionValue(), - ): SyncConfiguration { - return SyncConfiguration.Builder(user, partitionValue, PARTITION_BASED_SCHEMA) - .build() - } - - private fun getTestPartitionValue(): String { - if (!this::partitionValue.isInitialized) { - fail("Test not setup correctly. Partition value is missing") - } - return partitionValue - } - - private suspend fun SyncSession.runWhilePaused(block: suspend () -> Unit) { - pause() - block() - resume() - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt deleted file mode 100644 index f7b19a18f7..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SchemaProcessorTests.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.SchemaProcessor -import io.realm.kotlin.types.EmbeddedRealmObject -import kotlin.test.Test - -class CycleEmbeddedObject1 : EmbeddedRealmObject { - var name: String = "" - var o1: CycleEmbeddedObject2 = CycleEmbeddedObject2() -} - -class CycleEmbeddedObject2 : EmbeddedRealmObject { - var name: String = "" - var o1: CycleEmbeddedObject3 = CycleEmbeddedObject3() -} - -class CycleEmbeddedObject3 : EmbeddedRealmObject { - var name: String = "" - var o1: CycleEmbeddedObject1 = CycleEmbeddedObject1() -} - -class NoCycleEmbeddedObject1 : EmbeddedRealmObject { - var name: String = "" - var o1: NoCycleEmbeddedObject2 = NoCycleEmbeddedObject2() -} - -class NoCycleEmbeddedObject2 : EmbeddedRealmObject { - var name: String = "" -} - -class SchemaProcessorTests { - - @Test - fun cyclesThrow() { - assertFailsWithMessage("Cycles in embedded object schemas are not supported") { - SchemaProcessor.process( - "", - classes = setOf( - CycleEmbeddedObject1::class, - CycleEmbeddedObject2::class, - CycleEmbeddedObject3::class - ) - ) - } - } - - @Test - fun noCyclesDoesntThrow() { - SchemaProcessor.process( - "", - classes = setOf(NoCycleEmbeddedObject1::class, NoCycleEmbeddedObject2::class) - ) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt deleted file mode 100644 index be070c4c24..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionExtensionsTests.kt +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -@file:OptIn(ExperimentalFlexibleSyncApi::class) - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.annotations.ExperimentalFlexibleSyncApi -import io.realm.kotlin.mongodb.ext.subscribe -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSetState -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.WaitForSync -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.TimeoutCancellationException -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.Duration.Companion.seconds - -/** - * Class for testing the various extension methods we have for bridging the gap between Subscriptions - * and RealmQuery/RealmResults. - */ -class SubscriptionExtensionsTests { - - private lateinit var app: TestApp - private lateinit var realm: Realm - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - val config = SyncConfiguration.Builder( - user, - schema = FLEXIBLE_SYNC_SCHEMA - ) - .build() - realm = Realm.open(config) - } - - @AfterTest - fun tearDown() { - if (this::realm.isInitialized && !realm.isClosed()) { - realm.close() - } - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun realmQuery_subscribe_anonymous() = runBlocking { - val subs = realm.subscriptions - assertEquals(0, subs.size) - val results: RealmResults = realm.query().subscribe() - assertEquals(SubscriptionSetState.COMPLETE, subs.state) - assertEquals(1, subs.size) - val sub: Subscription = subs.first() - assertNull(sub.name) - assertEquals("TRUEPREDICATE", sub.queryDescription) - assertEquals(FlexParentObject::class.simpleName, sub.objectType) - } - - // Check that subscribing twice to a query will result in the same subscription - @Test - fun realmQuery_subscribe_anonymousTwice() = runBlocking { - val subs = realm.subscriptions - assertEquals(0, subs.size) - realm.query().subscribe() - realm.query().subscribe() - assertEquals(SubscriptionSetState.COMPLETE, subs.state) - assertEquals(1, subs.size) - val sub: Subscription = subs.first() - assertNull(sub.name) - assertEquals("TRUEPREDICATE", sub.queryDescription) - assertEquals(FlexParentObject::class.simpleName, sub.objectType) - } - - // Check that anonymous RealmQuery and RealmResults `subscribe` calls result in the same sub. - @Test - fun anonymousSubscriptionsOverlap() = runBlocking { - val subs = realm.subscriptions - assertEquals(0, subs.size) - realm.query().subscribe() - realm.query().find().subscribe() - assertEquals(SubscriptionSetState.COMPLETE, subs.state) - assertEquals(1, subs.size) - val sub: Subscription = subs.first() - assertNull(sub.name) - assertEquals("TRUEPREDICATE", sub.queryDescription) - assertEquals(FlexParentObject::class.simpleName, sub.objectType) - } - - // Verify that the realm query doesn't run against a frozen version prior to the Realm - // being updated from `waitForSynchronization`. - @Test - fun realmQuery_subscribe_queryResultIsLatestVersion() = runBlocking { - // Write data to a server Realm - val section = Random.nextInt() - val (email, password) = TestHelper.randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email, password) - val config = SyncConfiguration.Builder( - user1, - schema = FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { realm: Realm -> - realm.query("section = $0", section).subscribe() - }.build() - - Realm.open(config).use { realmFromAnotherDevice -> - realmFromAnotherDevice.writeBlocking { - copyToRealm(FlexParentObject(section)) - } - realmFromAnotherDevice.syncSession.uploadAllLocalChanges(30.seconds) - } - - // Data still hasn't reached this device - assertEquals(0, realm.query().count().find()) - // Check that subscribing to a query, will run the query on the data downloaded from - // the server and not just local data, due to WaitForSync.FIRST_TIME being the default. - val result = realm.query("section = $0", section).subscribe() - assertEquals(1, result.size) - assertEquals(section, result.first().section) - } - - @Test - fun realmQuery_subscribe_waitFirstTime() = runBlocking { - val section = Random.nextInt() - - // Unnamed - realm.query("section = $0", section).subscribe() // Default value is WaitForSync.FIRST_TIME - var updatedSubs = realm.subscriptions - assertEquals(1, updatedSubs.size) - assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - var sub: Subscription = updatedSubs.first() - assertNull(sub.name) - assertEquals("section == $section", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - - // Checking that we don't hit the network the 2nd time around - realm.syncSession.pause() - val resultsAnonymous = realm.query("section = $0", section).subscribe() - assertTrue(resultsAnonymous.isEmpty()) - realm.syncSession.resume() - - // Named - realm.query("section = $0", section).subscribe("my-name") // Default value is WaitForSync.FIRST_TIME - updatedSubs = realm.subscriptions - assertEquals(2, updatedSubs.size) - assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - sub = updatedSubs.last() - assertEquals("my-name", sub.name) - assertEquals("section == $section", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - - // Checking that we don't hit the network the 2nd time around - realm.syncSession.pause() - val resultsNamed = realm.query("section = $0", section).subscribe("my-name") - assertTrue(resultsNamed.isEmpty()) - realm.syncSession.resume() - } - - @Test - @Ignore - // See https://github.com/realm/realm-kotlin/issues/1823 - fun realmQuery_subscribe_waitNever() = runBlocking { - // Un-named - realm.query().subscribe(mode = WaitForSync.NEVER) - var updatedSubs = realm.subscriptions - assertEquals(1, updatedSubs.size) - // Updating the subscription will happen in the background, but - // hopefully hasn't reached COMPLETE yet. - assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - - // Named - realm.query().subscribe(name = "my-name", mode = WaitForSync.NEVER) - updatedSubs = realm.subscriptions - assertEquals(2, updatedSubs.size) - // Updating the subscription will happen in the background, but - // hopefully hasn't reached COMPLETE yet. - assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - } - - @Test - fun realmQuery_subscribe_waitAlways() = runBlocking { - val sectionId = Random.nextInt() - val results1 = realm.query("section = $0", sectionId).subscribe() // Default value is WaitForSync.FIRST_TIME - assertEquals(0, results1.size) - uploadServerData(sectionId, 5) - // Since the subscription is already present, we cannot control if the data is downloaded - // before creating the next subscription. Instead we pause the syncSession and verify - // that WaitForSync.ALWAYS timeout during network failures and resuming the session should - // then work - realm.syncSession.pause() - assertFailsWith { - realm.query("section = $0", sectionId).subscribe(timeout = 3.seconds, mode = WaitForSync.ALWAYS) - } - realm.syncSession.resume() - val results2 = realm.query("section = $0", sectionId).subscribe(mode = WaitForSync.ALWAYS) - assertEquals(5, results2.size) - } - - @Test - fun realmQuery_subscribe_timeOut_fails() = runBlocking { - assertFailsWith { - realm.query().subscribe(timeout = 1.nanoseconds) - } - assertFailsWith { - realm.query().subscribe(name = "foo", timeout = 1.nanoseconds) - } - } - - @Test - fun realmQuery_subscribe_throwsInsideWrite() { - realm.writeBlocking { - // `subscribe()` being a suspend function make in hard to call - // subscribe inside a write, but we should still detect it. - runBlocking { - assertFailsWith { - query().subscribe() - } - assertFailsWith { - query().subscribe(name = "my-name") - } - } - } - } - - @Test - @Ignore - // See https://github.com/realm/realm-kotlin/issues/1823 - fun realmResults_subscribe_waitFirstTime() = runBlocking { - val section = Random.nextInt() - - // Unnamed - realm.query("section == $0", section).find().subscribe() // Default value is WaitForSync.FIRST_TIME - var updatedSubs = realm.subscriptions - assertEquals(1, updatedSubs.size) - assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - var sub: Subscription = updatedSubs.first() - assertNull(sub.name) - assertEquals("section == $section", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - - // Checking that we don't hit the network the 2nd time around - realm.syncSession.pause() - val resultsAnonymous = realm.query("section = $0", section).subscribe() - assertTrue(resultsAnonymous.isEmpty()) - realm.syncSession.resume() - - // Named - realm.query("section == $section").find().subscribe("my-name") // Default value is WaitForSync.FIRST_TIME - updatedSubs = realm.subscriptions - assertEquals(2, updatedSubs.size) - assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - sub = updatedSubs.last() - assertEquals("my-name", sub.name) - assertEquals("section == $section", sub.queryDescription) - assertEquals("FlexParentObject", sub.objectType) - - // Checking that we don't hit the network the 2nd time around - realm.syncSession.pause() - val resultsNamed = realm.query("section = $0", section).find().subscribe("my-name") - assertTrue(resultsNamed.isEmpty()) - realm.syncSession.resume() - } - - @Test - fun realmResults_subscribe_waitOnNever() = runBlocking { - // Un-named - realm.query().find().subscribe(mode = WaitForSync.NEVER) - var updatedSubs = realm.subscriptions - assertEquals(1, updatedSubs.size) - // Updating the subscription will happen in the background, but - // hopefully hasn't reached COMPLETE yet. - assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - - // Named - realm.query().find().subscribe(name = "my-name", mode = WaitForSync.NEVER) - updatedSubs = realm.subscriptions - assertEquals(2, updatedSubs.size) - // Updating the subscription will happen in the background, but - // hopefully hasn't reached COMPLETE yet. - assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - } - - @Test - fun realmResults_subscribe_waitAlways() = runBlocking { - val sectionId = Random.nextInt() - val results1 = realm.query("section = $0", sectionId).find().subscribe() // Default value is WaitForSync.FIRST_TIME - assertEquals(0, results1.size) - uploadServerData(sectionId, 5) - // Since the subscription is already present, we cannot control if the data is downloaded - // before creating the next subscription. Instead we pause the syncSession and verify - // that WaitForSync.ALWAYS timeout during network failures and resuming the session should - // then work - realm.syncSession.pause() - assertFailsWith { - realm.query("section = $0", sectionId).find().subscribe(timeout = 3.seconds, mode = WaitForSync.ALWAYS) - } - realm.syncSession.resume() - val results2 = realm.query("section = $0", sectionId).find().subscribe(mode = WaitForSync.ALWAYS) - assertEquals(5, results2.size) - } - - @Test - fun realmResults_subscribe_subquery() = runBlocking { - val topQueryResult: RealmResults = realm.query("section = 42").find() - val subQueryResult: RealmResults = topQueryResult.query("name == $0", "Jane").find() - subQueryResult.subscribe() - val subs = realm.subscriptions - assertEquals(1, subs.size) - assertEquals("section == 42 and name == \"Jane\"", subs.first().queryDescription) - subQueryResult.subscribe("my-name") - assertEquals(2, subs.size) - val lastSub = subs.last() - assertEquals("my-name", lastSub.name) - assertEquals("section == 42 and name == \"Jane\"", lastSub.queryDescription) - } - - @Test - fun realmResults_subscribe_timeOut_fails() = runBlocking { - assertFailsWith { - realm.query().find().subscribe(timeout = 1.nanoseconds) - } - assertFailsWith { - realm.query().find().subscribe(name = "foo", timeout = 1.nanoseconds) - } - } - - @Test - fun realmResults_subscribe_throwsInsideWrite() = runBlocking { - realm.writeBlocking { - // `subscribe()` being a suspend function make in hard to call - // subscribe inside a write, but we should still detect it. - runBlocking { - assertFailsWith { - query().find().subscribe() - } - assertFailsWith { - query().find().subscribe(name = "my-name") - } - } - } - } - - @Test - fun updatingOnlyQueryWillTriggerFirstTimeBehavior() = runBlocking { - val section = Random.nextInt() - - // 1. Create a named subscription - realm.query("section = $0", section).subscribe("my-name", mode = WaitForSync.FIRST_TIME) - - // 2. Pause the connection in order to go offline - realm.syncSession.pause() - - // 3. Update the query of the named subscription. This should trigger FIRST_TIME behavior again. - // and because we are offline, the subscribe call should throw. - val query = realm.query("section = $0 AND TRUEPREDICATE", section) - assertFailsWith { - query.subscribe("my-name", updateExisting = true, mode = WaitForSync.FIRST_TIME, timeout = 1.seconds) - } - } - - private suspend fun uploadServerData(sectionId: Int, noOfObjects: Int) { - val user = app.createUserAndLogin() - val config = SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - it.query().subscribe() - } - .waitForInitialRemoteData() - .build() - - Realm.open(config).use { realm -> - realm.writeBlocking { - repeat(noOfObjects) { - copyToRealm(FlexParentObject(sectionId)) - } - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt deleted file mode 100644 index 6c28da7467..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionSetTests.kt +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.exceptions.BadFlexibleSyncQueryException -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSetState -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.waitForSynchronizationOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertSame -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.nanoseconds - -/** - * Class wrapping tests for SubscriptionSets - */ -class SubscriptionSetTests { - - private lateinit var app: TestApp - private lateinit var realm: Realm - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - val config = SyncConfiguration.Builder( - user, - schema = FLEXIBLE_SYNC_SCHEMA - ) - .build() - realm = Realm.open(config) - } - - @AfterTest - fun tearDown() { - if (this::realm.isInitialized && !realm.isClosed()) { - realm.close() - } - if (this::app.isInitialized) { - app.close() - } - } - - // Verify that we only have a single SubscriptionSet instance exposed to end users - @Test - fun realmSubscriptionsReturnSameInstance() { - val sub1 = realm.subscriptions - val sub2 = realm.subscriptions - assertSame(sub1, sub2) - } - - @Test - fun subscriptions_failOnNonFlexibleSyncRealms() { - TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer).use { testApp -> - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - testApp.createUserAndLogIn(email, password) - } - val config = SyncConfiguration.create( - user, - TestHelper.randomPartitionValue(), - PARTITION_BASED_SCHEMA - ) - Realm.open(config).use { partionBasedRealm -> - assertFailsWith { partionBasedRealm.subscriptions } - } - } - } - - @Test - fun subscriptions_throwsOnClosedRealm() { - realm.close() - assertFailsWith { realm.subscriptions } - } - - @Test - fun initialSubscriptions() { - val subscriptions = realm.subscriptions - assertEquals(0, subscriptions.size) - val initialState = subscriptions.state - val expectedStates = listOf( - SubscriptionSetState.PENDING, - SubscriptionSetState.BOOTSTRAPPING, - SubscriptionSetState.COMPLETE, - ) - assertTrue(expectedStates.contains(initialState), "State was: $initialState") - } - - @Test - fun findByQuery() = runBlocking { - val query = realm.query() - val subscriptions = realm.subscriptions - assertNull(subscriptions.findByQuery(query)) - subscriptions.update { add(query) } - val sub: Subscription = subscriptions.findByQuery(query)!! - assertNotNull(sub) - assertEquals("FlexParentObject", sub.objectType) - assertEquals("TRUEPREDICATE", sub.queryDescription) - } - - @Test - fun findByName() = runBlocking { - val subscriptions = realm.subscriptions - assertNull(subscriptions.findByName("foo")) - subscriptions.update { - realm.query().subscribe("foo") - } - val sub: Subscription = subscriptions.findByName("foo")!! - assertNotNull(sub) - assertEquals("foo", sub.name) - } - - @Test - fun state() = runBlocking { - val subscriptions = realm.subscriptions - subscriptions.update { - realm.query().subscribe("test1") - } - assertEquals(SubscriptionSetState.PENDING, subscriptions.state) - subscriptions.waitForSynchronizationOrFail() - assertEquals(SubscriptionSetState.COMPLETE, subscriptions.state) - subscriptions.update { - // Flexible Sync queries cannot use limit - realm.query("age > 42 LIMIT(1)").subscribe("test2") - } - assertFailsWith { - subscriptions.waitForSynchronization() - } - assertEquals(SubscriptionSetState.ERROR, subscriptions.state) - } - - @Test - fun size() = runBlocking { - val subscriptions = realm.subscriptions - assertEquals(0, subscriptions.size) - subscriptions.update { - realm.query().subscribe() - } - assertEquals(1, subscriptions.size) - subscriptions.update { - removeAll() - } - assertEquals(0, subscriptions.size) - } - - @Test - fun errorMessage() = runBlocking { - val subscriptions = realm.subscriptions - assertNull(subscriptions.errorMessage) - subscriptions.update { - realm.query("age > 42 LIMIT(1)").subscribe() - } - assertFailsWith { - subscriptions.waitForSynchronization() - } - assertTrue(subscriptions.errorMessage!!.contains("Invalid query: invalid RQL for table \"FlexParentObject\": syntax error: unexpected Limit, expecting Or or RightParenthesis")) - subscriptions.update { - removeAll() - } - subscriptions.waitForSynchronizationOrFail() - assertNull(subscriptions.errorMessage) - } - - @Test - fun iterator_zeroSize() { - val subscriptions = realm.subscriptions - val iterator: Iterator = subscriptions.iterator() - assertFalse(iterator.hasNext()) - assertFailsWith { iterator.next() } - } - - @Test - fun iterator() = runBlocking { - val subscriptions = realm.subscriptions - subscriptions.update { - realm.query().subscribe("sub1") - } - val iterator: Iterator = subscriptions.iterator() - assertTrue(iterator.hasNext()) - assertEquals("sub1", iterator.next().name) - assertFalse(iterator.hasNext()) - assertFailsWith { iterator.next() } - Unit - } - - @Test - fun waitForSynchronizationInitialSubscriptions() = runBlocking { - val subscriptions = realm.subscriptions - subscriptions.waitForSynchronizationOrFail() - assertEquals(SubscriptionSetState.COMPLETE, subscriptions.state) - assertEquals(0, subscriptions.size) - } - - @Test - fun waitForSynchronizationInitialEmptySubscriptionSet() = runBlocking { - val subscriptions = realm.subscriptions - subscriptions.update { /* Do nothing */ } - subscriptions.waitForSynchronizationOrFail() - assertEquals(SubscriptionSetState.COMPLETE, subscriptions.state) - assertEquals(0, subscriptions.size) - } - - @Test - fun waitForSynchronization_success() = runBlocking { - val updatedSubs = realm.subscriptions.update { - realm.query().subscribe("test") - } - assertNotEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - updatedSubs.waitForSynchronizationOrFail() - assertEquals(SubscriptionSetState.COMPLETE, updatedSubs.state) - } - - @Test - fun waitForSynchronization_error() = runBlocking { - val updatedSubs = realm.subscriptions.update { - realm.query("age > 42 LIMIT(1)").subscribe("test") - } - assertFailsWith { - updatedSubs.waitForSynchronization() - } - assertEquals(SubscriptionSetState.ERROR, updatedSubs.state) - assertTrue(updatedSubs.errorMessage!!.contains("Invalid query: invalid RQL for table \"FlexParentObject\": syntax error: unexpected Limit, expecting Or or RightParenthesis")) - } - - // Test case for https://github.com/realm/realm-core/issues/5504 - @Test - fun waitForSynchronization_errorOnDescriptors() = runBlocking { - val updatedSubs = realm.subscriptions.update { - realm.query().limit(1).subscribe("test") - } - assertFailsWith { - updatedSubs.waitForSynchronization() - } - assertEquals(SubscriptionSetState.ERROR, updatedSubs.state) - assertEquals("TRUEPREDICATE and TRUEPREDICATE LIMIT(1)", updatedSubs.first().queryDescription) - assertTrue(updatedSubs.errorMessage!!.contains("Invalid query: invalid RQL for table \"FlexParentObject\": syntax error: unexpected Limit, expecting Or or RightParenthesis")) - } - - @Test - fun waitForSynchronization_timeOut() = runBlocking { - val updatedSubs = realm.subscriptions.update { - realm.query().subscribe() - } - assertTrue(updatedSubs.waitForSynchronization(1.minutes)) - } - - @Test - fun waitForSynchronization_timeOutFails() = runBlocking { - val updatedSubs = realm.subscriptions.update { - realm.query().subscribe() - } - assertFalse(updatedSubs.waitForSynchronization(1.nanoseconds)) - } - - @Test - fun methodsOnClosedRealm() = runBlocking { - // SubscriptionSets own their own DB resources, which is disconnected from the - // user facing Realm. This means that the subscription set technically can still - // be modified after the Realm is closed, but since this would produce awkward interactions - // with other API's that work on the Realm file, we should disallow modifying the - // SubscriptionSet if the Realm is closed. Just accessing data should be fine. - val subs = realm.subscriptions.update { - realm.query().subscribe("sub") - }.also { - it.waitForSynchronizationOrFail() - } - realm.close() - - // Valid methods - assertEquals(1, subs.size) - assertEquals(SubscriptionSetState.COMPLETE, subs.state) - assertNull(subs.errorMessage) - assertNotNull(subs.findByName("sub")) - // `findByQuery` does not work as queries will throw on closed Realms. - val iter = subs.iterator() - assertTrue(iter.hasNext()) - assertNotNull(iter.next()) - - // These methods will throw - assertFailsWith { - subs.refresh() - } - assertFailsWith { - subs.waitForSynchronization() - } - assertFailsWith { - subs.update { /* Do nothing */ } - } - - // Reading subscription data will also work - assertEquals("sub", subs.first().name) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt deleted file mode 100644 index 3dbab803bd..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SubscriptionTests.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.Subscription -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.asQuery -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper.randomEmail -import io.realm.kotlin.test.util.toRealmInstant -import io.realm.kotlin.types.RealmInstant -import kotlinx.coroutines.delay -import kotlinx.datetime.Clock -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue - -/** - * Class wrapping tests for Subscriptions - * This class only covers the [Subscription] class. For creating, deleting or modifying - * subscriptions, see [MutableSubscriptionSetTests]. - */ -class SubscriptionTests { - - private lateinit var app: TestApp - private lateinit var realm: Realm - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer,) - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - val config = SyncConfiguration.Builder( - user, - schema = FLEXIBLE_SYNC_SCHEMA - ) - .build() - realm = Realm.open(config) - } - - @AfterTest - fun tearDown() { - realm.close() - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun managedProperties() = runBlocking { - val now: RealmInstant = Clock.System.now().toRealmInstant() - // On macOS, Core and Kotlin apparently doesn't agree on the exact timing, sometimes - // resulting in Core setting an earlier timestamp than "now". To prevent flaky tests - // we thus wait a little before letting Core write the timestamp. - // See https://github.com/realm/realm-kotlin/issues/846 - delay(1000) - val namedSub: Subscription = realm.subscriptions.update { realm -> - realm.query().subscribe("mySub") - }.first() - - assertEquals("mySub", namedSub.name) - assertEquals("ParentPk", namedSub.objectType) - assertEquals("TRUEPREDICATE", namedSub.queryDescription) - assertTrue(now <= namedSub.updatedAt, "$now <= ${namedSub.updatedAt}") - assertTrue(now <= namedSub.createdAt, "$now <= ${namedSub.createdAt}") - - val anonSub = realm.subscriptions.update { realm -> - removeAll() - add(realm.query()) - }.first() - assertNull(anonSub.name) - assertEquals("ParentPk", anonSub.objectType) - assertEquals("TRUEPREDICATE", anonSub.queryDescription) - assertTrue(now <= namedSub.updatedAt, "$now <= ${namedSub.updatedAt}") - assertTrue(now <= namedSub.createdAt, "$now <= ${namedSub.createdAt}") - } - - @Test - fun properties_areSnaphotValues() = runBlocking { - val snapshotSub: Subscription = realm.subscriptions.update { realm -> - add(realm.query(), name = "mySub") - }.first() - - // Delete all underlying subscriptions - realm.subscriptions.update { - removeAll() - } - - // Check that properties still work even if subscription is deleted elsewhere - assertEquals("mySub", snapshotSub.name) - assertEquals("ParentPk", snapshotSub.objectType) - assertEquals("TRUEPREDICATE", snapshotSub.queryDescription) - assertNotNull(snapshotSub.updatedAt) - assertNotNull(snapshotSub.createdAt) - Unit - } - - @Test - @Ignore - // See https://github.com/realm/realm-kotlin/issues/1823 - fun asQuery() = runBlocking { - val sub: Subscription = realm.subscriptions.update { realm -> - add(realm.query("name = $0", "my-name")) - }.first() - - realm.write { - copyToRealm( - ParentPk().apply { - name = "my-name" - } - ) - } - val query: RealmQuery = sub.asQuery() - assertEquals("name == \"my-name\"", query.description()) - assertEquals(1, query.count().find()) - } - - @Test - fun asQuery_throwsOnWrongType() = runBlocking { - val sub: Subscription = realm.subscriptions.update { realm -> - add(realm.query("name = $0", "my-name")) - }.first() - - assertFailsWith { - sub.asQuery() - } - Unit - } - - @Test - fun equals() = runBlocking { - val subs: SubscriptionSet = realm.subscriptions.update { realm -> - add(realm.query(), name = "mySub") - } - val sub1: Subscription = subs.first() - val sub2: Subscription = subs.first() - assertEquals(sub1, sub2) - } - - @Test - fun equals_falseForDifferentVersions() = runBlocking { - var sub1 = realm.subscriptions.update { realm -> - add(realm.query(), name = "mySub") - }.first() - val sub2 = realm.subscriptions.update { realm -> - /* Do nothing */ - }.first() - assertNotEquals(sub1, sub2) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt deleted file mode 100644 index 4dc2108318..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientResetIntegrationTests.kt +++ /dev/null @@ -1,1412 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") // Needed to call session.simulateError() - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.MutableRealm -import io.realm.kotlin.Realm -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.entities.sync.SyncPerson -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogCategory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.log.RealmLogger -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException -import io.realm.kotlin.mongodb.internal.SyncSessionImpl -import io.realm.kotlin.mongodb.sync.DiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.ManuallyRecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverOrDiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.notifications.ResultsChange -import io.realm.kotlin.query.RealmQuery -import io.realm.kotlin.test.mongodb.TEST_APP_FLEX -import io.realm.kotlin.test.mongodb.TEST_APP_PARTITION -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.BaseAppInitializer -import io.realm.kotlin.test.mongodb.util.addEmailProvider -import io.realm.kotlin.test.mongodb.util.initializeFlexibleSync -import io.realm.kotlin.test.mongodb.util.initializePartitionSync -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.trySendOrFail -import io.realm.kotlin.test.util.use -import io.realm.kotlin.types.RealmObject -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel -import kotlin.random.Random -import kotlin.reflect.KClass -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes - -class SyncClientResetIntegrationTests { - - /** - * Defines a Client Reset testing environment. This class is used for both PBS and FLX since - * individual tests perform the same assertions, whether a PBS or a FLX app. - * - * Things to take into account: - * - [insertElement] is used to populate the realm and also to write objects during Client Reset - * to test the different states of the before and after realm instances. - * - [recoverData] does all things recovery: query for the object we expect to be in the before - * realm and copies the data to the after realm. - * - [configBuilderGenerator] allows custom configuration of the [TestApp] - * - [recoveryDisabled] indicates whether or not the app to be created should have recovery mode - * enabled. - */ - @Suppress("LongParameterList") - class TestEnvironment constructor( - val clazz: KClass, - val appName: String, - val syncMode: SyncMode, - val recoveryDisabled: Boolean, - val configBuilderGenerator: (user: User) -> SyncConfiguration.Builder, - val insertElement: (realm: Realm) -> Unit, - val recoverData: (before: TypedRealm, after: MutableRealm) -> Unit, - ) { - private fun newChannel(): Channel> = Channel(1) - private fun getObjects(realm: TypedRealm): RealmQuery = realm.query(clazz) - private fun countObjects(realm: TypedRealm): Long = getObjects(realm).count().find() - private val logChannel: Channel = Channel(5) - - /** - * Runs the test. Steps as follows: create an app, create a user, log in execute your test - * logic. - */ - fun performTest( - block: TestEnvironment.( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) -> Unit - ) { - RealmLog.setLevel(LogLevel.INFO) - RealmLog.add(ClientResetLoggerInspector(logChannel)) - val app = TestApp( - this::class.simpleName, - object : BaseAppInitializer(appName, { app: BaasApp -> - addEmailProvider(app) - when (syncMode) { - SyncMode.PARTITION_BASED -> - initializePartitionSync(app, recoveryDisabled) - SyncMode.FLEXIBLE -> - initializeFlexibleSync(app, recoveryDisabled) - } - }) {}, - ) - try { - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - block(syncMode, app, user, configBuilderGenerator(user)) - } finally { - logChannel.close() - app.close() - } - } - } - - companion object { - - private val defaultTimeout = 1.minutes - - /** - * Factory for FLX testing environments. - */ - private fun createFlxEnvironment( - appName: String, - openRealmTimeout: Duration, - recoveryDisabled: Boolean = false - ): TestEnvironment { - val section = Random.nextInt() - return TestEnvironment( - clazz = FlexParentObject::class, - appName = appName, - syncMode = SyncMode.FLEXIBLE, - recoveryDisabled = recoveryDisabled, - configBuilderGenerator = { user -> - return@TestEnvironment SyncConfiguration.Builder( - user, - FLEXIBLE_SYNC_SCHEMA - ).initialSubscriptions { realm -> - realm.query( - "section = $0 AND name = $1", - section, - "blue" - ).also { add(it) } - }.waitForInitialRemoteData(openRealmTimeout) - }, - insertElement = { realm: Realm -> - realm.writeBlocking { - copyToRealm( - FlexParentObject().apply { - this.section = section - this.name = "blue" - } - ) - } - }, - recoverData = { before: TypedRealm, after: MutableRealm -> - // Perform manual copy - // see https://github.com/realm/realm-kotlin/issues/868 - after.copyToRealm( - FlexParentObject().apply { - assertNotNull( - before.query().first().find() - ).let { - // Perform manual copy - // see https://github.com/realm/realm-kotlin/issues/868 - this._id = it._id - this.section = it.section - this.name = it.name - this.age = it.age - } - } - ) - } - ) - } - - /** - * Factory for PBS testing environments. - */ - private fun createPbsEnvironment( - appName: String, - recoveryDisabled: Boolean = false - ): TestEnvironment = TestEnvironment( - clazz = SyncPerson::class, - appName = appName, - syncMode = SyncMode.PARTITION_BASED, - recoveryDisabled = recoveryDisabled, - configBuilderGenerator = { user -> - return@TestEnvironment SyncConfiguration.Builder( - user, - TestHelper.randomPartitionValue(), - schema = PARTITION_BASED_SCHEMA - ) - }, - insertElement = { realm: Realm -> - realm.writeBlocking { - copyToRealm(SyncPerson()) - } - }, - recoverData = { before: TypedRealm, after: MutableRealm -> - after.copyToRealm( - SyncPerson().apply { - assertNotNull( - before.query().first().find() - ).let { - // Perform manual copy - // see https://github.com/realm/realm-kotlin/issues/868 - this._id = it._id - this.age = it.age - this.firstName = it.firstName - this.lastName = it.lastName - } - } - ) - } - ) - - /** - * Starts a test for PBS given an environment and a custom test logic. - */ - fun performPbsTest( - environment: TestEnvironment? = null, - block: TestEnvironment.( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) -> Unit - ) { - environment ?: createPbsEnvironment(TEST_APP_PARTITION) - .also { - it.performTest(block) - } - } - - /** - * Starts a test for FLX given an environment and a custom test logic. - */ - fun performFlxTest( - environment: TestEnvironment? = null, - openRealmTimeout: Duration = defaultTimeout, - block: TestEnvironment.( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) -> Unit - ) { - environment ?: createFlxEnvironment(TEST_APP_FLEX, openRealmTimeout) - .also { - it.performTest(block) - } - } - } - - private enum class ClientResetEvents { - ON_BEFORE_RESET, - ON_AFTER_RESET, - ON_AFTER_RECOVERY, - ON_AFTER_DISCARD, - ON_MANUAL_RESET_FALLBACK - } - - private enum class ClientResetLogEvents { - DISCARD_LOCAL_ON_BEFORE_RESET, - DISCARD_LOCAL_ON_AFTER_RECOVERY, - DISCARD_LOCAL_ON_AFTER_DISCARD, - DISCARD_LOCAL_ON_ERROR, - MANUAL_ON_ERROR - } - - /** - * This class allows us to inspect if the default client reset strategies actually log the - * client reset events. - */ - private class ClientResetLoggerInspector( - val channel: Channel - ) : RealmLogger { - - override fun log( - category: LogCategory, - level: LogLevel, - throwable: Throwable?, - message: String?, - vararg args: Any? - ) { - message?.let { - if (message.contains("Client reset: attempting to automatically recover unsynced changes in Realm:")) { - channel.trySendOrFail(ClientResetLogEvents.DISCARD_LOCAL_ON_BEFORE_RESET) - } else if (message.contains("Client reset: successfully recovered all unsynced changes in Realm:")) { - channel.trySendOrFail(ClientResetLogEvents.DISCARD_LOCAL_ON_AFTER_RECOVERY) - } else if (message.contains("Client reset: couldn't recover successfully, all unsynced changes were discarded in Realm:")) { - channel.trySendOrFail(ClientResetLogEvents.DISCARD_LOCAL_ON_AFTER_DISCARD) - } else if (message.contains("Client reset: manual reset required for Realm in")) { - channel.trySendOrFail(ClientResetLogEvents.DISCARD_LOCAL_ON_ERROR) - } else if (message.contains("Client reset required on Realm:")) { - channel.trySendOrFail(ClientResetLogEvents.MANUAL_ON_ERROR) - } else { - // Ignore - } - } - } - } - - private lateinit var partitionValue: String - - @BeforeTest - fun setup() { - partitionValue = TestHelper.randomPartitionValue() - } - - @AfterTest - fun tearDown() { - RealmLog.reset() - } - - // --------------------------------------------------------------------------------------- - // DiscardUnsyncedChangesStrategy - // --------------------------------------------------------------------------------------- - - @Test - fun discardUnsyncedChanges_discards_pbs() { - performPbsTest { syncMode, app, user, builder -> - discardUnsyncedChanges_discards(syncMode, app, user, builder) - } - } - - @Test - fun discardUnsyncedChanges_discards_flx() { - performFlxTest { syncMode, app, user, builder -> - discardUnsyncedChanges_discards(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.discardUnsyncedChanges_discards( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - // Validate that the discard local strategy onBeforeReset and onAfterReset callbacks - // are invoked successfully when a client reset is triggered. - val channel = Channel(2) - val config = builder.syncClientResetStrategy( - object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - // This realm contains something as we wrote an object while the session was paused - assertEquals(1, countObjects(realm)) - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - // The before-Realm contains the object we wrote while the session was paused - assertEquals(1, countObjects(before)) - - // The after-Realm contains no objects - assertEquals(0, countObjects(after)) - - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("onManualResetFallback shouldn't be called: ${exception.message}") - } - } - ).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - // This channel helps to validate that the Realm gets updated - val objectChannel: Channel> = newChannel() - - val job = async { - getObjects(realm) - .asFlow() - .collect { - objectChannel.send(it) - } - } - - // No initial data - assertEquals(0, objectChannel.receiveOrFail().list.size) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) { - insertElement(realm) - assertEquals(1, objectChannel.receiveOrFail().list.size) - } - - // Validate that the client reset was triggered successfully - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_RESET, channel.receiveOrFail()) - - // TODO We must not need this. Force updating the instance pointer. - realm.write { } - - // Validate Realm instance has been correctly updated - assertEquals(0, objectChannel.receiveOrFail().list.size) - objectChannel.close() - job.cancel() - } - } - channel.close() - } - - @Test - fun discardUnsyncedChanges_discards_attemptRecover_pbs() { - performPbsTest { syncMode, app, user, builder -> - discardUnsyncedChanges_discards_attemptRecover(syncMode, app, user, builder) - } - } - - @Test - fun discardUnsyncedChanges_discards_attemptRecover_flx() { - performFlxTest { syncMode, app, user, builder -> - discardUnsyncedChanges_discards_attemptRecover(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.discardUnsyncedChanges_discards_attemptRecover( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - // Attempts to recover data if a client reset is triggered. - val channel = Channel(2) - val config = builder.syncClientResetStrategy( - object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - // The before-Realm contains the object we wrote while the session was paused - assertEquals(1, countObjects(before)) - - recoverData(before, after) - - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("onManualResetFallback shouldn't be called: ${exception.message}") - } - } - ).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - // This channel helps to validate that the Realm gets updated - val objectChannel: Channel> = newChannel() - - val job = async { - getObjects(realm) - .asFlow() - .collect { - objectChannel.send(it) - } - } - - // No initial data - assertEquals(0, objectChannel.receiveOrFail().list.size) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) { - // Write something while the session is paused to make sure the before realm contains something - insertElement(realm) - assertEquals(1, objectChannel.receiveOrFail().list.size) - } - - // Validate that the client reset was triggered successfully - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_RESET, channel.receiveOrFail()) - - // The state of the Realm is not stable at this point. It is unclear if - // https://github.com/realm/realm-core/issues/7065 is the root cause of this - // but we see multiple runs on Github Actions where these values are either 0 - // or 1. This could point to some kind of race condition. Running locally - // the behavior seems consistent. Maybe because our local machines are "fast enough". - // For now, accept both 0 and 1. - - // Object count down to 0 just after the reset - // assertEquals(0, objectChannel.receiveOrFail().list.size) - var size = objectChannel.receiveOrFail().list.size - assertTrue(size == 0 || size == 1, "Size was: $size") - - // TODO https://github.com/realm/realm-core/issues/7065 - // We must not need this. Force updating the instance pointer. - realm.write { } - - // Validate Realm instance has been correctly updated - // assertEquals(1, objectChannel.receiveOrFail().list.size) - size = objectChannel.receiveOrFail().list.size - assertTrue(size == 0 || size == 1, "Size was: $size") - objectChannel.close() - job.cancel() - } - } - channel.close() - } - - @Test - fun discardUnsyncedChanges_failure_pbs() { - performPbsTest { _, _, _, builder -> - discardUnsyncedChanges_failure_pbs(builder) - } - } - - @Test - fun discardUnsyncedChanges_failure_flx() { - performFlxTest { _, _, _, builder -> - discardUnsyncedChanges_failure_pbs(builder) - } - } - - private fun discardUnsyncedChanges_failure_pbs(builder: SyncConfiguration.Builder) { - // Validate that the discard local strategy onError callback is invoked successfully if - // a client reset fails. - // Channel size is 2 because both onError and onManualResetFallback are called - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not call onBeforeReset") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not call onAfterReset") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - // Note, this error message is just the one created by ObjectStore for - // testing the server will send a different message. This just ensures that - // we don't accidentally modify or remove the message. - assertEquals( - "[Sync][AutoClientResetFailed(1028)] Simulate Client Reset.", - exception.message - ) - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - with(realm.syncSession as SyncSessionImpl) { - simulateSyncError(ErrorCode.RLM_ERR_AUTO_CLIENT_RESET_FAILED) - - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - } - channel.close() - } - - @Test - fun discardUnsyncedChanges_executeClientReset_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - discardUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - @Test - fun discardUnsyncedChanges_executeClientReset_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - discardUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - private fun discardUnsyncedChanges_executeClientReset( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - var testRealm: Realm? = null - // Channel size is 2 because both onError and onManualResetFallback are called - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not call onBeforeReset") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not call onAfterReset") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - testRealm!!.close() - - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - - assertTrue(exception.executeClientReset()) - - // Validate that files have been moved after explicit reset - assertFalse(fileExists(originalFilePath)) - assertTrue(fileExists(recoveryFilePath)) - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - runBlocking { - Realm.open(config).use { realm -> - testRealm = realm - with(realm.syncSession) { - downloadAllServerChanges(defaultTimeout) - app.triggerClientReset(syncMode, this, user.id) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - } - channel.close() - } - - @Test - fun discardUnsyncedChanges_userExceptionCaptured_onBeforeReset_pbs() { - performPbsTest { syncMode, app, user, builder -> - discardUnsyncedChanges_userExceptionCaptured_onBeforeReset(syncMode, app, user, builder) - } - } - - @Test - fun discardUnsyncedChanges_userExceptionCaptured_onBeforeReset_flx() { - performFlxTest { syncMode, app, user, builder -> - discardUnsyncedChanges_userExceptionCaptured_onBeforeReset(syncMode, app, user, builder) - } - } - - private fun discardUnsyncedChanges_userExceptionCaptured_onBeforeReset( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - // Validates that any user exception during the automatic client reset is properly captured. - val channel = Channel(3) - val config = builder.syncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - throw IllegalStateException("User exception") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - // Send event anyways so that the asserts outside would fail - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - // Notify that this callback has been invoked - assertContains(exception.message!!, "User-provided callback failed") - - assertIs(exception.cause) - assertEquals( - "User exception", - exception.cause?.message - ) - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) - - // Validate that the client reset was triggered successfully - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - - channel.close() - } - - @Test - fun discardUnsyncedChanges_userExceptionCaptured_onAfterReset_pbs() { - performPbsTest { syncMode, app, user, builder -> - discardUnsyncedChanges_userExceptionCaptured_onAfterReset_pbs( - syncMode, - app, - user, - builder - ) - } - } - - @Test - fun discardUnsyncedChanges_userExceptionCaptured_onAfterReset_flx() { - performFlxTest { syncMode, app, user, builder -> - discardUnsyncedChanges_userExceptionCaptured_onAfterReset_pbs( - syncMode, - app, - user, - builder - ) - } - } - - private fun discardUnsyncedChanges_userExceptionCaptured_onAfterReset_pbs( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - // Validates that any user exception during the automatic client reset is properly captured. - // Channel size is 4 because both onError and onManualResetFallback are called - val channel = Channel(4) - val config = builder.syncClientResetStrategy(object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - // Notify that this callback has been invoked - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - throw IllegalStateException("User exception") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - // Notify that this callback has been invoked - assertContains(exception.message!!, "User-provided callback failed") - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) - - // Validate that the client reset was triggered successfully - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - channel.close() - } - - // --------------------------------------------------------------------------------------- - // Default from config: RecoverOrDiscardUnsyncedChangesStrategy - // --------------------------------------------------------------------------------------- - - @Test - fun defaultRecoverOrDiscardUnsyncedChanges_logsReported_pbs() { - performPbsTest { syncMode, app, user, builder -> - defaultRecoverOrDiscardUnsyncedChanges_logsReported(syncMode, app, user, builder) - } - } - - @Test - fun defaultRecoverOrDiscardUnsyncedChanges_logsReported_flx() { - performFlxTest { syncMode, app, user, builder -> - defaultRecoverOrDiscardUnsyncedChanges_logsReported(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.defaultRecoverOrDiscardUnsyncedChanges_logsReported( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val config = builder.build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) - - // Validate we receive logs on the regular path - assertEquals( - ClientResetLogEvents.DISCARD_LOCAL_ON_BEFORE_RESET, - logChannel.receiveOrFail() - ) - assertEquals( - ClientResetLogEvents.DISCARD_LOCAL_ON_AFTER_RECOVERY, - logChannel.receiveOrFail() - ) - // assertEquals(ClientResetLogEvents.DISCARD_LOCAL_ON_AFTER_RESET, logChannel.receiveOrFail()) - - (realm.syncSession as SyncSessionImpl).simulateSyncError( - ErrorCode.RLM_ERR_AUTO_CLIENT_RESET_FAILED - ) - // Validate that we receive logs on the error callback - val actual = logChannel.receiveOrFail() - assertEquals(ClientResetLogEvents.DISCARD_LOCAL_ON_ERROR, actual) - } - } - } - - // --------------------------------------------------------------------------------------- - // ManuallyRecoverUnsyncedChangesStrategy - // --------------------------------------------------------------------------------------- - - @Test - fun manuallyRecoverUnsyncedChanges_reported_pbs() = runBlocking { - performPbsTest { _, _, _, builder -> - manuallyRecoverUnsyncedChanges_reported(builder) - } - } - - @Test - fun manuallyRecoverUnsyncedChanges_reported_flx() = runBlocking { - performFlxTest { _, _, _, builder -> - manuallyRecoverUnsyncedChanges_reported(builder) - } - } - - private fun manuallyRecoverUnsyncedChanges_reported( - builder: SyncConfiguration.Builder - ) { - val channel = TestChannel() - - val config = builder.syncClientResetStrategy( - object : ManuallyRecoverUnsyncedChangesStrategy { - override fun onClientReset( - session: SyncSession, - exception: ClientResetRequiredException - ) { - channel.trySendOrFail(exception) - } - } - ).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - with((realm.syncSession as SyncSessionImpl)) { - simulateSyncError(ErrorCode.RLM_ERR_AUTO_CLIENT_RESET_FAILED) - - val exception = channel.receiveOrFail() - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - assertEquals( - "[Sync][AutoClientResetFailed(1028)] Simulate Client Reset.", - exception.message - ) - } - } - } - channel.close() - } - - @Test - fun manuallyRecoverUnsyncedChanges_executeClientReset_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - manuallyRecoverUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - @Test - fun manuallyRecoverUnsyncedChanges_executeClientReset_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - manuallyRecoverUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - private fun manuallyRecoverUnsyncedChanges_executeClientReset( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val channel = TestChannel() - - val config = builder.syncClientResetStrategy( - object : ManuallyRecoverUnsyncedChangesStrategy { - override fun onClientReset( - session: SyncSession, - exception: ClientResetRequiredException - ) { - channel.trySendOrFail(exception) - } - } - ).build() - - runBlocking { - Realm.open(config).use { realm -> - with(realm.syncSession) { - downloadAllServerChanges(defaultTimeout) - app.triggerClientReset(syncMode, this, user.id) - val exception = channel.receiveOrFail() - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - realm.close() - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - assertTrue(exception.executeClientReset()) - assertFalse(fileExists(originalFilePath)) - assertTrue(fileExists(recoveryFilePath)) - } - } - } - channel.close() - } - - // --------------------------------------------------------------------------------------- - // RecoverUnsyncedChangesStrategy - // --------------------------------------------------------------------------------------- - - @Test - fun recoverUnsyncedChanges_recover_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_recover(syncMode, app, user, builder) - } - } - - @Test - fun recoverUnsyncedChanges_recover_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_recover(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.recoverUnsyncedChanges_recover( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - assertEquals(1, countObjects(realm)) - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - assertEquals(1, countObjects(before)) - assertEquals(1, countObjects(after)) - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("This test case was not supposed to trigger RecoverUnsyncedChangesStrategy::onManualResetFallback(): ${exception.message}") - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) { - insertElement(realm) - assertEquals(1, countObjects(realm)) - } - - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_RESET, channel.receiveOrFail()) - } - } - channel.close() - } - - @Test - fun recoverUnsyncedChanges_resetErrorHandled_pbs() { - performPbsTest { _, _, _, builder -> - recoverUnsyncedChanges_resetErrorHandled(builder) - } - } - - @Test - fun recoverUnsyncedChanges_resetErrorHandled_flx() { - performFlxTest { _, _, _, builder -> - recoverUnsyncedChanges_resetErrorHandled(builder) - } - } - - private fun recoverUnsyncedChanges_resetErrorHandled( - builder: SyncConfiguration.Builder - ) { - val channel = TestChannel() - val config = builder.syncClientResetStrategy(object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("This test case was not supposed to trigger RecoverUnsyncedChangesStrategy::onBeforeReset()") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("This test case was not supposed to trigger RecoverUnsyncedChangesStrategy::onAfterReset()") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - channel.trySendOrFail(exception) - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - (realm.syncSession as SyncSessionImpl).simulateSyncError(ErrorCode.RLM_ERR_AUTO_CLIENT_RESET_FAILED) - val exception = channel.receiveOrFail() - - assertNotNull(exception.recoveryFilePath) - assertNotNull(exception.originalFilePath) - assertFalse(fileExists(exception.recoveryFilePath)) - assertTrue(fileExists(exception.originalFilePath)) - assertContains(exception.message!!, "Simulate Client Reset") - } - } - channel.close() - } - - @Test - fun recoverUnsyncedChanges_recoverFails_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_recoverFails(syncMode, app, user, builder) - } - } - - @Test - fun recoverUnsyncedChanges_recoverFails_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_recoverFails(syncMode, app, user, builder) - } - } - - private fun recoverUnsyncedChanges_recoverFails( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - throw IllegalStateException("User exception") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - // Send event anyways so that the asserts outside would fail - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RESET) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - // Notify that this callback has been invoked - assertContains(exception.message!!, "User-provided callback failed") - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) - - // Validate that the client reset was triggered successfully - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - channel.close() - } - - @Test - fun recoverUnsyncedChanges_executeClientReset_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - @Test - fun recoverUnsyncedChanges_executeClientReset_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - recoverUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - private fun recoverUnsyncedChanges_executeClientReset( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - var testRealm: Realm? = null - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("Trigger onManualResetFallback") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not call onAfterReset") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - testRealm!!.close() - - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - - assertTrue(exception.executeClientReset()) - - // Validate that files have been moved after explicit reset - assertFalse(fileExists(originalFilePath)) - assertTrue(fileExists(recoveryFilePath)) - assertContains(exception.message!!, "User-provided callback failed") - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - runBlocking { - Realm.open(config).use { realm -> - testRealm = realm - with(realm.syncSession) { - downloadAllServerChanges(defaultTimeout) - app.triggerClientReset(syncMode, this, user.id) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - } - channel.close() - } - - // --------------------------------------------------------------------------------------- - // RecoverOrDiscardUnsyncedChangesStrategy - // --------------------------------------------------------------------------------------- - - @Test - fun recoverOrDiscardUnsyncedChanges_recover_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_recover(syncMode, app, user, builder) - } - } - - @Test - fun recoverOrDiscardUnsyncedChanges_recover_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_recover(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.recoverOrDiscardUnsyncedChanges_recover( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val channel = Channel(2) - val config = builder.syncClientResetStrategy( - object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - channel.trySendOrFail(ClientResetEvents.ON_AFTER_RECOVERY) - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - fail("This test case was not supposed to trigger AutomaticRecoveryStrategy::onAfterDiscard()") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("This test case was not supposed to trigger AutomaticRecoveryStrategy::onError()") - } - } - ).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - app.triggerClientReset(syncMode, realm.syncSession, user.id) - insertElement(realm) - assertEquals(1, countObjects(realm)) - - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_RECOVERY, channel.receiveOrFail()) - } - } - channel.close() - } - - @Test - fun recoverOrDiscardUnsyncedChanges_discards_pbs() = runBlocking { - val suffix = Random.nextLong(1000, 9999) - val environment = createPbsEnvironment("PBS-NO-RECOVERY_$suffix", recoveryDisabled = true) - performPbsTest(environment) { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_discards(syncMode, app, user, builder) - } - } - - @Test - fun recoverOrDiscardUnsyncedChanges_discards_flx() = runBlocking { - val suffix = Random.nextLong(1000, 9999) - val environment = createPbsEnvironment("PBS-NO-RECOVERY_$suffix", recoveryDisabled = true) - performFlxTest(environment) { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_discards(syncMode, app, user, builder) - } - } - - private fun TestEnvironment<*>.recoverOrDiscardUnsyncedChanges_discards( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - val channel = Channel(2) - val config = builder.syncClientResetStrategy( - object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - assertEquals(1, countObjects(realm)) - channel.trySendOrFail(ClientResetEvents.ON_BEFORE_RESET) - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - fail("This test case was not supposed to trigger RecoverOrDiscardUnsyncedChangesStrategy::onAfterRecovery()") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - assertEquals(1, countObjects(before)) - assertEquals(0, countObjects(after)) - channel.trySendOrFail(ClientResetEvents.ON_AFTER_DISCARD) - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("This test case was not supposed to trigger RecoverOrDiscardUnsyncedChangesStrategy::onError()") - } - } - ).build() - - Realm.open(config).use { realm -> - runBlocking { - realm.syncSession.downloadAllServerChanges(defaultTimeout) - - // The apps in this test run with recovery mode disabled so no need to fiddle with the configuration - app.triggerClientReset(syncMode, realm.syncSession, user.id) { - insertElement(realm) - assertEquals(1, countObjects(realm)) - } - - assertEquals(ClientResetEvents.ON_BEFORE_RESET, channel.receiveOrFail()) - assertEquals(ClientResetEvents.ON_AFTER_DISCARD, channel.receiveOrFail()) - } - } - channel.close() - } - - @Test - fun recoverOrDiscardUnsyncedChanges_executeClientReset_pbs() = runBlocking { - performPbsTest { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - @Test - fun recoverOrDiscardUnsyncedChanges_executeClientReset_flx() = runBlocking { - performFlxTest { syncMode, app, user, builder -> - recoverOrDiscardUnsyncedChanges_executeClientReset(syncMode, app, user, builder) - } - } - - private fun recoverOrDiscardUnsyncedChanges_executeClientReset( - syncMode: SyncMode, - app: TestApp, - user: User, - builder: SyncConfiguration.Builder - ) { - var testRealm: Realm? = null - val channel = Channel(2) - val config = builder.syncClientResetStrategy(object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - @Suppress("TooGenericExceptionThrown") - throw RuntimeException("Trigger onManualResetFallback") - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - fail("Should not call onAfterReset") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - fail("Should not call onAfterDiscard") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - testRealm!!.close() - - val originalFilePath = assertNotNull(exception.originalFilePath) - val recoveryFilePath = assertNotNull(exception.recoveryFilePath) - assertTrue(fileExists(originalFilePath)) - assertFalse(fileExists(recoveryFilePath)) - - assertTrue(exception.executeClientReset()) - - // Validate that files have been moved after explicit reset - assertFalse(fileExists(originalFilePath)) - assertTrue(fileExists(recoveryFilePath)) - - assertTrue(exception.message!!.contains("User-provided callback failed")) - - channel.trySendOrFail(ClientResetEvents.ON_MANUAL_RESET_FALLBACK) - } - }).build() - - runBlocking { - Realm.open(config).use { realm -> - testRealm = realm - with(realm.syncSession) { - downloadAllServerChanges(defaultTimeout) - app.triggerClientReset(syncMode, this, user.id) - assertEquals(ClientResetEvents.ON_MANUAL_RESET_FALLBACK, channel.receiveOrFail()) - } - } - } - channel.close() - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt deleted file mode 100644 index e4403d0e84..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncClientTests.kt +++ /dev/null @@ -1,97 +0,0 @@ -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.use -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue - -/** - * Tests for [io.realm.kotlin.mongodb.sync.Sync] that is accessed through - * [io.realm.kotlin.mongodb.App.sync]. - */ -class SyncClientTests { - - private lateinit var user: User - private lateinit var app: App - - @BeforeTest - fun setup() { - app = TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - user = runBlocking { - app.createUserAndLogIn(email, password) - } - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun sync() { - assertNotNull(app.sync) - } - - // There is no way to test reconnect automatically, so just verify that code path does not crash. - @Test - fun reconnect_noRealms() { - app.sync.reconnect() - } - - // There is no way to test reconnect automatically, so just verify that code path does not crash. - @Test - fun reconnect() { - val config = SyncConfiguration.create(user, schema = FLEXIBLE_SYNC_SCHEMA) - Realm.open(config).use { - app.sync.reconnect() - } - } - - @Test - fun hasSyncSessions_noRealms() { - assertFalse(app.sync.hasSyncSessions) - } - - @Test - fun hasSyncSessions() { - val config = SyncConfiguration.create(user, schema = FLEXIBLE_SYNC_SCHEMA) - Realm.open(config).use { - assertTrue(app.sync.hasSyncSessions) - } - } - - @Test - fun waitForSessionsToTerminate_noRealms() { - app.sync.waitForSessionsToTerminate() - } - - @Test - fun waitForSessionsToTerminate() { - val config1 = SyncConfiguration.Builder(user, schema = FLEXIBLE_SYNC_SCHEMA).build() - val config2 = SyncConfiguration.Builder(user, schema = FLEXIBLE_SYNC_SCHEMA).name("other.realm").build() - - Realm.open(config1).use { - assertTrue(app.sync.hasSyncSessions) - Realm.open(config2).use { /* do nothing */ } - assertTrue(app.sync.hasSyncSessions) - } - app.sync.waitForSessionsToTerminate() - assertFalse(app.sync.hasSyncSessions) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt deleted file mode 100644 index 5474ac22cb..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncConfigTests.kt +++ /dev/null @@ -1,1226 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") // Needed to call session.simulateError() - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.CompactOnLaunchCallback -import io.realm.kotlin.InitialDataCallback -import io.realm.kotlin.MutableRealm -import io.realm.kotlin.Realm -import io.realm.kotlin.TypedRealm -import io.realm.kotlin.dynamic.DynamicRealmObject -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.entities.sync.flx.FlexChildObject -import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.pathOf -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.platform.singleThreadDispatcher -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.ClientResetRequiredException -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.sync.DiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.ManuallyRecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverOrDiscardUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.RecoverUnsyncedChangesStrategy -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncMode -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.TestHelper.getRandomKey -import io.realm.kotlin.test.util.TestHelper.randomEmail -import io.realm.kotlin.test.util.use -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmUUID -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.withContext -import org.mongodb.kbson.BsonObjectId -import kotlin.random.Random -import kotlin.reflect.KClass -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds - -const val DEFAULT_NAME = "test.realm" - -internal enum class ValueType { - STRING, LONG, INT, NULL, OBJECT_ID, UUID -} - -class SyncConfigTests { - - private lateinit var partitionValue: String - private lateinit var app: App - - @BeforeTest - fun setup() { - partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - RealmLog.reset() - } - - @Test - fun errorHandler() { - val errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - // No-op - } - } - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).also { builder -> - builder.errorHandler(errorHandler) - }.build() - assertEquals(errorHandler, config.errorHandler) - } - - @Test - fun errorHandler_default() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - assertNotNull(config.errorHandler) - } - - // Smoke-test...most functionality is tested in CompactOnLaunchTests - // See https://github.com/realm/realm-kotlin/issues/672 - @Test - fun compactOnLaunch_default() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - assertNull(config.compactOnLaunchCallback) - } - - // Smoke-test...most functionality is tested in CompactOnLaunchTests - // See https://github.com/realm/realm-kotlin/issues/672 - @Test - fun compactOnLaunch() { - val user = createTestUser() - val callback = CompactOnLaunchCallback { _, _ -> false } - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ) - .compactOnLaunch(callback) - .build() - assertEquals(callback, config.compactOnLaunchCallback) - } - - // Smoke-test...most functionality is tested in InitialDataTests - // See https://github.com/realm/realm-kotlin/pull/839 - @Test - fun initialData() { - val user = createTestUser() - val callback = InitialDataCallback { - copyToRealm( - ParentPk().apply { - _id = Random.nextLong().toString() - } - ) - } - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ) - .initialData(callback) - .build() - - assertEquals(callback, config.initialDataCallback) - Realm.open(config).use { - assertEquals(1, it.query().count().find()) - } - } - -// @Test -// fun errorHandler_fromAppConfiguration() { -// val user: User = createTestUser(app) -// val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) -// assertEquals(app.configuration.defaultErrorHandler, config.errorHandler) -// } -// -// @Test -// fun errorHandler_nullThrows() { -// val user: User = createTestUser(app) -// val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) -// assertFailsWith { builder.errorHandler(TestHelper.getNull()) } -// } -// -// @Test -// fun clientResetHandler() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val handler = object : SyncSession.ClientResetHandler { -// override fun onClientReset(session: SyncSession, error: ClientResetRequiredError) {} -// } -// val config = builder.clientResetHandler(handler).build() -// assertEquals(handler, config.clientResetHandler) -// } -// -// @Test -// fun clientResetHandler_fromAppConfiguration() { -// val user: User = createTestUser(app) -// val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) -// assertEquals(app.configuration.defaultClientResetHandler, config.clientResetHandler) -// } -// -// @Test -// fun clientResetHandler_nullThrows() { -// val user: User = createTestUser(app) -// val builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) -// assertFailsWith { builder.clientResetHandler(TestHelper.getNull()) } -// } - - @Test - fun clientResetStrategy_manual() { - val user = createTestUser() - val strategy = object : ManuallyRecoverUnsyncedChangesStrategy { - override fun onClientReset( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - } - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).syncClientResetStrategy(strategy) - .build() - assertEquals(strategy, config.syncClientResetStrategy) - } - - @Test - fun clientResetStrategy_automatic() { - val user = createTestUser() - val strategy = object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not be called") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - } - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).syncClientResetStrategy(strategy) - .build() - assertEquals(strategy, config.syncClientResetStrategy) - } - - @Test - fun clientResetStrategy_automaticOrDiscard() { - val user = createTestUser() - val strategy = object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not be called") - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - } - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).syncClientResetStrategy(strategy) - .build() - assertEquals(strategy, config.syncClientResetStrategy) - } - - @Test - fun equals_sameObject() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - assertEquals(config, config) - } - -// @Test -// fun equals_sameConfigurations() { -// val user = createTestUser() -// val config1 = SyncConfiguration.Builder( -// user = user, -// partitionValue = DEFAULT_PARTITION_VALUE, -// schema = setOf(Child::class) -// ).build() -// val config2 = SyncConfiguration.Builder( -// user = user, -// partitionValue = DEFAULT_PARTITION_VALUE, -// schema = setOf(Child::class) -// ).build() -// assertEquals(config1.partitionValue, config2.partitionValue) -// } -// -// @Test -// // FIXME Tests are not exhaustive -// fun equals_not() { -// val user1: User = createTestUser(app) -// val user2: User = createTestUser(app) -// val config1: SyncConfiguration = SyncConfiguration.Builder(user1, DEFAULT_PARTITION).build() -// val config2: SyncConfiguration = SyncConfiguration.Builder(user2, DEFAULT_PARTITION).build() -// assertFalse(config1 == config2) -// } -// -// @Test -// fun hashCode_equal() { -// val user: User = createTestUser(app) -// val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) -// assertEquals(config.hashCode(), config.hashCode()) -// } -// -// @Test -// fun hashCode_notEquals() { -// val user1: User = createTestUser(app) -// val user2: User = createTestUser(app) -// val config1: SyncConfiguration = SyncConfiguration.defaultConfig(user1, DEFAULT_PARTITION) -// val config2: SyncConfiguration = SyncConfiguration.defaultConfig(user2, DEFAULT_PARTITION) -// assertNotEquals(config1.hashCode(), config2.hashCode()) -// } - - @Test - fun equals_syncSpecificFields() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - assertEquals(config.user, user) - assertTrue(config.schema.containsAll(setOf(ParentPk::class, ChildPk::class))) - assertEquals(SyncMode.PARTITION_BASED, config.syncMode) - } - - @Test - fun name_partitionBasedDefaultName() { - val user: User = createTestUser() - // Long - verifyName( - SyncConfiguration.Builder(user, 1234L, setOf()), - "l_1234.realm" - ) - - // Int - verifyName( - SyncConfiguration.Builder(user, 123 as Int, setOf()), - "i_123.realm" - ) - - // String - verifyName( - SyncConfiguration.Builder(user, "mypartition", setOf()), - "s_mypartition.realm" - ) - } - - private fun verifyName(builder: SyncConfiguration.Builder, expectedFileName: String) { - val config = builder.build() - val suffix = pathOf("", "mongodb-realm", config.user.app.configuration.appId, config.user.id, expectedFileName) - assertTrue(config.path.contains(suffix), "${config.path} failed.") - assertEquals(expectedFileName, config.name) - } - - @Test - fun allPartitionTypes() { - val user: User = createTestUser() - - val partitionsAndRealmNames: Map = - enumValues().flatMap { valueType -> - when (valueType) { - ValueType.STRING -> listOf( - "string" to "s_string", - null as String? to "null" - ) - - ValueType.INT -> listOf( - 10.toInt() to "i_10", - null as Int? to "null", - ) - - ValueType.LONG -> listOf( - 20.toLong() to "l_20", - null as Long? to "null" - ) - - ValueType.OBJECT_ID -> listOf( - BsonObjectId("62aafc72b9c357695ac489a7") to "o_62aafc72b9c357695ac489a7", - null as BsonObjectId? to "null", - ) - - ValueType.UUID -> listOf( - RealmUUID.from("80ac3926-29a4-4315-b373-2e2a33cf694f") to "u_80ac3926-29a4-4315-b373-2e2a33cf694f", - null as RealmUUID? to "null", - ) - - ValueType.NULL -> listOf( - null to "null" - ) - else -> TODO("Test for partition type not defined") - } - }.toMap() - - // Validate SyncConfiguration.create - partitionsAndRealmNames.forEach { (partition: Any?, name: String) -> - val config = createWithGenericPartition(user, partition, setOf()) - assertTrue(config.path.endsWith("/$name.realm"), "${config.path} failed") - } - - // Validate SyncConfiguration.Builder - partitionsAndRealmNames.forEach { (partition: Any?, name: String) -> - val config = createBuilderWithGenericPartition(user, partition, setOf()) - assertTrue(config.path.endsWith("/$name.realm"), "${config.path} failed") - } - } - - fun createWithGenericPartition( - user: User, - partitionValue: Any?, - schema: Set> - ) = when (partitionValue) { - is String? -> SyncConfiguration.create(user, partitionValue, schema) - is Int? -> SyncConfiguration.create(user, partitionValue, schema) - is Long? -> SyncConfiguration.create(user, partitionValue, schema) - is BsonObjectId? -> SyncConfiguration.create(user, partitionValue, schema) - is RealmUUID? -> SyncConfiguration.create(user, partitionValue, schema) - else -> TODO("Undefined partition type") - } - - fun createBuilderWithGenericPartition( - user: User, - partitionValue: Any?, - schema: Set> - ) = when (partitionValue) { - is String? -> SyncConfiguration.Builder(user, partitionValue, schema).build() - is Int? -> SyncConfiguration.Builder(user, partitionValue, schema).build() - is Long? -> SyncConfiguration.Builder(user, partitionValue, schema).build() - is BsonObjectId? -> SyncConfiguration.Builder(user, partitionValue, schema).build() - is RealmUUID? -> SyncConfiguration.Builder(user, partitionValue, schema).build() - else -> TODO("Undefined partition type") - } - - @Test - fun name_withoutFileExtension() { - nameAssertions("my-file-name") - } - - @Test - fun name_withDotRealmFileExtension() { - nameAssertions("my-file-name.realm") - } - - @Test - fun name_otherFileExtension() { - nameAssertions("my-file-name.database") - } - - @Test - fun name_similarToDefaultObjectStoreName() { - nameAssertions("s_partition-9482732795133669400.realm") - } - - @Test - fun name_emptyValueThrows() { - val user: User = createTestUser() - val builder = SyncConfiguration.Builder(user, partitionValue, setOf()) - assertFailsWith { - builder.name("") - } - } - - @Test - fun name_illegalValueThrows() { - val user: User = createTestUser() - val builder = SyncConfiguration.Builder(user, partitionValue, setOf()) - assertFailsWith { - builder.name(".realm") - } - } - - @Test - fun encryption() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).also { builder -> - builder.encryptionKey(getRandomKey()) - }.build() - assertNotNull(config.encryptionKey) - } - - @Test - fun encryption_wrongLength() { - val user = createTestUser() - val builder = SyncConfiguration.Builder(user, partitionValue, setOf()) - assertFailsWith { builder.encryptionKey(byteArrayOf(1, 2, 3)) } - } - - // @Test -// fun initialData() { -// val user: User = createTestUser(app) -// val config = configFactory.createSyncConfigurationBuilder(user) -// .schema(SyncStringOnly::class.java) -// .initialData(object : Realm.Transaction { -// override fun execute(realm: Realm) { -// val stringOnly: SyncStringOnly = realm.createObject(ObjectId()) -// stringOnly.chars = "TEST 42" -// } -// }) -// .build() -// assertNotNull(config.initialDataTransaction) -// -// // open the first time - initialData must be triggered -// Realm.getInstance(config).use { realm -> -// val results: RealmResults = realm.where().findAll() -// assertEquals(1, results.size) -// assertEquals("TEST 42", results.first()!!.chars) -// } -// -// // open the second time - initialData must not be triggered -// Realm.getInstance(config).use { realm -> -// assertEquals(1, realm.where().count()) -// } -// } -// -// @Test -// fun defaultRxFactory() { -// val user: User = createTestUser(app) -// val config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) -// assertNotNull(config.rxFactory) -// } -// - @Test - fun toString_nonEmpty() { - val user: User = createTestUser() - val config: SyncConfiguration = SyncConfiguration.create(user, partitionValue, setOf()) - val configStr = config.toString() - assertTrue(configStr.isNotEmpty()) - } - - @Test - fun useConfigOnOtherThread() = runBlocking { - val user: User = createTestUser() - // This should set both errorHandler and autoMigration callback - val config: SyncConfiguration = SyncConfiguration.create(user, partitionValue, setOf()) - val dispatcher: CoroutineDispatcher = singleThreadDispatcher("config-test") - withContext(dispatcher) { - Realm.open(config).close() - } - } - - // -// // Check that it is possible for multiple users to reference the same Realm URL while each user still use their -// // own copy on the filesystem. This is e.g. what happens if a Realm is shared using a PermissionOffer. -// @Test -// fun multipleUsersReferenceSameRealm() { -// val user1 = createTestUser() -// val user2 = createTestUser() -// -// val config1 = createSyncConfig(user1, DEFAULT_PARTITION_VALUE) -// val config2 = createSyncConfig(user2, DEFAULT_PARTITION_VALUE) -// -// // Verify that two different configurations can be used for the same URL -// val realm1 = Realm.open(config1) -// val realm2 = Realm.open(config2) -// assertNotEquals(realm1, realm2) -// -// realm1.close() -// realm2.close() -// -// // Verify that we actually save two different files -// assertNotEquals(config1.path, config2.path) -// } -// - @Test - fun with_throwsIfNotLoggedIn() = runBlocking { - val user: User = createTestUser() - user.logOut() - assertFailsWith { SyncConfiguration.create(user, "string", setOf()) } - assertFailsWith { SyncConfiguration.create(user, 123 as Int, setOf()) } - assertFailsWith { SyncConfiguration.create(user, 123L, setOf()) } - Unit - } - - @Test - fun shouldWaitForInitialRemoteData() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, TestHelper.randomPartitionValue(), setOf()) - .waitForInitialRemoteData() - .build() - assertNotNull(config.initialRemoteData) - assertEquals(Duration.INFINITE, config.initialRemoteData!!.timeout) - } - - @Test - fun getInitialRemoteDataTimeout() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, TestHelper.randomPartitionValue(), PARTITION_BASED_SCHEMA) - .waitForInitialRemoteData(timeout = 10.seconds) - .build() - assertNotNull(config.initialRemoteData) - assertEquals(10.seconds, config.initialRemoteData!!.timeout) - } - - @Suppress("invisible_member", "invisible_reference") - @Test - fun supportedSchemaTypesWhenCreatingSyncConfiguration_partitionBased() { - val user = createTestUser() - val supportedSchemaTypes = setOf( - FlexParentObject::class, - FlexChildObject::class, - FlexEmbeddedObject::class, - ) - - val validateConfig = { config: io.realm.kotlin.Configuration -> - assertEquals(3, config.schema.size) - } - - // Partition-based Sync - enumValues().forEach { - when (it) { - ValueType.STRING -> { - validateConfig(SyncConfiguration.create(user, "", supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, "", supportedSchemaTypes).build()) - } - ValueType.INT -> { - validateConfig(SyncConfiguration.create(user, 1 as Int, supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, 1 as Int, supportedSchemaTypes).build()) - } - ValueType.LONG -> { - validateConfig(SyncConfiguration.create(user, 1L, supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, 1L, supportedSchemaTypes).build()) - } - ValueType.OBJECT_ID -> { - validateConfig(SyncConfiguration.create(user, BsonObjectId(), supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, BsonObjectId(), supportedSchemaTypes).build()) - } - ValueType.UUID -> { - validateConfig(SyncConfiguration.create(user, RealmUUID.random(), supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, RealmUUID.random(), supportedSchemaTypes).build()) - } - ValueType.NULL -> { - validateConfig(SyncConfiguration.create(user, null as String?, supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, null as String?, supportedSchemaTypes).build()) - } - else -> TODO("Test for partition type not defined") - } - } - } - - @Suppress("invisible_member", "invisible_reference") - @Test - fun supportedSchemaTypesWhenCreatingSyncConfiguration_flexibleSync() { - val user = createTestUser() - val supportedSchemaTypes = setOf( - FlexParentObject::class, - FlexChildObject::class, - FlexEmbeddedObject::class, - ) - - val validateConfig = { config: io.realm.kotlin.Configuration -> - assertEquals(3, config.schema.size) - } - - // Flexible Sync - validateConfig(SyncConfiguration.create(user, supportedSchemaTypes)) - validateConfig(SyncConfiguration.Builder(user, supportedSchemaTypes).build()) - } - - @Test - fun unsupportedSchemaTypesThrowException_partitionBasedSync() { - val user = createTestUser() - val unsupportedSchemaType = setOf(DynamicRealmObject::class) - assertFailsWithMessage(IllegalArgumentException::class, "Only subclasses of RealmObject and EmbeddedRealmObject are allowed in the schema. Found: io.realm.kotlin.dynamic.DynamicRealmObject. If io.realm.kotlin.dynamic.DynamicRealmObject is a valid subclass: This class has not been modified by the Realm Compiler Plugin. Has the Realm Gradle Plugin been applied to the project with this model class?") { - SyncConfiguration.create(user, "", unsupportedSchemaType) - } - assertFailsWithMessage(IllegalArgumentException::class, "Only subclasses of RealmObject and EmbeddedRealmObject are allowed in the schema. Found: io.realm.kotlin.dynamic.DynamicRealmObject. If io.realm.kotlin.dynamic.DynamicRealmObject is a valid subclass: This class has not been modified by the Realm Compiler Plugin. Has the Realm Gradle Plugin been applied to the project with this model class?") { - SyncConfiguration.Builder(user, "", unsupportedSchemaType) - } - } - - @Test - fun unsupportedSchemaTypesThrowException_flexibleSync() { - val user = createTestUser() - val unsupportedSchemaType = setOf(DynamicRealmObject::class) - assertFailsWithMessage(IllegalArgumentException::class, "Only subclasses of RealmObject and EmbeddedRealmObject are allowed in the schema. Found: io.realm.kotlin.dynamic.DynamicRealmObject. If io.realm.kotlin.dynamic.DynamicRealmObject is a valid subclass: This class has not been modified by the Realm Compiler Plugin. Has the Realm Gradle Plugin been applied to the project with this model class?") { - SyncConfiguration.create(user, unsupportedSchemaType) - } - assertFailsWithMessage(IllegalArgumentException::class, "Only subclasses of RealmObject and EmbeddedRealmObject are allowed in the schema. Found: io.realm.kotlin.dynamic.DynamicRealmObject. If io.realm.kotlin.dynamic.DynamicRealmObject is a valid subclass: This class has not been modified by the Realm Compiler Plugin. Has the Realm Gradle Plugin been applied to the project with this model class?") { - SyncConfiguration.Builder(user, unsupportedSchemaType) - } - } - -// -// @Test -// @Ignore("Not implemented yet") -// fun getSessionStopPolicy() { -// } -// -// @Test -// @Ignore("Not implemented yet") -// fun getUrlPrefix() { -// } - - @Ignore // Wait for https://github.com/realm/realm-kotlin/issues/648 - @Test - fun getPartitionValue() { - val user = createTestUser() - val config = SyncConfiguration.Builder( - schema = FLEXIBLE_SYNC_SCHEMA, - user = user, - partitionValue = partitionValue - ).build() - // Disabled until we have a proper BSON API - // assertEquals(DEFAULT_PARTITION_VALUE, config.partitionValue.asString()) - } - -// @Test -// fun clientResyncMode() { -// val user: User = createTestUser(app) -// -// // Default mode for full Realms -// var config: SyncConfiguration = SyncConfiguration.defaultConfig(user, DEFAULT_PARTITION) -// assertEquals(ClientResyncMode.MANUAL, config.clientResyncMode) -// -// // Manually set the mode -// config = SyncConfiguration.Builder(user, DEFAULT_PARTITION) -// .clientResyncMode(ClientResyncMode.DISCARD_LOCAL_REALM) -// .build() -// assertEquals(ClientResyncMode.DISCARD_LOCAL_REALM, config.clientResyncMode) -// } -// -// @Test -// fun clientResyncMode_throwsOnNull() { -// val user: User = createTestUser(app) -// val config: SyncConfiguration.Builder = SyncConfiguration.Builder(user, DEFAULT_PARTITION) -// try { -// config.clientResyncMode(TestHelper.getNull()) -// fail() -// } catch (ignore: IllegalArgumentException) { -// } -// } -// -// // If the same user create two configurations with different partition values they must -// // resolve to different paths on disk. -// @Test -// fun differentPartitionValuesAreDifferentRealms() { -// val user = createTestUser() -// val config1 = createSyncConfig(user, "realm1") -// val config2 = createSyncConfig(user, "realm2") -// assertNotEquals(config1.path, config2.path) -// -// assertTrue(config1.path.endsWith("${app.configuration.appId}/${user.id}/s_realm1.realm")) -// assertTrue(config2.path.endsWith("${app.configuration.appId}/${user.id}/s_realm2.realm")) -// -// // Check for https://github.com/realm/realm-java/issues/6882 -// val realm1 = Realm.open(config1) -// try { -// val realm2 = Realm.open(config2) -// realm2.close() -// } finally { -// realm1.close() -// } -// } -// -// -// @Test -// fun loggedOutUsersThrows() { -// val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") -// user.logOut() -// assertFailsWith { -// SyncConfiguration.defaultConfig(user, ObjectId()) -// } -// assertFailsWith { -// SyncConfiguration.defaultConfig(app.currentUser(), ObjectId()) -// } -// } -// -// @Test -// fun allowQueriesOnUiThread_defaultsToTrue() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.build() -// assertTrue(configuration.isAllowQueriesOnUiThread) -// } -// -// @Test -// fun allowQueriesOnUiThread_explicitFalse() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.allowQueriesOnUiThread(false) -// .build() -// assertFalse(configuration.isAllowQueriesOnUiThread) -// } -// -// @Test -// fun allowQueriesOnUiThread_explicitTrue() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.allowQueriesOnUiThread(true) -// .build() -// assertTrue(configuration.isAllowQueriesOnUiThread) -// } -// -// @Test -// fun allowWritesOnUiThread_defaultsToFalse() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.build() -// assertFalse(configuration.isAllowWritesOnUiThread) -// } -// -// @Test -// fun allowWritesOnUiThread_explicitFalse() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.allowWritesOnUiThread(false) -// .build() -// assertFalse(configuration.isAllowWritesOnUiThread) -// } -// -// @Test -// fun allowWritesOnUiThread_explicitTrue() { -// val builder: SyncConfiguration.Builder = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// val configuration = builder.allowWritesOnUiThread(true) -// .build() -// assertTrue(configuration.isAllowWritesOnUiThread) -// } -// -// @Test -// fun rxFactory_defaultNonNull() { -// val configuration = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .build() -// assertNotNull(configuration.rxFactory) -// } -// -// @Test -// fun rxFactory_nullThrows() { -// assertFailsWith { -// SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .rxFactory(TestHelper.getNull()) -// }.let { -// assertTrue(it.message!!.contains("null")) -// } -// } -// -// @Test -// fun rxFactory() { -// val factory = object: RxObservableFactory { -// override fun from(realm: Realm): Flowable { -// return Flowable.just(null) -// } -// -// override fun from(realm: DynamicRealm): Flowable { -// return Flowable.just(null) -// } -// -// override fun from(realm: Realm, results: RealmResults): Flowable> { -// return Flowable.just(null) -// } -// -// override fun from(realm: DynamicRealm, results: RealmResults): Flowable> { -// return Flowable.just(null) -// } -// -// override fun from(realm: Realm, list: RealmList): Flowable> { -// return Flowable.just(null) -// } -// -// override fun from(realm: DynamicRealm, list: RealmList): Flowable> { -// return Flowable.just(null) -// } -// -// override fun from(realm: Realm, `object`: E): Flowable { -// return Flowable.just(null) -// } -// -// override fun from(realm: DynamicRealm, `object`: DynamicRealmObject): Flowable { -// return Flowable.just(null) -// } -// -// override fun from(realm: Realm, query: RealmQuery): Single> { -// return Single.just(null) -// } -// -// override fun from(realm: DynamicRealm, query: RealmQuery): Single> { -// return Single.just(null) -// } -// -// override fun changesetsFrom(realm: Realm, results: RealmResults): Observable>> { -// return Observable.just(null) -// } -// -// override fun changesetsFrom(realm: DynamicRealm, results: RealmResults): Observable>> { -// return Observable.just(null) -// } -// -// override fun changesetsFrom(realm: Realm, list: RealmList): Observable>> { -// return Observable.just(null) -// } -// -// override fun changesetsFrom(realm: DynamicRealm, list: RealmList): Observable>> { -// return Observable.just(null) -// } -// -// override fun changesetsFrom(realm: Realm, `object`: E): Observable> { -// return Observable.just(null) -// } -// -// override fun changesetsFrom(realm: DynamicRealm, `object`: DynamicRealmObject): Observable> { -// return Observable.just(null) -// } -// -// } -// -// val configuration1 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .rxFactory(factory) -// .build() -// assertEquals(factory, configuration1.rxFactory) -// -// val configuration2 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .build() -// assertNotEquals(factory, configuration2.rxFactory) -// } -// -// @Test -// fun flowFactory_defaultNonNull() { -// val configuration = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .build() -// assertNotNull(configuration.flowFactory) -// } -// -// @Test -// fun flowFactory_nullThrows() { -// assertFailsWith { -// SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .flowFactory(TestHelper.getNull()) -// }.let { -// assertTrue(it.message!!.contains("null")) -// } -// } -// -// @Test -// fun flowFactory() { -// val factory = object : FlowFactory { -// override fun from(realm: Realm): Flow { -// return flowOf() -// } -// -// override fun from(dynamicRealm: DynamicRealm): Flow { -// return flowOf() -// } -// -// override fun from(realm: Realm, results: RealmResults): Flow> { -// return flowOf() -// } -// -// override fun from(dynamicRealm: DynamicRealm, results: RealmResults): Flow> { -// return flowOf() -// } -// -// override fun from(realm: Realm, realmList: RealmList): Flow> { -// return flowOf() -// } -// -// override fun from(dynamicRealm: DynamicRealm, realmList: RealmList): Flow> { -// return flowOf() -// } -// -// override fun from(realm: Realm, realmObject: T): Flow { -// return flowOf() -// } -// -// override fun from(dynamicRealm: DynamicRealm, dynamicRealmObject: DynamicRealmObject): Flow { -// return flowOf() -// } -// -// override fun changesetFrom(realm: Realm, results: RealmResults): Flow>> { -// return flowOf() -// } -// -// override fun changesetFrom(dynamicRealm: DynamicRealm, results: RealmResults): Flow>> { -// return flowOf() -// } -// -// override fun changesetFrom(realm: Realm, list: RealmList): Flow>> { -// return flowOf() -// } -// -// override fun changesetFrom(dynamicRealm: DynamicRealm, list: RealmList): Flow>> { -// return flowOf() -// } -// -// override fun changesetFrom(realm: Realm, realmObject: T): Flow> { -// return flowOf() -// } -// -// override fun changesetFrom(dynamicRealm: DynamicRealm, dynamicRealmObject: DynamicRealmObject): Flow> { -// return flowOf() -// } -// } -// -// val configuration1 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .flowFactory(factory) -// .build() -// assertEquals(factory, configuration1.flowFactory) -// -// val configuration2 = SyncConfiguration.Builder(createTestUser(app), DEFAULT_PARTITION) -// .build() -// assertNotEquals(factory, configuration2.flowFactory) -// } - - @Test - fun syncClientResetStrategy_partitionBased() { - val resetHandler = object : DiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not be called") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - } - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .syncClientResetStrategy(resetHandler) - .build() - assertEquals(resetHandler, config.syncClientResetStrategy) - } - - @Test - fun syncClientResetStrategy_partitionBased_defaultNotNull() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .build() - assertNotNull(config.syncClientResetStrategy) - assertTrue(config.syncClientResetStrategy is RecoverOrDiscardUnsyncedChangesStrategy) - } - - @Test - fun syncClientResetStrategy_flexibleBased() { - val resetHandler = object : ManuallyRecoverUnsyncedChangesStrategy { - override fun onClientReset( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - } - val user = createTestUser() - val config = SyncConfiguration.Builder(user, setOf()) - .syncClientResetStrategy(resetHandler) - .build() - assertEquals(resetHandler, config.syncClientResetStrategy) - } - - @Test - fun syncClientResetStrategy_defaultNotNull() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, setOf()) - .build() - assertNotNull(config.syncClientResetStrategy) - assertTrue(config.syncClientResetStrategy is RecoverOrDiscardUnsyncedChangesStrategy) - } - - @Test - fun syncClientResetStrategy_automatic() { - val strategy = object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Callback should not be reachable") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Callback should not be reachable") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Callback should not be reachable") - } - } - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .syncClientResetStrategy(strategy) - .build() - assertEquals(strategy, config.syncClientResetStrategy) - } - - @Test - fun syncClientResetStrategy_automaticOrDiscard() { - val strategy = object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Callback should not be reachable") - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - fail("Callback should not be reachable") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - fail("Callback should not be reachable") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Callback should not be reachable") - } - } - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .syncClientResetStrategy(strategy) - .build() - assertEquals(strategy, config.syncClientResetStrategy) - } - - @Test - fun recoverUnsyncedChangesStrategyMode() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .syncClientResetStrategy(object : RecoverUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not be called") - } - - override fun onAfterReset(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - }) - .build() - assertTrue(config.syncClientResetStrategy is RecoverUnsyncedChangesStrategy) - } - - @Test - fun recoverOrDiscardUnsyncedChangesStrategyMode() { - val user = createTestUser() - val config = SyncConfiguration.Builder(user, partitionValue, setOf()) - .syncClientResetStrategy(object : RecoverOrDiscardUnsyncedChangesStrategy { - override fun onBeforeReset(realm: TypedRealm) { - fail("Should not be called") - } - - override fun onAfterRecovery(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onAfterDiscard(before: TypedRealm, after: MutableRealm) { - fail("Should not be called") - } - - override fun onManualResetFallback( - session: SyncSession, - exception: ClientResetRequiredException - ) { - fail("Should not be called") - } - }) - .build() - assertTrue(config.syncClientResetStrategy is RecoverOrDiscardUnsyncedChangesStrategy) - } - - private fun createTestUser(): User = runBlocking { - val (email, password) = randomEmail() to "password1234" - app.createUserAndLogIn(email, password) - } - - private fun nameAssertions(fileName: String) { - val user: User = createTestUser() - val config: SyncConfiguration = SyncConfiguration.Builder(user, partitionValue, setOf()) - .name(fileName) - .build() - - val expectedFilename = if (fileName.endsWith(".realm")) fileName else "$fileName.realm" - - val suffix = pathOf("", "mongodb-realm", user.app.configuration.appId, user.id, expectedFilename) - assertTrue(config.path.endsWith(suffix), "${config.path} failed.") - assertEquals(expectedFilename, config.name, "${config.name} failed.") - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt deleted file mode 100644 index ec372c0bb3..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncSessionTests.kt +++ /dev/null @@ -1,624 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common - -import io.ktor.client.plugins.ClientRequestException -import io.realm.kotlin.Realm -import io.realm.kotlin.RealmConfiguration -import io.realm.kotlin.entities.sync.BinaryObject -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ObjectIdPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.sync.ConnectionState -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.trySendOrFail -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withTimeout -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import org.mongodb.kbson.BsonObjectId -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNotSame -import kotlin.test.assertSame -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.Duration.Companion.seconds - -class SyncSessionTests { - - private lateinit var partitionValue: String - private lateinit var user: User - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - user = runBlocking { - app.createUserAndLogIn(email, password) - } - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun session() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - val session: SyncSession = realm.syncSession - assertNotNull(session) - } - } - - @Test - fun sessionPauseAndResume() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - runBlocking { - // default state should be active - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - - // pausing the session sets it in Inactive state - realm.syncSession.pause() - assertEquals(SyncSession.State.PAUSED, realm.syncSession.state) - - // resuming the session sets it in Active state - realm.syncSession.resume() - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - } - } - } - - @Test - fun sessionResumeMultipleTimes() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - runBlocking { - // default state should be active - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - - // resuming an active session should do nothing - realm.syncSession.resume() - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - - // resuming an active session should do nothing - realm.syncSession.resume() - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - } - } - } - - @Test - fun sessionPauseMultipleTimes() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - runBlocking { - // default state should be active - assertEquals(SyncSession.State.ACTIVE, realm.syncSession.state) - - // resuming an active session should do nothing - realm.syncSession.pause() - assertEquals(SyncSession.State.PAUSED, realm.syncSession.state) - - // resuming an active session should do nothing - realm.syncSession.pause() - assertEquals(SyncSession.State.PAUSED, realm.syncSession.state) - } - } - } - - // The same object is returned for each call to `Realm.session` - @Test - fun session_identity() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - val session1: SyncSession = realm.syncSession - val session2: SyncSession = realm.syncSession - assertSame(session1, session2) - } - } - - // If multiple instances of the same Realm is opened. The Kotlin SyncSession objects will - // differ, but they point to the same underlying Core Sync Session. - @Test - @Suppress("invisible_reference", "invisible_member") - fun session_sharedStateBetweenRealms() { - val config1 = createSyncConfig(user, "realm1.realm") - val config2 = createSyncConfig(user, "realm2.realm") - Realm.open(config1).use { realm1: Realm -> - Realm.open(config2).use { realm2: Realm -> - assertNotEquals(realm1.configuration.path, realm2.configuration.path) - assertNotSame(realm1.syncSession, realm2.syncSession) - RealmInterop.realm_equals( - (realm1.syncSession as io.realm.kotlin.mongodb.internal.SyncSessionImpl).nativePointer, - (realm2.syncSession as io.realm.kotlin.mongodb.internal.SyncSessionImpl).nativePointer - ) - } - } - } - - @Test - fun session_localRealmThrows() { - val config = RealmConfiguration.Builder(schema = setOf(ParentPk::class, ChildPk::class)) - .directory(PlatformUtils.createTempDir()) - .build() - Realm.open(config).use { realm -> - assertFailsWith { realm.syncSession } - } - } - - @Test - fun downloadAllServerChanges_illegalArgumentThrows() { - openSyncRealm { realm -> - val session: SyncSession = realm.syncSession - assertFailsWith { - session.downloadAllServerChanges(0.seconds) - } - assertFailsWith { - session.downloadAllServerChanges((-1).seconds) - } - } - } - - @Test - fun downloadAllServerChanges_returnFalseOnTimeOut() { - openSyncRealmWithPreconditions({ realm -> - // Write a large ByteArray so that we increase the chance the timeout works - realm.writeBlocking { - val obj = BinaryObject() - copyToRealm(obj) - } - }) { realm -> - val session = realm.syncSession - assertFalse(session.downloadAllServerChanges(timeout = 1.nanoseconds)) - } - } - - @Test - fun uploadAllLocalChanges_illegalArgumentThrows() { - openSyncRealm { realm -> - val session: SyncSession = realm.syncSession - assertFailsWith { - session.uploadAllLocalChanges(0.seconds) - } - assertFailsWith { - session.uploadAllLocalChanges((-1).seconds) - } - } - } - - @Test - fun uploadAllLocalChanges_returnFalseOnTimeOut() { - openSyncRealmWithPreconditions({ realm -> - // Write a large ByteArray so that we increase the chance the timeout works - realm.writeBlocking { - val obj = BinaryObject() - copyToRealm(obj) - } - }) { realm -> - val session = realm.syncSession - assertFalse(session.uploadAllLocalChanges(timeout = 1.nanoseconds)) - } - } - - @Test - @Ignore // See https://github.com/realm/realm-kotlin/issues/872 - fun uploadAndDownloadChangesSuccessfully() = runBlocking { - val user1 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - val user2 = app.createUserAndLogIn(TestHelper.randomEmail(), "123456") - - val config1 = SyncConfiguration.Builder( - user1, - partitionValue, - schema = PARTITION_BASED_SCHEMA - ).name("user1.realm") - .build() - val config2 = SyncConfiguration.Builder( - user2, - partitionValue, - schema = PARTITION_BASED_SCHEMA - ).name("user2.realm") - .build() - - Realm.open(config1).use { realm1 -> - Realm.open(config2).use { realm2 -> - realm1.write { - for (i in 0 until 10) { - copyToRealm( - ParentPk().apply { - _id = i.toString() - } - ) - } - } - assertEquals(10, realm1.query().count().find()) - assertEquals(0, realm2.query().count().find()) - realm1.syncSession.uploadAllLocalChangesOrFail() - - // Due to the Server Translator, there is a small delay between data - // being uploaded and it not being immediately ready for download - // on another Realm. In order to reduce the flakyness, we are - // re-evaluating the assertion multiple times. - for (i in 1200 downTo 0) { // Wait for max 2 minutes. - assertTrue(realm2.syncSession.downloadAllServerChanges()) - when (val size = realm2.query().count().find()) { - 10L -> break // Test succeeded - 0L -> { - // Race condition: Server has not yet propagated data to user 2. - if (i == 0) { - throw kotlin.AssertionError("Realm failed to receive download data. Size: $size") - } - delay(100) - } - else -> { - // The server might not send all data at once, so just print intermediate - // steps here for debugging purposes - println("Received size: $size") - } - } - } - } - } - } - - // SyncSessions available inside the error handler are disconnected from the underlying - // Realm instance and some APIs could have a difficult time understanding semantics. For now, we - // just disallow calling these APIs from these instances. - @Test - fun syncSessionFromErrorHandlerCannotUploadAndDownloadChanges() = runBlocking { - // Open Realm with a user that has no read nor write permissions - // See 'canWritePartition' in TestAppInitializer.kt. - val (email, password) = "test_nowrite_noread_${TestHelper.randomEmail()}" to "password1234" - val user = app.createUserAndLogIn(email, password) - val channel = TestChannel() - val config = SyncConfiguration.Builder( - schema = PARTITION_BASED_SCHEMA, - user = user, - partitionValue = partitionValue - ).errorHandler { session, _ -> - channel.trySendOrFail(session) - }.build() - - var realm: Realm? = null - val deferred = async { - realm = Realm.open(config) - } - val session = channel.receiveOrFail() - try { - assertFailsWithMessage("Operation is not allowed inside a `SyncSession.ErrorHandler`.") { - runBlocking { - session.uploadAllLocalChangesOrFail() - } - } - assertFailsWithMessage("Operation is not allowed inside a `SyncSession.ErrorHandler`.") { - runBlocking { - session.downloadAllServerChanges() - } - } - } finally { - channel.close() - deferred.cancel() - realm?.let { it.close() } - } - } - - // TODO https://github.com/realm/realm-core/issues/5365. - // Note, it hasn't been verified that pause sync actually trigger this message. So test might - // need to be reworked once the core issue is fixed. - @Ignore - @Test - fun uploadDownload_throwsUnderlyingSyncError() { - openSyncRealm { realm -> - val session = realm.syncSession - app.pauseSync() - assertFailsWith { - session.uploadAllLocalChangesOrFail() - }.also { - assertTrue(it.message!!.contains("End of input", ignoreCase = true), it.message) - } - assertFailsWith { - session.downloadAllServerChanges() - }.also { - assertTrue(it.message!!.contains("End of input", ignoreCase = true), it.message) - } - app.startSync() - } - } - - /** - * - Insert a document in MongoDB using the command server - * - Fetch the object id of the newly inserted document - * - Open a Realm with the same partition key as the inserted document - * - Wait for Sync to fetch the document as a valid RealmObject with the matching ObjectId as a PK - */ - @Test - fun syncingObjectIdFromMongoDB() = runBlocking { - val adminApi = app.asTestApp - val config = SyncConfiguration.Builder(user, partitionValue, schema = PARTITION_BASED_SCHEMA).build() - Realm.open(config).use { realm -> - val json: JsonObject = adminApi.insertDocument( - ObjectIdPk::class.simpleName!!, - """ - { - "name": "$partitionValue", - "realm_id" : "$partitionValue" - } - """.trimIndent() - )!! - val oid = json["insertedId"]!!.jsonObject["${'$'}oid"]!!.jsonPrimitive.content - assertNotNull(oid) - - val channel = TestChannel() - val job = async { - realm.query("_id = $0", BsonObjectId(oid)).first() - .asFlow().collect { - if (it.obj != null) { - channel.send(it.obj!!) - } - } - } - - val insertedObject = channel.receiveOrFail(5.minutes) - assertEquals(oid, insertedObject._id.toHexString()) - assertEquals(partitionValue, insertedObject.name) - job.cancel() - } - } - - /** - * - Insert a RealmObject and sync it - * - Query the MongoDB database to make sure the document was inserted with the correct ObjectID - */ - @Test - fun syncingObjectIdFromRealm() = runBlocking { - val adminApi = app.asTestApp - val objectId = BsonObjectId() - val oid = objectId.toHexString() - - val job = async { - val config = SyncConfiguration.Builder( - user, - partitionValue, - schema = PARTITION_BASED_SCHEMA - ) - .build() - Realm.open(config).use { realm -> - val objWithPK = ObjectIdPk().apply { - name = partitionValue - _id = objectId - } - - realm.write { - copyToRealm(objWithPK) - } - - realm.syncSession.uploadAllLocalChangesOrFail() - } - } - - var oidAsString: String? = null - var attempts = 150 // waiting a max of 30s - do { - delay(200) // let Sync integrate the changes - @Suppress("EmptyCatchBlock") // retrying - try { - val syncedDocumentJson = - adminApi.queryDocument( - clazz = ObjectIdPk::class.simpleName!!, - query = """{"_id":{"${'$'}oid":"$oid"}}""" - ) - oidAsString = - syncedDocumentJson?.get("_id")?.jsonObject?.get("${'$'}oid")?.jsonPrimitive?.content - } catch (e: ClientRequestException) { - } - } while (oidAsString == null && attempts-- > 0) - - assertEquals(oid, oidAsString) - job.cancel() - } - - @Test - fun getConfiguration() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - assertSame(config, realm.syncSession.configuration) - } - } - - @Ignore // TODO Find another way to test with with developer mode v2 - @Test - fun getConfiguration_inErrorHandlerThrows() = runBlocking { - // Open and close a realm with a schema. - val channel = TestChannel() - val (email, password) = TestHelper.randomEmail() to "password1234" - val user = app.createUserAndLogIn(email, password) - val config1 = SyncConfiguration.Builder( - schema = FLEXIBLE_SYNC_SCHEMA, - user = user, - partitionValue = partitionValue - ).name("test1.realm").build() - Realm.open(config1).use { realm1 -> - realm1.syncSession.uploadAllLocalChangesOrFail() - // Make sure to sync the realm with the server before opening the second instance - assertTrue(realm1.syncSession.uploadAllLocalChanges(1.minutes)) - } - - // Open another realm with the same entity but change the type of a field - // in the schema to trigger a sync error to be caught by the error handler. - val config2 = SyncConfiguration.Builder( - schema = setOf(io.realm.kotlin.entities.sync.bogus.ChildPk::class), - user = user, - partitionValue = partitionValue - ) - .name("test2.realm") - .errorHandler { session, _ -> channel.trySendOrFail(session) } - .build() - Realm.open(config2).use { realm2 -> - // Await the sync session sent. - val session = channel.receiveOrFail() - - // Validate that the session was captured and that the configuration cannot be accessed. - assertIs(session) - assertFailsWithMessage("Operation is not allowed inside a") { - session.configuration - } - - channel.close() - } - } - - @Test - fun getUser() { - val config = createSyncConfig(user) - Realm.open(config).use { realm: Realm -> - assertSame(user, realm.syncSession.user) - } - } - - @Test - fun connectionState() = runBlocking { - Realm.open(createSyncConfig(user)).use { realm: Realm -> - // We don't know what the state will be, but just verify that we can retrieve it - // without issues - assertNotNull(realm.syncSession.connectionState) - } - } - - @Test - fun connectionState_asFlow() = runBlocking { - Realm.open(createSyncConfig(user)).use { realm: Realm -> - val flow = realm.syncSession.connectionStateAsFlow() - // Adopted from realm-java tests ... - // Sometimes the connection is already established and then we cannot expect any - // updates, but as this is highly likely just safely ignore this to avoid flaky tests - // on CI - try { - val (oldState, newState) = withTimeout(5.seconds) { flow.first() } - assertNotEquals(oldState, newState) - assertEquals(realm.syncSession.connectionState, newState) - } catch (e: TimeoutCancellationException) { - assertTrue { realm.syncSession.connectionState == ConnectionState.CONNECTED } - // Make some visible sign that we have skipped waiting for events - println("Skipping flow tests as connection is already established") - } - } - } - - @Test - fun connectionState_completeOnClose() = runBlocking { - val channel = TestChannel(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - val realm = Realm.open(createSyncConfig(user)) - try { - val flow1 = realm.syncSession.connectionStateAsFlow() - val job = async { - withTimeout(10.seconds) { - // We are not guarantee that the connectionFlow will trigger, so are forced - // to send the event before. This still leaves a small chance of a race - // condition, but I assume that the jump between coroutines is always slower - // than executing to instructions in sequence. - channel.send(true) - flow1.collect { } - } - } - channel.receiveOrFail() - realm.close() - job.await() - } finally { - channel.close() - if (!realm.isClosed()) { - realm.close() - } - } - } - - private fun openSyncRealmWithPreconditions( - preconditions: (suspend (Realm) -> Unit)? = null, - block: suspend (Realm) -> Unit - ) { - val config = SyncConfiguration.Builder( - user, - partitionValue, - schema = setOf(ParentPk::class, ChildPk::class, BinaryObject::class) - ).build() - - if (preconditions != null) { - Realm.open(config).use { realm -> - runBlocking { - preconditions(realm) - } - } - } - - Realm.open(config).use { realm -> - runBlocking { - block(realm) - } - } - } - - private fun openSyncRealm(block: suspend (Realm) -> Unit) { - openSyncRealmWithPreconditions(null, block) - } - - private fun createSyncConfig( - user: User, - name: String = DEFAULT_NAME, - ): SyncConfiguration = SyncConfiguration.Builder( - schema = setOf(ParentPk::class, ChildPk::class), - user = user, - partitionValue = partitionValue - ).name(name) - .build() -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt deleted file mode 100644 index 4d30150a34..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/SyncedRealmTests.kt +++ /dev/null @@ -1,2210 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.RealmConfiguration -import io.realm.kotlin.VersionId -import io.realm.kotlin.entities.JsonStyleRealmObject -import io.realm.kotlin.entities.sync.BinaryObject -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.entities.sync.flx.FlexChildObject -import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.asFlow -import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.ext.realmAnyDictionaryOf -import io.realm.kotlin.ext.realmAnyListOf -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.RealmInterop -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.internal.platform.pathOf -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.App -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.DownloadingRealmTimeOutException -import io.realm.kotlin.mongodb.exceptions.SyncException -import io.realm.kotlin.mongodb.exceptions.UnrecoverableSyncException -import io.realm.kotlin.mongodb.exceptions.WrongSyncTypeException -import io.realm.kotlin.mongodb.internal.SyncSessionImpl -import io.realm.kotlin.mongodb.subscriptions -import io.realm.kotlin.mongodb.sync.InitialSubscriptionsCallback -import io.realm.kotlin.mongodb.sync.SubscriptionSetState -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.sync.SyncSession -import io.realm.kotlin.mongodb.sync.SyncSession.ErrorHandler -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.notifications.InitialRealm -import io.realm.kotlin.notifications.RealmChange -import io.realm.kotlin.notifications.ResultsChange -import io.realm.kotlin.notifications.UpdatedRealm -import io.realm.kotlin.query.RealmResults -import io.realm.kotlin.schema.RealmClass -import io.realm.kotlin.schema.RealmSchema -import io.realm.kotlin.schema.ValuePropertyType -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.common.utils.uploadAllLocalChangesOrFail -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.TestHelper.randomEmail -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.trySendOrFail -import io.realm.kotlin.test.util.use -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmDictionary -import io.realm.kotlin.types.RealmList -import kotlinx.atomicfu.atomic -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.takeWhile -import kotlinx.coroutines.withTimeout -import okio.FileSystem -import okio.Path -import okio.Path.Companion.toPath -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId -import kotlin.random.Random -import kotlin.random.nextULong -import kotlin.reflect.KClass -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.Duration.Companion.seconds - -@Suppress("LargeClass") -@OptIn(ExperimentalKBsonSerializerApi::class) -class SyncedRealmTests { - - companion object { - // Initial version of any new typed Realm (due to schema being written) - private val INITIAL_VERSION = VersionId(2) - } - - private lateinit var partitionValue: String - private lateinit var realm: Realm - private lateinit var syncConfiguration: SyncConfiguration - private lateinit var app: App - - @BeforeTest - fun setup() { - partitionValue = TestHelper.randomPartitionValue() - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - syncConfiguration = createPartitionSyncConfig( - user = user, - partitionValue = partitionValue, - ) - } - - @AfterTest - fun tearDown() { - if (this::realm.isInitialized && !realm.isClosed()) { - realm.close() - } - if (this::app.isInitialized) { - app.asTestApp.close() - } - - RealmLog.reset() - } - - @Test - fun canOpen() { - realm = Realm.open(syncConfiguration) - assertNotNull(realm) - } - - @Test - fun canSync() = runBlocking { - // A user has two realms in different files, 1 stores an object locally and 2 receives the - // update from the server after the object is synchronized. - val (email, password) = randomEmail() to "password1234" - val user = app.createUserAndLogIn(email, password) - val partitionValue = Random.nextULong().toString() - - val config1 = createPartitionSyncConfig( - user = user, partitionValue = partitionValue, name = "db1", - errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - fail("Realm 1: $error") - } - } - ) - Realm.open(config1).use { realm1 -> - val config2 = createPartitionSyncConfig( - user = user, partitionValue = partitionValue, name = "db2", - errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - fail("Realm 2: $error") - } - } - ) - Realm.open(config2).use { realm2 -> - val child = ChildPk().apply { - _id = "CHILD_A" - name = "A" - } - - val channel = TestChannel>() - - // There was a race condition where construction of a query against the user facing frozen - // version could throw, due to the underlying version being deleted when the live realm was - // advanced on remote changes. - // Haven't been able to make a reproducible recipe for triggering this, so just keeping the - // query around to monitor that we don't reintroduce the issue: - // https://github.com/realm/realm-kotlin/issues/683 - // For the record, we seemed to hit the race more often when syncing existing data, which - // can be achieved by just reusing the same partition value and running this test multiple - // times. - assertEquals(0, realm1.query().find().size, realm1.toString()) - - val observer = async { - realm2.query() - .asFlow() - .collect { childResults: ResultsChange -> - channel.send(childResults) - } - } - - assertEquals(0, channel.receiveOrFail().list.size) - - realm1.write { - copyToRealm(child) - } - - val childResults = channel.receiveOrFail() - val childPk = childResults.list[0] - assertEquals("CHILD_A", childPk._id) - observer.cancel() - channel.close() - } - } - } - - // Test for https://github.com/realm/realm-kotlin/issues/1070 - @Test - fun realmAsFlow_acrossSyncedChanges() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - - val config1 = createPartitionSyncConfig( - user = user1, - name = "db1.realm", - partitionValue = partitionValue - ) - val realm1 = Realm.open(config1) - // We don't fully control all events from the server, so do not check - // for an empty buffer because we might accidentally see multiple updates - // from the server. - val c = TestChannel>(failIfBufferIsEmptyOnCancel = false) - val observer = async { - realm1.asFlow().collect { - c.send(it) - } - } - val event: RealmChange = c.receiveOrFail() - assertTrue(event is InitialRealm) - - // Write remote change - createPartitionSyncConfig( - user = user2, - name = "db2.realm", - partitionValue = partitionValue, - ).let { config -> - Realm.open(config).use { realm -> - realm.write { - val id = "id-${Random.nextLong()}" - copyToRealm(SyncObjectWithAllTypes().apply { _id = id }) - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } - - // Wait for Realm.asFlow() to be updated based on remote change. - try { - withTimeout(timeout = 10.seconds) { - while (true) { - val updateEvent: RealmChange = c.receiveOrFail() - assertTrue(updateEvent is UpdatedRealm) - if (updateEvent.realm.query().find().size == 1) - break - } - } - } finally { - c.cancel() - realm1.close() - observer.cancel() - } - } - - @Test - fun canOpenWithRemoteSchema() { - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - val partitionValue = Random.nextLong().toString() - // Setup two realms that synchronizes with the backend - val config1 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db1") - val realm1 = Realm.open(config1) - assertNotNull(realm1) - val config2 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db2") - val realm2 = Realm.open(config2) - assertNotNull(realm2) - - // Block until we see changed written to one realm in the other to ensure that schema is - // aligned with backend - runBlocking { - val synced = async { - realm2.query(ChildPk::class).asFlow().takeWhile { it.list.size != 0 }.collect { } - } - realm1.write { copyToRealm(ChildPk()) } - synced.await() - } - - // Open a third realm to verify that it can open it when there is a schema on the backend - // There is no guarantee that this wouldn't succeed if all internal realms (user facing, - // writer and notifier) are opened before the schema is synced from the server, but - // empirically it has shown not to be the case and cause trouble if opening the second or - // third realm with the wrong sync-intended schema mode. - val config3 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db3") - val realm3 = Realm.open(config3) - assertNotNull(realm3) - - realm1.close() - realm2.close() - realm3.close() - } - - @Test - fun errorHandlerProcessFatalSyncErrors() { - val channel = TestChannel() - val user = runBlocking { - app.login(Credentials.anonymous()) - } - - val config = SyncConfiguration.Builder( - schema = setOf(ParentPk::class, ChildPk::class), - user = user, - partitionValue = partitionValue - ).errorHandler { _, error -> - channel.trySendOrFail(error) - }.build() - - runBlocking { - val deferred = async { - Realm.open(config).use { realm -> - RealmInterop.realm_sync_session_handle_error_for_testing( - syncSession = (realm.syncSession as SyncSessionImpl).nativePointer, - error = ErrorCode.RLM_ERR_ACCOUNT_NAME_IN_USE, - errorMessage = "Non fatal error", - isFatal = false, - ) - - RealmInterop.realm_sync_session_handle_error_for_testing( - syncSession = (realm.syncSession as SyncSessionImpl).nativePointer, - error = ErrorCode.RLM_ERR_INTERNAL_SERVER_ERROR, - errorMessage = "Fatal error", - isFatal = true, - ) - } - } - - // First error - channel.receiveOrFail().let { error -> - assertNotNull(error.message) - assertIs(error) - } - - // Second - channel.receiveOrFail().let { error -> - assertNotNull(error.message) - // Deprecated - assertIs(error) - assertIs(error) - assertTrue(error.isFatal) - } - - deferred.cancel() - } - } - - @Test - fun errorHandlerReceivesPermissionDeniedSyncError() { - val channel = TestChannel() - // Remove permissions to generate a sync error containing ONLY the original path - // This way we assert we don't read wrong data from the user_info field - val (email, password) = "test_nowrite_noread_${randomEmail()}" to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - val config = SyncConfiguration.Builder( - schema = setOf(ParentPk::class, ChildPk::class), - user = user, - partitionValue = partitionValue - ).errorHandler { _, error -> - channel.trySendOrFail(error) - }.build() - - runBlocking { - val deferred = async { - Realm.open(config).use { - // Make sure that the test eventually fail. Coroutines can cancel a delay - // so this doesn't always block the test for 10 seconds. - delay(10_000) - channel.send(AssertionError("Realm was successfully opened")) - } - } - - val error = channel.receiveOrFail() - val message = error.message - assertNotNull(message) - assertTrue(error is UnrecoverableSyncException, "Was $error") - assertTrue( - message.lowercase().contains("permission denied"), - "The error should be 'PermissionDenied' but it was: $message" - ) - deferred.cancel() - } - } - - @Test - fun errorHandler_wrongSyncTypeException() { - val channel = TestChannel() - // Remove permissions to generate a sync error containing ONLY the original path - // This way we assert we don't read wrong data from the user_info field - val (email, password) = "test_nowrite_noread_${randomEmail()}" to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - // Opens FLX synced realm against a PBS app - val config = SyncConfiguration.Builder( - schema = setOf(ParentPk::class, ChildPk::class), - user = user, - ).errorHandler { _, error -> - channel.trySendOrFail(error) - }.build() - - runBlocking { - val deferred = async { - Realm.open(config).use { - // Make sure that the test eventually fail. Coroutines can cancel a delay - // so this doesn't always block the test for 10 seconds. - delay(10_000) - channel.send(AssertionError("Realm was successfully opened")) - } - } - - val error = channel.receiveOrFail() - val message = error.message - assertNotNull(message) - assertIs(error) - assertTrue(error.isFatal) - // Deprecated - assertIs(error) - assertTrue( - message.contains("Client connected using flexible sync when app is using partition-based sync"), - "Was: $message" - ) - deferred.cancel() - } - } - - @Test - fun testErrorHandler() { - // Open a realm with a schema. Close it without doing anything else - val channel = TestChannel() - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - val config1 = SyncConfiguration.Builder( - schema = setOf(ChildPk::class), - user = user, - partitionValue = partitionValue - ).name("test1.realm").build() - val realm1 = Realm.open(config1) - assertNotNull(realm1) - - // Open another realm with the same entity but change the type of a field in the schema to - // trigger a sync error to be caught by the error handler - runBlocking { - realm1.syncSession.uploadAllLocalChangesOrFail() - val config2 = SyncConfiguration.Builder( - schema = setOf(io.realm.kotlin.entities.sync.bogus.ChildPk::class), - user = user, - partitionValue = partitionValue - ).name("test2.realm") - .errorHandler(object : ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - channel.trySendOrFail(error) - } - }).build() - val realm2 = Realm.open(config2) - assertNotNull(realm2) - - // Await for exception to happen - val exception = channel.receiveOrFail() - - channel.close() - - // Validate that the exception was captured and contains serialized fields - assertIs(exception) - exception.message.let { errorMessage -> - assertNotNull(errorMessage) - // Some race on JVM in particular mean that different errors can be reported. - if (errorMessage.contains("[Sync]")) { - assertTrue(errorMessage.contains("[BadChangeset(1015)]"), errorMessage) - assertTrue(errorMessage.contains("Schema mismatch"), errorMessage) - } else { - fail("Unexpected error message: $errorMessage") - } - } - - // Housekeeping for test Realms - realm1.close() - realm2.close() - } - } - - // It is unclear what we mean by "MainThread" in KMP, until we do, this functionality is - // disabled. See https://github.com/realm/realm-kotlin/issues/847 - @Test - @Ignore - fun waitForInitialRemoteData_mainThreadThrows() = runBlocking(Dispatchers.Main) { - val user = app.asTestApp.createUserAndLogin() - val config = SyncConfiguration.Builder(user, TestHelper.randomPartitionValue(), PARTITION_BASED_SCHEMA) - .waitForInitialRemoteData() - .build() - assertFailsWith { - Realm.open(config) - } - } - - // Test for https://github.com/realm/realm-kotlin/issues/1401 - // Make sure that server errors are correctly passed to the user if `waitForInitialRemoteData` - // fails. - @Test - fun waitForInitialRemoteData_propagateServerErrors() { - val user = app.asTestApp.createUserAndLogin() - val config = SyncConfiguration.Builder(user, 0L, setOf()) - .waitForInitialRemoteData() - .build() - assertFailsWithMessage("Illegal Realm path (BIND): expected partition of type \"string\" but found \"long\"") { - Realm.open(config) - } - } - - @Test - fun waitForInitialRemoteData() = runBlocking { - val partitionValue = TestHelper.randomPartitionValue() - - // 1. Copy a valid Realm to the server - val user1 = app.asTestApp.createUserAndLogin() - val config1: SyncConfiguration = SyncConfiguration.create(user1, partitionValue, PARTITION_BASED_SCHEMA) - Realm.open(config1).use { realm -> - realm.write { - for (index in 0..9) { - copyToRealm( - ParentPk().apply { - _id = "$partitionValue-$index" - } - ) - } - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - - // 2. Sometimes it can take a little while for the data to be available to other users, - // so make sure data has reached server. - val user2 = app.asTestApp.createUserAndLogin() - val config2: SyncConfiguration = SyncConfiguration.create(user2, partitionValue, PARTITION_BASED_SCHEMA) - assertNotEquals(config1.path, config2.path) - Realm.open(config2).use { realm -> - val count = realm.query() - .asFlow() - .map { it.list.size } - .first { it == 10 } - assertEquals(10, count) - } - - // 3. Finally verify `waitForInitialData` is working - val user3 = app.asTestApp.createUserAndLogin() - val config3: SyncConfiguration = SyncConfiguration.Builder(user3, partitionValue, PARTITION_BASED_SCHEMA) - .waitForInitialRemoteData() - .build() - assertNotEquals(config1.path, config3.path) - Realm.open(config3).use { realm -> - assertEquals(10, realm.query().count().find()) - } - } - - @Test - fun waitForInitialData_timeOut() = runBlocking { - val partitionValue = TestHelper.randomPartitionValue() - - // High enough to introduce latency when download Realm initial data - // but not too high so that we reach Atlas' transmission limit - val objectCount = 5 - - // 1. Copy a valid Realm to the server - val user1 = app.asTestApp.createUserAndLogin() - val config1: SyncConfiguration = SyncConfiguration.create(user1, partitionValue, PARTITION_BASED_SCHEMA) - Realm.open(config1).use { realm -> - realm.write { - for (index in 0 until objectCount) { - copyToRealm( - BinaryObject().apply { - _id = "$partitionValue-$index" - } - ) - } - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - - // 2. Sometimes it can take a little while for the data to be available to other users, - // so make sure data has reached server. - val user2 = app.asTestApp.createUserAndLogin() - val config2: SyncConfiguration = SyncConfiguration.create(user2, partitionValue, PARTITION_BASED_SCHEMA) - assertNotEquals(config1.path, config2.path) - Realm.open(config2).use { realm -> - val count = realm.query() - .asFlow() - .filter { it.list.size == objectCount } - .map { it.list.size } - .first() - assertEquals(objectCount, count) - } - - // 3. Finally verify `waitForInitialData` is working - val user3 = app.asTestApp.createUserAndLogin() - val config3: SyncConfiguration = SyncConfiguration.Builder(user3, partitionValue, PARTITION_BASED_SCHEMA) - .waitForInitialRemoteData(1.nanoseconds) - .build() - assertNotEquals(config1.path, config3.path) - assertFailsWith { - Realm.open(config3).use { - fail("Realm should not open in time") - } - } - Unit - } - - // This tests will start and cancel getting a Realm 10 times. The Realm should be resilient - // towards that. We cannot do much better since we cannot control the order of events internally - // which would be needed to correctly test all error paths. - @Test - @Ignore // See https://github.com/realm/realm-kotlin/issues/851 - fun waitForInitialData_resilientInCaseOfRetries() = runBlocking { - val user = app.asTestApp.createUserAndLogin() - val partitionValue = TestHelper.randomPartitionValue() - val config: SyncConfiguration = SyncConfiguration.Builder(user, partitionValue, PARTITION_BASED_SCHEMA) - .waitForInitialRemoteData(1.nanoseconds) - .build() - - for (i in 0..9) { - assertFalse(fileExists(config.path), "Index: $i, Path: ${config.path}") - Realm.open(config).use { - fail("Index $i") - } - } - } - - // Currently there isn't a good good way to delete synced Realms that has been opened, but - // `Sync.waitForSessionsToTerminate` can be used in some cases. - // - // See https://github.com/realm/realm-core/issues/5542 for more details - @Test - @Suppress("LongMethod") - fun deleteRealm() { - val fileSystem = FileSystem.SYSTEM - val user = app.asTestApp.createUserAndLogin() - val configuration: SyncConfiguration = - SyncConfiguration.create(user, partitionValue, setOf()) - val syncDir: Path = - pathOf(app.configuration.syncRootDirectory, "mongodb-realm", app.configuration.appId, user.id).toPath() - - val bgThreadReadyChannel = TestChannel() - val readyToCloseChannel = TestChannel() - val closedChannel = TestChannel() - - kotlinx.coroutines.runBlocking { - val testRealm = Realm.open(configuration) - - val deferred = async { - // Create another Realm to ensure the log files are generated. - val anotherRealm = Realm.open(configuration) - bgThreadReadyChannel.send(Unit) - readyToCloseChannel.receiveOrFail() - anotherRealm.close() - closedChannel.send(Unit) - } - - // Waits for background thread opening the same Realm. - bgThreadReadyChannel.receiveOrFail() - - // Check the realm got created correctly and signal that it can be closed. - fileSystem.list(syncDir) - .also { testDirPathList -> - assertEquals(4, testDirPathList.size) // db file, .lock, .management, .note - readyToCloseChannel.trySendOrFail(Unit) - } - testRealm.close() - closedChannel.receiveOrFail() - - // Delete realm now that it's fully closed. - app.sync.waitForSessionsToTerminate() - Realm.deleteRealm(configuration) - - // Lock file should never be deleted. - fileSystem.list(syncDir) - .also { testDirPathList -> - assertEquals(1, testDirPathList.size) // only .lock file remains - assertTrue(fileSystem.exists("${configuration.path}.lock".toPath())) - } - - deferred.cancel() - bgThreadReadyChannel.close() - readyToCloseChannel.close() - closedChannel.close() - } - } - - @Test - fun schemaRoundTrip() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - - // Create object with all types - val id = "id-${Random.nextLong()}" - val masterObject = SyncObjectWithAllTypes.createWithSampleData(id) - - createPartitionSyncConfig( - user = user1, - partitionValue = partitionValue, - ).let { config -> - Realm.open(config).use { realm -> - realm.write { - copyToRealm(masterObject) - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } - createPartitionSyncConfig( - user = user2, - partitionValue = partitionValue, - ).let { config -> - Realm.open(config).use { realm -> - val list: RealmResults = - realm.query("_id = $0", id) - .asFlow() - .first { - it.list.size >= 1 - }.list - assertEquals(1, list.size) - assertTrue(SyncObjectWithAllTypes.compareAgainstSampleData(list.first())) - } - } - } - - @Test - fun roundtripCollectionsInMixed() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer).use { app -> - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - - // Create object with all types - val selector = ObjectId().toString() - var parentId: ObjectId? = null - var childId: ObjectId? = null - - createFlexibleSyncConfig( - user = user1, - initialSubscriptions = { realm -> - realm.query("selector = $0", selector).subscribe() - } - ).let { config -> - Realm.open(config).use { realm -> - realm.write { - val child = ( - JsonStyleRealmObject().apply { - this.selector = selector - } - ) - childId = child.id - - parentId = copyToRealm( - JsonStyleRealmObject().apply { - this.selector = selector - value = realmAnyDictionaryOf( - "primitive" to 1, - // List with nested dictionary - "list" to realmAnyListOf( - 1, - "Realm", - child, - realmAnyDictionaryOf( - "listkey1" to 1, - "listkey2" to "Realm", - "listkey3" to child - ) - ), - "dictionary" to realmAnyDictionaryOf( - "dictkey1" to 1, - "dictkey2" to "Realm", - "dictkey3" to child, - "dictkey4" to realmAnyListOf(1, 2, 3) - ) - ) - } - ).id - } - realm.syncSession.uploadAllLocalChangesOrFail() - } - } - createFlexibleSyncConfig( - user = user2, - initialSubscriptions = { realm -> - realm.query("selector = $0", selector).subscribe() - } - ).let { config -> - Realm.open(config).use { realm -> - realm.syncSession.downloadAllServerChanges(10.seconds) - val flow = realm.query("_id = $0", parentId).asFlow() - val parent = withTimeout(10.seconds) { - flow.first { - it.list.size >= 1 - }.list[0] - } - parent.let { - val value = it.value!!.asDictionary() - assertEquals(RealmAny.Companion.create(1), value["primitive"]) - value["list"]!!.asList().let { - assertEquals(1, it[0]!!.asInt()) - assertEquals("Realm", it[1]!!.asString()) - assertEquals(childId, it[2]!!.asRealmObject().id) - it[3]!!.asDictionary().let { dict -> - assertEquals(1, dict["listkey1"]!!.asInt()) - assertEquals("Realm", dict["listkey2"]!!.asString()) - assertEquals( - childId, - dict["listkey3"]!!.asRealmObject().id - ) - } - assertEquals(childId, it[2]!!.asRealmObject().id) - } - value["dictionary"]!!.asDictionary().let { - assertEquals(1, it["dictkey1"]!!.asInt()) - assertEquals("Realm", it["dictkey2"]!!.asString()) - assertEquals( - childId, - it["dictkey3"]!!.asRealmObject().id - ) - it["dictkey4"]!!.asList().let { - assertEquals(realmAnyListOf(1, 2, 3).asList(), it) - } - } - } - } - } - } - } - - @Test - fun collectionsInMixed_asFlow() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer).use { app -> - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - - // Create object with all types - val selector = ObjectId().toString() - - val updateChannel = TestChannel() - - val configWriter = createFlexibleSyncConfig( - user = user1, - initialSubscriptions = { realm -> - realm.query("selector = $0", selector).subscribe() - } - ) - val configReader = createFlexibleSyncConfig( - user = user2, - initialSubscriptions = { realm -> - realm.query("selector = $0", selector).subscribe() - }, - ) { - this.waitForInitialRemoteData(10.seconds) - } - Realm.open(configWriter).use { writerRealm -> - val source = writerRealm.write { - copyToRealm( - JsonStyleRealmObject().apply { - this.selector = selector - value = realmAnyDictionaryOf( - // List with nested dictionary - "list" to realmAnyListOf( - 1, - "Realm", - realmAnyDictionaryOf("listkey1" to 1) - ), - // Dictionary with nested list - "dictionary" to realmAnyDictionaryOf( - "dictkey1" to 1, - "dictkey2" to "Realm", - "dictkey3" to realmAnyListOf(1, 2, 3) - ) - ) - } - ) - } - writerRealm.syncSession.uploadAllLocalChangesOrFail() - - Realm.open(configReader).use { readerRealm -> - val reader = - readerRealm.query("selector = $0", selector).find() - .single() - val listener = async { - reader.asFlow().collect { - updateChannel.trySendOrFail(it.obj!!) - } - } - // Flush initial event from channel - updateChannel.receiveOrFail() - - // List add - writerRealm.write { - findLatest(source)!!.run { - value!!.asDictionary()["list"]!!.asList() - .add(RealmAny.Companion.create(6)) - } - } - updateChannel.receiveOrFail().run { - val updatedList = value!!.asDictionary()["list"]!!.asList() - assertEquals(4, updatedList.size) - assertEquals(1, updatedList[0]!!.asInt()) - assertEquals("Realm", updatedList[1]!!.asString()) - assertIs>(updatedList[2]!!.asDictionary()) - assertEquals(6, updatedList[3]!!.asInt()) - } - - // List removal - writerRealm.write { - findLatest(source)!!.run { - value!!.asDictionary()["list"]!!.asList().removeAt(1) - } - } - updateChannel.receiveOrFail().run { - val updatedList = value!!.asDictionary()["list"]!!.asList() - assertEquals(3, updatedList.size) - assertEquals(1, updatedList[0]!!.asInt()) - assertIs>(updatedList[1]!!.asDictionary()) - assertEquals(6, updatedList[2]!!.asInt()) - } - - // Dictionary add - writerRealm.write { - findLatest(source)!!.run { - value!!.asDictionary()["dictionary"]!!.asDictionary()["dictkey4"] = - RealmAny.Companion.create(6) - } - } - updateChannel.receiveOrFail().run { - val updatedDictionary = - value!!.asDictionary()["dictionary"]!!.asDictionary() - assertEquals(4, updatedDictionary.size) - assertEquals(1, updatedDictionary["dictkey1"]!!.asInt()) - assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString()) - assertIs>(updatedDictionary["dictkey3"]!!.asList()) - assertEquals(6, updatedDictionary["dictkey4"]!!.asInt()) - } - - // Dictionary removal - writerRealm.write { - findLatest(source)!!.run { - value!!.asDictionary()["dictionary"]!!.asDictionary().remove("dictkey3") - } - } - updateChannel.receiveOrFail().run { - val updatedDictionary = - value!!.asDictionary()["dictionary"]!!.asDictionary() - assertEquals(3, updatedDictionary.size) - assertEquals(1, updatedDictionary["dictkey1"]!!.asInt()) - assertEquals("Realm", updatedDictionary["dictkey2"]!!.asString()) - assertEquals(6, updatedDictionary["dictkey4"]!!.asInt()) - } - - listener.cancel() - } - } - } - } - - // After https://github.com/realm/realm-core/pull/5784 was merged, ObjectStore will now - // return the full on-disk schema from ObjectStore, but for typed Realms the user visible schema - // should still only return classes and properties that was defined by the user. - @Test - @Ignore // TODO Need to adopt this to developer mode - fun onlyLocalSchemaIsVisible() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - - createPartitionSyncConfig( - user = user1, - partitionValue = partitionValue, - // schema = setOf(SyncObjectWithAllTypes::class, ChildPk::class) - ).let { config -> - Realm.open(config).use { realm -> - realm.syncSession.uploadAllLocalChangesOrFail() - val schema: RealmSchema = realm.schema() - val childPkSchema: RealmClass? = schema["ChildPk"] - assertNotNull(childPkSchema) - assertNotNull(childPkSchema["name"]) - assertNotNull(childPkSchema["age"]) - assertNotNull(childPkSchema["link"]) - assertNotNull(childPkSchema["linkedFrom"]) - } - } - createPartitionSyncConfig( - user = user2, - partitionValue = partitionValue, - // schema = setOf(io.realm.kotlin.entities.sync.subset.ChildPk::class) - ).let { config -> - Realm.open(config).use { realm -> - // Make sure that server schema changes are integrated - realm.syncSession.downloadAllServerChanges(60.seconds) - val schema: RealmSchema = realm.schema() - assertNull(schema["SyncObjectWithAllTypes"]) - val childPkSchema: RealmClass? = schema["ChildPk"] - assertNotNull(childPkSchema) - assertNotNull(childPkSchema["name"]) - assertFalse((childPkSchema["_id"]!!.type as ValuePropertyType).isIndexed) - assertNull(childPkSchema["age"]) - assertNull(childPkSchema["link"]) - assertNull(childPkSchema["linkedFrom"]) - } - } - } - - @Suppress("LongMethod") - @Test - fun mutableRealmInt_convergesAcrossClients() = runBlocking { - // Updates and initial data upload are carried out using this config - val config0 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db1", - ) - - // Config for update 1 - val config1 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db2", - ) - - // Config for update 2 - val config2 = createPartitionSyncConfig( - user = app.createUserAndLogIn(randomEmail(), "password1234"), - partitionValue = partitionValue, - name = "db3", - ) - - val counterValue = TestChannel() - - // Asynchronously receive updates - val updates = async { - Realm.open(config0).use { realm -> - realm.query() - .first() - .asFlow() - .collect { - if (it.obj != null) { - val counter = it.obj!!.mutableRealmIntField - counterValue.send(counter.get()) - } - } - } - } - - // Upload initial data - blocking to ensure the two clients find an object to update - val masterObject = SyncObjectWithAllTypes().apply { _id = "id-${Random.nextLong()}" } - Realm.open(config0).use { realm -> - realm.writeBlocking { copyToRealm(masterObject) } - realm.syncSession.uploadAllLocalChangesOrFail() - } - assertEquals(42, counterValue.receiveOrFail(message = "Failed to receive 42")) - - // Increment counter asynchronously after download initial data (1) - val increment1 = async { - Realm.open(config1).use { realm -> - realm.syncSession.downloadAllServerChanges(30.seconds) - realm.write { - realm.query() - .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) - } - realm.syncSession.uploadAllLocalChanges(10.seconds) - } - } - assertEquals(43, counterValue.receiveOrFail(message = "Failed to receive 43")) - - // Increment counter asynchronously after download initial data (2) - val increment2 = async { - Realm.open(config2).use { realm -> - realm.syncSession.downloadAllServerChanges(30.seconds) - realm.write { - realm.query() - .first() - .find() - .let { assertNotNull(findLatest(assertNotNull(it))) } - .mutableRealmIntField - .increment(1) - } - realm.syncSession.uploadAllLocalChanges(10.seconds) - } - } - assertEquals(44, counterValue.receiveOrFail(message = "Failed to receive 44")) - - increment1.cancel() - increment2.cancel() - updates.cancel() - } - - private fun createWriteCopyLocalConfig( - name: String, - directory: String = PlatformUtils.createTempDir(), - encryptionKey: ByteArray? = null, - schemaVersion: Long = 0 - ): RealmConfiguration { - val builder = RealmConfiguration.Builder( - schema = setOf( - SyncObjectWithAllTypes::class, - FlexParentObject::class, - FlexChildObject::class, - FlexEmbeddedObject::class - ) - ).directory(directory) - .schemaVersion(schemaVersion) - .name(name) - if (encryptionKey != null) { - builder.encryptionKey(encryptionKey) - } - return builder.build() - } - - @Test - fun writeCopyTo_localToPartitionBasedSync() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - val localConfig = createWriteCopyLocalConfig("local.realm") - val partitionValue = TestHelper.randomPartitionValue() - val syncConfig1 = createPartitionSyncConfig( - user = user1, - name = "sync1.realm", - partitionValue = partitionValue, - ) - val syncConfig2 = createPartitionSyncConfig( - user = user2, - name = "sync2.realm", - partitionValue = partitionValue, - ) - Realm.open(localConfig).use { localRealm -> - localRealm.writeBlocking { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = "local object" - } - ) - } - // Copy to partition-based Realm - localRealm.writeCopyTo(syncConfig1) - } - // Open Sync Realm and ensure that data can be used and uploaded - Realm.open(syncConfig1).use { syncRealm1: Realm -> - assertEquals(1, syncRealm1.query().count().find()) - assertEquals( - "local object", - syncRealm1.query().first().find()!!.stringField - ) - syncRealm1.writeBlocking { - query().first().find()!!.apply { - stringField = "updated local object" - } - } - syncRealm1.syncSession.uploadAllLocalChanges(30.seconds) - } - // Check that uploaded data can be used - Realm.open(syncConfig2).use { syncRealm2: Realm -> - val obj = syncRealm2.query().asFlow() - .first { it.list.size == 1 } - .list - .first() - assertEquals("updated local object", obj.stringField) - } - } - - @Test - fun writeCopyTo_localToFlexibleSync_throws() = runBlocking { - TestApp( - this::class.simpleName, - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val (email1, password1) = randomEmail() to "password1234" - val user1 = flexApp.createUserAndLogIn(email1, password1) - val localConfig = createWriteCopyLocalConfig("local.realm") - val flexSyncConfig = createFlexibleSyncConfig( - user = user1 - ) - Realm.open(localConfig).use { localRealm -> - localRealm.writeBlocking { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = "local object" - } - ) - } - assertFailsWith { - localRealm.writeCopyTo(flexSyncConfig) - } - } - } - } - - @Test - fun writeCopyTo_partitionBasedToLocal() = runBlocking { - val (email, password) = randomEmail() to "password1234" - val user = app.createUserAndLogIn(email, password) - val dir = PlatformUtils.createTempDir() - val localConfig = createWriteCopyLocalConfig("local.realm", directory = dir) - val migratedLocalConfig = - createWriteCopyLocalConfig("local.realm", directory = dir, schemaVersion = 1) - val partitionValue = TestHelper.randomPartitionValue() - val syncConfig = createPartitionSyncConfig( - user = user, - name = "sync1.realm", - partitionValue = partitionValue, - ) - Realm.open(syncConfig).use { syncRealm -> - // Write local data - syncRealm.writeBlocking { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = "local object" - } - ) - } - - // Ensure that we have have synchronized the server schema, including the - // partition field. - syncRealm.syncSession.uploadAllLocalChanges(30.seconds) - syncRealm.syncSession.downloadAllServerChanges(30.seconds) - - // Copy to partition-based Realm - syncRealm.writeCopyTo(localConfig) - } - - // Opening the local Realm with the same schema will throw a schema mismatch, because - // the server schema contains classes and fields not in the local schema. - assertFailsWith { - Realm.open(localConfig) - } - - // Opening with a migration should work fine - Realm.open(migratedLocalConfig).use { localRealm: Realm -> - assertEquals(1, localRealm.query().count().find()) - assertEquals( - "local object", - localRealm.query().first().find()!!.stringField - ) - } - } - - @Test - fun writeCopyTo_flexibleSyncToLocal() = runBlocking { - TestApp( - "writeCopyTo_flexibleSyncToLocal", - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val (email1, password1) = randomEmail() to "password1234" - val user = flexApp.createUserAndLogIn(email1, password1) - val localConfig = createWriteCopyLocalConfig("local.realm") - val syncConfig = createFlexibleSyncConfig( - user = user, - name = "sync.realm", - initialSubscriptions = { - it.query().subscribe(name = "parentSubscription") - } - ) - Realm.open(syncConfig).use { flexSyncRealm: Realm -> - flexSyncRealm.writeBlocking { - copyToRealm( - FlexParentObject().apply { - name = "local object" - } - ) - } - - // Copy to local Realm - flexSyncRealm.writeCopyTo(localConfig) - } - // Open Local Realm and check that data can read. - Realm.open(localConfig).use { localRealm: Realm -> - assertEquals(1, localRealm.query().count().find()) - assertEquals("local object", localRealm.query().first().find()!!.name) - } - } - } - - @Test - fun writeCopyTo_partitionBasedToDifferentPartitionKey() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - val syncConfig1 = createPartitionSyncConfig( - user = user1, - name = "sync1.realm", - partitionValue = TestHelper.randomPartitionValue(), - ) - val syncConfig2 = createPartitionSyncConfig( - user = user2, - name = "sync2.realm", - partitionValue = TestHelper.randomPartitionValue(), - ) - Realm.open(syncConfig1).use { syncRealm1 -> - syncRealm1.writeBlocking { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = "local object" - } - ) - } - // Copy to partition-based Realm - syncRealm1.syncSession.uploadAllLocalChanges(30.seconds) - // Work-around for https://github.com/realm/realm-core/issues/4865 - // Calling syncRealm1.syncSession.downloadAllServerChanges doesn't seem to - // fix it in all cases - delay(1000) - syncRealm1.writeCopyTo(syncConfig2) - } - // Open Sync Realm and ensure that data can be used and uploaded - Realm.open(syncConfig2).use { syncRealm2: Realm -> - val result = syncRealm2.query().find() - assertEquals(1, result.size) - assertEquals("local object", result.first().stringField) - syncRealm2.syncSession.uploadAllLocalChanges(30.seconds) - } - } - - @Test - fun writeCopyTo_partitionBasedToSamePartitionKey() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val user2 = app.createUserAndLogIn(email2, password2) - val partitionValue = TestHelper.randomPartitionValue() - val syncConfig1 = createPartitionSyncConfig( - user = user1, - name = "sync1.realm", - partitionValue = partitionValue, - ) - val syncConfig2 = createPartitionSyncConfig( - user = user2, - name = "sync2.realm", - partitionValue = partitionValue, - ) - Realm.open(syncConfig1).use { syncRealm1 -> - // Write local data - syncRealm1.writeBlocking { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = "local object" - } - ) - } - // Copy to partition-based Realm - syncRealm1.syncSession.uploadAllLocalChanges(30.seconds) - // Work-around for https://github.com/realm/realm-core/issues/4865 - // Calling syncRealm1.syncSession.downloadAllServerChanges doesn't seem to - // fix it in all cases - delay(1000) - syncRealm1.writeCopyTo(syncConfig2) - } - // Open Sync Realm and ensure that data can be used and uploaded - Realm.open(syncConfig2).use { syncRealm2: Realm -> - val result = syncRealm2.query().find() - assertEquals(1, result.size) - assertEquals("local object", result.first().stringField) - syncRealm2.syncSession.uploadAllLocalChanges(30.seconds) - } - } - - @Test - fun writeCopyTo_flexibleSyncToFlexibleSync() = runBlocking { - TestApp( - "writeCopyTo_flexibleSyncToFlexibleSync", - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val section = Random.nextInt() - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = flexApp.createUserAndLogIn(email1, password1) - val user2 = flexApp.createUserAndLogIn(email2, password2) - val syncConfig1 = createFlexibleSyncConfig( - user = user1, - name = "sync1.realm", - errorHandler = { _, error -> - fail(error.toString()) - }, - initialSubscriptions = { realm: Realm -> - realm.query("section = $0", section).subscribe(name = "parentSubscription") - } - ) - val syncConfig2 = createFlexibleSyncConfig( - user = user2, - name = "sync2.realm", - errorHandler = { _, error -> - fail(error.toString()) - } - ) - - Realm.open(syncConfig1).use { flexRealm1: Realm -> - // It is not possible to use `writeCopyTo` if data is written to the Realm before - // the SubscriptionSet is `COMPLETE`. Work around the issue for now. - flexRealm1.subscriptions.waitForSynchronization(30.seconds) - flexRealm1.write { - copyToRealm( - FlexParentObject(section).apply { - name = "User1Object" - } - ) - } - flexRealm1.syncSession.uploadAllLocalChanges(30.seconds) - assertEquals(SubscriptionSetState.COMPLETE, flexRealm1.subscriptions.state) - // Copy to another flex RealmRealm - flexRealm1.writeCopyTo(syncConfig2) - assertTrue(fileExists(syncConfig2.path)) - - // Open the copied Realm and verify we can read and write data - Realm.open(syncConfig2).use { flexRealm2: Realm -> - // Subscriptions are copied - assertEquals(1, flexRealm2.subscriptions.size) - assertEquals("parentSubscription", flexRealm2.subscriptions.first().name) - assertEquals(SubscriptionSetState.COMPLETE, flexRealm2.subscriptions.state) - - // As is data - assertEquals(1, flexRealm2.query().count().find()) - assertEquals("User1Object", flexRealm2.query().first().find()!!.name) - - flexRealm2.subscriptions.waitForSynchronization(30.seconds) - flexRealm2.write { - copyToRealm( - FlexParentObject(section).apply { - name = "User2Object" - } - ) - } - flexRealm2.syncSession.uploadAllLocalChanges(30.seconds) - assertEquals(2, flexRealm2.query().count().find()) - } - } - } - } - - @Test - fun writeCopyTo_dataNotUploaded_throws() = runBlocking { - val (email1, password1) = randomEmail() to "password1234" - val user1 = app.createUserAndLogIn(email1, password1) - val syncConfigA = createPartitionSyncConfig( - user = user1, - name = "a.realm", - partitionValue = TestHelper.randomPartitionValue(), - ) - val syncConfigB = createPartitionSyncConfig( - user = user1, - name = "b.realm", - partitionValue = TestHelper.randomPartitionValue(), - ) - Realm.open(syncConfigA).use { realm -> - realm.syncSession.pause() - realm.writeBlocking { - copyToRealm(SyncObjectWithAllTypes()) - } - assertFailsWith { - realm.writeCopyTo(syncConfigB) - } - } - } - - // Test for https://github.com/realm/realm-kotlin/issues/1068 - // Note, this test is not 100% sure to surface the bug, but manual testing has shown that it - // works well enough. Also, even if it doesn't surface the bug, it will not the fail the test. - @Test - fun accessSessionAfterRemoteChange() = runBlocking { - TestApp( - "accessSessionAfterRemoteChange", - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val section = Random.nextInt() - val (email1, password1) = randomEmail() to "password1234" - val (email2, password2) = randomEmail() to "password1234" - val user1 = flexApp.createUserAndLogIn(email1, password1) - val user2 = flexApp.createUserAndLogIn(email2, password2) - val syncConfig1 = createFlexibleSyncConfig( - user = user1, - name = "sync1.realm", - initialSubscriptions = { realm: Realm -> - realm.query("section = $0", section).subscribe() - } - ) - val syncConfig2 = createFlexibleSyncConfig( - user = user2, - name = "sync2.realm", - initialSubscriptions = { realm: Realm -> - realm.query("section = $0", section).subscribe() - } - ) - val realm1 = Realm.open(syncConfig1) - - Realm.open(syncConfig2).use { realm2 -> - realm2.write { - copyToRealm(FlexParentObject(section)) - } - realm2.syncSession.uploadAllLocalChangesOrFail() - } - - // Reading the object means we received it from the other Realm - withTimeout(1.minutes) { - val obj: FlexParentObject = realm1.query("section = $0", section).asFlow() - .map { it.list } - .filter { it.isNotEmpty() } - .first().first() - assertEquals(section, obj.section) - - // 1. Local write to work around https://github.com/realm/realm-kotlin/issues/1070 - realm1.write { } - - // 2. Trigger GC. This will GC the RealmReference JVM object, making the native reference - // eligible for closing. - PlatformUtils.triggerGC() - - // 3. On the next update of Realm, we run through the weak list of all previous - // RealmReferences and close all native pointers with their JVM object GC'ed. - // This should now include the object created in step 1. - realm1.write { } - } - - // 4. With the original native dbPointer now being closed, accessing the syncSession for - // the first time should still work. - try { - realm1.syncSession.pause() - assertEquals(SyncSession.State.PAUSED, realm1.syncSession.state) - } finally { - realm1.close() - } - } - } - - // This test verifies that the user facing Realm instance is actually advanced on an on-needed - // basis even though there is no actual listener or explicit await download/upload calls. - @Test - fun advanceRealmWithoutListening() { - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - partitionValue = TestHelper.randomPartitionValue() - - val config1 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db1") - val config2 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db2") - - Realm.open(config1).use { realm1 -> - Realm.open(config2).use { realm2 -> - runBlocking { - async { - realm1.write { - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - } - realm1.syncSession.uploadAllLocalChanges(30.seconds) - } - withTimeout(30.seconds) { - while (realm2.query().find().size < 3) { - delay(1.seconds) - } - } - } - } - } - } - - @Test - @Ignore // Test to generate a realm file to use in assetFile_partitionBasedSync. Copy the - // generated file to - // - test-sync/src/androidMain/assets/asset-pbs.realm - // - test-sync/src/jvmTest/resources/asset-pbs.realm - // - test-sync/src/iosTest/resources/asset-pbs.realm - // - test-sync/src/macosTest/resources/asset-pbs.realm - fun createInitialRealmPbs() { - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - val config1 = RealmConfiguration.Builder(setOf(ParentPk::class, ChildPk::class)) - .directory(PlatformUtils.createTempDir()) - .initialData { - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - } - .build() - val config2 = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "db1") - Realm.open(config1).use { - assertEquals(2, it.query().find().size) - it.writeCopyTo(config2) - assertNotNull(it) - } - // Debug this test, breakpoint here and grab the bundled realm from the location - println("Partition based sync bundled realm is in ${config2.path}") - } - - @Test - fun initialRealm_partitionBasedSync() { - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - runBlocking { - app.asTestApp.deleteDocuments(app.configuration.appId, "ParentPk", "{}") - } - - val config1 = createPartitionSyncConfig( - user = user, partitionValue = partitionValue, name = "db1", - errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - fail("Realm 1: $error") - } - }, - ) { - initialRealmFile("asset-pbs.realm") - initialData { - assertEquals(2, query().find().size) - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - copyToRealm(ParentPk().apply { _id = ObjectId().toString() }) - assertEquals(4, query().find().size) - } - } - Realm.open(config1).use { realm1 -> - runBlocking { - assertEquals(4, realm1.query().find().size) - realm1.syncSession.uploadAllLocalChanges(30.seconds) - } - } - - val initialDataVerified = atomic(false) - val config2 = createPartitionSyncConfig( - user = user, partitionValue = partitionValue, name = "db2", - errorHandler = object : SyncSession.ErrorHandler { - override fun onError(session: SyncSession, error: SyncException) { - fail("Realm 2: $error") - } - } - ) { - waitForInitialRemoteData(30.seconds) - initialData { - // Verify that initial data is running after data is synced - assertEquals(4, query().find().size) - initialDataVerified.value = true - } - } - Realm.open(config2).use { } - assertTrue { initialDataVerified.value } - } - - @Test - @Ignore // Test to generate a realm file to use in assetFile_partitionBasedSync. Copy the - // generated file to - // - test-sync/src/androidMain/assets/asset-fs.realm - // - test-sync/src/jvmTest/resources/asset-fs.realm - // - test-sync/src/iosTest/resources/asset-fs.realm - // - test-sync/src/macosTest/resources/asset-fs.realm - fun createInitialRealmFx() = runBlocking { - TestApp( - "createInitialRealmFx", - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val section = Random.nextInt() - val (email1, password1) = randomEmail() to "password1234" - val user1 = flexApp.createUserAndLogIn(email1, password1) - val syncConfig1 = createFlexibleSyncConfig( - user = user1, - name = "sync1.realm", - errorHandler = { _, error -> - fail(error.toString()) - }, - initialSubscriptions = { realm: Realm -> - realm.query() - .subscribe(name = "parentSubscription") - } - ) - val syncConfig2 = createFlexibleSyncConfig( - user = user1, - name = "asset-fs.realm", - errorHandler = { _, error -> - fail(error.toString()) - }, - ) - - Realm.open(syncConfig1).use { flexRealm1: Realm -> - // It is not possible to use `writeCopyTo` if data is written to the Realm before - // the SubscriptionSet is `COMPLETE`. Work around the issue for now. - flexRealm1.subscriptions.waitForSynchronization(30.seconds) - flexRealm1.write { - copyToRealm( - FlexParentObject(section).apply { - name = "User1Object" - } - ) - } - flexRealm1.syncSession.uploadAllLocalChanges(30.seconds) - assertEquals(SubscriptionSetState.COMPLETE, flexRealm1.subscriptions.state) - // Copy to another flex RealmRealm - flexRealm1.writeCopyTo(syncConfig2) - assertTrue(fileExists(syncConfig2.path)) - // Debug this test, breakpoint here and grab the bundled realm from the location - println("Flexible sync bundled realm is in ${syncConfig2.path}") - } - } - } - - // Sanity check that we can in fact open a flexible sync realm file as initial file - @Test - fun initialRealm_flexibleSync() = runBlocking { - @OptIn(ExperimentalKBsonSerializerApi::class) - TestApp( - "initialRealm_flexibleSync", - DefaultFlexibleSyncAppInitializer, - builder = { - it.syncRootDirectory(PlatformUtils.createTempDir("flx-sync-")) - } - ).use { flexApp -> - val (email1, password1) = randomEmail() to "password1234" - val user1 = flexApp.createUserAndLogIn(email1, password1) - val syncConfig1 = createFlexibleSyncConfig( - user = user1, - name = "sync1.realm", - errorHandler = { _, error -> - fail(error.toString()) - }, - ) { - initialRealmFile("asset-fs.realm") - initialData { - assertEquals(1, query().find().size) - } - } - - Realm.open(syncConfig1).use { flexRealm1: Realm -> - assertEquals(1, flexRealm1.subscriptions.size) - assertNotNull(flexRealm1.subscriptions.findByName("parentSubscription")) - } - } - } - - @Test - fun partitionBasedSyncConfig_throwsWithLocalInitialRealmFile() { - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - val local = createPartitionSyncConfig(user = user, partitionValue = partitionValue, name = "local") { - initialRealmFile("asset-local.realm") - } - assertFalse(fileExists(local.path)) - assertFailsWithMessage("has history type 'Local in-Realm'") { - Realm.open(local) - } - } - - @Test - fun flexibleSync_throwsWithLocalInitialRealmFile() { - - val (email, password) = randomEmail() to "password1234" - val user = runBlocking { - app.createUserAndLogIn(email, password) - } - - val local = createFlexibleSyncConfig(user = user, name = "local") { - initialRealmFile("asset-local.realm") - } - assertFalse(fileExists(local.path)) - assertFailsWithMessage("has history type 'Local in-Realm'") { - Realm.open(local) - } - } - -// @Test -// fun initialVersion() { -// assertEquals(INITIAL_VERSION, realm.version()) -// } -// -// @Test -// fun versionIncreaseOnWrite() { -// assertEquals(INITIAL_VERSION, realm.version()) -// realm.writeBlocking { /* Do Nothing */ } -// assertEquals(VersionId(3), realm.version()) -// } -// -// @Test -// fun versionDoesNotChangeWhenCancellingWrite() { -// assertEquals(INITIAL_VERSION, realm.version()) -// realm.writeBlocking { cancelWrite() } -// assertEquals(INITIAL_VERSION, realm.version()) -// } -// -// @Test -// fun versionThrowsIfRealmIsClosed() { -// realm.close() -// assertFailsWith { realm.version() } -// } -// -// @Test -// fun versionInsideWriteIsLatest() { -// assertEquals(INITIAL_VERSION, realm.version()) -// realm.writeBlocking { -// assertEquals(INITIAL_VERSION, version()) -// cancelWrite() -// } -// assertEquals(INITIAL_VERSION, realm.version()) -// } -// -// @Test -// fun numberOfActiveVersions() { -// assertEquals(2, realm.getNumberOfActiveVersions()) -// realm.writeBlocking { -// assertEquals(2, getNumberOfActiveVersions()) -// } -// assertEquals(2, realm.getNumberOfActiveVersions()) -// } -// -// @Test -// @Ignore // FIXME This fails on MacOS only. Are versions cleaned up more aggressively there? -// fun throwsIfMaxNumberOfActiveVersionsAreExceeded() { -// realm.close() -// val config = RealmConfiguration.Builder( -// path = "$tmpDir/exceed-versions.realm", -// schema = setOf(Parent::class, Child::class) -// ).maxNumberOfActiveVersions(1).build() -// realm = Realm.open(config) -// // Pin the version, so when starting a new transaction on the first Realm, -// // we don't release older versions. -// val otherRealm = Realm.open(config) -// -// try { -// assertFailsWith { realm.writeBlocking { } } -// } finally { -// otherRealm.close() -// } -// } -// -// @Suppress("invisible_member") -// @Test -// fun write() = runBlocking { -// val name = "Realm" -// val child: Child = realm.write { -// this.copyToRealm(Child()).apply { this.name = name } -// } -// assertEquals(name, child.name) -// val objects = realm.objects() -// val childFromResult = objects[0] -// assertEquals(name, childFromResult.name) -// } -// -// @Suppress("invisible_member") -// @Test -// fun exceptionInWriteWillRollback() = runBlocking { -// class CustomException : Exception() -// -// assertFailsWith { -// realm.write { -// val name = "Realm" -// this.copyToRealm(Child()).apply { this.name = name } -// throw CustomException() -// } -// } -// assertEquals(0, realm.objects().size) -// } -// -// @Test -// fun writeBlocking() { -// val managedChild = realm.writeBlocking { copyToRealm(Child().apply { name = "John" }) } -// assertTrue(managedChild.isManaged()) -// assertEquals("John", managedChild.name) -// } -// -// @Suppress("invisible_member") -// @Test -// fun writeBlockingAfterWrite() = runBlocking { -// val name = "Realm" -// val child: Child = realm.write { -// this.copyToRealm(Child()).apply { this.name = name } -// } -// assertEquals(name, child.name) -// assertEquals(1, realm.objects().size) -// -// realm.writeBlocking { -// this.copyToRealm(Child()).apply { this.name = name } -// } -// Unit -// } -// -// @Suppress("invisible_member") -// @Test -// fun exceptionInWriteBlockingWillRollback() { -// class CustomException : Exception() -// assertFailsWith { -// realm.writeBlocking { -// val name = "Realm" -// this.copyToRealm(Child()).apply { this.name = name } -// throw CustomException() -// } -// } -// assertEquals(0, realm.objects().size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun simultaneousWritesAreAllExecuted() = runBlocking { -// val jobs: List = IntRange(0, 9).map { -// launch { -// realm.write { -// copyToRealm(Parent()) -// } -// } -// } -// jobs.map { it.join() } -// -// // Ensure that all writes are actually committed -// realm.close() -// assertTrue(realm.isClosed()) -// realm = Realm.open(configuration) -// assertEquals(10, realm.objects(Parent::class).size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun writeBlockingWhileWritingIsSerialized() = runBlocking { -// val writeStarted = Mutex(true) -// val writeEnding = Mutex(true) -// val writeBlockingQueued = Mutex(true) -// async { -// realm.write { -// writeStarted.unlock() -// while (writeBlockingQueued.isLocked) { -// PlatformUtils.sleep(1.milliseconds) -// } -// writeEnding.unlock() -// } -// } -// writeStarted.lock() -// runBlocking { -// val async = async { -// realm.writeBlocking { -// assertFalse { writeEnding.isLocked } -// } -// } -// writeBlockingQueued.unlock() -// async.await() -// } -// } -// -// @Test -// @Suppress("invisible_member") -// fun close() = runBlocking { -// realm.write { -// copyToRealm(Parent()) -// } -// realm.close() -// assertTrue(realm.isClosed()) -// -// realm = Realm.open(configuration) -// assertEquals(1, realm.objects(Parent::class).size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun closeCausesOngoingWriteToThrow() = runBlocking { -// val writeStarted = Mutex(true) -// val write = async { -// assertFailsWith { -// realm.write { -// writeStarted.unlock() -// copyToRealm(Parent()) -// // realm.close is blocking until write block is done, so we cannot wait on -// // specific external events, so just sleep a bit :/ -// PlatformUtils.sleep(Duration.Companion.milliseconds(100)) -// } -// } -// } -// writeStarted.lock() -// realm.close() -// assert(write.await() is RuntimeException) -// realm = Realm.open(configuration) -// assertEquals(0, realm.objects().size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun writeAfterCloseThrows() = runBlocking { -// realm.close() -// assertTrue(realm.isClosed()) -// assertFailsWith { -// realm.write { -// copyToRealm(Child()) -// } -// } -// Unit -// } -// -// @Test -// @Suppress("invisible_member") -// fun coroutineCancelCausesRollback() = runBlocking { -// val mutex = Mutex(true) -// val job = async { -// realm.write { -// copyToRealm(Parent()) -// mutex.unlock() -// // Ensure that we keep on going until actually cancelled -// while (isActive) { -// PlatformUtils.sleep(1.milliseconds) -// } -// } -// } -// mutex.lock() -// job.cancelAndJoin() -// -// // Ensure that write is not committed -// realm.close() -// assertTrue(realm.isClosed()) -// realm = Realm.open(configuration) -// // This assertion doesn't hold on MacOS as all code executes on the same thread as the -// // dispatcher is a run loop on the local thread, thus, the main flow is not picked up when -// // the mutex is unlocked. Doing so would require the write block to be able to suspend in -// // some way (or the writer to be backed by another thread). -// assertEquals(0, realm.objects(Parent::class).size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun writeAfterCoroutineCancel() = runBlocking { -// val mutex = Mutex(true) -// val job = async { -// realm.write { -// copyToRealm(Parent()) -// mutex.unlock() -// // Ensure that we keep on going until actually cancelled -// while (isActive) { -// PlatformUtils.sleep(1.milliseconds) -// } -// } -// } -// -// mutex.lock() -// job.cancelAndJoin() -// -// // Verify that we can do other writes after cancel -// realm.write { -// copyToRealm(Parent()) -// } -// -// // Ensure that only one write is actually committed -// realm.close() -// assertTrue(realm.isClosed()) -// realm = Realm.open(configuration) -// // This assertion doesn't hold on MacOS as all code executes on the same thread as the -// // dispatcher is a run loop on the local thread, thus, the main flow is not picked up when -// // the mutex is unlocked. Doing so would require the write block to be able to suspend in -// // some way (or the writer to be backed by another thread). -// assertEquals(1, realm.objects(Parent::class).size) -// } -// -// @Test -// @Suppress("invisible_member") -// fun writesOnFrozenRealm() { -// val dispatcher = newSingleThreadContext("background") -// runBlocking { -// realm.write { -// copyToRealm(Parent()) -// } -// } -// runBlocking(dispatcher) { -// realm.write { -// copyToRealm(Parent()) -// } -// } -// assertEquals(2, realm.objects().size) -// } -// -// @Test -// fun closeClosesAllVersions() { -// runBlocking { -// realm.write { copyToRealm(Parent()) } -// } -// val parent: Parent = realm.objects().first() -// runBlocking { -// realm.write { copyToRealm(Parent()) } -// } -// realm.close() -// assertFailsWith { -// parent.version() -// } -// } -// -// @Test -// fun closingIntermediateVersionsWhenNoLongerReferenced() { -// assertEquals(0, intermediateReferences.value.size) -// var parent: Parent? = realm.writeBlocking { copyToRealm(Parent()) } -// assertEquals(1, intermediateReferences.value.size) -// realm.writeBlocking { } -// assertEquals(2, intermediateReferences.value.size) -// -// // Clear reference -// parent = null -// // Trigger GC -// triggerGC() -// // Close of intermediate version is currently only done when updating the realm after a write -// realm.writeBlocking { } -// assertEquals(1, intermediateReferences.value.size) -// } -// -// @Suppress("invisible_reference") -// private val intermediateReferences: AtomicRef>>> -// get() { -// @Suppress("invisible_member") -// return (realm as io.realm.kotlin.internal.RealmImpl).intermediateReferences -// } - - @Suppress("LongParameterList") - private fun createPartitionSyncConfig( - user: User, - partitionValue: String, - name: String = DEFAULT_NAME, - encryptionKey: ByteArray? = null, - errorHandler: ErrorHandler? = null, - schema: Set> = PARTITION_BASED_SCHEMA, - block: SyncConfiguration.Builder.() -> Unit = {} - ): SyncConfiguration = SyncConfiguration.Builder( - schema = schema, - user = user, - partitionValue = partitionValue - ).name(name).also { builder -> - if (encryptionKey != null) builder.encryptionKey(encryptionKey) - if (errorHandler != null) builder.errorHandler(errorHandler) - block(builder) - }.build() - - @Suppress("LongParameterList") - private fun createFlexibleSyncConfig( - user: User, - name: String = DEFAULT_NAME, - encryptionKey: ByteArray? = null, - errorHandler: ErrorHandler? = null, - schema: Set> = FLEXIBLE_SYNC_SCHEMA, - initialSubscriptions: InitialSubscriptionsCallback? = null, - block: SyncConfiguration.Builder.() -> Unit = {}, - ): SyncConfiguration = SyncConfiguration.Builder( - user = user, - schema = schema - ).name(name).also { builder -> - if (encryptionKey != null) builder.encryptionKey(encryptionKey) - if (errorHandler != null) builder.errorHandler(errorHandler) - if (initialSubscriptions != null) builder.initialSubscriptions(false, initialSubscriptions) - block(builder) - }.build() -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt deleted file mode 100644 index 5c5593f9c6..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserProfileTests.kt +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -@file:OptIn(ExperimentalKBsonSerializerApi::class, ExperimentalRealmSerializerApi::class) - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.internal.interop.sync.NetworkTransport -import io.realm.kotlin.internal.interop.sync.Response -import io.realm.kotlin.internal.interop.sync.ResponseCallback -import io.realm.kotlin.mongodb.ext.profile -import io.realm.kotlin.mongodb.ext.profileAsBsonDocument -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.mongodb.kbson.BsonType -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContains -import kotlin.test.assertEquals -import kotlin.test.fail - -@Serializable -data class UserProfile( - val name: String, - val email: String, - @SerialName("picture_url") val pictureUrl: String, - @SerialName("first_name") val firstName: String, - @SerialName("last_name") val lastName: String, - val gender: String, - val birthday: String, - @SerialName("min_age") val minAge: Long, - @SerialName("max_age") val maxAge: Long, -) - -class UserProfileTests { - companion object { - const val ACCESS_TOKEN = - """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODM5NjcyMDgsImlhdCI6MTU4Mzk2NTQwOCwiaXNzIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWEzIiwic3RpdGNoX2RldklkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIiwic3RpdGNoX2RvbWFpbklkIjoiNWU2OTYzZGVhZmVhNjMyNTQ1ODFjMDI1Iiwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoiYWNjZXNzIn0.J4mp8LnlsxTQRV_7W2Er4qY0tptR76PJGG1k6HSMmUYqgfpJC2Fnbcf1VCoebzoNolH2-sr8AHDVBBCyjxRjqoY9OudFHmWZKmhDV1ysxPP4XmID0nUuN45qJSO8QEAqoOmP1crXjrUZWedFw8aaCZE-bxYfvcDHyjBcbNKZqzawwUw2PyTOlrNjgs01k2J4o5a5XzYkEsJuzr4_8UqKW6zXvYj24UtqnqoYatW5EzpX63m2qig8AcBwPK4ZHb5wEEUdf4QZxkRY5QmTgRHP8SSqVUB_mkHgKaizC_tSB3E0BekaDfLyWVC1taAstXJNfzgFtLI86AzuXS2dCiCfqQ""" - const val REFRESH_TOKEN = - """eyJhbGciOiJSUzI1NiIsImtpZCI6IjVlNjk2M2RmYWZlYTYzMjU0NTgxYzAyNiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODkxNDk0MDgsImlhdCI6MTU4Mzk2NTQwOCwic3RpdGNoX2RhdGEiOm51bGwsInN0aXRjaF9kZXZJZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCIsInN0aXRjaF9kb21haW5JZCI6IjVlNjk2M2RlYWZlYTYzMjU0NTgxYzAyNSIsInN0aXRjaF9pZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMyIsInN0aXRjaF9pZGVudCI6eyJpZCI6IjVlNjk2NGUwYWZlYTYzMjU0NTgxYzFhMC1oaWF2b3ZkbmJxbGNsYXBwYnl1cmJpaW8iLCJwcm92aWRlcl90eXBlIjoiYW5vbi11c2VyIiwicHJvdmlkZXJfaWQiOiI1ZTY5NjNlMGFmZWE2MzI1NDU4MWMwNGEifSwic3ViIjoiNWU2OTY0ZTBhZmVhNjMyNTQ1ODFjMWExIiwidHlwIjoicmVmcmVzaCJ9.FhLdpmL48Mw0SyUKWuaplz3wfeS8TCO8S7I9pIJenQww9nPqQ7lIvykQxjCCtinGvsZIJKt_7R31xYCq4Jp53Nw81By79IwkXtO7VXHPsXXZG5_2xV-s0u44e85sYD5su_H-xnx03sU2piJbWJLSB8dKu3rMD4mO-S0HNXCCAty-JkYKSaM2-d_nS8MNb6k7Vfm7y69iz_uwHc-bb_1rPg7r827K6DEeEMF41Hy3Nx1kCdAUOM9-6nYv3pZSU1PFrGYi2uyTXPJ7R7HigY5IGHWd0hwONb_NUr4An2omqfvlkLEd77ut4V9m6mExFkoKzRz7shzn-IGkh3e4h7ECGA""" - const val USER_ID = "5e6964e0afea63254581c1a1" - const val DEVICE_ID = "000000000000000000000000" - - val userProfile = UserProfile( - name = "NAME", - email = "unique_user@domain.com", - pictureUrl = "PICTURE_URL", - firstName = "FIRST_NAME", - lastName = "LAST_NAME", - gender = "GENDER", - birthday = "BIRTHDAY", - minAge = 1L, - maxAge = 99L, - ) - } - - private lateinit var app: TestApp - lateinit var profileBody: Map - - private fun setDefaultProfile() { - profileBody = mapOf( - "name" to userProfile.name, - "email" to userProfile.email, - "picture_url" to userProfile.pictureUrl, - "first_name" to userProfile.firstName, - "last_name" to userProfile.lastName, - "gender" to userProfile.gender, - "birthday" to userProfile.birthday, - "min_age" to userProfile.minAge, - "max_age" to userProfile.maxAge - ) - } - - private fun setEmptyProfile() { - profileBody = mapOf() - } - - @BeforeTest - fun setUp() { - app = TestApp( - this::class.simpleName, - DefaultPartitionBasedAppInitializer, - networkTransport = object : NetworkTransport { - override val authorizationHeaderName: String? - get() = "" - override val customHeaders: Map - get() = mapOf() - - override fun sendRequest( - method: String, - url: String, - headers: Map, - body: String, - callback: ResponseCallback - ) { - val profileData = profileBody.map { (k, v) -> - val value = when (v) { - is String -> Json.encodeToString(v) - is Long -> Json.encodeToString(v) - else -> TODO("Unsupported user data type $k : $v") - } - "\"$k\" : $value" - }.joinToString(separator = ",", prefix = "{", postfix = "}") { it } - val result: String = when { - url.endsWith("/providers/local-userpass/login") -> - """ - { - "access_token": "$ACCESS_TOKEN", - "refresh_token": "$REFRESH_TOKEN", - "user_id": "$USER_ID", - "device_id": "$DEVICE_ID" - } - """.trimIndent() - - url.endsWith("/auth/profile") -> - """ - { - "user_id": "5e6964e0afea63254581c1a1", - "domain_id": "000000000000000000000000", - "identities": [ - { - "id": "5e68f51ade5ba998bb17500d", - "provider_type": "local-userpass", - "provider_id": "000000000000000000000003", - "provider_data": { - "email": "unique_user@domain.com" - } - } - ], - "data": $profileData, - "type": "normal", - "roles": [ - { - "role_name": "GROUP_OWNER", - "group_id": "5e68f51e087b1b33a53f56d5" - } - ] - } - """.trimIndent() - - url.endsWith("/location") -> - """ - { "deployment_model" : "GLOBAL", - "location": "US-VA", - "hostname": "http://localhost:9090", - "ws_hostname": "ws://localhost:9090" - } - """.trimIndent() - url.endsWith("/providers/local-userpass/register") || - url.endsWith("auth/session") -> "" - else -> fail("Unexpected request url: $url") - } - callback.response( - Response( - httpResponseCode = 200, - customResponseCode = 0, - headers = mapOf("Content-Type" to "application/json"), - body = result - ) - ) - } - - override fun close() = Unit - } - ) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun profileAsBsonDocument() { - setDefaultProfile() - val user = app.createUserAndLogin() - val document = user.profileAsBsonDocument() - - assertEquals(profileBody.keys.size, document.keys.size) - - document.entries.forEach { (key: String, value: BsonValue) -> - assertContains(profileBody.keys, key) - val parsedValue: Any = when (value.bsonType) { - BsonType.STRING -> value.asString().value - BsonType.INT64 -> value.asInt64().value - else -> TODO() - } - assertEquals(profileBody[key], parsedValue, "failed comparing key $key") - } - } - - @Test - fun profile() { - setDefaultProfile() - - val user = app.createUserAndLogin() - setOf( - user.profile(), - user.profile(UserProfile.serializer()), - ).forEach { profile -> - assertEquals(Companion.userProfile, userProfile) - } - } - - @Test - fun profileEmpty() { - setEmptyProfile() - - val user = app.createUserAndLogin() - assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { - user.profile() - } - assertFailsWithMessage("Could not decode field 'name': Undefined value on a non-optional field") { - user.profile(UserProfile.serializer()) - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt deleted file mode 100644 index 6efbbd81e0..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/UserTests.kt +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -@file:OptIn(ExperimentalRealmSerializerApi::class) - -package io.realm.kotlin.test.mongodb.common - -import io.realm.kotlin.Realm -import io.realm.kotlin.annotations.ExperimentalRealmSerializerApi -import io.realm.kotlin.entities.sync.CollectionDataType -import io.realm.kotlin.internal.platform.fileExists -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.AuthenticationProvider -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.ext.customData -import io.realm.kotlin.mongodb.ext.customDataAsBsonDocument -import io.realm.kotlin.mongodb.ext.insertOne -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.mongo.CustomDataType -import io.realm.kotlin.test.mongodb.common.mongo.TEST_SERVICE_NAME -import io.realm.kotlin.test.mongodb.common.mongo.customEjsonSerializer -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.TestHelper.randomEmail -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.Bson -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNotSame -import kotlin.test.assertNull -import kotlin.test.assertSame -import kotlin.test.assertTrue - -const val CUSTOM_USER_DATA_FIELD = "custom_field" -const val CUSTOM_USER_DATA_VALUE = "custom_data" - -class UserTests { - - private lateinit var app: TestApp - - @BeforeTest - fun setUp() { - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.asTestApp.close() - } - } - - @Test - fun getId() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - assertEquals(24, anonUser.id.length) - anonUser.logOut() - assertEquals(24, anonUser.id.length) - } - - @Test - fun getApp() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - assertEquals(anonUser.app, app.asTestApp.app) - } - - @Test - fun getState_anonymousUser() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - assertEquals(User.State.LOGGED_IN, anonUser.state) - anonUser.logOut() - assertEquals(User.State.REMOVED, anonUser.state) - } - - @Test - fun getState_emailUser() = runBlocking { - val emailUser = createUserAndLogin() - assertEquals(User.State.LOGGED_IN, emailUser.state) - emailUser.logOut() - assertEquals(User.State.LOGGED_OUT, emailUser.state) - emailUser.remove() - assertEquals(User.State.REMOVED, emailUser.state) - } - - @Test - fun getIdentities() = runBlocking { - val email = randomEmail() - val emailUser = createUserAndLogin(email, "123456") - assertEquals(1, emailUser.identities.size) - emailUser.identities.first().let { - assertEquals(24, it.id.length) - assertEquals(AuthenticationProvider.EMAIL_PASSWORD, it.provider) - } - - val anonUser = app.login(Credentials.anonymous()) - assertEquals(1, anonUser.identities.size) - anonUser.identities.first().let { - assertEquals(49, it.id.length) - assertEquals(AuthenticationProvider.ANONYMOUS, it.provider) - } - } - - @Test - fun getProviderType() = runBlocking { - val email = randomEmail() - val emailUser = createUserAndLogin(email, "123456") - assertEquals(AuthenticationProvider.EMAIL_PASSWORD, emailUser.identities.first().provider) - emailUser.logOut() - // AuthenticationProvider is not removed once user is logged out - assertEquals(AuthenticationProvider.EMAIL_PASSWORD, emailUser.identities.first().provider) - } - - @Test - fun getAccessToken() = runBlocking { - val email = randomEmail() - val emailUser = createUserAndLogin(email, "123456") - assertFalse(emailUser.accessToken.isEmpty()) - emailUser.logOut() - // AccessToken is removed once user is logged out - assertTrue(emailUser.accessToken.isEmpty()) - } - - @Test - fun getRefreshToken() = runBlocking { - val email = randomEmail() - val emailUser = createUserAndLogin(email, "123456") - assertFalse(emailUser.refreshToken.isEmpty()) - emailUser.logOut() - // RefreshToken is removed once user is logged out - assertTrue(emailUser.refreshToken.isEmpty()) - } - - @Test - fun getDeviceId() = runBlocking { - val email = randomEmail() - val emailUser = createUserAndLogin(email, "123456") - assertFalse(emailUser.deviceId.isEmpty()) - emailUser.logOut() - // DeviceId is not removed once user is logged out - assertFalse(emailUser.deviceId.isEmpty()) - } - - @Test - fun logOut() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - - // Anonymous users are removed upon log out - assertEquals(anonUser, app.currentUser) - assertEquals(User.State.LOGGED_IN, anonUser.state) - anonUser.logOut() - assertEquals(User.State.REMOVED, anonUser.state) - assertNull(app.currentUser) - - // Users registered with Email/Password will register as Logged Out - val user2 = createUserAndLogin() - val current: User = app.currentUser!! - assertEquals(user2, current) - user2.logOut() - assertEquals(User.State.LOGGED_OUT, user2.state) - // Same effect on all instances - assertEquals(User.State.LOGGED_OUT, current.state) - // And no current user anymore - assertNull(app.currentUser) - } - - @Test - fun logOutUserInstanceImpactsCurrentUser() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - - val currentUser = app.currentUser!! - assertEquals(User.State.LOGGED_IN, currentUser.state) - assertEquals(User.State.LOGGED_IN, anonUser.state) - assertEquals(currentUser, anonUser) - - anonUser.logOut() - - assertNotEquals(User.State.LOGGED_OUT, currentUser.state) - assertNotEquals(User.State.LOGGED_OUT, anonUser.state) - assertNull(app.currentUser) - } - - @Test - fun logOutCurrentUserImpactsOtherInstances() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - - val currentUser = app.currentUser!! - assertEquals(User.State.LOGGED_IN, currentUser.state) - assertEquals(User.State.LOGGED_IN, anonUser.state) - assertEquals(currentUser, anonUser) - - currentUser.logOut() - - assertNotEquals(User.State.LOGGED_OUT, currentUser.state) - assertNotEquals(User.State.LOGGED_OUT, anonUser.state) - assertNull(app.currentUser) - } - - @Test - fun repeatedLogInAndOut() = runBlocking { - val (email, password) = randomEmail() to "password1234" - val initialUser = createUserAndLogin(email, password) - assertEquals(User.State.LOGGED_IN, initialUser.state) - initialUser.logOut() - assertEquals(User.State.LOGGED_OUT, initialUser.state) - - repeat(3) { - // FIXME assert with user.profile.email instead when user.profile API is ready - val user = app.login(Credentials.emailPassword(email, password)) - assertEquals(User.State.LOGGED_IN, user.state) - user.logOut() - assertEquals(User.State.LOGGED_OUT, user.state) - } - } - -// @Test -// fun linkUser_emailPassword() { -// assertEquals(1, anonUser.identities.size) -// -// val email = TestHelper.getRandomEmail() -// val password = "123456" -// app.emailPassword.registerUser(email, password) // TODO Test what happens if auto-confirm is enabled -// var linkedUser: User = anonUser.linkCredentials(Credentials.emailPassword(email, password)) -// -// assertTrue(anonUser === linkedUser) -// assertEquals(2, linkedUser.identities.size) -// assertEquals(Credentials.Provider.EMAIL_PASSWORD, linkedUser.identities[1].provider) -// -// // Validate that we cannot link a second set of credentials -// val otherEmail = TestHelper.getRandomEmail() -// val otherPassword = "123456" -// app.emailPassword.registerUser(otherEmail, otherPassword) -// -// val credentials = Credentials.emailPassword(otherEmail, otherPassword) -// -// assertFails { -// linkedUser = anonUser.linkCredentials(credentials) -// } -// } -// -// @Test -// fun linkUser_userApiKey() { -// // Generate API key -// val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") -// val apiKey: ApiKey = user.apiKeys.create("my-key"); -// user.logOut() -// -// anonUser = app.login(Credentials.anonymous()) -// -// assertEquals(1, anonUser.identities.size) -// -// // Linking with another user's API key is not allowed and must raise an AppException -// val exception = assertFailsWith { -// anonUser.linkCredentials(Credentials.apiKey(apiKey.value)) -// } -// -// assertEquals("invalid user link request", exception.errorMessage); -// assertEquals(ErrorCode.Category.FATAL, exception.errorCode.category); -// assertEquals("realm::app::ServiceError", exception.errorCode.type); -// assertEquals(6, exception.errorCode.intValue()); -// } -// -// @Test -// fun linkUser_customFunction() { -// assertEquals(1, anonUser.identities.size) -// -// val document = Document(mapOf( -// "mail" to TestHelper.getRandomEmail(), -// "id" to TestHelper.getRandomId() + 666 -// )) -// -// val credentials = Credentials.customFunction(document) -// -// val linkedUser = anonUser.linkCredentials(credentials) -// -// assertTrue(anonUser === linkedUser) -// assertEquals(2, linkedUser.identities.size) -// assertEquals(Credentials.Provider.CUSTOM_FUNCTION, linkedUser.identities[1].provider) -// } -// -// @Test -// fun linkUser_existingCredentialsThrows() { -// val email = TestHelper.getRandomEmail() -// val password = "123456" -// val emailUser: User = app.registerUserAndLogin(email, password) -// try { -// anonUser.linkCredentials(Credentials.emailPassword(email, password)) -// fail() -// } catch (ex: AppException) { -// assertEquals(ErrorCode.INVALID_SESSION, ex.errorCode) -// } -// } -// -// @Test -// fun linkUser_invalidArgsThrows() { -// try { -// anonUser.linkCredentials(TestHelper.getNull()) -// fail() -// } catch (ignore: IllegalArgumentException) { -// } -// } -// -// @Test -// fun linkUserAsync() = looperThread.runBlocking { -// assertEquals(1, anonUser.identities.size) -// val email = TestHelper.getRandomEmail() -// val password = "123456" -// app.emailPassword.registerUser(email, password) // TODO Test what happens if auto-confirm is enabled -// -// anonUser.linkCredentialsAsync(Credentials.emailPassword(email, password)) { result -> -// val linkedUser: User = result.orThrow -// assertTrue(anonUser === linkedUser) -// assertEquals(2, linkedUser.identities.size) -// assertEquals(Credentials.Provider.EMAIL_PASSWORD, linkedUser.identities[1].provider) -// looperThread.testComplete() -// } -// } -// -// @Test -// fun linkUserAsync_throwsOnNonLooperThread() { -// try { -// anonUser.linkCredentialsAsync(Credentials.emailPassword(TestHelper.getRandomEmail(), "123456")) { fail() } -// fail() -// } catch (ignore: java.lang.IllegalStateException) { -// } -// } -// - - @Test - fun removeUser() { - runBlocking { - // Removing logged in user - val user1 = createUserAndLogin() - assertEquals(user1, app.currentUser) - assertEquals(1, app.allUsers().size) - assertEquals(user1, user1.remove()) - assertEquals(User.State.REMOVED, user1.state) - assertNull(app.currentUser) - assertEquals(0, app.allUsers().size) - - // Remove logged out user - val user2 = createUserAndLogin() - user2.logOut() - assertNull(app.currentUser) - assertEquals(1, app.allUsers().size) - assertEquals(user2, user2.remove()) - assertEquals(User.State.REMOVED, user2.state) - assertEquals(0, app.allUsers().size) - } - } - - @Test - fun removeUser_throwsIfUserAlreadyRemoved() { - runBlocking { - val user1 = createUserAndLogin() - assertEquals(user1, user1.remove()) - assertFailsWith { - user1.remove() - } - } - } - - @Test - fun deleteUser() { - runBlocking { - val user1 = createUserAndLogin() - val config = SyncConfiguration.create( - user1, - TestHelper.randomPartitionValue(), - PARTITION_BASED_SCHEMA - ) - Realm.open(config).close() - assertTrue(fileExists(config.path)) - assertEquals(user1, app.currentUser) - assertEquals(1, app.allUsers().size) - user1.delete() - assertEquals(User.State.REMOVED, user1.state) - assertNull(app.currentUser) - assertEquals(0, app.allUsers().size) - assertFalse(fileExists(config.path)) - } - } - - @Test - fun deleteUser_loggedOutThrows() { - runBlocking { - val user1 = createUserAndLogin() - val config = SyncConfiguration.create( - user1, - TestHelper.randomPartitionValue(), - PARTITION_BASED_SCHEMA - ) - Realm.open(config).close() - user1.logOut() - assertTrue(fileExists(config.path)) - assertFailsWith { - user1.delete() - } - assertTrue(fileExists(config.path)) - } - } - - @Test - fun deleteUser_throwsIfUserAlreadyDeleted() { - runBlocking { - val user1 = createUserAndLogin() - user1.delete() - assertFailsWith { - user1.delete() - } - } - } - - @Test - fun linkCredentials_emailPassword() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - assertEquals(1, anonUser.identities.size) - val (email, password) = randomEmail() to "123456" - app.emailPasswordAuth.registerUser(email, password) - val linkedUser = anonUser.linkCredentials(Credentials.emailPassword(email, password)) - - assertSame(anonUser, linkedUser) - assertEquals(2, linkedUser.identities.size) - assertEquals(AuthenticationProvider.EMAIL_PASSWORD, linkedUser.identities[1].provider) - - // Validate that we cannot link a second set of credentials - val otherEmail = randomEmail() - val otherPassword = "123456" - app.emailPasswordAuth.registerUser(otherEmail, otherPassword) - val credentials = Credentials.emailPassword(otherEmail, otherPassword) - - assertFailsWith { - anonUser.linkCredentials(credentials) - }.let { - assertTrue( - it.message!!.contains("unauthorized"), - it.message - ) - } - } - - @Test - fun linkCredentials_twoEmailAccountsThrows() = runBlocking { - val (email1, password1) = randomEmail() to "123456" - app.emailPasswordAuth.registerUser(email1, password1) - val credentials1 = Credentials.emailPassword(email1, password1) - val emailUser1 = app.login(credentials1) - val (email2, password2) = randomEmail() to "123456" - app.emailPasswordAuth.registerUser(email2, password2) - val credentials2 = Credentials.emailPassword(email2, password2) - assertFailsWith { - emailUser1.linkCredentials(credentials2) - }.let { - assertTrue( - it.message!!.contains("unauthorized"), - it.message - ) - } - } - - @Test - fun linkCredentials_addingAnonymousToEmailThrows() = runBlocking { - val (email1, password1) = randomEmail() to "123456" - app.emailPasswordAuth.registerUser(email1, password1) - val credentials1 = Credentials.emailPassword(email1, password1) - val emailUser1 = app.login(credentials1) - assertFailsWith { - emailUser1.linkCredentials(Credentials.anonymous()) - }.let { - assertTrue( - it.message!!.contains("Cannot add anonymous credentials to an existing user"), - it.message - ) - } - } - - @Test - fun linkCredentials_linkingWithItselfThrows() = runBlocking { - var anonUser = app.login(Credentials.anonymous()) - val email = randomEmail() - val password = "123456" - app.emailPasswordAuth.registerUser(email, password) - val creds = Credentials.emailPassword(email, password) - app.login(creds) - assertFailsWith { - anonUser.linkCredentials(creds) - }.let { - assertTrue( - it.message!!.contains("unauthorized"), - it.message - ) - } - } - - // TODO Add support for ApiKeyAuth: https://github.com/realm/realm-kotlin/issues/432 - // @Test - // fun linkUser_userApiKey() { - // // Generate API key - // val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") - // val apiKey: ApiKey = user.apiKeys.create("my-key"); - // user.logOut() - // - // anonUser = app.login(Credentials.anonymous()) - // - // Assert.assertEquals(1, anonUser.identities.size) - // - // // Linking with another user's API key is not allowed and must raise an AppException - // val exception = assertFailsWith { - // anonUser.linkCredentials(Credentials.apiKey(apiKey.value)) - // } - // - // Assert.assertEquals("invalid user link request", exception.errorMessage); - // assertEquals(ErrorCode.Category.FATAL, exception.errorCode.category); - // Assert.assertEquals("realm::app::ServiceError", exception.errorCode.type); - // Assert.assertEquals(6, exception.errorCode.intValue()); - // } - - // TODO Add support for logging in using a custom function: https://github.com/realm/realm-kotlin/issues/741 - // @Test - // fun linkUser_customFunction() = runBlocking { - // var anonUser = app.login(Credentials.anonymous()) - // Assert.assertEquals(1, anonUser.identities.size) - // - // val document = Document(mapOf( - // "mail" to TestHelper.getRandomEmail(), - // "id" to TestHelper.getRandomId() + 666 - // )) - // - // val credentials = Credentials.customFunction(document) - // - // val linkedUser = anonUser.linkCredentials(credentials) - // - // Assert.assertTrue(anonUser === linkedUser) - // Assert.assertEquals(2, linkedUser.identities.size) - // assertEquals(AuthenticationProvider.CUSTOM_FUNCTION, linkedUser.identities[1].provider) - // } - -// @Test -// fun getApiKeyAuthProvider() { -// val user: User = app.registerUserAndLogin(TestHelper.getRandomEmail(), "123456") -// val provider1: ApiKeyAuth = user.apiKeys -// assertEquals(user, provider1.user) -// -// user.logOut() -// -// try { -// user.apiKeys -// fail() -// } catch (ex: IllegalStateException) { -// } -// } -// -// @Test -// fun revokedRefreshTokenIsNotSameAfterLogin() = looperThread.runBlocking { -// val password = "password" -// val user = app.registerUserAndLogin(TestHelper.getRandomEmail(), password) -// val refreshToken = user.refreshToken -// -// app.addAuthenticationListener(object : AuthenticationListener { -// override fun loggedIn(user: User) {} -// -// override fun loggedOut(loggerOutUser: User) { -// app.loginAsync(Credentials.emailPassword(loggerOutUser.profile.email, password)) { -// val loggedInUser = it.orThrow -// assertTrue(loggerOutUser !== loggedInUser) -// assertNotEquals(refreshToken, loggedInUser.refreshToken) -// looperThread.testComplete() -// } -// } -// }) -// user.logOut() -// } -// - - @Test - fun isLoggedIn() = runBlocking { - val anonUser = app.login(Credentials.anonymous()) - - val user = createUserAndLogin() - - assertTrue(anonUser.loggedIn) - assertTrue(user.loggedIn) - - anonUser.logOut() - assertFalse(anonUser.loggedIn) - assertTrue(user.loggedIn) - - user.logOut() - assertFalse(user.loggedIn) - } - - @Test - fun equals() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = createUserAndLogin(email, password) - assertEquals(user, user) - user.logOut() - - // Verify that it is not same object but uses underlying User equality on identity - val sameUserNewLogin = app.login(Credentials.emailPassword(email, password)) - assertNotSame(user, sameUserNewLogin) - assertEquals(user, sameUserNewLogin) - - val differentUser = createUserAndLogin(randomEmail(), password) - assertNotEquals(user, differentUser) - } - - @Test - fun hashCode_user() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = createUserAndLogin(email, password) - user.logOut() - - val sameUserNewLogin = app.login(Credentials.emailPassword(email, password)) - // Verify that two equal users also returns same hashCode - assertFalse(user === sameUserNewLogin) - assertEquals(user.hashCode(), sameUserNewLogin.hashCode()) - } - - @Serializable - data class SerializableCustomData( - @SerialName("_id") val id: String, - @SerialName("user_id") val userId: String, - @SerialName("custom_field") val customField: String - ) - - @Test - fun customData_initiallyNull() { - val user = runBlocking { - val (email, password) = randomEmail() to "123456" - createUserAndLogin(email, password) - } - // Newly registered users do not have any custom data with current test server setup - assertNull(user.customDataAsBsonDocument()) - assertNull(user.customData()) - assertNull(user.customData(SerializableCustomData.serializer())) - } - - @Test - fun customData_refresh() { - val user = runBlocking { - val (email, password) = randomEmail() to "123456" - createUserAndLogin(email, password) - } - // Newly registered users do not have any custom data with current test server setup - assertNull(user.customDataAsBsonDocument()) - assertNull(user.customData()) - assertNull(user.customData(SerializableCustomData.serializer())) - - updatecustomDataAsBsonDocument( - user, - BsonDocument(CUSTOM_USER_DATA_FIELD to BsonString(CUSTOM_USER_DATA_VALUE)) - ) - - runBlocking { - user.refreshCustomData() - } - val userData = user.customDataAsBsonDocument() - assertNotNull(userData) - assertEquals(CUSTOM_USER_DATA_VALUE, userData[CUSTOM_USER_DATA_FIELD]!!.asString().value) - - setOf( - user.customData(), - user.customData(SerializableCustomData.serializer()) - ).forEach { serializableCustomData -> - assertNotNull(serializableCustomData) - assertEquals(CUSTOM_USER_DATA_VALUE, serializableCustomData.customField) - } - } - - @Test - fun customData_refreshByLogout() { - val (email, password) = randomEmail() to "123456" - val user = runBlocking { - createUserAndLogin(email, password) - } - // Newly registered users do not have any custom data with current test server setup - assertNull(user.customDataAsBsonDocument()) - assertNull(user.customData()) - assertNull(user.customData(SerializableCustomData.serializer())) - - updatecustomDataAsBsonDocument( - user, - BsonDocument(CUSTOM_USER_DATA_FIELD to BsonString(CUSTOM_USER_DATA_VALUE)) - ) - - // But will be updated when authorization token is refreshed - runBlocking { - user.logOut() - app.login(Credentials.emailPassword(email, password)) - } - - val userData = user.customDataAsBsonDocument() - assertNotNull(userData) - assertEquals(CUSTOM_USER_DATA_VALUE, userData[CUSTOM_USER_DATA_FIELD]!!.asString().value) - - setOf( - user.customData(), - user.customData(SerializableCustomData.serializer()) - ).forEach { serializableCustomData -> - assertNotNull(serializableCustomData) - assertEquals(CUSTOM_USER_DATA_VALUE, serializableCustomData.customField) - } - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun mongoClient_defaultSerializer() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = runBlocking { - createUserAndLogin(email, password) - } - @OptIn(ExperimentalRealmSerializerApi::class) - val client: MongoClient = user.mongoClient(TEST_SERVICE_NAME) - assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun mongoClient_customSerializer() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = runBlocking { - createUserAndLogin(email, password) - } - val collectionWithDefaultSerializer = - user.mongoClient(TEST_SERVICE_NAME) - .database(app.clientAppId) - .collection("CollectionDataType") - assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("dog-1")) - } - - val collectionWithCustomSerializer = - user.mongoClient(TEST_SERVICE_NAME, customEjsonSerializer).database(app.clientAppId) - .collection("CollectionDataType") - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("dog-1"))) - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun mongoClient_unknownClient() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = runBlocking { - createUserAndLogin(email, password) - } - val mongoClient = user.mongoClient("UNKNOWN_SERVICE") - val collection = - mongoClient.database(app.clientAppId).collection("CollectionDataType") - assertFailsWithMessage("Cannot access member 'insertOne' of undefined") { - collection.insertOne(CollectionDataType("object-1")) - } - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun mongoClient_throwsOnLoggedOutUser() = runBlocking { - val (email, password) = randomEmail() to "123456" - val user = runBlocking { - createUserAndLogin(email, password) - } - user.logOut() - assertFailsWithMessage("Cannot obtain a MongoClient from a logged out user") { - user.mongoClient("UNKNOWN_SERVICE") - } - } - - private fun updatecustomDataAsBsonDocument(user: User, data: BsonDocument) { - // Name of collection and property used for storing custom user data. Must match server config.json - val COLLECTION_NAME = "UserData" - val USER_ID_FIELD = "user_id" - - runBlocking { - app.insertDocument( - COLLECTION_NAME, - Bson.toJson(data.append(USER_ID_FIELD, BsonString(user.id))) - ) - } - } - - private suspend fun createUserAndLogin( - email: String = randomEmail(), - password: String = "123456" - ): User { - app.emailPasswordAuth.registerUser(email, password) - return app.login(Credentials.emailPassword(email, password)) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt deleted file mode 100644 index 2acb753971..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/KtorNetworkTransportTest.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -@file:OptIn(ExperimentalCoroutinesApi::class) - -package io.realm.kotlin.test.mongodb.common.internal - -import io.ktor.http.HttpMethod -import io.realm.kotlin.internal.interop.sync.Response -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.internal.platform.singleThreadDispatcher -import io.realm.kotlin.internal.util.CoroutineDispatcherFactory -import io.realm.kotlin.internal.util.use -import io.realm.kotlin.mongodb.internal.KtorNetworkTransport -import io.realm.kotlin.test.mongodb.TEST_SERVER_BASE_URL -import io.realm.kotlin.test.mongodb.util.AppInitializer -import io.realm.kotlin.test.mongodb.util.AppServicesClient -import io.realm.kotlin.test.mongodb.util.BaasApp -import io.realm.kotlin.test.mongodb.util.Function -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.trySendOrFail -import kotlinx.coroutines.CloseableCoroutineDispatcher -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals - -val TEST_METHODS = listOf( - HttpMethod.Get, - HttpMethod.Post, - HttpMethod.Patch, - HttpMethod.Put, - HttpMethod.Delete, -) - -object KtorTestAppInitializer : AppInitializer { - override suspend fun initialize(client: AppServicesClient, app: BaasApp) { - client.initialize(app, TEST_METHODS) - } - - // Setups the app with the functions and https endpoints required to run the KtorNetworkTransportTests - suspend fun AppServicesClient.initialize(app: BaasApp, methods: List) = - with(app) { - // We have to create a function per method because the request parameter does not - // has what method triggered it. - methods.forEach { httpMethod: HttpMethod -> - val method = httpMethod.value - val function = addFunction( - Function( - name = "test_network_transport_$method", - runAsSystem = true, - source = - """ - exports = async function (request, response) { - response.setHeader('Content-Type', 'text/plain'); - let isSuccess = request.query["success"] == "true"; - - if (isSuccess) { - response.setStatusCode(200); - response.setBody("$method-success"); - } else { - response.setStatusCode(500); - response.setBody("$method-failure"); - } - } - """.trimIndent() - ) - ) - - addEndpoint( - """ - { - "route": "/test_network_transport", - "function_name": "${function.name}", - "function_id": "${function._id}", - "http_method": "$method", - "validation_method": "NO_VALIDATION", - "secret_id": "", - "secret_name": "", - "create_user_on_auth": false, - "fetch_custom_user_data": false, - "respond_result": false, - "disabled": false, - "return_type": "JSON" - } - """.trimIndent() - ) - } - } - - override val name: String = "ktor-test-app" -} - -internal class KtorNetworkTransportTest { - private lateinit var transport: KtorNetworkTransport - private lateinit var endpoint: String - - // Delete method must have an empty body or the server app fails to process it. - private val emptyBodyMethods = setOf(HttpMethod.Get, HttpMethod.Delete) - private lateinit var dispatcher: CloseableCoroutineDispatcher - private lateinit var appClient: AppServicesClient - - @BeforeTest - fun setUp() { - dispatcher = singleThreadDispatcher("test-ktor-dispatcher") - val dispatcherFactory = CoroutineDispatcherFactory.unmanaged(dispatcher) - - transport = KtorNetworkTransport( - timeoutMs = 60000, - dispatcherHolder = dispatcherFactory.create() - ) - - val app = runBlocking(dispatcher) { - appClient = AppServicesClient.build( - baseUrl = TEST_SERVER_BASE_URL, - debug = false, - dispatcher = dispatcher - ) - appClient.run { - getOrCreateApp(KtorTestAppInitializer) - } - } - - endpoint = "$TEST_SERVER_BASE_URL/app/${app.clientAppId}/endpoint/test_network_transport" - } - - @AfterTest - fun tearDown() { - transport.close() - dispatcher.close() - appClient.closeClient() - } - - @Test - fun requestSuccessful() = runBlocking { - val url = "$endpoint?success=true" - for (method in TEST_METHODS) { - val body = if (emptyBodyMethods.contains(method)) "" else "{ \"body\" : \"some content\" }" - val response = TestChannel().use { channel -> - transport.sendRequest( - method.value.lowercase(), - url, - mapOf(), - body - ) { response -> channel.trySendOrFail(response) } - channel.receiveOrFail() - } - assertEquals(200, response.httpResponseCode, "$method failed") - assertEquals(0, response.customResponseCode, "$method failed") - assertEquals("${method.value}-success", response.body, "$method failed") - } - } - - @Test - fun requestFailedOnServer() = runBlocking { - val url = "$endpoint?success=false" - for (method in TEST_METHODS) { - val body = if (emptyBodyMethods.contains(method)) "" else "{ \"body\" : \"some content\" }" - - val response = TestChannel().use { channel -> - transport.sendRequest( - method.value.lowercase(), - url, - mapOf(), - body - ) { response -> channel.trySendOrFail(response) } - channel.receiveOrFail() - } - assertEquals(500, response.httpResponseCode, "$method failed") - assertEquals(0, response.customResponseCode, "$method failed") - assertEquals("${method.value}-failure", response.body, "$method failed") - } - } - - // Make sure that the client doesn't crash if attempting to send invalid JSON - // This is mostly a guard against Java crashing if ObjectStore serializes the wrong - // way by accident. - @Test - fun requestSendsIllegalJson() = runBlocking { - val url = "$endpoint?success=true" - for (method in TEST_METHODS) { - val body = if (emptyBodyMethods.contains(method)) "" else "Boom!" - - val response = TestChannel().use { channel -> - transport.sendRequest( - method.value.lowercase(), - url, - mapOf(), - body - ) { response -> channel.trySendOrFail(response) } - channel.receiveOrFail() - } - assertEquals(200, response.httpResponseCode, "$method failed") - assertEquals(0, response.customResponseCode, "$method failed") - assertEquals("${method.value}-success", response.body, "$method failed") - } - } - - @Test - @Ignore - // TODO Need to ensure errors from network layers are propagated. Could be done by - // interrupting like on Java (OkHttpNetworkTransportTests.requestInterrupted), but could maybe - // be simpler without need to send signals in a platform agnostic way - // https://github.com/realm/realm-kotlin/issues/451 - fun errorPropagation() { - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/RealmSyncUtilsTest.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/RealmSyncUtilsTest.kt deleted file mode 100644 index 6c6f5a1fc0..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/RealmSyncUtilsTest.kt +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -@file:Suppress("invisible_member", "invisible_reference") - -package io.realm.kotlin.test.mongodb.common.internal - -import io.realm.kotlin.internal.interop.CoreError -import io.realm.kotlin.internal.interop.ErrorCategory -import io.realm.kotlin.internal.interop.ErrorCode -import io.realm.kotlin.internal.interop.UnknownCodeDescription -import io.realm.kotlin.internal.interop.sync.AppError -import io.realm.kotlin.internal.interop.sync.SyncError -import io.realm.kotlin.mongodb.internal.convertAppError -import io.realm.kotlin.mongodb.internal.convertSyncError -import kotlin.test.Test -import kotlin.test.assertEquals - -const val UNMAPPED_CATEGORY_CODE: Int = 0 -const val UNMAPPED_ERROR_CODE: Int = -1 - -class RealmSyncUtilsTest { - - @Test - fun convertSyncErrorCode_unmappedErrorCode2() { - val syncException = convertSyncError( - SyncError( - CoreError( - categoriesNativeValue = ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR.nativeValue, - errorCodeNativeValue = UNMAPPED_ERROR_CODE, - messageNativeValue = "Placeholder message" - ) - ) - ) - - assertEquals( - "[Client][Unknown($UNMAPPED_ERROR_CODE)] Placeholder message.", - syncException.message - ) - } - - @Test - fun convertSyncErrorCode_unmappedErrorCategory() { - val syncException = convertSyncError( - SyncError( - CoreError( - categoriesNativeValue = UNMAPPED_CATEGORY_CODE, - errorCodeNativeValue = UNMAPPED_ERROR_CODE, - messageNativeValue = "Placeholder message" - ) - ) - ) - - assertEquals( - "[$UNMAPPED_CATEGORY_CODE][Unknown($UNMAPPED_ERROR_CODE)] Placeholder message.", - syncException.message - ) - } - - // Core also has a concept of an "unknown" error code. It is being reported the same way - // as a truly unknown code with the description "Unknown()" - @Test - fun convertSyncErrorCode_unknownNativeErrrorCode() { - val syncException = convertSyncError( - SyncError( - CoreError( - categoriesNativeValue = ErrorCategory.RLM_ERR_CAT_CLIENT_ERROR.nativeValue, - errorCodeNativeValue = ErrorCode.RLM_ERR_UNKNOWN.nativeValue, - messageNativeValue = "Placeholder message" - ) - ) - ) - - assertEquals( - "[Client][Unknown(2000000)] Placeholder message.", - syncException.message - ) - } - - @Test - fun convertAppError_unmappedErrorCode() { - val appException = convertAppError( - AppError( - categoryFlags = ErrorCategory.RLM_ERR_CAT_CUSTOM_ERROR.nativeValue, - code = UnknownCodeDescription(UNMAPPED_ERROR_CODE), - message = "Placeholder message", - httpStatusCode = UNMAPPED_ERROR_CODE, - linkToServerLog = null - ) - ) - - assertEquals("[Custom][Unknown($UNMAPPED_ERROR_CODE)] Placeholder message.", appException.message) - } - - @Test - fun convertAppError_unmappedErrorCategory() { - val appException = convertAppError( - AppError( - categoryFlags = UnknownCodeDescription(UNMAPPED_CATEGORY_CODE).nativeValue, - code = UnknownCodeDescription(UNMAPPED_ERROR_CODE), - message = "Placeholder message", - httpStatusCode = UNMAPPED_ERROR_CODE, - linkToServerLog = null - ) - ) - - assertEquals( - "[$UNMAPPED_CATEGORY_CODE][Unknown($UNMAPPED_ERROR_CODE)] Placeholder message.", - appException.message - ) - } - - @Test - fun convertAppError_unmappedErrorCategoryAndErrorCode_noMessage() { - val appException = convertAppError( - AppError( - categoryFlags = UnknownCodeDescription(UNMAPPED_CATEGORY_CODE).nativeValue, - code = UnknownCodeDescription(UNMAPPED_ERROR_CODE), - message = null, - httpStatusCode = UNMAPPED_ERROR_CODE, - linkToServerLog = null - ) - ) - - assertEquals("[$UNMAPPED_CATEGORY_CODE][Unknown($UNMAPPED_ERROR_CODE)]", appException.message) - } - - @Test - fun convertAppError_unmappedErrorCategoryAndErrorCode_linkServerLog() { - val appException = convertAppError( - AppError( - categoryFlags = UnknownCodeDescription(UNMAPPED_CATEGORY_CODE).nativeValue, - code = UnknownCodeDescription(UNMAPPED_ERROR_CODE), - message = "Placeholder message", - httpStatusCode = UNMAPPED_ERROR_CODE, - linkToServerLog = "http://realm.io" - ) - ) - - assertEquals( - "[$UNMAPPED_CATEGORY_CODE][Unknown($UNMAPPED_ERROR_CODE)] Placeholder message. Server log entry: http://realm.io", - appException.message - ) - } - - @Test - fun convertAppError_unmappedErrorCategoryAndErrorCode_noMessage_linkServerLog() { - val appException = convertAppError( - AppError( - categoryFlags = UnknownCodeDescription(UNMAPPED_CATEGORY_CODE).nativeValue, - code = UnknownCodeDescription(UNMAPPED_ERROR_CODE), - message = null, - httpStatusCode = UNMAPPED_ERROR_CODE, - linkToServerLog = "http://realm.io" - ) - ) - - assertEquals( - "[$UNMAPPED_CATEGORY_CODE][Unknown($UNMAPPED_ERROR_CODE)] Server log entry: http://realm.io", - appException.message - ) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/SyncConnectionParamsTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/SyncConnectionParamsTests.kt deleted file mode 100644 index 4ece5ae87c..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/internal/SyncConnectionParamsTests.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_member", "invisible_reference") -package io.realm.kotlin.test.mongodb.common.internal - -import io.realm.kotlin.internal.interop.SyncConnectionParams -import kotlin.test.Test -import kotlin.test.assertEquals - -/** - * Verify that input to io.realm.kotlin.internal.interop.SyncConnectionParams is accessible. - */ -internal class SyncConnectionParamsTests { - - @Test - fun allProperties() { - val props = SyncConnectionParams( - sdkVersion = "sdkVersion", - bundleId = "bundleId", - platformVersion = "platformVersion", - device = "device", - deviceVersion = "deviceVersion", - framework = SyncConnectionParams.Runtime.JVM, - frameworkVersion = "frameworkVersion", - ) - assertEquals("Kotlin", props.sdkName) - assertEquals("sdkVersion", props.sdkVersion) - assertEquals("bundleId", props.bundleId) - assertEquals("platformVersion", props.platformVersion) - assertEquals("device", props.device) - assertEquals("deviceVersion", props.deviceVersion) - assertEquals("JVM", props.framework) - assertEquals("frameworkVersion", props.frameworkVersion) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt deleted file mode 100644 index e27973998f..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoClientTests.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common.mongo - -import io.realm.kotlin.entities.sync.CollectionDataType -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.ext.collection -import io.realm.kotlin.mongodb.ext.insertOne -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.realmSerializerModule -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import kotlinx.serialization.SerializationException -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.serialization.EJson -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs - -internal const val TEST_SERVICE_NAME = "BackingDB" - -@OptIn(ExperimentalKBsonSerializerApi::class) -class MongoClientTests { - - lateinit var app: TestApp - lateinit var client: MongoClient - - @BeforeTest - fun setUp() { - app = TestApp( - this::class.simpleName, - DefaultFlexibleSyncAppInitializer, - ) - val user = app.createUserAndLogin() - client = user.mongoClient(TEST_SERVICE_NAME, eJson = EJson(serializersModule = realmSerializerModule(setOf(CollectionDataType::class)))) - } - - @AfterTest - fun teadDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun properties() { - assertEquals(TEST_SERVICE_NAME, client.serviceName) - } - - @Test - fun database_defaultSerializer() = runBlocking { - assertIs(client.database(app.clientAppId).collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun database_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = client.database(app.clientAppId) - .collection("CollectionDataType") - assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) - } - val collectionWithCustomSerializer = client.database(app.clientAppId, customEjsonSerializer) - .collection("CollectionDataType") - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) - } - - @Test - fun database_createsCollectionOnInsertToUnknownDatabase() = runBlocking { - val database = client.database("Unknown") - val collection = database.collection("NewCollection") - assertIs(collection.insertOne(CollectionDataType("object-1")) as Int) - } - - @Test - fun collection_defaultSerializer() = runBlocking { - assertIs(client.collection().insertOne(CollectionDataType("object-1"))) - } - - @Test - fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = client.collection() - assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.withDocumentClass().insertOne(CustomDataType("object-1")) - } - - val collectionWithCustomSerializer = client.collection( - customEjsonSerializer - ) - assertIs( - collectionWithCustomSerializer.insertOne( - CustomDataType("object-1") - ) - ) - } - - @Test - fun collection_unknownSchemaType() = runBlocking { - val collectionWithDefaultSerializer = client.collection() - assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\".") { - collectionWithDefaultSerializer.insertOne(NonSchemaType()) - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt deleted file mode 100644 index 91ef0aea63..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoCollectionTests.kt +++ /dev/null @@ -1,1501 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common.mongo - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.COLLECTION_SCHEMAS -import io.realm.kotlin.entities.sync.ChildCollectionDataType -import io.realm.kotlin.entities.sync.CollectionDataType -import io.realm.kotlin.entities.sync.EmbeddedChildCollectionDataType -import io.realm.kotlin.entities.sync.ParentCollectionDataType -import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.exceptions.ServiceException -import io.realm.kotlin.mongodb.ext.aggregate -import io.realm.kotlin.mongodb.ext.collection -import io.realm.kotlin.mongodb.ext.count -import io.realm.kotlin.mongodb.ext.deleteMany -import io.realm.kotlin.mongodb.ext.deleteOne -import io.realm.kotlin.mongodb.ext.find -import io.realm.kotlin.mongodb.ext.findOne -import io.realm.kotlin.mongodb.ext.findOneAndDelete -import io.realm.kotlin.mongodb.ext.findOneAndReplace -import io.realm.kotlin.mongodb.ext.findOneAndUpdate -import io.realm.kotlin.mongodb.ext.insertMany -import io.realm.kotlin.mongodb.ext.insertOne -import io.realm.kotlin.mongodb.ext.updateMany -import io.realm.kotlin.mongodb.ext.updateOne -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoCollection -import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.mongodb.mongo.realmSerializerModule -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.notifications.ResultsChange -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.common.utils.retry -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.use -import io.realm.kotlin.types.BaseRealmObject -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.annotations.PersistedName -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationException -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.modules.SerializersModule -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonValue -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId -import org.mongodb.kbson.serialization.EJson -import org.mongodb.kbson.serialization.encodeToBsonValue -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotEquals -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -/** - * Test class that verifies MongoDB Client API interactions through the - * `MongoClient.database(...).collection(...)`-API. - */ -class MongoCollectionFromDatabaseTests : MongoCollectionTests() { - - lateinit var database: MongoDatabase - - @BeforeTest - override fun setUp() { - super.setUp() - val databaseName = app.configuration.appId - @OptIn(ExperimentalKBsonSerializerApi::class) - database = client.database(databaseName) - @OptIn(ExperimentalKBsonSerializerApi::class) - collection = collection() - } - - @Test - override fun findOne_unknownCollection() = runBlocking { - // Unknown collections will create the collection if inserting document, so only use - // NonSchemaType for queries - @OptIn(ExperimentalKBsonSerializerApi::class) - val unknownCollection = collection() - assertNull(unknownCollection.findOne()) - } -} - -/** - * Test class that verifies MongoDB Client API interactions through the - * `MongoClient.collection(...)`-API. - */ -class MongoCollectionFromClientTests : MongoCollectionTests() { - - @BeforeTest - override fun setUp() { - super.setUp() - @OptIn(ExperimentalKBsonSerializerApi::class) - collection = collection() - } - - @Test - fun name_persistedName() { - @OptIn(ExperimentalKBsonSerializerApi::class) - assertEquals("CollectionDataType", client.collection().name) - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - override fun findOne_unknownCollection() = runBlocking { - @OptIn(ExperimentalKBsonSerializerApi::class) - val unknownCollection = collection() - assertFailsWithMessage("no matching collection found that maps to a table with title \"NonSchemaType\"") { - unknownCollection.findOne() - } - } -} - -sealed class MongoCollectionTests { - - lateinit var app: TestApp - lateinit var user: User - lateinit var client: MongoClient - lateinit var collection: MongoCollection - - @BeforeTest - open fun setUp() { - app = TestApp( - testId = this::class.simpleName, - DefaultFlexibleSyncAppInitializer, - builder = { - it.httpLogObfuscator(null) - } - ) - - app.asTestApp.run { - runBlocking { - COLLECTION_SCHEMAS.forEach { - deleteDocuments(app.configuration.appId, it.simpleName!!, "{}") - } - } - } - user = app.createUserAndLogin() - @OptIn(ExperimentalKBsonSerializerApi::class) - client = user.mongoClient( - serviceName = TEST_SERVICE_NAME, - eJson = EJson( - serializersModule = realmSerializerModule( - setOf( - ParentCollectionDataType::class, - ChildCollectionDataType::class - ) - ) - ) - ) - } - - @AfterTest - fun tearDown() { - app.asTestApp.run { - runBlocking { - COLLECTION_SCHEMAS.forEach { - deleteDocuments(app.configuration.appId, it.simpleName!!, "{}") - } - } - } - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun name() { - assertEquals("CollectionDataType", collection.name) - } - - @Test - fun withDocumentClass() = runBlocking { - // Original typing - assertIs(collection.insertOne(CollectionDataType("object-1", Random.nextInt()))) - assertIs(collection.findOne()) - - // Reshaped - @OptIn(ExperimentalKBsonSerializerApi::class) - val bsonCollection: MongoCollection = collection.withDocumentClass() - assertIs(bsonCollection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-2")))) - assertIs(bsonCollection.findOne()) - assertEquals(2, bsonCollection.count()) - } - - @Test - fun withDocumentClass_withCustomSerialization() = runBlocking { - @OptIn(ExperimentalKBsonSerializerApi::class) - val reshapedCollectionWithDefaultSerializer: MongoCollection = - collection.withDocumentClass() - - assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - reshapedCollectionWithDefaultSerializer.insertOne(CustomDataType("object-2")) - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - val reshapedCollectionWithCustomSerializer: MongoCollection = - collection.withDocumentClass(customEjsonSerializer) - - assertIs(reshapedCollectionWithCustomSerializer.insertOne(CustomDataType("object-2"))) - } - - @Test - fun count() = runBlocking { - assertEquals(0, collection.count()) - - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) - - assertEquals(10, collection.count()) - assertEquals(5, collection.count(limit = 5)) - assertEquals(2, collection.count(filter = BsonDocument("name" to BsonString("object-0")))) - assertEquals(2, collection.count(filter = BsonDocument("name", "object-0"))) - assertEquals( - 1, - collection.count(filter = BsonDocument("name" to BsonString("object-0")), limit = 1) - ) - } - - @Test - fun count_invalidFilter() = runBlocking { - assertFailsWithMessage("operator") { - collection.count(filter = BsonDocument("\$who", "object-0")) - } - } - - @Test - fun findOne() = runBlocking { - // Empty collections - assertNull(collection.findOne()) - - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) - - // No match - assertNull(collection.findOne(filter = BsonDocument("name", "cat"))) - - // Multiple matches, still only one document - // Default types - collection.findOne(filter = BsonDocument("name", "object-0")).run { - assertIs(this) - assertEquals("object-0", this.name) - } - - // Projection - val collectionDataType = CollectionDataType("object-6") - assertEquals(collectionDataType._id, collection.insertOne(collectionDataType)) - - collection.findOne(filter = BsonDocument("name", "object-6")).run { - assertIs(this) - assertEquals("object-6", this.name) - assertEquals(collectionDataType._id, this._id) - } - - // Project without name - collection.findOne( - filter = BsonDocument("name", "object-6"), - projection = BsonDocument("""{ "name" : 0}""") - ).run { - assertIs(this) - assertEquals("Default", this.name) - // _id is included by default - assertEquals(collectionDataType._id, this._id) - } - // Project without _id, have to be explicitly excluded - collection.findOne( - filter = BsonDocument("name", "object-6"), - projection = BsonDocument("""{ "_id" : 0}""") - ).run { - assertIs(this) - assertEquals(collectionDataType.name, name) - assertNotEquals(collectionDataType._id, this._id) - } - - // Sort - collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(-1)))).run { - assertIs(this) - assertEquals("object-6", this.name) - } - collection.findOne(sort = BsonDocument(mapOf("name" to BsonInt32(1)))).run { - assertIs(this) - assertEquals("object-0", this.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOne_explicitTypes() = runBlocking { - // Empty collection - assertNull(collection.findOne()) - - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) - - // Explicit types - val findOne: BsonDocument? = collection.withDocumentClass().findOne(filter = BsonDocument("name", "object-0")) - findOne.run { - assertIs(this) - assertEquals("object-0", this["name"]!!.asString().value) - } - } - - @Test - fun findOne_links() = runBlocking { - Realm.open( - SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query()) - add(it.query()) - } - .build() - ).use { realm -> - val syncedParent = realm.write { - copyToRealm(ParentCollectionDataType().apply { child = ChildCollectionDataType() }) - } - realm.syncSession.uploadAllLocalChanges(30.seconds) - - @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection() - - val mongoDBClientParent = retry( - { parentCollection.findOne() }, - { response: ParentCollectionDataType? -> response != null }, - ) - assertEquals(syncedParent._id, mongoDBClientParent!!._id) - assertEquals(syncedParent.child!!._id, mongoDBClientParent.child!!._id) - } - } - - @Test - fun findOne_typedLinks() = runBlocking { - Realm.open( - SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query()) - add(it.query()) - } - .build() - ).use { realm -> - val syncedParent = realm.write { - copyToRealm( - ParentCollectionDataType().apply { - any = RealmAny.create(ChildCollectionDataType()) - } - ) - } - // We need to upload schema before proceeding - realm.syncSession.uploadAllLocalChanges(30.seconds) - - @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection() - - val mongoDBClientParent = retry( - action = { parentCollection.findOne() }, - until = { response -> response != null } - ) - assertEquals(syncedParent._id, mongoDBClientParent!!._id) - assertEquals(syncedParent.any!!.asRealmObject()._id, mongoDBClientParent.any!!.asRealmObject()._id) - } - } - - @Test - fun findOne_typedLinks_unknownClassBecomesDictionary() = runBlocking { - Realm.open( - SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query()) - add(it.query()) - } - .build() - ).use { realm -> - realm.write { - copyToRealm( - ParentCollectionDataType().apply { - any = RealmAny.create(ChildCollectionDataType()) - } - ) - } - // We need to upload schema before proceeding - realm.syncSession.uploadAllLocalChanges(30.seconds) - - @OptIn(ExperimentalKBsonSerializerApi::class) - val parentCollection = collection( - EJson( - serializersModule = realmSerializerModule( - setOf(ParentCollectionDataType::class) - ) - ) - ) - - // We need to await until the response is non-null otherwise there will be nothing - // to deserialize and no exception will be thrown - retry( - action = { parentCollection.findOne() }, - until = { response -> response != null } - )!!.run { - assertEquals("ChildCollectionDataType", any!!.asDictionary()["${"$"}ref"]!!.asString()) - } - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOne_embeddedObjects() = runBlocking { - // Empty collections - assertNull(collection.findOne()) - - val parentCollection = collection() - parentCollection.insertOne( - ParentCollectionDataType().apply { - embeddedChild = EmbeddedChildCollectionDataType().apply { name = "EMBEDDED-NAME" } - } - ) - - parentCollection.find().single().run { - assertEquals("EMBEDDED-NAME", embeddedChild!!.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOne_extraFieldsAreDiscarded() = runBlocking { - val withDocumentClass = collection.withDocumentClass() - withDocumentClass.insertOne( - BsonDocument( - mapOf( - "_id" to BsonInt32(Random.nextInt()), - "name" to BsonString("object-1"), - "extra" to BsonString("extra"), - ) - ) - ) - - // Show that remote method returns extra properties - withDocumentClass.findOne()!!.let { - assertEquals("extra", it["extra"]!!.asString().value) - } - // But these properties are silently discarded by the serialization framework - assertIs(collection.findOne()) - } - - @Test - fun findOne_missingFieldsGetsDefaults() = runBlocking { - collection.insertOne(CollectionDataType("object-1")) - collection.findOne( - projection = BsonDocument("""{ "name" : 0}""") - ).run { - assertIs(this) - assertEquals("Default", this.name) - } - } - - @Test - abstract fun findOne_unknownCollection() - - @Test - fun findOne_fails() = runBlocking { - assertFailsWithMessage("operator") { - collection.findOne(BsonDocument("\$who", 1)) - } - } - - @Test - fun find() = runBlocking { - assertTrue { collection.find().isEmpty() } - - val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - - assertEquals(10, collection.find().size) - - collection.find(filter = BsonDocument("name", "object-1")).let { - assertEquals(2, it.size) - } - - // Limit - collection.find( - limit = 5, - ).let { - assertEquals(5, it.size) - } - - // Projection - collection.find( - filter = BsonDocument("name", "object-1"), - projection = BsonDocument("""{ "name" : 0}"""), - ).let { - assertEquals(2, it.size) - it.forEach { - assertEquals("Default", it.name) - // _id is included by default - assertTrue(it._id in ids) - } - } - collection.find( - filter = BsonDocument("name", "object-1"), - projection = BsonDocument("""{ "_id" : 0}"""), - ).let { - assertEquals(2, it.size) - it.forEach { - assertEquals("object-1", it.name) - // Objects have new ids - assertFalse(it._id in ids) - } - } - - // Sort - collection.find(sort = BsonDocument("""{ "name" : -1}""")).let { results -> - assertEquals(names.sorted().reversed(), results.map { it.name }) - } - collection.find(sort = BsonDocument("""{ "name" : 1}""")).let { results -> - assertEquals(names.sorted(), results.map { it.name }) - } - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun find_explicitTypes() = runBlocking { - collection.find().let { assertTrue { it.isEmpty() } } - - val reshapedCollection = collection.withDocumentClass() - - val names = (1..10).map { "object-${it % 5}" } - reshapedCollection.insertMany(names.map { BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString(it)) }) - - reshapedCollection.find().let { results -> - results.forEach { - assertIs(it) - assertTrue { it.asDocument()["name"]!!.asString().value in names } - } - } - } - - @Test - fun find_fails() = runBlocking { - assertFailsWithMessage("unknown top level operator: \$who.") { - collection.find(filter = BsonDocument("\$who", 1)).first() - } - } - - @Test - fun aggregate() = runBlocking { - collection.aggregate(listOf()).let { assertTrue { it.isEmpty() } } - - val names = (1..10).map { "object-${it % 5}" } - collection.insertMany(names.map { CollectionDataType(it) }) - - collection.aggregate( - pipeline = listOf() - ).let { - assertEquals(10, it.size) - it.forEach { - assertTrue { it.name in names } - } - } - - collection.aggregate( - pipeline = listOf(BsonDocument("\$sort", BsonDocument("name", -1)), BsonDocument("\$limit", 2)) - ).let { - assertEquals(2, it.size) - it.forEach { - assertEquals("object-4", it.name) - } - } - } - - @Test - fun aggregate_fails() = runBlocking { - assertFailsWithMessage("Unrecognized pipeline stage name: '\$who'.") { - collection.aggregate(pipeline = listOf(BsonDocument("\$who", 1))) - } - } - - @Test - fun insertOne() = runBlocking { - assertEquals(0, collection.find().size) - - collection.insertOne(CollectionDataType("object-1")).let { - assertIs(it) - } - assertEquals(1, collection.find().size) - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun insertOne_links() = runBlocking { - // Open a synced realm and verified that the linked entities we upload through the - Realm.open( - SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query()) - add(it.query()) - } - .build() - ).use { realm -> - // We need to upload schema before proceeding - realm.syncSession.uploadAllLocalChanges() - - // We set up listeners to be able to react on when the objects are seen in the synced realm - val childChannel = Channel>(10) - val childListener = - async { realm.query().asFlow().collect { childChannel.send(it) } } - childChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertTrue { it.list.isEmpty() } - } - val parentChannel = Channel>(10) - val parentListener = - async { realm.query().asFlow().collect { parentChannel.send(it) } } - parentChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertTrue { it.list.isEmpty() } - } - - val childCollection = collection() - val unmanagedChild = ChildCollectionDataType() - assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) - // We can't rely on the translator to incorporate the insertOnes in order so we need to - // assure that the child is actually added before verifying the link in the parent. - childChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertEquals(unmanagedChild._id, it.list.first()._id) - } - - val parentCollection = collection() - val unmanagedParent = ParentCollectionDataType().apply { - this.child = unmanagedChild - } - val actual = parentCollection.insertOne(unmanagedParent) - assertEquals(unmanagedParent._id, actual) - - // Verifying that the parent include the correct link - parentChannel.receiveOrFail( - timeout = 5.seconds, - message = "Didn't receive update value" - ).let { - val parent = it.list.first() - assertEquals(unmanagedParent._id, parent._id) - parent.child!!.let { - assertEquals(unmanagedChild._id, it._id) - assertEquals(unmanagedChild.name, it.name) - } - } - parentListener.cancel() - childListener.cancel() - } - } - - @Test - @OptIn(ExperimentalKBsonSerializerApi::class) - fun insertOne_typedLinks() = runBlocking { - // Open a synced realm and verified that the linked entities we upload through the - Realm.open( - SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA) - .initialSubscriptions { - add(it.query()) - add(it.query()) - } - .build() - ).use { realm -> - // We need to upload schema before proceeding - realm.syncSession.uploadAllLocalChanges() - - // We set up listeners to be able to react on when the objects are seen in the synced realm - val childChannel = Channel>(10) - val childListener = - async { realm.query().asFlow().collect { childChannel.send(it) } } - childChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertTrue { it.list.isEmpty() } - } - val parentChannel = Channel>(10) - val parentListener = - async { realm.query().asFlow().collect { parentChannel.send(it) } } - parentChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertTrue { it.list.isEmpty() } - } - - val childCollection = collection() - assertEquals(0, childCollection.find().size) - val unmanagedChild = ChildCollectionDataType() - assertEquals(unmanagedChild._id, childCollection.insertOne(unmanagedChild)) - // We can't rely on the translator to incorporate the insertOnes in order so we need to - // assure that the child is actually added before verifying the link in the parent. - childChannel.receiveOrFail(message = "Didn't receive initial value").let { - assertEquals(unmanagedChild._id, it.list.first()._id) - } - - val parentCollection = collection() - val unmanagedParent = ParentCollectionDataType().apply { - this.any = RealmAny.create(unmanagedChild) - } - - val actual = parentCollection.insertOne(unmanagedParent) - assertEquals(unmanagedParent._id, actual) - - // Verifying that the parent include the correct link - parentChannel.receiveOrFail( - timeout = 5.seconds, - message = "Didn't receive update value" - ).let { - val parent = it.list.first() - assertEquals(unmanagedParent._id, parent._id) - parent.any!!.asRealmObject().let { - assertEquals(unmanagedChild._id, it._id) - assertEquals(unmanagedChild.name, it.name) - } - } - parentListener.cancel() - childListener.cancel() - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun insertOne_embeddedObjects() = runBlocking { - val parentCollection = collection() - // Empty collections - assertNull(parentCollection.findOne()) - parentCollection.insertOne( - ParentCollectionDataType().apply { - embeddedChild = EmbeddedChildCollectionDataType().apply { name = "EMBEDDED-NAME" } - } - ) - - parentCollection.find().single().run { - assertEquals("EMBEDDED-NAME", embeddedChild!!.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun insertOne_explicitTypes() = runBlocking { - assertEquals(0, collection.find().size) - // Inserting document without _id will use ObjectId as _id - collection.withDocumentClass().insertOne( - BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1")) - ).let { - assertIs(it) - } - // Inserted document will have ObjectId key and cannot be serialized into CollectionDataType - // so find must also use BsonDocument - assertEquals(1, collection.find().size) - } - - @Test - fun insertOne_throwsOnExistingPrimaryKey() = runBlocking { - assertEquals(0, collection.find().size) - - val document = CollectionDataType("object-1") - collection.insertOne(document).let { - assertIs(it) - } - assertFailsWithMessage("Duplicate key error") { - collection.insertOne(document) - } - assertEquals(1, collection.find().size) - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun insertOne_throwsOnMissingRequiredFields() = runBlocking { - assertFailsWithMessage("insert not permitted") { - collection.withDocumentClass().insertOne(BsonDocument("_id", ObjectId())) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun insertOne_throwsOnTypeMismatch() = runBlocking { - assertFailsWithMessage("insert not permitted") { - collection.withDocumentClass().insertOne( - BsonDocument(mapOf("_id" to ObjectId(), "name" to BsonString("object-1"))) - ) - } - } - @Test - fun insertMany() = runBlocking { - assertEquals(0, collection.find().size) - - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }).let { ids -> - assertEquals(10, ids.size) - ids.forEach { id -> - assertIs(id) - } - } - - assertEquals(10, collection.find().size) - } - - @Test - fun insertMany_explictTyped() = runBlocking { - assertEquals(0, collection.find().size) - - collection.insertMany( - (1..10).map { - BsonDocument( - "_id" to BsonInt32(Random.nextInt()), - "name" to BsonString("object-${it % 5}") - ) - } - ).let { - assertEquals(10, it.size) - it.forEach { - assertIs(it) - } - } - assertEquals(10, collection.find().size) - } - - // InsertMany with links - - @Test - fun insertMany_throwsOnEmptyList() = runBlocking { - assertFailsWithMessage("must provide at least one element") { - collection.insertMany(emptyList()) - } - } - - @Test - fun insertMany_throwsOnExistingPrimaryKey() = runBlocking { - assertEquals(0, collection.find().size) - - val document = CollectionDataType("object-1") - assertFailsWithMessage("Duplicate key error") { - collection.insertMany(listOf(document.apply { name = "sadf" }, document)) - } - // Above call will throw an error, but we have actually inserted one document - assertEquals(1, collection.find().size) - } - - @Test - fun insertMany_throwsOnMissingRequiredFields() = runBlocking { - assertFailsWithMessage("insert not permitted") { - collection.insertMany(listOf(BsonDocument())) - } - } - - @Test - fun insertMany_throwsOnTypeMismatch() = runBlocking { - assertFailsWithMessage("insert not permitted") { - collection.insertMany( - listOf( - BsonDocument( - mapOf( - "_id" to ObjectId(), - "name" to BsonString("object-1") - ) - ) - ) - ) - } - } - - @Test - fun deleteOne() = runBlocking { - assertFalse { collection.deleteOne(filter = BsonDocument()) } - - assertEquals( - 2, - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1") - ) - ).size - ) - assertEquals(2, collection.count(filter = BsonDocument("""{ "name": "object-1" }"""))) - - assertTrue { collection.deleteOne(filter = BsonDocument("""{ "name": "object-1" }""")) } - assertEquals(1, collection.count(filter = BsonDocument("""{ "name": "object-1" }"""))) - } - - @Test - fun deleteOne_fails() = runBlocking { - assertFailsWithMessage("unknown top level operator: \$who.") { - collection.deleteOne(filter = BsonDocument("\$who", 1)) - } - } - - @Test - fun deleteMany() = runBlocking { - assertEquals(0, collection.deleteMany(filter = BsonDocument())) - - collection.insertMany((1..10).map { CollectionDataType("object-${it % 5}") }) - assertEquals(10, collection.find().size) - - assertEquals(2, collection.deleteMany(filter = BsonDocument("""{ "name": "object-1" }"""))) - - assertEquals(8, collection.find().size) - - assertEquals(8, collection.deleteMany(filter = BsonDocument())) - } - - @Test - fun deleteMany_fails() = runBlocking { - assertFailsWithMessage("unknown top level operator: \$who.") { - collection.deleteMany(filter = BsonDocument("\$who", 1)) - } - } - - @Test - fun updateOne() = runBlocking { - assertEquals(0, collection.count()) - - assertEquals( - 4, - collection.insertMany( - listOf( - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-1"), - CollectionDataType("object-1") - ) - ).size - ) - assertEquals(4, collection.count()) - - // Update no match - collection.updateOne( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - update = BsonDocument("\$set", BsonDocument("""{ "name": "UPDATED"}""")), - ).let { (updated, upsertedId) -> - assertFalse(updated) - assertNull(upsertedId) - } - - // Update with match match - collection.updateOne( - filter = BsonDocument("""{ "name": "object-1"}"""), - update = BsonDocument("\$set", BsonDocument("""{ "name": "object-2"}""")), - ).let { (updated, upsertedId) -> - assertTrue(updated) - assertNull(upsertedId) - } - assertEquals(4, collection.count()) - assertEquals(3, collection.count(filter = BsonDocument("""{"name": "object-1"}"""))) - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) - - // Upsert no match - collection.updateOne( - filter = BsonDocument("""{ "name": "object-3"}"""), - update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), - upsert = true - ).let { (updated, upsertedId) -> - assertFalse(updated) - assertIs(upsertedId) - } - assertEquals(5, collection.count()) - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) - - // Upsert with match - collection.updateOne( - filter = BsonDocument(json = """{ "name": "object-2"}"""), - update = BsonDocument(""" { "name": "object-3"}"""), - upsert = true - ).let { (updated, upsertedId) -> - assertTrue(updated) - assertNull(upsertedId) - } - assertEquals(5, collection.count()) - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "object-2"}"""))) - } - - @Test - fun updateOne_explicitTypes() = runBlocking { - val upsertWithoutMatch = collection.updateOne( - filter = BsonDocument("""{ "name": "object-3"}"""), - update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), - upsert = true - ) - upsertWithoutMatch.let { (updated, upsertedId) -> - assertFalse(updated) - assertIs(upsertedId) - } - } - - @Test - fun updateOne_fails() = runBlocking { - assertFailsWithMessage("unknown top level operator: \$who.") { - collection.updateOne(filter = BsonDocument("\$who", 1), update = BsonDocument()) - } - } - - @Test - fun updateMany() = runBlocking { - assertEquals(0, collection.count()) - assertEquals( - 4, - collection.insertMany( - listOf( - CollectionDataType("x"), - CollectionDataType("x"), - CollectionDataType("y"), - CollectionDataType("z") - ) - ).size - ) - assertEquals(4, collection.count()) - // Update with no match - collection.updateMany( - filter = BsonDocument("""{"name": "NOMATCH"}"""), - update = BsonDocument("""{"name": "UPDATED"}"""), - ).let { (modifiedCount, upsertedId) -> - assertEquals(0L, modifiedCount) - assertNull(upsertedId) - } - assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) - assertEquals(4, collection.count()) - - // Update with match - collection.updateMany( - filter = BsonDocument("""{ "name": "x"}"""), - update = BsonDocument("""{ "name": "UPDATED"}"""), - ).let { (modifiedCount, upsertedId) -> - assertEquals(2L, modifiedCount) - assertNull(upsertedId) - } - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) - assertEquals(4, collection.count()) - - // Upsert no match - collection.updateMany( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), - upsert = true - ).let { (modifiedCount, upsertedId) -> - assertEquals(0L, modifiedCount) - assertIs(upsertedId) - } - assertEquals(5, collection.count()) - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPSERTED"}"""))) - - // Upsert with match - collection.updateMany( - filter = BsonDocument("""{ "name": "y"}"""), - update = BsonDocument(""" { "name": "z"}"""), upsert = true - ).let { (modifiedCount, upsertedId) -> - assertEquals(1L, modifiedCount) - assertNull(upsertedId) - } - assertEquals(5, collection.count()) - assertEquals(0, collection.count(filter = BsonDocument("""{"name": "y"}"""))) - } - - @Test - fun updateMany_explicitTypes() = runBlocking { - collection.updateMany( - filter = BsonDocument("""{ "name": "object-3"}"""), - update = BsonDocument(""" { "name": "object-2", "_id" : ${Random.nextInt()}}"""), - upsert = true - ).let { (modifiedCount, upsertedId) -> - assertEquals(0, modifiedCount) - assertIs(upsertedId) - } - } - - @Test - fun updateMany_fails() = runBlocking { - assertFailsWithMessage("Unknown modifier: \$who") { - collection.updateOne(filter = BsonDocument(), update = BsonDocument("\$who", 1)) - } - } - - @Test - fun findOneAndUpdate() = runBlocking { - assertNull(collection.findOneAndUpdate(filter = BsonDocument(), update = BsonDocument())) - - val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - - // Update with no match - assertNull( - collection.findOneAndUpdate( - filter = BsonDocument("""{"name": "NOMATCH"}"""), - update = BsonDocument("""{"name": "UPDATED"}"""), - ) - ) - assertEquals(0, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) - - // Update with match - return old - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "object-1"}"""), - update = BsonDocument("""{ "name": "UPDATED"}"""), - )!!.let { - assertEquals("object-1", it.name) - } - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) - // Update with match - return new - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "object-1"}"""), - update = BsonDocument("""{ "name": "UPDATED"}"""), - returnNewDoc = true - )!!.let { - assertEquals("UPDATED", it.name) - } - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "UPDATED"}"""))) - - // Upsert no match - assertNull( - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), - upsert = true, - ) - ) - // Upsert no match - return new document - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - update = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), - upsert = true, - returnNewDoc = true, - )!!.let { - assertEquals("UPSERTED", it.name) - } - - // Upsert with match - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "object-2"}"""), - update = BsonDocument(""" { "name": "UPSERTED" }"""), - upsert = true, - )!!.let { - assertEquals("object-2", it.name) - } - collection.findOneAndUpdate( - filter = BsonDocument("""{ "name": "object-2"}"""), - update = BsonDocument(""" { "name": "UPSERTED"}"""), - upsert = true, - returnNewDoc = true, - )!!.let { - assertEquals("UPSERTED", it.name) - } - - // Project without name - collection.findOneAndUpdate( - filter = BsonDocument("name", "object-3"), - update = BsonDocument(""" { "name": "UPDATED"}"""), - projection = BsonDocument("""{ "name" : 0}""") - )!!.run { - assertEquals("Default", this.name) - // _id is included by default and matched one of the previously inserted objects - assertTrue { this._id in ids } - } - // Project without _id, have to be explicitly excluded - collection.findOneAndUpdate( - filter = BsonDocument("name", "object-3"), - update = BsonDocument(""" { "name": "UPDATED"}"""), - projection = BsonDocument("""{ "_id" : 0}""") - )!!.run { - assertEquals("object-3", name) - // We don't know the id as the constructor default generated a new one - assertFalse { this._id in ids } - } - - // Sort - val sortedNames: List = collection.find().map { it.name }.sorted() - collection.findOneAndUpdate( - filter = BsonDocument(), - update = BsonDocument(""" { "name": "FIRST"}"""), - sort = BsonDocument(mapOf("name" to BsonInt32(1))) - )!!.run { - assertEquals(sortedNames.first(), this.name) - } - collection.findOneAndUpdate( - filter = BsonDocument(), - update = BsonDocument(""" { "name": "LAST"}"""), - sort = BsonDocument(mapOf("name" to BsonInt32(-1))) - )!!.run { - assertEquals(sortedNames.last(), this.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOneAndUpdate_explicitTypes() = runBlocking { - collection.insertOne(CollectionDataType("object-1")) - - collection.withDocumentClass().findOneAndUpdate( - filter = BsonDocument("""{ "name": "object-1"}"""), - update = BsonDocument("""{ "name": "UPDATED"}"""), - )!!.let { - assertEquals("object-1", it.asDocument()["name"]!!.asString().value) - } - } - - @Test - fun findOneAndUpdate_fails() = runBlocking { - assertFailsWithMessage("Unknown modifier: \$who") { - collection.findOneAndUpdate(filter = BsonDocument(), update = BsonDocument("\$who", 1)) - } - } - - @Test - fun findOneAndReplace() = runBlocking { - assertNull(collection.findOneAndReplace(filter = BsonDocument(), document = BsonDocument())) - - val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - - // Replace with no match - assertNull( - collection.findOneAndReplace( - filter = BsonDocument("""{"name": "NOMATCH"}"""), - document = BsonDocument(""" { "name": "REPLACED", "_id" : ${Random.nextInt()}}"""), - ) - ) - assertEquals(0, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) - - // Replace with match - return old - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "object-1"}"""), - document = BsonDocument(""" { "name": "REPLACED"}"""), - )!!.let { - assertEquals("object-1", it.name) - } - assertEquals(1, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) - - // Replace with match - return new - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "object-1"}"""), - document = BsonDocument("""{ "name": "REPLACED"}"""), - returnNewDoc = true - )!!.let { - assertEquals("REPLACED", it.name) - } - assertEquals(2, collection.count(filter = BsonDocument("""{"name": "REPLACED"}"""))) - - // Upsert no match - assertNull( - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - document = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), - upsert = true, - ) - ) - // Upsert no match - return new document - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "NOMATCH"}"""), - document = BsonDocument(""" { "name": "UPSERTED", "_id" : ${Random.nextInt()}}"""), - upsert = true, - returnNewDoc = true, - )!!.let { - assertEquals("UPSERTED", it.name) - } - - // Upsert with match - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "object-2"}"""), - document = BsonDocument(""" { "name": "UPSERTED" }"""), - upsert = true, - )!!.let { - assertEquals("object-2", it.name) - } - collection.findOneAndReplace( - filter = BsonDocument("""{ "name": "object-2"}"""), - document = BsonDocument(""" { "name": "UPSERTED"}"""), - upsert = true, - returnNewDoc = true, - )!!.let { - assertEquals("UPSERTED", it.name) - } - - // Project without name - collection.findOneAndReplace( - filter = BsonDocument("name", "object-3"), - document = BsonDocument(""" { "name": "REPLACED"}"""), - projection = BsonDocument("""{ "name" : 0}""") - )!!.run { - assertEquals("Default", this.name) - // _id is included by default and matched one of the previously inserted objects - assertTrue { this._id in ids } - } - // Project without _id, have to be explicitly excluded - collection.findOneAndReplace( - filter = BsonDocument("name", "object-3"), - document = BsonDocument(""" { "name": "REPLACED"}"""), - projection = BsonDocument("""{ "_id" : 0}""") - )!!.run { - assertEquals("object-3", name) - // We don't know the id as the constructor default generated a new one - assertFalse { this._id in ids } - } - - // Sort - val sortedNames: List = collection.find().map { it.name!! }.sorted() - collection.findOneAndReplace( - filter = BsonDocument(), - document = BsonDocument(""" { "name": "FIRST"}"""), - sort = BsonDocument(mapOf("name" to BsonInt32(1))) - )!!.run { - assertEquals(sortedNames.first(), this.name) - } - collection.findOneAndReplace( - filter = BsonDocument(), - document = BsonDocument(""" { "name": "LAST"}"""), - sort = BsonDocument(mapOf("name" to BsonInt32(-1))) - )!!.run { - assertEquals(sortedNames.last(), this.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOneAndReplace_explicitTypes() = runBlocking { - collection.insertOne(CollectionDataType("object-1")) - - collection.withDocumentClass().findOneAndReplace( - filter = BsonDocument("""{ "name": "object-1"}"""), - document = BsonDocument("""{ "name": "REPLACED"}"""), - )!!.let { - assertEquals("object-1", it.asDocument()["name"]!!.asString().value) - } - } - - @Test - fun findOneAndReplace_fails() = runBlocking { - assertFailsWithMessage("the replace operation document must not contain atomic operators") { - collection.findOneAndReplace( - filter = BsonDocument(), - document = BsonDocument("\$who", 1) - ) - } - } - - @Test - fun findOneAndDelete() = runBlocking { - assertNull(collection.findOneAndDelete(filter = BsonDocument(), projection = BsonDocument())) - - val names = (1..10).map { "object-${it % 5}" } - val ids: List = collection.insertMany(names.map { CollectionDataType(it) }) - - // Delete with no match - assertNull(collection.findOneAndDelete(filter = BsonDocument("""{"name": "NOMATCH"}"""))) - assertEquals(10, collection.count(filter = BsonDocument())) - - // Delete with match - collection.findOneAndDelete( - filter = BsonDocument("""{ "name": "object-1"}"""), - )!!.let { - assertEquals("object-1", it.name) - } - assertEquals(9, collection.count(filter = BsonDocument())) - - // Project without name - collection.findOneAndDelete( - filter = BsonDocument("name", "object-3"), - projection = BsonDocument("""{ "name" : 0}""") - )!!.run { - assertEquals("Default", this.name) - // _id is included by default and matched one of the previously inserted objects - assertTrue { this._id in ids } - } - // Project without _id, have to be explicitly excluded - collection.findOneAndDelete( - filter = BsonDocument("name", "object-3"), - projection = BsonDocument("""{ "_id" : 0}""") - )!!.run { - assertEquals("object-3", name) - // We don't know the id as the constructor default generated a new one - assertFalse { this._id in ids } - } - - // Sort - val sortedNames: List = collection.find().map { it.name!! }.sorted() - collection.findOneAndDelete( - filter = BsonDocument(), - sort = BsonDocument(mapOf("name" to BsonInt32(1))) - )!!.run { - assertEquals(sortedNames.first(), this.name) - } - collection.findOneAndDelete( - filter = BsonDocument(), - sort = BsonDocument(mapOf("name" to BsonInt32(-1))) - )!!.run { - assertEquals(sortedNames.last(), this.name) - } - } - - @OptIn(ExperimentalKBsonSerializerApi::class) - @Test - fun findOneAndDelete_explicitTypes() = runBlocking { - collection.insertOne(CollectionDataType("object-1")) - - collection.withDocumentClass().findOneAndDelete( - filter = BsonDocument("""{ "name": "object-1"}"""), - )!!.let { - assertEquals("object-1", it.asDocument()["name"]!!.asString().value) - } - } - - @Test - fun findOneAndDelete_fails() = runBlocking { - assertFailsWithMessage("unknown top level operator: \$who.") { - collection.findOneAndDelete(filter = BsonDocument("\$who", 1)) - } - } - - @Test - fun throwsOnLoggedOutUser() = runBlocking { - user.logOut() - assertFailsWithMessage("unauthorized") { - collection.findOne() - } - } -} - -// Helper method to be able to differentiate collection creation across test classes -@OptIn(ExperimentalKBsonSerializerApi::class) -inline fun MongoCollectionTests.collection(eJson: EJson? = null): MongoCollection { - return when (this) { - is MongoCollectionFromDatabaseTests -> database.collection(T::class.simpleName!!, eJson) - is MongoCollectionFromClientTests -> client.collection(eJson) - } -} - -// Helper method to easy BsonDocument construction from Kotlin types and avoid BsonValue wrappers. -@Suppress("name") -@OptIn(ExperimentalKBsonSerializerApi::class) -inline operator fun BsonDocument.Companion.invoke( - key: String, - value: T, -): BsonDocument { - return BsonDocument(key to EJson.Default.encodeToBsonValue(value)) -} - -// Class that is unknown to the server. Should never be inserted as the server would then -// automatically create the collection. -@Serializable -class NonSchemaType : RealmObject { - var name = "Unknown" - - // Supplying custom companion object to work around that multiple K2 FIR extension clashes if - // they both generate a Companion. - // https://youtrack.jetbrains.com/issue/KT-62194/K2-Two-compiler-plugins-interference-in-generated-companion-object - companion object -} - -// Distinct data type with same fields as the above CollectionDataType used to showcase injection -// of custom serializers. -@PersistedName("CollectionDataType") -class CustomDataType(var name: String, var _id: Int = Random.nextInt()) : RealmObject { - @Suppress("unused") - constructor() : this("Default") -} - -// Custom serializers to showcase that we can inject serializers throughout the MongoClient APIs. -class CustomDataTypeSerializer : KSerializer { - - val serializer = BsonValue.serializer() - override val descriptor: SerialDescriptor = serializer.descriptor - override fun deserialize(decoder: Decoder): CustomDataType { - return decoder.decodeSerializableValue(serializer).let { - val _id = it.asDocument()["_id"]!!.asInt32().value - val name = it.asDocument()["name"]!!.asString().value - CustomDataType(name, _id) - } - } - - override fun serialize(encoder: Encoder, value: CustomDataType) { - val document = BsonDocument() - document["_id"] = BsonInt32(value._id) - document["name"] = BsonString(value.name) - encoder.encodeSerializableValue(serializer, document) - } -} - -@OptIn(ExperimentalKBsonSerializerApi::class) -val customEjsonSerializer = EJson( - serializersModule = SerializersModule { - contextual(CustomDataType::class, CustomDataTypeSerializer()) - } -) diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt deleted file mode 100644 index c7311fc5b3..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDBSerializerTests.kt +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -@file:OptIn(ExperimentalKBsonSerializerApi::class) - -package io.realm.kotlin.test.mongodb.common.mongo - -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.entities.sync.flx.FlexEmbeddedObject -import io.realm.kotlin.entities.sync.flx.FlexParentObject -import io.realm.kotlin.ext.asRealmObject -import io.realm.kotlin.ext.realmAnyDictionaryOf -import io.realm.kotlin.ext.realmAnyListOf -import io.realm.kotlin.ext.realmDictionaryOf -import io.realm.kotlin.ext.realmListOf -import io.realm.kotlin.ext.realmSetOf -import io.realm.kotlin.mongodb.mongo.realmSerializerModule -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmUUID -import kotlinx.serialization.SerializationException -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import org.mongodb.kbson.ObjectId -import org.mongodb.kbson.serialization.EJson -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class MongoDBSerializerTests { - - lateinit var eJson: EJson - - @BeforeTest - fun setUp() { - @Suppress("invisible_member") - eJson = EJson( - serializersModule = realmSerializerModule( - setOf( - SyncObjectWithAllTypes::class, - FlexParentObject::class, - FlexEmbeddedObject::class - ) - ) - ) - } - - @Test - fun serialize() { - assertEqualWithoutWhitespace(EXPECTED_EJSON, eJson.encodeToString(syncObjectWithAllTypes)) - } - - @Test - fun serialize_embeddedObject() { - assertEqualWithoutWhitespace( - EXPECTED_EJSON_EMBEDDED, - eJson.encodeToString(syncObjectWithEmbeddedObject) - ) - } - - @Test - fun deserialize() { - val actual = eJson.decodeFromString(EXPECTED_EJSON) - with(actual) { - // Verify all different types - assertEquals(syncObjectWithAllTypes._id, _id) - assertEquals(syncObjectWithAllTypes.stringField, stringField) - assertEquals(syncObjectWithAllTypes.byteField, byteField) - assertEquals(syncObjectWithAllTypes.charField, charField) - assertEquals(syncObjectWithAllTypes.shortField, shortField) - assertEquals(syncObjectWithAllTypes.intField, intField) - assertEquals(syncObjectWithAllTypes.longField, longField) - assertEquals(syncObjectWithAllTypes.booleanField, booleanField) - assertEquals(syncObjectWithAllTypes.doubleField, doubleField) - assertEquals(syncObjectWithAllTypes.floatField, floatField) - assertEquals(syncObjectWithAllTypes.decimal128Field, decimal128Field) - assertEquals(syncObjectWithAllTypes.realmInstantField, realmInstantField) - assertContentEquals(syncObjectWithAllTypes.binaryField, binaryField) - assertEquals(syncObjectWithAllTypes.mutableRealmIntField, mutableRealmIntField) - assertEquals(syncObjectWithAllTypes.objectField!!._id, objectField!!._id) - assertEquals(syncObjectWithAllTypes.objectIdField, objectIdField) - assertEquals(syncObjectWithAllTypes.realmUUIDField, realmUUIDField) - assertEquals(syncObjectWithAllTypes.realmInstantField, realmInstantField) - // Verify RealmAny with nested lists and dictionaries - nullableRealmAnyField!!.asList().let { - val expectedRealmAnyList = syncObjectWithAllTypes.nullableRealmAnyField!!.asList() - assertEquals(expectedRealmAnyList.size, it.size) - assertEquals( - expectedRealmAnyList[0]!!.asRealmObject()._id, - it[0]!!.asRealmObject()._id - ) - // Nested list - val expectedNestedList = expectedRealmAnyList[1]!!.asList() - val actualNestList = it[1]!!.asList() - assertEquals(expectedNestedList.size, actualNestList.size) - assertEquals(expectedNestedList[0], actualNestList[0]) - assertEquals(expectedNestedList[1], actualNestList[1]) - assertEquals( - expectedNestedList[2]!!.asRealmObject()._id, - actualNestList[2]!!.asRealmObject()._id - ) - // Nested dictionary - val expectedNestedDictionary = expectedRealmAnyList[2]!!.asDictionary() - val actualNestDictionary = it[2]!!.asDictionary() - assertEquals(expectedNestedDictionary.size, actualNestDictionary.size) - assertEquals(expectedNestedDictionary["int"], actualNestDictionary["int"]) - assertEquals(expectedNestedDictionary["string"], actualNestDictionary["string"]) - assertEquals( - expectedNestedDictionary["object"]!!.asRealmObject()._id, - actualNestDictionary["object"]!!.asRealmObject()._id - ) - } - // Smoke test collection fields. Assume that type specific details are verified by the above - // tests - assertEquals(syncObjectWithAllTypes.stringRealmList, stringRealmList) - assertEquals(syncObjectWithAllTypes.stringRealmSet, stringRealmSet) - assertEquals(syncObjectWithAllTypes.stringRealmDictionary, stringRealmDictionary) - } - } - - @Test - fun deserialize_embeddedObject() { - val actual = eJson.decodeFromString(EXPECTED_EJSON_EMBEDDED) - with(actual) { - assertEquals(syncObjectWithEmbeddedObject._id, _id) - assertEquals(syncObjectWithEmbeddedObject.embedded!!.embeddedName, embedded!!.embeddedName) - } - } - - @Test - fun deserialize_defaults() { - eJson.decodeFromString("{}") - } - - @Test - fun deserialize_throwsOnMalformedJSON() { - assertFailsWith { - eJson.decodeFromString("""{ "missing_value" : }""") - } - } - - @Test - fun deserialize_throwsOnUnknownField() { - assertFailsWithMessage("Unknown field 'unknown_field' for type SyncObjectWithAllTypes") { - eJson.decodeFromString("""{ "unknown_field": 1 }""") - } - } - - @Test - fun deserialize_unknownClassRefIsTreatedAsEmbeddedDict() { - val o = eJson.decodeFromString("""{ "nullableRealmAnyField": { "${"$"}ref": "unknown_class" } }""") - assertEquals("unknown_class", o.nullableRealmAnyField!!.asDictionary()["${"$"}ref"]!!.asString()) - } - @Test - fun deserialize_missingIdIsTreatedAsEmbeddedDict() { - val o = eJson.decodeFromString( - """ - { "nullableRealmAnyField": { "${"$"}ref" : "SyncObjectWithAllTyped", "unknown_field" : "UNKNOWN" } } - """.trimIndent() - ) - val realmAnyDictionary = o.nullableRealmAnyField!!.asDictionary() - assertEquals("SyncObjectWithAllTyped", realmAnyDictionary["${"$"}ref"]!!.asString()) - assertEquals("UNKNOWN", realmAnyDictionary["unknown_field"]!!.asString()) - } -} - -private fun assertEqualWithoutWhitespace(a: String, b: String) { - assertEquals(a.replace("\\s+".toRegex(), ""), b.replace("\\s+".toRegex(), "")) -} - -// Ensure test is reproducible by clearing random/time dependant values -// EJSON cannot represent nano second precision, so nanosecond fraction must be 0 -private val date = RealmInstant.from(172, 0) -private val objectId = ObjectId(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) -private val uuid = RealmUUID.Companion.from(byteArrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)) -private val child = SyncObjectWithAllTypes().apply { _id = "CHILD" } - -private val syncObjectWithAllTypes: SyncObjectWithAllTypes = SyncObjectWithAllTypes().apply { - - _id = "PARENT" - realmInstantField = date - objectIdField = objectId - objectIdRealmList = realmListOf(objectId) - objectIdRealmSet = realmSetOf(objectId) - objectIdRealmDictionary = realmDictionaryOf("key" to objectId) - realmUUIDField = uuid - realmUUIDRealmList = realmListOf(uuid) - realmUUIDRealmSet = realmSetOf(uuid) - realmUUIDRealmDictionary = realmDictionaryOf("key" to uuid) - objectField = child - nullableRealmAnyField = realmAnyListOf( - child, - realmAnyListOf(1, "Realm", child), - realmAnyDictionaryOf("int" to 1, "string" to "Realm", "object" to child) - ) -} - -private val EXPECTED_EJSON_EMBEDDED = """ - {"_id":{"${"$"}oid":"000102030405060708090a0b"},"section":{"${"$"}numberInt":"0"},"name":"","age":{"${"$"}numberInt":"42"},"child":null,"embedded":{"embeddedName":"EMBEDDED"}} -""" - -private val syncObjectWithEmbeddedObject = FlexParentObject().apply { - _id = objectId - embedded = FlexEmbeddedObject().apply { - embeddedName = "EMBEDDED" - } -} - -private val EXPECTED_EJSON = """ -{ - "_id":"PARENT", - "stringField":"hello world", - "byteField":{ - "${"$"}numberInt":"0" - }, - "charField":{ - "${"$"}numberInt":"0" - }, - "shortField":{ - "${"$"}numberInt":"0" - }, - "intField":{ - "${"$"}numberInt":"0" - }, - "longField":{ - "${"$"}numberLong":"0" - }, - "booleanField":true, - "doubleField":{ - "${"$"}numberDouble":"0.0" - }, - "floatField":{ - "${"$"}numberDouble":"0.0" - }, - "decimal128Field":{ - "${"$"}numberDecimal":"0" - }, - "realmInstantField":{ - "${"$"}date":{ - "${"$"}numberLong":"172000" - } - }, - "objectIdField":{ - "${"$"}oid":"000102030405060708090a0b" - }, - "realmUUIDField":{ - "${"$"}binary":{ - "base64":"AAECAwQFBgcICQoLDA0ODw==", - "subType":"04" - } - }, - "binaryField":{ - "${"$"}binary":{ - "base64":"Kg==", - "subType":"00" - } - }, - "mutableRealmIntField":{ - "${"$"}numberLong":"42" - }, - "objectField":"CHILD", - "stringNullableField":null, - "byteNullableField":null, - "charNullableField":null, - "shortNullableField":null, - "intNullableField":null, - "longNullableField":null, - "booleanNullableField":null, - "doubleNullableField":null, - "floatNullableField":null, - "decimal128NullableField":null, - "realmInstantNullableField":null, - "objectIdNullableField":null, - "realmUUIDNullableField":null, - "binaryNullableField":null, - "objectNullableField":null, - "mutableRealmIntNullableField":null, - "nullableRealmAnyField":[ - { - "${"$"}ref":"SyncObjectWithAllTypes", - "${"$"}id":"CHILD" - }, - [ - { - "${"$"}numberLong":"1" - }, - "Realm", - { - "${"$"}ref":"SyncObjectWithAllTypes", - "${"$"}id":"CHILD" - } - ], - { - "int":{ - "${"$"}numberLong":"1" - }, - "string":"Realm", - "object":{ - "${"$"}ref":"SyncObjectWithAllTypes", - "${"$"}id":"CHILD" - } - } - ], - "nullableRealmAnyForObjectField":null, - "stringRealmList":[ - "hello world" - ], - "byteRealmList":[ - { - "${"$"}numberInt":"0" - } - ], - "charRealmList":[ - { - "${"$"}numberInt":"0" - } - ], - "shortRealmList":[ - { - "${"$"}numberInt":"0" - } - ], - "intRealmList":[ - { - "${"$"}numberInt":"0" - } - ], - "longRealmList":[ - { - "${"$"}numberLong":"0" - } - ], - "booleanRealmList":[ - true - ], - "doubleRealmList":[ - { - "${"$"}numberDouble":"0.0" - } - ], - "floatRealmList":[ - { - "${"$"}numberDouble":"0.0" - } - ], - "decimal128RealmList":[ - { - "${"$"}numberDecimal":"0.0" - } - ], - "realmInstantRealmList":[ - { - "${"$"}date":{ - "${"$"}numberLong":"-9223372036854775808" - } - } - ], - "objectIdRealmList":[ - { - "${"$"}oid":"000102030405060708090a0b" - } - ], - "realmUUIDRealmList":[ - { - "${"$"}binary":{ - "base64":"AAECAwQFBgcICQoLDA0ODw==", - "subType":"04" - } - } - ], - "binaryRealmList":[ - { - "${"$"}binary":{ - "base64":"Kg==", - "subType":"00" - } - } - ], - "objectRealmList":[ - - ], - "nullableRealmAnyRealmList":[ - { - "${"$"}numberLong":"42" - } - ], - "stringRealmSet":[ - "hello world" - ], - "byteRealmSet":[ - { - "${"$"}numberInt":"0" - } - ], - "charRealmSet":[ - { - "${"$"}numberInt":"0" - } - ], - "shortRealmSet":[ - { - "${"$"}numberInt":"0" - } - ], - "intRealmSet":[ - { - "${"$"}numberInt":"0" - } - ], - "longRealmSet":[ - { - "${"$"}numberLong":"0" - } - ], - "booleanRealmSet":[ - true - ], - "doubleRealmSet":[ - { - "${"$"}numberDouble":"0.0" - } - ], - "floatRealmSet":[ - { - "${"$"}numberDouble":"0.0" - } - ], - "decimal128RealmSet":[ - { - "${"$"}numberDecimal":"0.0" - } - ], - "realmInstantRealmSet":[ - { - "${"$"}date":{ - "${"$"}numberLong":"-9223372036854775808" - } - } - ], - "objectIdRealmSet":[ - { - "${"$"}oid":"000102030405060708090a0b" - } - ], - "realmUUIDRealmSet":[ - { - "${"$"}binary":{ - "base64":"AAECAwQFBgcICQoLDA0ODw==", - "subType":"04" - } - } - ], - "binaryRealmSet":[ - { - "${"$"}binary":{ - "base64":"Kg==", - "subType":"00" - } - } - ], - "objectRealmSet":[ - - ], - "nullableRealmAnyRealmSet":[ - { - "${"$"}numberLong":"42" - } - ], - "stringRealmDictionary":{ - "A":"hello world" - }, - "byteRealmDictionary":{ - "A":{ - "${"$"}numberInt":"0" - } - }, - "charRealmDictionary":{ - "A":{ - "${"$"}numberInt":"0" - } - }, - "shortRealmDictionary":{ - "A":{ - "${"$"}numberInt":"0" - } - }, - "intRealmDictionary":{ - "A":{ - "${"$"}numberInt":"0" - } - }, - "longRealmDictionary":{ - "A":{ - "${"$"}numberLong":"0" - } - }, - "booleanRealmDictionary":{ - "A":true - }, - "doubleRealmDictionary":{ - "A":{ - "${"$"}numberDouble":"0.0" - } - }, - "floatRealmDictionary":{ - "A":{ - "${"$"}numberDouble":"0.0" - } - }, - "decimal128RealmDictionary":{ - "A":{ - "${"$"}numberDecimal":"0.0" - } - }, - "realmInstantRealmDictionary":{ - "A":{ - "${"$"}date":{ - "${"$"}numberLong":"-9223372036854775808" - } - } - }, - "objectIdRealmDictionary":{ - "key":{ - "${"$"}oid":"000102030405060708090a0b" - } - }, - "realmUUIDRealmDictionary":{ - "key":{ - "${"$"}binary":{ - "base64":"AAECAwQFBgcICQoLDA0ODw==", - "subType":"04" - } - } - }, - "binaryRealmDictionary":{ - "A":{ - "${"$"}binary":{ - "base64":"Kg==", - "subType":"00" - } - } - }, - "nullableObjectRealmDictionary":{ - - }, - "nullableRealmAnyRealmDictionary":{ - "A":{ - "${"$"}numberLong":"42" - } - } -} -""".trimIndent() diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt deleted file mode 100644 index fc99981af3..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/mongo/MongoDatabaseTests.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common.mongo - -import io.realm.kotlin.entities.sync.CollectionDataType -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.ext.insertOne -import io.realm.kotlin.mongodb.mongo.MongoClient -import io.realm.kotlin.mongodb.mongo.MongoDatabase -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import kotlinx.serialization.SerializationException -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import kotlin.random.Random -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertIs - -@OptIn(ExperimentalKBsonSerializerApi::class) -class MongoDatabaseTests { - - lateinit var app: TestApp - lateinit var client: MongoClient - lateinit var databaseName: String - lateinit var database: MongoDatabase - - @BeforeTest - fun setUp() { - app = TestApp( - this::class.simpleName, - DefaultFlexibleSyncAppInitializer, - ) - val user = app.createUserAndLogin() - client = user.mongoClient(TEST_SERVICE_NAME) - databaseName = app.clientAppId - database = client.database(databaseName) - } - - @AfterTest - fun teadDown() { - if (this::app.isInitialized) { - app.close() - } - } - - @Test - fun properties() { - assertEquals(databaseName, database.name) - } - - @Test - fun collection_defaultTypes() = runBlocking { - val collection = database.collection("CollectionDataType") - val value = collection.insertOne(BsonDocument("_id" to BsonInt32(Random.nextInt()), "name" to BsonString("object-1"))) - assertIs(value) - } - - @Test - fun collection_typed() = runBlocking { - val collection = database.collection("CollectionDataType") - val value = collection.insertOne(CollectionDataType("object-1", Random.nextInt())) - assertIs(value) - } - - @Test - fun collection_defaultSerializer() = runBlocking { - assertIs(database.collection("CollectionDataType").insertOne(CollectionDataType("object-1"))) - } - - @Test - fun collection_customSerializer() = runBlocking { - val collectionWithDefaultSerializer = database.collection("CollectionDataType") - assertFailsWithMessage("Serializer for class 'CustomDataType' is not found.") { - collectionWithDefaultSerializer.insertOne(CustomDataType("object-1")) - } - - val collectionWithCustomSerializer = database.collection("CollectionDataType", customEjsonSerializer) - assertIs(collectionWithCustomSerializer.insertOne(CustomDataType("object-1"))) - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt deleted file mode 100644 index 93032998eb..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/nonlatin/NonLatinTests.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.realm.kotlin.test.mongodb.common.nonlatin - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.ObjectIdPk -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.asTestApp -import io.realm.kotlin.test.mongodb.common.PARTITION_BASED_SCHEMA -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.util.TestChannel -import io.realm.kotlin.test.util.TestHelper -import io.realm.kotlin.test.util.receiveOrFail -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.async -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import kotlin.test.AfterTest -import kotlin.test.BeforeTest -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -class NonLatinTests { - - private lateinit var partitionValue: String - private lateinit var user: User - private lateinit var app: TestApp - - @BeforeTest - fun setup() { - partitionValue = TestHelper.randomPartitionValue() - @OptIn(ExperimentalKBsonSerializerApi::class) - app = TestApp(this::class.simpleName, DefaultPartitionBasedAppInitializer) - val (email, password) = TestHelper.randomEmail() to "password1234" - user = runBlocking { - app.createUserAndLogIn(email, password) - } - } - - @AfterTest - fun tearDown() { - if (this::app.isInitialized) { - app.close() - } - } - - /** - * - Insert a string with the null character in MongoDB using the command server - */ - @Test - fun readNullCharacterFromMongoDB() = runBlocking { - val adminApi = app.asTestApp - val config = SyncConfiguration.Builder(user, partitionValue, schema = PARTITION_BASED_SCHEMA).build() - Realm.open(config).use { realm -> - val json: JsonObject = adminApi.insertDocument( - ObjectIdPk::class.simpleName!!, - """ - { - "name": "foo\u0000bar", - "realm_id" : "$partitionValue" - } - """.trimIndent() - )!! - val oid = json["insertedId"]!!.jsonObject["${'$'}oid"]!!.jsonPrimitive.content - assertNotNull(oid) - - val channel = TestChannel() - val job = async { - realm.query("_id = $0", BsonObjectId(oid)).first() - .asFlow().collect { - if (it.obj != null) { - channel.send(it.obj!!) - } - } - } - - val insertedObject = channel.receiveOrFail() - assertEquals(oid, insertedObject._id.toHexString()) - val char1 = "foo\u0000bar".toCharArray() - val char2 = insertedObject.name.toCharArray() - assertEquals("foo\u0000bar", insertedObject.name) - assertContentEquals(char1, char2) - job.cancel() - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/serializer/BsonEncoderTests.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/serializer/BsonEncoderTests.kt deleted file mode 100644 index d8228b36ab..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/serializer/BsonEncoderTests.kt +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -@file:Suppress("invisible_reference", "invisible_member") - -package io.realm.kotlin.test.mongodb.common.serializer - -import io.realm.kotlin.internal.toDuration -import io.realm.kotlin.mongodb.internal.BsonEncoder -import io.realm.kotlin.test.mongodb.common.utils.assertFailsWithMessage -import io.realm.kotlin.test.util.TypeDescriptor -import io.realm.kotlin.types.MutableRealmInt -import io.realm.kotlin.types.RealmAny -import io.realm.kotlin.types.RealmInstant -import io.realm.kotlin.types.RealmObject -import io.realm.kotlin.types.RealmUUID -import org.mongodb.kbson.BsonArray -import org.mongodb.kbson.BsonBinary -import org.mongodb.kbson.BsonBinarySubType -import org.mongodb.kbson.BsonBoolean -import org.mongodb.kbson.BsonDateTime -import org.mongodb.kbson.BsonDecimal128 -import org.mongodb.kbson.BsonDocument -import org.mongodb.kbson.BsonDouble -import org.mongodb.kbson.BsonInt32 -import org.mongodb.kbson.BsonInt64 -import org.mongodb.kbson.BsonInvalidOperationException -import org.mongodb.kbson.BsonNull -import org.mongodb.kbson.BsonObjectId -import org.mongodb.kbson.BsonRegularExpression -import org.mongodb.kbson.BsonString -import org.mongodb.kbson.BsonValue -import kotlin.reflect.KClass -import kotlin.test.Test -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNull - -class BsonEncoderTests { - @kotlinx.serialization.Serializable - private class SerializableClass - - private class DecoderAsserter( - val type: KClass<*>, - val value: Any? = null, - val bsonValue: BsonValue - ) - - private val primitiveAsserters = TypeDescriptor.classifiers - .filter { (key, _) -> - key != RealmAny::class && // Tested by `realmAnyEncoding` - key != RealmObject::class // Non-primitives are not supported - }.map { (key, _) -> - when (key) { - Byte::class -> DecoderAsserter( - type = Byte::class, - value = 10.toByte(), - bsonValue = BsonInt32(10.toByte().toInt()), - ) - Short::class -> DecoderAsserter( - type = Short::class, - value = 20.toShort(), - bsonValue = BsonInt32(20.toShort().toInt()) - ) - Int::class -> DecoderAsserter( - type = Int::class, - value = 30, - bsonValue = BsonInt32(30) - ) - Long::class -> DecoderAsserter( - type = Long::class, - value = 40L, - bsonValue = BsonInt64(40L) - ) - Float::class -> DecoderAsserter( - type = Float::class, - value = 50F, - bsonValue = BsonDouble(50F.toDouble()) - ) - Double::class -> DecoderAsserter( - type = Double::class, - value = 2.0, - bsonValue = BsonDouble(2.0) - ) - BsonDecimal128::class -> DecoderAsserter( - type = BsonDecimal128::class, - value = BsonDecimal128("1.2345E678"), - bsonValue = BsonDecimal128("1.2345E678") - ) - Boolean::class -> DecoderAsserter( - type = Boolean::class, - value = true, - bsonValue = BsonBoolean.TRUE_VALUE - ) - String::class -> DecoderAsserter( - type = String::class, - value = "hello world", - bsonValue = BsonString("hello world") - ) - Char::class -> DecoderAsserter( - type = Char::class, - value = 'c', - bsonValue = BsonString('c'.toString()) - ) - ByteArray::class -> DecoderAsserter( - type = ByteArray::class, - value = byteArrayOf(0x00, 0x01, 0x03), - bsonValue = BsonBinary( - BsonBinarySubType.BINARY, - byteArrayOf(0x00, 0x01, 0x03) - ) - ) - ByteArray::class -> DecoderAsserter( - type = ByteArray::class, - value = null, - bsonValue = BsonNull - ) - MutableRealmInt::class -> DecoderAsserter( - type = MutableRealmInt::class, - value = MutableRealmInt.create(15), - bsonValue = BsonInt64(15), - ) - RealmUUID::class -> RealmUUID.from("ffffffff-ffff-ffff-ffff-ffffffffffff") - .let { uuid -> - DecoderAsserter( - type = RealmUUID::class, - value = uuid, - bsonValue = BsonBinary(BsonBinarySubType.UUID_STANDARD, uuid.bytes), - ) - } - BsonObjectId::class -> DecoderAsserter( - type = BsonObjectId::class, - value = BsonObjectId("507f191e810c19729de860ea"), - bsonValue = BsonObjectId("507f191e810c19729de860ea") - ) - RealmInstant::class -> RealmInstant.from( - epochSeconds = 1668425451, - nanosecondAdjustment = 862000000 - ).let { instant -> - DecoderAsserter( - type = RealmInstant::class, - value = instant, - bsonValue = BsonDateTime(instant.toDuration().inWholeMilliseconds), - ) - } - else -> throw IllegalStateException("classifier $key not implemented") - } - } - - private val listValueAsserter = DecoderAsserter( - type = List::class, - value = primitiveAsserters.map { it.value }, - bsonValue = BsonArray(primitiveAsserters.map { it.bsonValue }) - ) - - // Map containing all BsonValues defined in primitiveAsserters with "$index" -> BsonValue - private val mapValueAsserter = DecoderAsserter( - type = Map::class, - value = primitiveAsserters.mapIndexed { index, asserter -> - "$index" to asserter.value - }.toMap(), - bsonValue = BsonDocument( - primitiveAsserters.mapIndexed { index, asserter -> - "$index" to asserter.bsonValue - }.toMap() - ) - ) - - @Test - fun encodeToBsonValue() { - (primitiveAsserters + listValueAsserter + mapValueAsserter).forEach { asserter -> - assertEquals( - asserter.bsonValue, - BsonEncoder.encodeToBsonValue(asserter.value) - ) - } - } - - @Test - fun encodeBsonValueToBsonValue() { - (primitiveAsserters + listValueAsserter + mapValueAsserter).forEach { asserter -> - assertEquals( - asserter.bsonValue, - BsonEncoder.encodeToBsonValue(asserter.bsonValue) - ) - } - } - - @Test - fun decodeFromBsonElement() { - primitiveAsserters.forEach { asserter -> - when (asserter.value) { - null -> assertNull( - BsonEncoder.decodeFromBsonValue( - resultClass = asserter.type, // Arbitrary class to encode to null - bsonValue = asserter.bsonValue - ) - ) - is ByteArray -> assertContentEquals( - asserter.value, - BsonEncoder.decodeFromBsonValue( - resultClass = asserter.type, - bsonValue = asserter.bsonValue - ) as ByteArray, - "Failed to validate types ${asserter.type.simpleName} and ${asserter.bsonValue::class.simpleName}" - ) - else -> assertEquals( - asserter.value, - BsonEncoder.decodeFromBsonValue( - resultClass = asserter.type, - bsonValue = asserter.bsonValue - ), - "Failed to validate types ${asserter.type.simpleName} and ${asserter.bsonValue::class.simpleName}" - ) - } - } - } - - @Test - fun decodeBsonElementFromBsonElement() { - primitiveAsserters.forEach { asserter -> - BsonEncoder.decodeFromBsonValue( - resultClass = asserter.bsonValue::class, - bsonValue = asserter.bsonValue - ) - } - } - - @Test - fun encodeToString_throwsUnsupportedType() { - assertFailsWithMessage("Failed to convert arguments, type 'SerializableClass' not supported. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, Array, Collection, Map and primitives are valid arguments types.") { - BsonEncoder.encodeToBsonValue(SerializableClass()) - } - } - - @Test - fun decodeFromBsonElement_throwsUnsupportedType() { - assertFailsWithMessage("Unsupported type 'SerializableClass'. Only Bson, MutableRealmInt, RealmUUID, ObjectId, RealmInstant, RealmAny, and primitives are valid decoding types.") { - BsonEncoder.decodeFromBsonValue( - resultClass = SerializableClass::class, - bsonValue = BsonString("") - ) - } - } - - @Test - fun decodeFromBsonElement_throwsWrongType() { - primitiveAsserters.forEach { asserter -> - assertFailsWithMessage("Cannot decode BsonValue") { - BsonEncoder.decodeFromBsonValue( - resultClass = asserter.type, - bsonValue = BsonRegularExpression("") - ) - } - } - } - - private val numericalClassifiers = TypeDescriptor.classifiers.filter { - it.value == TypeDescriptor.CoreFieldType.INT || - it.value == TypeDescriptor.CoreFieldType.MUTABLE_REALM_INT || - it.value == TypeDescriptor.CoreFieldType.FLOAT -// it.value == TypeDescriptor.CoreFieldType.DOUBLE // conversion from Decimal128 to Double not supported yet - }.filter { - it.key != Char::class // Char is encoded as a BsonString - } - - @Test - fun convertNumbersWithoutLoosingPrecision() { - numericalClassifiers.map { - it.key as KClass<*> to BsonDouble(1.0) - }.forEach { (clazz: KClass<*>, bsonValue: BsonValue) -> - BsonEncoder.decodeFromBsonValue( - resultClass = clazz, - bsonValue = bsonValue - ) - } - } - - @Test - fun convertNumbersLoosingPrecision() { - numericalClassifiers.map { - it.key as KClass<*> to BsonDouble(1.3) - }.forEach { (clazz: KClass<*>, bsonValue: BsonValue) -> - BsonEncoder.decodeFromBsonValue( - resultClass = clazz, - bsonValue = bsonValue - ) - } - } - - @Test - fun convertWrongMapToBsonDocument_throw() { - assertFailsWithMessage("Failed to convert Map to BsonDocument. Keys don't support null values.") { - BsonEncoder.encodeToBsonValue(mapOf(null to 1)) - } - - assertFailsWithMessage("Failed to convert Map to BsonDocument. Key type must be String, Int found.") { - BsonEncoder.encodeToBsonValue(mapOf(1 to 1)) - } - } - - @Test - fun realmAnyEncoding() { - listOf( - RealmAny.create(30) to BsonInt64(30), - RealmAny.create(true) to BsonBoolean.TRUE_VALUE, - RealmAny.create("30") to BsonString("30"), - RealmAny.create(0.5f) to BsonDouble(0.5f.toDouble()), - RealmAny.create(0.5) to BsonDouble(0.5), - BsonObjectId().let { - RealmAny.create(it) to it - }, - RealmInstant.now().let { - RealmAny.create(it) to BsonDateTime(it.toDuration().inWholeMilliseconds) - }, - ByteArray(0x00).let { - RealmAny.create(it) to BsonBinary(it) - }, - RealmUUID.random().let { - RealmAny.create(it) to BsonBinary(BsonBinarySubType.UUID_STANDARD, it.bytes) - } - ).forEach { (value, expected) -> - assertEquals(expected, BsonEncoder.encodeToBsonValue(value)) - } - } - - @Test - fun realmAny_decodeUnsupportedTypeThrows() { - assertFailsWithMessage("Cannot decode a REGULAR_EXPRESSION into RealmAny.") { - BsonEncoder.decodeFromBsonValue( - resultClass = RealmAny::class, - bsonValue = BsonRegularExpression("") - ) - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt deleted file mode 100644 index 0e14201e26..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/CustomLogCollector.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.common.utils - -import io.realm.kotlin.log.LogCategory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLogger -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock - -/** - * Logged collecting all logs it has seen. - */ -class CustomLogCollector : RealmLogger { - - private val mutex = Mutex() - private val _logs = mutableListOf() - /** - * Returns a snapshot of the current state of the logs. - */ - val logs: List - get() = runBlocking { - mutex.withLock { - _logs.toList() - } - } - - override fun log(category: LogCategory, level: LogLevel, throwable: Throwable?, message: String?, vararg args: Any?) { - val logMessage: String = message!! - runBlocking { - mutex.withLock { - _logs.add(logMessage) - } - } - } -} diff --git a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt b/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt deleted file mode 100644 index 8ac26c2ab8..0000000000 --- a/packages/test-sync/src/commonTest/kotlin/io/realm/kotlin/test/mongodb/common/utils/Utils.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ -package io.realm.kotlin.test.mongodb.common.utils - -import io.realm.kotlin.mongodb.sync.SubscriptionSet -import io.realm.kotlin.mongodb.sync.SyncSession -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import kotlin.reflect.KClass -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue -import kotlin.test.fail -import kotlin.time.Duration -import kotlin.time.Duration.Companion.minutes -import kotlin.time.Duration.Companion.seconds - -// NOTE: Copy from :base:commonTest. It is unclear if there is an easy way to share test code like -// this between :base and :sync - -/** - * Assert that a statement fails with a specific Exception type AND message. The message match is - * fuzzy, i.e. we only check that the provided message is contained within the whole exception - * message. The match is case sensitive. - */ -fun assertFailsWithMessage(exceptionClass: KClass, exceptionMessage: String, block: () -> Unit): T { - val exception: T = assertFailsWith(exceptionClass, null, block) - if (exception.message?.contains(exceptionMessage, ignoreCase = false) != true) { - throw AssertionError( - """ - The exception message did not match. - - Expected: - $exceptionMessage - - Actual: - ${exception.message} - - """.trimIndent() - ) - } - return exception -} - -inline fun assertFailsWithMessage(exceptionMessage: String, noinline block: () -> Unit): T = - assertFailsWithMessage(T::class, exceptionMessage, block) - -inline fun CoroutineScope.assertFailsWithMessage(exceptionMessage: String, noinline block: suspend CoroutineScope.() -> Unit): T = - assertFailsWithMessage(T::class, exceptionMessage) { - runBlocking(this.coroutineContext) { - block() - } - } - -suspend inline fun SubscriptionSet<*>.waitForSynchronizationOrFail() { - val timeout = 5.minutes - assertTrue(this.waitForSynchronization(timeout), "Failed to synchronize subscriptions in time: $timeout") -} - -suspend inline fun SyncSession.uploadAllLocalChangesOrFail() { - val timeout = 5.minutes - assertTrue(this.uploadAllLocalChanges(timeout), "Failed to upload local changes in time: $timeout") -} - -suspend fun retry(action: suspend () -> R?, until: (R?) -> Boolean, retries: Int = 5, delay: Duration = 1.seconds): R? { - repeat(retries) { - action().let { - if (until(it)) { - return it - } else { - delay(delay) - } - } - } - fail("Exceeded retries") -} diff --git a/packages/test-sync/src/iosTest/resources/asset-fs.realm b/packages/test-sync/src/iosTest/resources/asset-fs.realm deleted file mode 100644 index 5805e4550e..0000000000 Binary files a/packages/test-sync/src/iosTest/resources/asset-fs.realm and /dev/null differ diff --git a/packages/test-sync/src/iosTest/resources/asset-local.realm b/packages/test-sync/src/iosTest/resources/asset-local.realm deleted file mode 100644 index bff506e02d..0000000000 Binary files a/packages/test-sync/src/iosTest/resources/asset-local.realm and /dev/null differ diff --git a/packages/test-sync/src/iosTest/resources/asset-pbs.realm b/packages/test-sync/src/iosTest/resources/asset-pbs.realm deleted file mode 100644 index 2b1e3ad94c..0000000000 Binary files a/packages/test-sync/src/iosTest/resources/asset-pbs.realm and /dev/null differ diff --git a/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt b/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt deleted file mode 100644 index ff42423a37..0000000000 --- a/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.realm.kotlin.test.mongodb - -actual fun baasTestUrl(): String = SyncServerConfig.url diff --git a/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt b/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt deleted file mode 100644 index f6147403d4..0000000000 --- a/packages/test-sync/src/jvmMain/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.sync - -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers - -actual fun getDispatcher(): CoroutineDispatcher = Dispatchers.IO diff --git a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/compiler/AsymmetricObjectTests.kt b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/compiler/AsymmetricObjectTests.kt deleted file mode 100644 index 15a2fc9c0c..0000000000 --- a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/compiler/AsymmetricObjectTests.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2022 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.compiler - -import com.tschuchort.compiletesting.KotlinCompilation -import com.tschuchort.compiletesting.SourceFile -import io.realm.kotlin.test.util.Compiler -import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi -import org.junit.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -@OptIn(ExperimentalCompilerApi::class) -class AsymmetricObjectTests { - - @Test - fun `cannot reference asymmetric objects`() { - val result = Compiler.compileFromSource( - source = SourceFile.kotlin( - "referenceAsymmetricObjects.kt", - """ - import io.realm.kotlin.types.AsymmetricRealmObject - import io.realm.kotlin.types.EmbeddedRealmObject - import io.realm.kotlin.types.RealmObject - import io.realm.kotlin.types.annotations.PrimaryKey - - class A : AsymmetricRealmObject { - @PrimaryKey - var _id: String = "" - var child: A? = null - } - """.trimIndent() - ) - ) - assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) - assertTrue(result.messages.contains("AsymmetricObjects can only reference EmbeddedRealmObject classes")) - } - - @Test - fun `cannot reference asymmetric objects in collections`() { - val result = Compiler.compileFromSource( - source = SourceFile.kotlin( - "referenceAsymmetricObjects.kt", - """ - import io.realm.kotlin.ext.realmDictionaryOf - import io.realm.kotlin.ext.realmListOf - import io.realm.kotlin.ext.realmSetOf - import io.realm.kotlin.types.AsymmetricRealmObject - import io.realm.kotlin.types.EmbeddedRealmObject - import io.realm.kotlin.types.RealmDictionary - import io.realm.kotlin.types.RealmList - import io.realm.kotlin.types.RealmObject - import io.realm.kotlin.types.RealmSet - import io.realm.kotlin.types.annotations.PrimaryKey - - class A : AsymmetricRealmObject { - @PrimaryKey - var _id: String = "" - var children1: RealmList = realmListOf() - var children2: RealmSet = realmSetOf() - var children3: RealmDictionary = realmDictionaryOf() - } - """.trimIndent() - ) - ) - assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) - assertTrue(result.messages.contains("Unsupported type for RealmList: 'A'")) - assertTrue(result.messages.contains("Unsupported type for RealmSet: 'A'")) - assertTrue(result.messages.contains("Unsupported type for RealmDictionary: 'A'")) - } - - @Test - fun `cannot reference standard realmobjects`() { - val result = Compiler.compileFromSource( - source = SourceFile.kotlin( - "referenceRealmObjects.kt", - """ - import io.realm.kotlin.ext.realmListOf - import io.realm.kotlin.types.AsymmetricRealmObject - import io.realm.kotlin.types.EmbeddedRealmObject - import io.realm.kotlin.types.RealmList - import io.realm.kotlin.types.RealmObject - import io.realm.kotlin.types.annotations.PrimaryKey - - class B : RealmObject { - var _id: String = "" - } - - class A : AsymmetricRealmObject { - @PrimaryKey - var _id: String = "" - var child: B? = null - var children: RealmList = realmListOf() - } - """.trimIndent() - ) - ) - assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) - assertTrue(result.messages.contains("AsymmetricObjects can only reference EmbeddedRealmObject classes")) - } - - @Test - fun `embedded objects cannot reference asymmetric objects`() { - val result = Compiler.compileFromSource( - source = SourceFile.kotlin( - "embeddedCannotReferenceAsymmetric.kt", - """ - import io.realm.kotlin.ext.realmListOf - import io.realm.kotlin.types.AsymmetricRealmObject - import io.realm.kotlin.types.EmbeddedRealmObject - import io.realm.kotlin.types.RealmList - import io.realm.kotlin.types.RealmObject - import io.realm.kotlin.types.annotations.PrimaryKey - - class B : AsymmetricRealmObject { - @PrimaryKey - var _id: String = "" - } - - class A : EmbeddedRealmObject { - var child: B? = null - var children: RealmList = realmListOf() - } - """.trimIndent() - ) - ) - assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) - assertTrue(result.messages.contains("RealmObjects and EmbeddedRealmObjects cannot reference AsymmetricRealmObjects")) - assertTrue(result.messages.contains("Unsupported type for RealmList: 'B'")) - } - - @Test - fun `realmobjects cannot reference asymmetric objects`() { - val result = Compiler.compileFromSource( - source = SourceFile.kotlin( - "embeddedCannotReferenceAsymmetric.kt", - """ - import io.realm.kotlin.ext.realmListOf - import io.realm.kotlin.types.AsymmetricRealmObject - import io.realm.kotlin.types.RealmList - import io.realm.kotlin.types.RealmObject - import io.realm.kotlin.types.annotations.PrimaryKey - - class B : AsymmetricRealmObject { - @PrimaryKey - var _id: String = "" - } - - class A : RealmObject { - var child: B? = null - var children: RealmList = realmListOf() - } - """.trimIndent() - ) - ) - assertEquals(KotlinCompilation.ExitCode.COMPILATION_ERROR, result.exitCode) - assertTrue(result.messages.contains("RealmObjects and EmbeddedRealmObjects cannot reference AsymmetricRealmObjects")) - assertTrue(result.messages.contains("Unsupported type for RealmList: 'B'")) - } -} diff --git a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/FlexibleSyncSchemaTests.kt b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/FlexibleSyncSchemaTests.kt deleted file mode 100644 index c5d73bc4a2..0000000000 --- a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/FlexibleSyncSchemaTests.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.jvm - -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA_COUNT -import io.realm.kotlin.types.RealmObject -import kotlin.reflect.full.isSubclassOf -import kotlin.test.Test -import kotlin.test.assertEquals - -class FlexibleSyncSchemaTests { - - @Test - fun flexibleSyncSchemaCount() { - assertEquals( - FLEXIBLE_SYNC_SCHEMA_COUNT, - FLEXIBLE_SYNC_SCHEMA.filter { it.isSubclassOf(RealmObject::class) }.size - ) - } -} diff --git a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/PlatformNetworkingTests.kt b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/PlatformNetworkingTests.kt deleted file mode 100644 index 0ddcd8b6ac..0000000000 --- a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/PlatformNetworkingTests.kt +++ /dev/null @@ -1,94 +0,0 @@ -package io.realm.kotlin.test.mongodb.jvm - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.SyncObjectWithAllTypes -import io.realm.kotlin.ext.query -import io.realm.kotlin.internal.platform.runBlocking -import io.realm.kotlin.log.LogCategory -import io.realm.kotlin.log.LogLevel -import io.realm.kotlin.log.RealmLog -import io.realm.kotlin.mongodb.User -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.mongodb.syncSession -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.common.FLEXIBLE_SYNC_SCHEMA -import io.realm.kotlin.test.mongodb.common.utils.CustomLogCollector -import io.realm.kotlin.test.mongodb.createUserAndLogIn -import io.realm.kotlin.test.mongodb.use -import io.realm.kotlin.test.mongodb.util.DefaultFlexibleSyncAppInitializer -import io.realm.kotlin.test.util.use -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withTimeout -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue -import kotlin.time.Duration.Companion.seconds - -class PlatformNetworkingTests { - - private val TIMEOUT = 10.seconds - - @Test - @Ignore // https://github.com/realm/realm-kotlin/issues/1819 - fun syncRoundTrip_coreNetworking() = runBlocking { - roundTrip(platformNetworking = false) - } - - @Test - fun syncRoundTrip_platformNetworking() = runBlocking { - roundTrip(platformNetworking = true) - } - - private suspend fun roundTrip(platformNetworking: Boolean) { - TestApp(this::class.simpleName, DefaultFlexibleSyncAppInitializer, builder = { - it.usePlatformNetworking(platformNetworking) - }).use { app -> - val selector = org.mongodb.kbson.ObjectId().toString() - - // Setup logger to capture WebSocketClient log messages - val logger = CustomLogCollector() - RealmLog.add(logger) - RealmLog.setLevel(LogLevel.DEBUG, LogCategory.Realm.Sdk) - - Realm.open(createSyncConfig(app.createUserAndLogIn(), selector)) - .use { uploadRealm -> - Realm.open(createSyncConfig(app.createUserAndLogIn(), selector)) - .use { realm -> - uploadRealm.write { - copyToRealm( - SyncObjectWithAllTypes().apply { - stringField = selector - } - ) - } - uploadRealm.syncSession.uploadAllLocalChanges(TIMEOUT) - withTimeout(TIMEOUT) { - realm.query().asFlow().first { - it.list.size == 1 - }.list.first().also { - assertEquals(selector, it.stringField) - } - } - } - } - assertTrue( - if (platformNetworking) { - logger.logs.any { it.contains("\\[Websocket.*\\] onOpen".toRegex()) } - } else { - logger.logs.none { it.contains("\\[Websocket.*\\] onOpen".toRegex()) } - }, - "Failed to verify log statements for : platformNetworking=$platformNetworking" - ) - } - } - - private fun createSyncConfig( - user: User, - selector: String - ): SyncConfiguration { - return SyncConfiguration.Builder(user, FLEXIBLE_SYNC_SCHEMA).initialSubscriptions { - add(it.query("stringField = $0", selector)) - }.build() - } -} diff --git a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt b/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt deleted file mode 100644 index 3d0d9e5df5..0000000000 --- a/packages/test-sync/src/jvmTest/kotlin/io/realm/kotlin/test/mongodb/jvm/RealmTests.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.jvm - -import io.realm.kotlin.Realm -import io.realm.kotlin.entities.sync.ChildPk -import io.realm.kotlin.entities.sync.ParentPk -import io.realm.kotlin.mongodb.Credentials -import io.realm.kotlin.mongodb.sync.SyncConfiguration -import io.realm.kotlin.test.mongodb.TestApp -import io.realm.kotlin.test.mongodb.util.DefaultPartitionBasedAppInitializer -import io.realm.kotlin.test.platform.PlatformUtils -import io.realm.kotlin.test.util.TestHelper -import kotlinx.coroutines.runBlocking -import org.mongodb.kbson.ExperimentalKBsonSerializerApi -import kotlin.test.Ignore -import kotlin.test.Test -import kotlin.test.assertEquals - -class RealmTests { - - // Ensure that we cleanup all threads owned by Realm when closing the Realm as it will - // otherwise prevent things like the JVM main thread from exiting. - // - // Since both coroutines and OkHttp spin up a fair amount of workers with unpredictable - // lifecycle, it is very difficult to track all threads. Instead this test just makes a best - // effort in detecting the cases we do know about. - @Test - @Ignore // See https://github.com/realm/realm-kotlin/issues/1627 - fun cleanupAllRealmThreadsOnClose() = runBlocking { - @OptIn(ExperimentalKBsonSerializerApi::class) - val app = TestApp("cleanupAllRealmThreadsOnClose", DefaultPartitionBasedAppInitializer) - val user = app.login(Credentials.anonymous()) - val configuration = SyncConfiguration.create(user, TestHelper.randomPartitionValue(), setOf(ParentPk::class, ChildPk::class)) - Realm.open(configuration).close() - app.close() - - // Wait max 30 seconds for threads to settle - var activeThreads: List = emptyList() - var fullyClosed = false - var count = 5 - while (!fullyClosed && count > 0) { - PlatformUtils.triggerGC() - // Ensure we only have daemon threads after closing Realms and Apps - activeThreads = Thread.getAllStackTraces().keys - .filter { !it.isDaemon } - .filterNot { - // Android Studio or Gradle worker threads - it.name.startsWith("/127.0.0.1") - } - .filterNot { - // Test thread - it.name.startsWith("Test worker") - } - if (activeThreads.isEmpty()) { - fullyClosed = true - } else { - count -= 1 - } - } - assertEquals(0, activeThreads.size, "Active threads where found ($activeThreads.size): ${threadTrace(activeThreads)}") - } - - private fun threadTrace(threads: List? = null): String { - val sb = StringBuilder() - sb.appendLine("--------------------------------") - val stack: List = threads ?: Thread.getAllStackTraces().keys.toList() - stack - .sortedBy { it.name } - .forEach { t: Thread -> - sb.appendLine("${t.name} - Is Daemon ${t.isDaemon} - Is Alive ${t.isAlive}") - } - sb.appendLine("All threads: ${stack.size}") - sb.appendLine("Active threads: ${Thread.activeCount()}") - return sb.toString() - } -} diff --git a/packages/test-sync/src/jvmTest/resources/asset-fs.realm b/packages/test-sync/src/jvmTest/resources/asset-fs.realm deleted file mode 100644 index 5805e4550e..0000000000 Binary files a/packages/test-sync/src/jvmTest/resources/asset-fs.realm and /dev/null differ diff --git a/packages/test-sync/src/jvmTest/resources/asset-local.realm b/packages/test-sync/src/jvmTest/resources/asset-local.realm deleted file mode 100644 index bff506e02d..0000000000 Binary files a/packages/test-sync/src/jvmTest/resources/asset-local.realm and /dev/null differ diff --git a/packages/test-sync/src/jvmTest/resources/asset-pbs.realm b/packages/test-sync/src/jvmTest/resources/asset-pbs.realm deleted file mode 100644 index 2b1e3ad94c..0000000000 Binary files a/packages/test-sync/src/jvmTest/resources/asset-pbs.realm and /dev/null differ diff --git a/packages/test-sync/src/macosTest/resources/asset-fs.realm b/packages/test-sync/src/macosTest/resources/asset-fs.realm deleted file mode 100644 index 5805e4550e..0000000000 Binary files a/packages/test-sync/src/macosTest/resources/asset-fs.realm and /dev/null differ diff --git a/packages/test-sync/src/macosTest/resources/asset-local.realm b/packages/test-sync/src/macosTest/resources/asset-local.realm deleted file mode 100644 index bff506e02d..0000000000 Binary files a/packages/test-sync/src/macosTest/resources/asset-local.realm and /dev/null differ diff --git a/packages/test-sync/src/macosTest/resources/asset-pbs.realm b/packages/test-sync/src/macosTest/resources/asset-pbs.realm deleted file mode 100644 index 2b1e3ad94c..0000000000 Binary files a/packages/test-sync/src/macosTest/resources/asset-pbs.realm and /dev/null differ diff --git a/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt b/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt deleted file mode 100644 index ff42423a37..0000000000 --- a/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/BaasUtils.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.realm.kotlin.test.mongodb - -actual fun baasTestUrl(): String = SyncServerConfig.url diff --git a/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt b/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt deleted file mode 100644 index 7008f3e4b1..0000000000 --- a/packages/test-sync/src/nativeDarwin/kotlin/io/realm/kotlin/test/mongodb/sync/SyncDispatcher.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2021 Realm Inc. - * - * 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. - */ - -package io.realm.kotlin.test.mongodb.sync - -import io.realm.kotlin.internal.platform.singleThreadDispatcher -import kotlinx.coroutines.CoroutineDispatcher - -actual fun getDispatcher(): CoroutineDispatcher = singleThreadDispatcher("Sync Dispatcher")