├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── actions │ └── gradle-cache │ │ └── action.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── android.yml │ └── publish-new-version.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── io │ │ └── getstream │ │ └── resultdemo │ │ ├── MainActivity.kt │ │ ├── MainViewModel.kt │ │ ├── MainViewModelFactory.kt │ │ ├── NetworkModule.kt │ │ ├── Poster.kt │ │ ├── PosterService.kt │ │ └── StreamResultApp.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-anydpi-v33 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ └── data_extraction_rules.xml ├── build.gradle.kts ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── getstream │ └── Configurations.kt ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── settings.gradle.kts ├── spotless ├── copyright.kt ├── copyright.kts └── copyright.xml ├── stream-result-call-retrofit ├── .gitignore ├── api │ └── stream-result-call-retrofit.api ├── build.gradle.kts ├── consumer-proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── kotlin │ │ └── io │ │ └── getstream │ │ └── result │ │ └── call │ │ └── retrofit │ │ ├── DefaultErrorParser.kt │ │ ├── DefaultErrorResponse.kt │ │ ├── ErrorParser.kt │ │ ├── RetrofitCall.kt │ │ ├── RetrofitCallAdapter.kt │ │ └── RetrofitCallAdapterFactory.kt │ └── test │ └── kotlin │ └── io │ └── getstream │ └── result │ └── call │ └── retrofit │ ├── BlockedRetrofit2Call.kt │ ├── Mother.kt │ ├── RetrofitCallTest.kt │ └── TestCoroutineExtension.kt ├── stream-result-call ├── .gitignore ├── api │ ├── android │ │ └── stream-result-call.api │ ├── jvm │ │ └── stream-result-call.api │ └── stream-result-call.api ├── build.gradle.kts └── src │ ├── androidTest │ └── kotlin │ │ └── io │ │ └── getstream │ │ └── result │ │ └── call │ │ ├── BlockedCall.kt │ │ ├── BlockedTask.kt │ │ ├── CallTests.kt │ │ ├── CoroutineCallTest.kt │ │ ├── DistinctCallTest.kt │ │ ├── DoOnResultCallTest.kt │ │ ├── DoOnStartCallTest.kt │ │ ├── FlatMapCallTest.kt │ │ ├── MapCallTest.kt │ │ ├── Mother.kt │ │ ├── ReturnOnErrorCallTest.kt │ │ ├── TestCoroutineExtension.kt │ │ └── ZipCallTest.kt │ └── commonMain │ └── kotlin │ └── io │ └── getstream │ └── result │ └── call │ ├── Call.kt │ ├── CoroutineCall.kt │ ├── DistinctCall.kt │ ├── DoOnResultCall.kt │ ├── DoOnStartCall.kt │ ├── ErrorCall.kt │ ├── FlatMapCall.kt │ ├── MapCall.kt │ ├── ResultCall.kt │ ├── ReturnOnErrorCall.kt │ ├── SharedCalls.kt │ ├── WithPreconditionCall.kt │ ├── ZipCall.kt │ ├── dispatcher │ └── CallDispatcherProvider.kt │ ├── internal │ └── SynchronizedReference.kt │ └── retry │ ├── CallRetryService.kt │ ├── RetryCall.kt │ └── RetryPolicy.kt └── stream-result ├── .gitignore ├── api ├── android │ └── stream-result.api ├── jvm │ └── stream-result.api └── stream-result.api ├── build.gradle.kts └── src └── commonMain ├── AndroidManifest.xml └── kotlin └── io └── getstream └── result ├── Error.kt ├── Result.kt └── internal └── StreamHandsOff.kt /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | # Most of the standard properties are supported 4 | indent_size=2 5 | max_line_length=100 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **SDK version** 14 | - x.x.x 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Device:** 27 | - Vendor and model: [e.g. Samsung S8] 28 | - Android version: [e.g. 9] 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Ask a question 4 | url: https://github.com/GetStream/stream-log/discussions 5 | about: If you just have a question, please open a discussion instead of an issue. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Request new API or customization options to be added to the SDK. 4 | title: '' 5 | labels: feature request 6 | assignees: '' 7 | --- 8 | 9 | Describe the feature you'd like to see in the SDK 10 | -------------------------------------------------------------------------------- /.github/actions/gradle-cache/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Gradle Cache' 2 | description: 'Cache Gradle Build Cache to improve workflow execution time' 3 | inputs: 4 | key-prefix: 5 | description: 'A prefix on the key used to store/restore cache' 6 | required: true 7 | runs: 8 | using: "composite" 9 | steps: 10 | - uses: actions/cache@v3.0.11 11 | with: 12 | path: | 13 | ~/.gradle/caches 14 | ~/.gradle/wrapper 15 | key: ${{ runner.os }}-${{ inputs.key-prefix }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/Dependencies.kt') }} 16 | restore-keys: | 17 | ${{ runner.os }}-${{ inputs.key-prefix }}- 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gradle" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | # Allow up to 10 open pull requests for pip dependencies 13 | open-pull-requests-limit: 10 14 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### 🎯 Goal 2 | 3 | _Describe why we are making this change_ 4 | 5 | ### 🛠 Implementation details 6 | 7 | _Describe the implementation_ 8 | 9 | ### 🎨 UI Changes 10 | 11 | _Add relevant screenshots_ 12 | 13 | | Before | After | 14 | | --- | --- | 15 | | img | img | 16 | 17 | _Add relevant videos_ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 34 | 35 | 36 |
BeforeAfter
29 | 32 |
37 | 38 | ### 🧪 Testing 39 | 40 | _Explain how this change can be tested (or why it can't be tested)_ 41 | 42 | _Provide a patch below if it is necessary for testing_ 43 | 44 |
45 | 46 | Provide the patch summary here 47 | 48 | ``` 49 | Provide the patch code here 50 | ``` 51 | 52 |
53 | 54 | 55 | ### ☑️Contributor Checklist 56 | 57 | #### General 58 | - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) 59 | - [ ] Assigned a person / code owner group (required) 60 | - [ ] Thread with the PR link started in a respective Slack channel (required) 61 | - [ ] PR targets the `develop` branch 62 | - [ ] PR is linked to the GitHub issue it resolves 63 | 64 | #### Code & documentation 65 | - [ ] Changelog is updated with client-facing changes 66 | - [ ] New code is covered by unit tests 67 | - [ ] Comparison screenshots added for visual changes 68 | - [ ] Affected documentation updated (KDocs, docusaurus, tutorial) 69 | 70 | ### ☑️Reviewer Checklist 71 | - [ ] UI Components sample runs & works 72 | - [ ] Compose sample runs & works 73 | - [ ] UI Changes correct (before & after images) 74 | - [ ] Bugs validated (bugfixes) 75 | - [ ] New feature tested and works 76 | - [ ] Release notes and docs clearly describe changes 77 | - [ ] All code we touched has new or updated KDocs 78 | 79 | ### 🎉 GIF 80 | 81 | _Please provide a suitable gif that describes your work on this pull request_ 82 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v3.6.0 17 | with: 18 | distribution: adopt 19 | java-version: 17 20 | 21 | - name: Cache Gradle and wrapper 22 | uses: actions/cache@v2 23 | with: 24 | path: | 25 | ~/.gradle/caches 26 | ~/.gradle/wrapper 27 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 28 | restore-keys: | 29 | ${{ runner.os }}-gradle- 30 | - name: Make Gradle executable 31 | run: chmod +x ./gradlew 32 | 33 | - name: Build with Gradle 34 | run: ./gradlew build -------------------------------------------------------------------------------- /.github/workflows/publish-new-version.yml: -------------------------------------------------------------------------------- 1 | name: Publish New Version 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | bump: 7 | type: choice 8 | description: "Type of version bump to perform" 9 | options: 10 | - patch 11 | - minor 12 | - major 13 | 14 | jobs: 15 | publish: 16 | uses: GetStream/android-ci-actions/.github/workflows/release-new-version.yml@main 17 | with: 18 | ref: "develop" 19 | bump: ${{ inputs.bump }} 20 | file-path: ./buildSrc/src/main/kotlin/io/getstream/Configurations.kt 21 | use-official-plugin: false 22 | excluded-modules: "app" 23 | documentation-tasks: "sourcesJar" 24 | secrets: 25 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 26 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 27 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 28 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 29 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 30 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} 31 | STREAM_PUBLIC_BOT_TOKEN: ${{ secrets.STREAM_PUBLIC_BOT_TOKEN }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .composite 3 | buildSrc/build 4 | .gradle 5 | /local.properties 6 | 7 | # Built application files 8 | *.apk 9 | *.aar 10 | *.ap_ 11 | *.aab 12 | *.exec 13 | 14 | library/.env 15 | 16 | # Files for the ART/Dalvik VM 17 | *.dex 18 | 19 | # Java class files 20 | *.class 21 | .kotlin 22 | 23 | # Generated files 24 | bin/ 25 | gen/ 26 | out/ 27 | # Uncomment the following line in case you need and you don't have the release build type files in your app 28 | # release/ 29 | 30 | # Gradle files 31 | .gradle/ 32 | build/ 33 | 34 | # Local configuration file (sdk path, etc) 35 | local.properties 36 | 37 | # Proguard folder generated by Eclipse 38 | proguard/ 39 | 40 | # Log Files 41 | *.log 42 | 43 | # Android Studio Navigation editor temp files 44 | .navigation/ 45 | 46 | # Android Studio captures folder 47 | captures/ 48 | 49 | # Keystore files 50 | # Uncomment the following lines if you do not want to check your keystore files in. 51 | #*.jks 52 | #*.keystore 53 | 54 | # External native build folder generated in Android Studio 2.2 and later 55 | .externalNativeBuild 56 | .cxx/ 57 | 58 | # Google Services (e.g. APIs or Firebase) 59 | # google-services.json 60 | 61 | # Freeline 62 | freeline.py 63 | freeline/ 64 | freeline_project_description.json 65 | 66 | # fastlane 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots 70 | fastlane/test_output 71 | fastlane/readme.md 72 | 73 | # Version control 74 | vcs.xml 75 | .gitignore.swp 76 | 77 | # lint 78 | lint/intermediates/ 79 | lint/generated/ 80 | lint/outputs/ 81 | lint/tmp/ 82 | # lint/reports/ 83 | 84 | /.idea/* 85 | !/.idea/codeInsightSettings.xml 86 | !/.idea/codeStyles/ 87 | !/.idea/inspectionProfiles/ 88 | !/.idea/scopes/ 89 | .DS_Store 90 | /build 91 | /captures 92 | /attachments/* 93 | .cxx 94 | /projectFilesBackup/ 95 | /projectFilesBackup1/ 96 | *.gpg 97 | 98 | # Ignore Dokka files into docusaurus project 99 | docusaurus/docs/Android/Dokka 100 | # Ignore Algolia credentials 101 | docusaurus/.env 102 | 103 | .sign/keystore.properties 104 | .sign/release.keystore 105 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | GetStream. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 3 | 4 | ## Preparing a pull request for review 5 | Ensure your change is properly formatted by running: 6 | 7 | ```gradle 8 | ./gradlew spotlessApply 9 | ``` 10 | 11 | Please correct any failures before requesting a review. 12 | 13 | ## Code reviews 14 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) for more information on using pull requests. 15 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | import io.getstream.Configurations 19 | 20 | @Suppress("DSL_SCOPE_VIOLATION") 21 | plugins { 22 | id(libs.plugins.android.application.get().pluginId) 23 | id(libs.plugins.kotlin.android.get().pluginId) 24 | id(libs.plugins.ksp.get().pluginId) 25 | } 26 | 27 | android { 28 | namespace = "io.getstream.resultdemo" 29 | compileSdk = Configurations.compileSdk 30 | 31 | defaultConfig { 32 | applicationId = "io.getstream.resultdemo" 33 | minSdk = Configurations.minSdk 34 | targetSdk = Configurations.targetSdk 35 | versionName = Configurations.versionName 36 | } 37 | 38 | compileOptions { 39 | sourceCompatibility = JavaVersion.VERSION_17 40 | targetCompatibility = JavaVersion.VERSION_17 41 | } 42 | 43 | lint { 44 | abortOnError = false 45 | } 46 | } 47 | 48 | dependencies { 49 | // result 50 | implementation(project(":stream-result")) 51 | implementation(project(":stream-result-call")) 52 | implementation(project(":stream-result-call-retrofit")) 53 | 54 | // androidx 55 | implementation(libs.androidx.material) 56 | implementation(libs.androidx.viewmodel.ktx) 57 | 58 | // network & coroutines 59 | implementation(libs.retrofit) 60 | implementation(libs.retrofit.moshi) 61 | implementation(libs.okhttp.logging) 62 | implementation(libs.kotlinx.coroutines) 63 | 64 | // moshi 65 | implementation(libs.moshi) 66 | ksp(libs.moshi.codegen) 67 | 68 | // logger 69 | implementation(libs.stream.log) 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import android.os.Bundle 19 | import android.widget.TextView 20 | import androidx.appcompat.app.AppCompatActivity 21 | import androidx.lifecycle.lifecycleScope 22 | import kotlinx.coroutines.flow.collectLatest 23 | import kotlinx.coroutines.launch 24 | 25 | class MainActivity : AppCompatActivity() { 26 | 27 | private val viewModelFactory: MainViewModelFactory = MainViewModelFactory() 28 | private val viewModel: MainViewModel by lazy { 29 | viewModelFactory.create(MainViewModel::class.java) 30 | } 31 | 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(R.layout.activity_main) 35 | 36 | viewModel.fetchPosterList() 37 | 38 | lifecycleScope.launch { 39 | viewModel.posters.collectLatest { 40 | val textView = findViewById(R.id.text) 41 | textView.text = it.toString() 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import androidx.lifecycle.ViewModel 19 | import androidx.lifecycle.viewModelScope 20 | import io.getstream.log.StreamLog 21 | import io.getstream.log.streamLog 22 | import io.getstream.result.Error 23 | import io.getstream.result.call.doOnResult 24 | import io.getstream.result.call.doOnStart 25 | import io.getstream.result.call.map 26 | import io.getstream.result.call.retry 27 | import io.getstream.result.call.retry.RetryPolicy 28 | import kotlinx.coroutines.flow.SharingStarted 29 | import kotlinx.coroutines.flow.flow 30 | import kotlinx.coroutines.flow.stateIn 31 | import kotlinx.coroutines.launch 32 | 33 | public class MainViewModel constructor( 34 | private val posterService: PosterService 35 | ) : ViewModel() { 36 | 37 | val posters = flow { 38 | val posters = posterService.fetchPosterList() 39 | // retry if the network request fails. 40 | .retry(viewModelScope, retryPolicy) 41 | // do something before running the network request. 42 | .doOnStart(viewModelScope) { 43 | StreamLog.streamLog { "doOnStart" } 44 | } 45 | // do something after running the network request. 46 | .doOnResult(viewModelScope) { 47 | StreamLog.streamLog { "doOnResult" } 48 | } 49 | // map the type of call. 50 | .map { 51 | StreamLog.streamLog { "map" } 52 | it.first() 53 | } 54 | .await() 55 | emit(posters) 56 | }.stateIn( 57 | scope = viewModelScope, 58 | started = SharingStarted.WhileSubscribed(5000), 59 | initialValue = emptyList() 60 | ) 61 | 62 | fun fetchPosterList() { 63 | viewModelScope.launch { 64 | val result = posterService.fetchPosterList() 65 | // retry if the network request fails. 66 | .retry(viewModelScope, retryPolicy) 67 | // do something before running the network request. 68 | .doOnStart(viewModelScope) { 69 | StreamLog.streamLog { "doOnStart" } 70 | } 71 | // do something after running the network request. 72 | .doOnResult(viewModelScope) { 73 | StreamLog.streamLog { "doOnResult" } 74 | } 75 | // map the type of call. 76 | .map { 77 | StreamLog.streamLog { "map" } 78 | it.first() 79 | } 80 | .await() 81 | 82 | result.onSuccess { 83 | StreamLog.streamLog { "onSuccess: $it" } 84 | }.onError { 85 | StreamLog.streamLog { "onError: $it" } 86 | } 87 | } 88 | } 89 | 90 | private val retryPolicy = object : RetryPolicy { 91 | override fun shouldRetry(attempt: Int, error: Error): Boolean = attempt <= 3 92 | 93 | override fun retryTimeout(attempt: Int, error: Error): Int = 3000 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/MainViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import androidx.lifecycle.ViewModel 19 | import androidx.lifecycle.ViewModelProvider 20 | 21 | @Suppress("UNCHECKED_CAST") 22 | public class MainViewModelFactory : ViewModelProvider.Factory { 23 | 24 | override fun create(modelClass: Class): T { 25 | if (modelClass.isAssignableFrom(MainViewModel::class.java)) { 26 | return MainViewModel(NetworkModule.posterService) as T 27 | } 28 | throw IllegalArgumentException("Unknown ViewModel class.") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import io.getstream.result.call.retrofit.RetrofitCallAdapterFactory 19 | import retrofit2.Retrofit 20 | import retrofit2.converter.moshi.MoshiConverterFactory 21 | import retrofit2.create 22 | 23 | object NetworkModule { 24 | 25 | private val retrofit: Retrofit = Retrofit.Builder() 26 | .baseUrl( 27 | "https://gist.githubusercontent.com/skydoves/176c209dbce4a53c0ff9589e07255f30/raw/6489d9712702e093c4df71500fb822f0d408ef75/" 28 | ) 29 | .addConverterFactory(MoshiConverterFactory.create()) 30 | .addCallAdapterFactory(RetrofitCallAdapterFactory.create()) 31 | .build() 32 | 33 | val posterService: PosterService = retrofit.create() 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/Poster.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2020 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import com.squareup.moshi.JsonClass 19 | 20 | @JsonClass(generateAdapter = true) 21 | data class Poster( 22 | val id: Long, 23 | val name: String, 24 | val release: String, 25 | val playtime: String, 26 | val description: String, 27 | val plot: String, 28 | val poster: String, 29 | val gif: String 30 | ) 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/PosterService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2020 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import io.getstream.result.call.CoroutineCall 19 | import io.getstream.result.call.retrofit.RetrofitCall 20 | import retrofit2.http.GET 21 | 22 | interface PosterService { 23 | 24 | @GET("DisneyPosters2.json") 25 | fun fetchPosterList(): RetrofitCall> 26 | 27 | @GET("DisneyPosters2.json") 28 | suspend fun fetchCoroutinesPosterList(): CoroutineCall> 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/resultdemo/StreamResultApp.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.resultdemo 17 | 18 | import android.app.Application 19 | import io.getstream.log.AndroidStreamLogger 20 | 21 | class StreamResultApp : Application() { 22 | 23 | override fun onCreate() { 24 | super.onCreate() 25 | 26 | AndroidStreamLogger.install() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v33/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FFBB86FC 19 | #FF6200EE 20 | #FF3700B3 21 | #FF03DAC5 22 | #FF018786 23 | #FF000000 24 | #FFFFFFFF 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | stream-result 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | @Suppress("DSL_SCOPE_VIOLATION") 2 | plugins { 3 | alias(libs.plugins.android.application) apply false 4 | alias(libs.plugins.android.library) apply false 5 | alias(libs.plugins.kotlin.android) apply false 6 | alias(libs.plugins.kotlin.serialization) apply false 7 | alias(libs.plugins.kotlin.multiplatform) apply false 8 | alias(libs.plugins.kotlin.atomicfu) apply false 9 | alias(libs.plugins.ksp) apply false 10 | alias(libs.plugins.nexus.plugin) apply false 11 | alias(libs.plugins.spotless) 12 | alias(libs.plugins.dokka) 13 | alias(libs.plugins.kotlinBinaryCompatibilityValidator) 14 | } 15 | 16 | apiValidation { 17 | ignoredProjects.addAll(listOf("app")) 18 | nonPublicMarkers.add("kotlin.PublishedApi") 19 | } 20 | 21 | subprojects { 22 | tasks.withType().all { 23 | kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString() 24 | } 25 | 26 | apply(plugin = rootProject.libs.plugins.spotless.get().pluginId) 27 | extensions.configure { 28 | kotlin { 29 | target("**/*.kt") 30 | targetExclude("${layout.buildDirectory}/**/*.kt") 31 | ktlint().setUseExperimental(true).editorConfigOverride( 32 | mapOf( 33 | "indent_size" to "2", 34 | "continuation_indent_size" to "2" 35 | ) 36 | ) 37 | licenseHeaderFile(rootProject.file("spotless/copyright.kt")) 38 | trimTrailingWhitespace() 39 | endWithNewline() 40 | } 41 | format("kts") { 42 | target("**/*.kts") 43 | targetExclude("${layout.buildDirectory}/**/*.kts") 44 | licenseHeaderFile(rootProject.file("spotless/copyright.kt"), "(^(?![\\/ ]\\*).*$)") 45 | } 46 | format("xml") { 47 | target("**/*.xml") 48 | targetExclude("**/build/**/*.xml") 49 | licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])") 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/io/getstream/Configurations.kt: -------------------------------------------------------------------------------- 1 | package io.getstream 2 | 3 | object Configurations { 4 | const val compileSdk = 35 5 | const val targetSdk = 35 6 | const val minSdk = 21 7 | const val majorVersion = 1 8 | const val minorVersion = 3 9 | const val patchVersion = 3 10 | const val versionName = "$majorVersion.$minorVersion.$patchVersion" 11 | const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT" 12 | const val artifactGroup = "io.getstream" 13 | } 14 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory 2 | org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true 3 | 4 | # https://docs.gradle.org/current/userguide/build_cache.html 5 | org.gradle.caching=true 6 | 7 | # When configured, Gradle will run in incubating parallel mode. 8 | # This option should only be used with decoupled projects. More details, visit 9 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 10 | org.gradle.parallel=true 11 | 12 | # Configure only necessary projects, useful with multimodule projects 13 | org.gradle.configureondemand=true 14 | 15 | # AndroidX Migration https://developer.android.com/jetpack/androidx/migrate 16 | android.useAndroidX=true 17 | 18 | # Disable Kotlin Multiplatform Hierachy Template 19 | kotlin.mpp.applyDefaultHierarchyTemplate=false 20 | 21 | # Removes uneccessary default build features 22 | android.defaults.buildfeatures.aidl=false 23 | android.defaults.buildfeatures.buildconfig=false 24 | android.defaults.buildfeatures.renderscript=false 25 | android.defaults.buildfeatures.resvalues=false 26 | android.defaults.buildfeatures.shaders=false 27 | 28 | # Enables namespacing of each library's R class so that its R class includes only the 29 | # resources declared in the library itself and none from the library's dependencies, 30 | # thereby reducing the size of the R class for that library 31 | # https://developer.android.com/studio/releases/gradle-plugin#4.1-nontransitive-r-class 32 | android.nonTransitiveRClass=true 33 | 34 | ## Maven Central Publication ## 35 | systemProp.org.gradle.internal.publish.checksums.insecure=true 36 | 37 | # Increase timeout when pushing to Sonatype (otherwise we get timeouts) 38 | systemProp.org.gradle.internal.http.socketTimeout=120000 39 | 40 | POM_URL=https://github.com/getstream/stream-result/ 41 | POM_SCM_URL=https://github.com/getstream/stream-result/ 42 | POM_SCM_CONNECTION=scm:git:git://github.com/getstream/stream-result.git 43 | POM_SCM_DEV_CONNECTION=scm:git:git://github.com/getstream/stream-result.git 44 | 45 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 46 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 47 | POM_LICENCE_DIST=repo 48 | 49 | POM_DEVELOPER_ID=skydoves 50 | POM_DEVELOPER_NAME=Jaewoong Eum 51 | POM_DEVELOPER_URL=https://github.com/skydoves/ 52 | POM_DEVELOPER_EMAIL=skydoves2@gmail.com 53 | 54 | SONATYPE_HOST=DEFAULT 55 | RELEASE_SIGNING_ENABLED=true 56 | SONATYPE_AUTOMATIC_RELEASE=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | atomicfuVersion = "0.27.0" 3 | junitJupiterApi = "5.11.4" 4 | streamLog = "1.3.1" 5 | androidGradlePlugin = "8.6.1" 6 | androidxActivity = "1.4.0" 7 | androidxAppCompat = "1.7.0" 8 | androidxMaterial = "1.12.0" 9 | androidxAnnotation = "1.9.1" 10 | androidxCore = "1.15.0" 11 | androidxLifecycle = "2.8.6" 12 | androidxNavigation = "2.5.0" 13 | androidxTest = "1.6.1" 14 | androidxJunit = "1.2.1" 15 | kotlin = "2.1.0" 16 | atomicfu = "0.25.0" 17 | dokka = "1.9.0" 18 | kluent = "1.73" 19 | kotlinxCoroutines = "1.10.1" 20 | kotlinxSerialization = "1.7.3" 21 | ksp = "2.0.21-1.0.27" 22 | okhttp = "4.12.0" 23 | retrofit = "2.11.0" 24 | moshi="1.15.2" 25 | spotless = "6.7.0" 26 | nexusPlugin = "0.30.0" 27 | compatibilityValidator = "0.17.0" 28 | androidxMacroBenchmark = "1.3.3" 29 | androidxProfileinstaller = "1.4.1" 30 | androidxUiAutomator = "2.3.0" 31 | junit5 = "1.11.0.0" 32 | mockito = "5.15.2" 33 | mockitoKotlin = "4.1.0" 34 | 35 | [libraries] 36 | atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "atomicfuVersion" } 37 | junit-jupiter-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitJupiterApi" } 38 | junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitJupiterApi" } 39 | stream-log = { group = "io.getstream", name = "stream-log", version.ref = "streamLog" } 40 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" } 41 | androidx-annotation = { group = "androidx.annotation", name = "annotation", version.ref = "androidxAnnotation" } 42 | androidx-material = { module = "com.google.android.material:material", version.ref = "androidxMaterial" } 43 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" } 44 | androidx-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" } 45 | androidx-profileinstaller = { group = "androidx.profileinstaller", name = "profileinstaller", version.ref = "androidxProfileinstaller" } 46 | androidx-benchmark-macro = { group = "androidx.benchmark", name = "benchmark-macro-junit4", version.ref = "androidxMacroBenchmark" } 47 | androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidxTest" } 48 | androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTest" } 49 | androidx-test-junit = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxJunit" } 50 | androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "androidxUiAutomator" } 51 | kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } 52 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } 53 | okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } 54 | okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } 55 | retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } 56 | retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" } 57 | testing-kluent = { group = "org.amshove.kluent", name = "kluent", version.ref = "kluent" } 58 | testing-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } 59 | testing-mockito = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } 60 | testing-mockito-kotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version.ref = "mockitoKotlin" } 61 | 62 | moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } 63 | moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } 64 | 65 | # Dependencies of the included build-logic 66 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } 67 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } 68 | spotless-gradlePlugin = { group = "com.diffplug.spotless", name = "spotless-plugin-gradle", version.ref = "spotless" } 69 | 70 | [plugins] 71 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } 72 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } 73 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 74 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 75 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 76 | ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } 77 | spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } 78 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 79 | nexus-plugin = { id = "com.vanniktech.maven.publish", version.ref = "nexusPlugin" } 80 | kotlinBinaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "compatibilityValidator" } 81 | kotlin-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "atomicfu" } 82 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/stream-result/762c411e944099768e94eaaf69f023fb86001b07/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ], 6 | "packageRules": [ 7 | { 8 | "groupName": "Kotlin, KSP and Compose Compiler", 9 | "groupSlug": "kotlin", 10 | "matchPackagePrefixes": [ 11 | "com.google.devtools.ksp", 12 | "androidx.compose.compiler" 13 | ], 14 | "matchPackagePatterns": [ 15 | "org.jetbrains.kotlin.*" 16 | ] 17 | }, 18 | { 19 | "description": "Automatically merge minor and patch-level updates", 20 | "matchUpdateTypes": ["minor", "patch", "digest"], 21 | "automerge": true, 22 | "automergeType": "pr", 23 | "platformAutomerge": true 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:Suppress("UnstableApiUsage") 2 | pluginManagement { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | gradlePluginPortal() 7 | maven(url = "https://plugins.gradle.org/m2/") 8 | } 9 | } 10 | dependencyResolutionManagement { 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven(url = "https://plugins.gradle.org/m2/") 15 | } 16 | } 17 | rootProject.name = "stream-result" 18 | include(":app") 19 | include(":stream-result") 20 | include(":stream-result-call") 21 | include(":stream-result-call-retrofit") -------------------------------------------------------------------------------- /spotless/copyright.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-$YEAR Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /spotless/copyright.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-$YEAR Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /spotless/copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /stream-result-call-retrofit/api/stream-result-call-retrofit.api: -------------------------------------------------------------------------------- 1 | public abstract interface class io/getstream/result/call/retrofit/ErrorParser { 2 | public abstract fun fromJson (Ljava/lang/String;)Ljava/lang/Object; 3 | public abstract fun toError (Lokhttp3/Response;)Lio/getstream/result/Error; 4 | public abstract fun toError (Lokhttp3/ResponseBody;)Lio/getstream/result/Error; 5 | } 6 | 7 | public final class io/getstream/result/call/retrofit/ErrorParser$DefaultImpls { 8 | public static fun toError (Lio/getstream/result/call/retrofit/ErrorParser;Lokhttp3/Response;)Lio/getstream/result/Error; 9 | public static fun toError (Lio/getstream/result/call/retrofit/ErrorParser;Lokhttp3/ResponseBody;)Lio/getstream/result/Error; 10 | } 11 | 12 | public final class io/getstream/result/call/retrofit/RetrofitCall : io/getstream/result/call/Call { 13 | public fun (Lretrofit2/Call;Lio/getstream/result/call/retrofit/ErrorParser;Lkotlinx/coroutines/CoroutineScope;)V 14 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 15 | public fun cancel ()V 16 | public fun enqueue ()V 17 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 18 | public fun execute ()Lio/getstream/result/Result; 19 | } 20 | 21 | public final class io/getstream/result/call/retrofit/RetrofitCallAdapterFactory : retrofit2/CallAdapter$Factory { 22 | public static final field Companion Lio/getstream/result/call/retrofit/RetrofitCallAdapterFactory$Companion; 23 | public synthetic fun (Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retrofit/ErrorParser;Lkotlin/jvm/internal/DefaultConstructorMarker;)V 24 | public fun get (Ljava/lang/reflect/Type;[Ljava/lang/annotation/Annotation;Lretrofit2/Retrofit;)Lretrofit2/CallAdapter; 25 | } 26 | 27 | public final class io/getstream/result/call/retrofit/RetrofitCallAdapterFactory$Companion { 28 | public final fun create (Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retrofit/ErrorParser;)Lio/getstream/result/call/retrofit/RetrofitCallAdapterFactory; 29 | public static synthetic fun create$default (Lio/getstream/result/call/retrofit/RetrofitCallAdapterFactory$Companion;Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retrofit/ErrorParser;ILjava/lang/Object;)Lio/getstream/result/call/retrofit/RetrofitCallAdapterFactory; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | import com.vanniktech.maven.publish.AndroidMultiVariantLibrary 19 | import io.getstream.Configurations 20 | 21 | plugins { 22 | id(libs.plugins.android.library.get().pluginId) 23 | id(libs.plugins.kotlin.android.get().pluginId) 24 | id(libs.plugins.kotlin.serialization.get().pluginId) 25 | id(libs.plugins.nexus.plugin.get().pluginId) 26 | id("de.mannodermaus.android-junit5") version "1.11.2.0" 27 | } 28 | 29 | mavenPublishing { 30 | val artifactId = "stream-result-call-retrofit" 31 | coordinates( 32 | Configurations.artifactGroup, 33 | artifactId, 34 | Configurations.versionName 35 | ) 36 | 37 | configure( 38 | AndroidMultiVariantLibrary( 39 | sourcesJar = true, 40 | publishJavadocJar = false, 41 | ) 42 | ) 43 | 44 | pom { 45 | name.set(artifactId) 46 | description.set("Railway-oriented library to easily model and handle success/failure for Kotlin, Android, and Retrofit.") 47 | } 48 | } 49 | 50 | android { 51 | namespace = "io.getstream.result.call.retrofit" 52 | compileSdk = Configurations.compileSdk 53 | 54 | defaultConfig { 55 | minSdk = Configurations.minSdk 56 | consumerProguardFiles("consumer-proguard-rules.pro") 57 | } 58 | 59 | compileOptions { 60 | sourceCompatibility = JavaVersion.VERSION_17 61 | targetCompatibility = JavaVersion.VERSION_17 62 | } 63 | 64 | } 65 | 66 | tasks.withType { 67 | kotlinOptions.freeCompilerArgs += listOf( 68 | "-Xexplicit-api=strict" 69 | ) 70 | } 71 | 72 | dependencies { 73 | api(project(":stream-result-call")) 74 | 75 | implementation(libs.okhttp) 76 | implementation(libs.retrofit) 77 | implementation(libs.kotlinx.coroutines) 78 | implementation(libs.kotlinx.serialization.json) 79 | implementation(libs.stream.log) 80 | 81 | testImplementation(libs.testing.kluent) 82 | testImplementation(libs.testing.coroutines.test) 83 | testImplementation(libs.testing.mockito) 84 | testImplementation(libs.testing.mockito.kotlin) 85 | testImplementation(libs.testing.mockito.kotlin) 86 | testImplementation(libs.androidx.test.junit) 87 | testImplementation(libs.junit.jupiter.api) 88 | testRuntimeOnly(libs.junit.jupiter.engine) 89 | } -------------------------------------------------------------------------------- /stream-result-call-retrofit/consumer-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | ## Stream Result Proguard Rules 2 | 3 | # Rules necessary for R8 full mode 4 | -keep class io.getstream.result.call.retrofit.RetrofitCall { *; } 5 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/DefaultErrorParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import kotlinx.serialization.json.Json 19 | 20 | internal class DefaultErrorParser : ErrorParser { 21 | 22 | @Suppress("UNCHECKED_CAST") 23 | override fun fromJson(raw: String): T { 24 | val serializer = DefaultErrorResponse.serializer() 25 | return Json.decodeFromString(serializer, raw) as T 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/DefaultErrorResponse.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import kotlinx.serialization.Serializable 19 | 20 | @Serializable 21 | internal data class DefaultErrorResponse constructor( 22 | val code: Int = -1, 23 | var message: String = "", 24 | var statusCode: Int = -1 25 | ) 26 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/ErrorParser.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import io.getstream.result.Error 19 | import okhttp3.Response 20 | import okhttp3.ResponseBody 21 | 22 | public interface ErrorParser { 23 | 24 | public fun fromJson(raw: String): T 25 | 26 | public fun toError(okHttpResponse: Response): Error { 27 | val statusCode: Int = okHttpResponse.code 28 | 29 | return try { 30 | // Try to parse default Stream error body 31 | val body = okHttpResponse.peekBody(Long.MAX_VALUE).string() 32 | 33 | if (body.isEmpty()) { 34 | Error.NetworkError( 35 | message = okHttpResponse.message, 36 | serverErrorCode = statusCode, 37 | statusCode = statusCode 38 | ) 39 | } else { 40 | Error.NetworkError( 41 | message = okHttpResponse.message, 42 | serverErrorCode = statusCode, 43 | statusCode = statusCode 44 | ) 45 | } 46 | } catch (expected: Throwable) { 47 | Error.ThrowableError( 48 | message = expected.message ?: expected.stackTraceToString(), 49 | cause = expected 50 | ) 51 | } 52 | } 53 | 54 | public fun toError(errorResponseBody: ResponseBody): Error { 55 | return try { 56 | val errorResponse: DefaultErrorResponse = fromJson(errorResponseBody.string()) 57 | val (code, message, statusCode) = errorResponse 58 | 59 | Error.NetworkError( 60 | serverErrorCode = code, 61 | message = message, 62 | statusCode = statusCode 63 | ) 64 | } catch (expected: Throwable) { 65 | Error.ThrowableError( 66 | message = expected.message ?: expected.stackTraceToString(), 67 | cause = expected 68 | ) 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/RetrofitCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import io.getstream.result.Error 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.Call 21 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.SupervisorJob 24 | import kotlinx.coroutines.cancelChildren 25 | import kotlinx.coroutines.job 26 | import kotlinx.coroutines.launch 27 | import kotlinx.coroutines.plus 28 | import kotlinx.coroutines.runBlocking 29 | import kotlinx.coroutines.withContext 30 | import retrofit2.Response 31 | import retrofit2.awaitResponse 32 | 33 | public class RetrofitCall( 34 | private val call: retrofit2.Call, 35 | private val errorParser: ErrorParser<*>, 36 | scope: CoroutineScope 37 | ) : Call { 38 | private val callScope = scope + SupervisorJob(scope.coroutineContext.job) 39 | 40 | override fun cancel() { 41 | call.cancel() 42 | callScope.coroutineContext.cancelChildren() 43 | } 44 | 45 | override fun execute(): Result = runBlocking { await() } 46 | 47 | override fun enqueue(callback: Call.Callback) { 48 | callScope.launch { notifyResult(call.getResult(), callback) } 49 | } 50 | 51 | override suspend fun await(): Result = Call.runCatching { 52 | withContext(callScope.coroutineContext) { 53 | call.getResult() 54 | } 55 | } 56 | 57 | private suspend fun notifyResult(result: Result, callback: Call.Callback) = 58 | withContext(CallDispatcherProvider.Main) { 59 | callback.onResult(result) 60 | } 61 | 62 | private fun Throwable.toFailedResult( 63 | message: String 64 | ): Result = Result.Failure(this.toFailedError(message)) 65 | 66 | private fun Throwable.toFailedError( 67 | message: String 68 | ): Error = Error.ThrowableError(cause = this, message = message) 69 | 70 | @Suppress("TooGenericExceptionCaught") 71 | private suspend fun retrofit2.Call.getResult(): Result = 72 | withContext(callScope.coroutineContext) { 73 | try { 74 | awaitResponse().getResult() 75 | } catch (t: Throwable) { 76 | t.toFailedResult(message = t.localizedMessage ?: "retrofit response exception") 77 | } 78 | } 79 | 80 | @Suppress("TooGenericExceptionCaught") 81 | private suspend fun Response.getResult(): Result = withContext(callScope.coroutineContext) { 82 | if (isSuccessful) { 83 | try { 84 | Result.Success(body()!!) 85 | } catch (t: Throwable) { 86 | val toFailedResult = 87 | t.toFailedResult(message = t.localizedMessage ?: "retrofit response exception") 88 | toFailedResult 89 | } 90 | } else { 91 | val errorBody = errorBody() 92 | 93 | if (errorBody != null) { 94 | Result.Failure(errorParser.toError(errorBody)) 95 | } else { 96 | Result.Failure(errorParser.toError(raw())) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/RetrofitCallAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import io.getstream.result.call.Call 19 | import kotlinx.coroutines.CoroutineScope 20 | import retrofit2.CallAdapter 21 | import java.lang.reflect.Type 22 | 23 | internal class RetrofitCallAdapter( 24 | private val responseType: Type, 25 | private val coroutineScope: CoroutineScope, 26 | private val errorParser: ErrorParser<*> 27 | ) : CallAdapter> { 28 | override fun responseType(): Type = responseType 29 | override fun adapt(call: retrofit2.Call): Call { 30 | return RetrofitCall(call, errorParser, coroutineScope) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/main/kotlin/io/getstream/result/call/retrofit/RetrofitCallAdapterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import kotlinx.coroutines.CoroutineScope 19 | import kotlinx.coroutines.Dispatchers 20 | import retrofit2.CallAdapter 21 | import retrofit2.Retrofit 22 | import java.lang.reflect.ParameterizedType 23 | import java.lang.reflect.Type 24 | 25 | public class RetrofitCallAdapterFactory private constructor( 26 | private val coroutineScope: CoroutineScope, 27 | private val errorParser: ErrorParser<*> 28 | ) : CallAdapter.Factory() { 29 | 30 | override fun get( 31 | returnType: Type, 32 | annotations: Array, 33 | retrofit: Retrofit 34 | ): CallAdapter<*, *>? { 35 | if (getRawType(returnType) != RetrofitCall::class.java) { 36 | return null 37 | } 38 | if (returnType !is ParameterizedType) { 39 | throw IllegalArgumentException("Call return type must be parameterized as Call") 40 | } 41 | val responseType: Type = getParameterUpperBound(0, returnType) 42 | return RetrofitCallAdapter(responseType, coroutineScope, errorParser) 43 | } 44 | 45 | public companion object { 46 | public fun create( 47 | coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO), 48 | errorParser: ErrorParser<*> = DefaultErrorParser() 49 | ): RetrofitCallAdapterFactory = RetrofitCallAdapterFactory(coroutineScope, errorParser) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/test/kotlin/io/getstream/result/call/retrofit/BlockedRetrofit2Call.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retrofit 17 | 18 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 19 | import kotlinx.coroutines.CoroutineScope 20 | import kotlinx.coroutines.delay 21 | import kotlinx.coroutines.launch 22 | import kotlinx.coroutines.runBlocking 23 | import kotlinx.coroutines.withContext 24 | import okhttp3.Request 25 | import okio.Timeout 26 | import org.mockito.kotlin.mock 27 | import retrofit2.Call 28 | import retrofit2.Callback 29 | import retrofit2.Response 30 | import java.io.IOException 31 | import java.util.concurrent.atomic.AtomicBoolean 32 | 33 | internal class BlockedRetrofit2Call( 34 | private val scope: CoroutineScope, 35 | private val value: T? = null, 36 | private val error: IOException? = null 37 | ) : retrofit2.Call { 38 | 39 | init { 40 | if (value == null) { 41 | requireNotNull(error) { 42 | "BlockedRetrofit2Call should be initialized with an error or value not null" 43 | } 44 | } 45 | if (error == null) { 46 | requireNotNull(value) { 47 | "BlockedRetrofit2Call should be initialized with an error or value not null" 48 | } 49 | } 50 | if (error != null && value != null) error("BlockedRetrofit2Call can't be initialized with a value and an error") 51 | } 52 | 53 | private val isBlocked = AtomicBoolean(true) 54 | private val started = AtomicBoolean(false) 55 | private val completed = AtomicBoolean(false) 56 | private val cancelled = AtomicBoolean(false) 57 | 58 | fun unblock() { 59 | isBlocked.set(false) 60 | } 61 | 62 | private suspend fun run() = withContext(CallDispatcherProvider.IO) { 63 | started.set(true) 64 | while (isBlocked.get()) { 65 | delay(10) 66 | } 67 | if (!cancelled.get()) completed.set(true) 68 | } 69 | 70 | fun isStarted(): Boolean = started.get() 71 | fun isCompleted(): Boolean = completed.get() 72 | override fun enqueue(callback: Callback) { 73 | scope.launch { 74 | run() 75 | if (value != null) callback.onResponse(this@BlockedRetrofit2Call, Response.success(value)) 76 | if (error != null) callback.onFailure(this@BlockedRetrofit2Call, error) 77 | } 78 | } 79 | 80 | override fun execute(): Response = runBlocking { 81 | run() 82 | if (value != null) { 83 | Response.success(value) 84 | } else { 85 | throw error!! 86 | } 87 | } 88 | 89 | override fun isExecuted(): Boolean = started.get() 90 | override fun cancel() { cancelled.set(true) } 91 | override fun isCanceled(): Boolean = cancelled.get() 92 | override fun request(): Request = mock() 93 | override fun timeout(): Timeout = mock() 94 | override fun clone(): Call = BlockedRetrofit2Call(scope, value, error) 95 | } 96 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/test/kotlin/io/getstream/result/call/retrofit/Mother.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import kotlin.random.Random 19 | 20 | private val charPool: CharArray = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toCharArray() 21 | 22 | public fun positiveRandomInt(maxInt: Int = Int.MAX_VALUE - 1): Int = 23 | Random.nextInt(1, maxInt + 1) 24 | 25 | public fun positiveRandomLong(maxLong: Long = Long.MAX_VALUE - 1): Long = 26 | Random.nextLong(1, maxLong + 1) 27 | 28 | public fun randomInt(): Int = Random.nextInt() 29 | public fun randomIntBetween(min: Int, max: Int): Int = Random.nextInt(min, max + 1) 30 | public fun randomLong(): Long = Random.nextLong() 31 | public fun randomLongBetween(min: Long, max: Long = Long.MAX_VALUE - 1): Long = Random.nextLong(min, max + 1) 32 | public fun randomBoolean(): Boolean = Random.nextBoolean() 33 | public fun randomString(size: Int = 20): String = buildString(capacity = size) { 34 | repeat(size) { 35 | append(charPool.random()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /stream-result-call-retrofit/src/test/kotlin/io/getstream/result/call/retrofit/TestCoroutineExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("EXPERIMENTAL_API_USAGE") 17 | 18 | package io.getstream.result.call.retrofit 19 | 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.test.TestCoroutineScheduler 23 | import kotlinx.coroutines.test.TestDispatcher 24 | import kotlinx.coroutines.test.TestScope 25 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 26 | import kotlinx.coroutines.test.resetMain 27 | import kotlinx.coroutines.test.setMain 28 | import org.junit.jupiter.api.extension.AfterAllCallback 29 | import org.junit.jupiter.api.extension.AfterEachCallback 30 | import org.junit.jupiter.api.extension.BeforeAllCallback 31 | import org.junit.jupiter.api.extension.BeforeEachCallback 32 | import org.junit.jupiter.api.extension.ExtensionContext 33 | 34 | public class TestCoroutineExtension : 35 | BeforeEachCallback, 36 | BeforeAllCallback, 37 | AfterEachCallback, 38 | AfterAllCallback { 39 | 40 | private var _scope: TestScope? = null 41 | public val dispatcher: TestDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler()) 42 | public val scope: TestScope 43 | get() = requireNotNull(_scope) 44 | private var beforeAllCalled: Boolean = false 45 | 46 | override fun beforeAll(context: ExtensionContext) { 47 | Dispatchers.setMain(dispatcher) 48 | CallDispatcherProvider.set( 49 | mainDispatcher = dispatcher, 50 | ioDispatcher = dispatcher 51 | ) 52 | beforeAllCalled = true 53 | } 54 | 55 | override fun afterEach(context: ExtensionContext) { 56 | check(beforeAllCalled) { "TestCoroutineExtension field must be static" } 57 | } 58 | 59 | override fun afterAll(context: ExtensionContext) { 60 | Dispatchers.resetMain() 61 | CallDispatcherProvider.reset() 62 | _scope = null 63 | } 64 | 65 | override fun beforeEach(context: ExtensionContext?) { 66 | _scope = TestScope(dispatcher) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stream-result-call/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /stream-result-call/api/android/stream-result-call.api: -------------------------------------------------------------------------------- 1 | public abstract interface class io/getstream/result/call/Call { 2 | public static final field Companion Lio/getstream/result/call/Call$Companion; 3 | public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 4 | public abstract fun cancel ()V 5 | public abstract fun enqueue ()V 6 | public abstract fun enqueue (Lio/getstream/result/call/Call$Callback;)V 7 | public abstract fun execute ()Lio/getstream/result/Result; 8 | } 9 | 10 | public abstract interface class io/getstream/result/call/Call$Callback { 11 | public abstract fun onResult (Lio/getstream/result/Result;)V 12 | } 13 | 14 | public final class io/getstream/result/call/Call$Companion { 15 | public final fun callCanceledError ()Lio/getstream/result/Result; 16 | public final fun runCatching (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 17 | public static synthetic fun runCatching$default (Lio/getstream/result/call/Call$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 18 | } 19 | 20 | public final class io/getstream/result/call/Call$DefaultImpls { 21 | public static fun enqueue (Lio/getstream/result/call/Call;)V 22 | } 23 | 24 | public final class io/getstream/result/call/CallKt { 25 | public static final fun doOnResult (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/Call; 26 | public static final fun doOnStart (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 27 | public static final fun enqueue (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V 28 | public static synthetic fun enqueue$default (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 29 | public static final fun flatMap (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 30 | public static final fun forceNewRequest (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 31 | public static final fun launch (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;)V 32 | public static final fun map (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 33 | public static final fun of (Lio/getstream/result/call/Call;Lio/getstream/result/Result;)Lio/getstream/result/call/Call; 34 | public static final fun onErrorReturn (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/ReturnOnErrorCall; 35 | public static final fun retry (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retry/RetryPolicy;)Lio/getstream/result/call/Call; 36 | public static final fun share (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Lio/getstream/result/call/Call; 37 | public static final fun toUnitCall (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 38 | public static final fun withPrecondition (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 39 | public static final fun zipWith (Lio/getstream/result/call/Call;Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 40 | } 41 | 42 | public final class io/getstream/result/call/CoroutineCall : io/getstream/result/call/Call { 43 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 44 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 45 | public fun cancel ()V 46 | public fun enqueue ()V 47 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 48 | public fun execute ()Lio/getstream/result/Result; 49 | } 50 | 51 | public final class io/getstream/result/call/DistinctCall : io/getstream/result/call/Call { 52 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V 53 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 54 | public fun cancel ()V 55 | public fun enqueue ()V 56 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 57 | public fun execute ()Lio/getstream/result/Result; 58 | public final fun originCall ()Lio/getstream/result/call/Call; 59 | } 60 | 61 | public final class io/getstream/result/call/ReturnOnErrorCall : io/getstream/result/call/Call { 62 | public fun (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 63 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 64 | public fun cancel ()V 65 | public fun enqueue ()V 66 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 67 | public fun execute ()Lio/getstream/result/Result; 68 | } 69 | 70 | public final class io/getstream/result/call/SharedCalls : kotlin/coroutines/CoroutineContext$Element { 71 | public static final field Key Lio/getstream/result/call/SharedCalls$Key; 72 | public fun ()V 73 | public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; 74 | public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; 75 | public fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; 76 | public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; 77 | public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; 78 | } 79 | 80 | public final class io/getstream/result/call/SharedCalls$Key : kotlin/coroutines/CoroutineContext$Key { 81 | } 82 | 83 | public final class io/getstream/result/call/dispatcher/CallDispatcherProvider { 84 | public static final field INSTANCE Lio/getstream/result/call/dispatcher/CallDispatcherProvider; 85 | public final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher; 86 | public final fun getImmediate ()Lkotlinx/coroutines/CoroutineDispatcher; 87 | public final fun getMain ()Lkotlinx/coroutines/CoroutineDispatcher; 88 | public final fun reset ()V 89 | public final fun set (Lkotlinx/coroutines/CoroutineDispatcher;Lkotlinx/coroutines/CoroutineDispatcher;)V 90 | } 91 | 92 | public abstract interface class io/getstream/result/call/retry/RetryPolicy { 93 | public abstract fun retryTimeout (ILio/getstream/result/Error;)I 94 | public abstract fun shouldRetry (ILio/getstream/result/Error;)Z 95 | } 96 | 97 | -------------------------------------------------------------------------------- /stream-result-call/api/jvm/stream-result-call.api: -------------------------------------------------------------------------------- 1 | public abstract interface class io/getstream/result/call/Call { 2 | public static final field Companion Lio/getstream/result/call/Call$Companion; 3 | public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 4 | public abstract fun cancel ()V 5 | public abstract fun enqueue ()V 6 | public abstract fun enqueue (Lio/getstream/result/call/Call$Callback;)V 7 | public abstract fun execute ()Lio/getstream/result/Result; 8 | } 9 | 10 | public abstract interface class io/getstream/result/call/Call$Callback { 11 | public abstract fun onResult (Lio/getstream/result/Result;)V 12 | } 13 | 14 | public final class io/getstream/result/call/Call$Companion { 15 | public final fun callCanceledError ()Lio/getstream/result/Result; 16 | public final fun runCatching (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 17 | public static synthetic fun runCatching$default (Lio/getstream/result/call/Call$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 18 | } 19 | 20 | public final class io/getstream/result/call/Call$DefaultImpls { 21 | public static fun enqueue (Lio/getstream/result/call/Call;)V 22 | } 23 | 24 | public final class io/getstream/result/call/CallKt { 25 | public static final fun doOnResult (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/Call; 26 | public static final fun doOnStart (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 27 | public static final fun enqueue (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V 28 | public static synthetic fun enqueue$default (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 29 | public static final fun flatMap (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 30 | public static final fun forceNewRequest (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 31 | public static final fun launch (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;)V 32 | public static final fun map (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 33 | public static final fun of (Lio/getstream/result/call/Call;Lio/getstream/result/Result;)Lio/getstream/result/call/Call; 34 | public static final fun onErrorReturn (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/ReturnOnErrorCall; 35 | public static final fun retry (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retry/RetryPolicy;)Lio/getstream/result/call/Call; 36 | public static final fun share (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Lio/getstream/result/call/Call; 37 | public static final fun toUnitCall (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 38 | public static final fun withPrecondition (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 39 | public static final fun zipWith (Lio/getstream/result/call/Call;Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 40 | } 41 | 42 | public final class io/getstream/result/call/CoroutineCall : io/getstream/result/call/Call { 43 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 44 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 45 | public fun cancel ()V 46 | public fun enqueue ()V 47 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 48 | public fun execute ()Lio/getstream/result/Result; 49 | } 50 | 51 | public final class io/getstream/result/call/DistinctCall : io/getstream/result/call/Call { 52 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V 53 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 54 | public fun cancel ()V 55 | public fun enqueue ()V 56 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 57 | public fun execute ()Lio/getstream/result/Result; 58 | public final fun originCall ()Lio/getstream/result/call/Call; 59 | } 60 | 61 | public final class io/getstream/result/call/ReturnOnErrorCall : io/getstream/result/call/Call { 62 | public fun (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 63 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 64 | public fun cancel ()V 65 | public fun enqueue ()V 66 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 67 | public fun execute ()Lio/getstream/result/Result; 68 | } 69 | 70 | public final class io/getstream/result/call/SharedCalls : kotlin/coroutines/CoroutineContext$Element { 71 | public static final field Key Lio/getstream/result/call/SharedCalls$Key; 72 | public fun ()V 73 | public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; 74 | public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; 75 | public fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; 76 | public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; 77 | public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; 78 | } 79 | 80 | public final class io/getstream/result/call/SharedCalls$Key : kotlin/coroutines/CoroutineContext$Key { 81 | } 82 | 83 | public final class io/getstream/result/call/dispatcher/CallDispatcherProvider { 84 | public static final field INSTANCE Lio/getstream/result/call/dispatcher/CallDispatcherProvider; 85 | public final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher; 86 | public final fun getImmediate ()Lkotlinx/coroutines/CoroutineDispatcher; 87 | public final fun getMain ()Lkotlinx/coroutines/CoroutineDispatcher; 88 | public final fun reset ()V 89 | public final fun set (Lkotlinx/coroutines/CoroutineDispatcher;Lkotlinx/coroutines/CoroutineDispatcher;)V 90 | } 91 | 92 | public abstract interface class io/getstream/result/call/retry/RetryPolicy { 93 | public abstract fun retryTimeout (ILio/getstream/result/Error;)I 94 | public abstract fun shouldRetry (ILio/getstream/result/Error;)Z 95 | } 96 | 97 | -------------------------------------------------------------------------------- /stream-result-call/api/stream-result-call.api: -------------------------------------------------------------------------------- 1 | public abstract interface class io/getstream/result/call/Call { 2 | public static final field Companion Lio/getstream/result/call/Call$Companion; 3 | public abstract fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 4 | public abstract fun cancel ()V 5 | public abstract fun enqueue ()V 6 | public abstract fun enqueue (Lio/getstream/result/call/Call$Callback;)V 7 | public abstract fun execute ()Lio/getstream/result/Result; 8 | } 9 | 10 | public abstract interface class io/getstream/result/call/Call$Callback { 11 | public abstract fun onResult (Lio/getstream/result/Result;)V 12 | } 13 | 14 | public final class io/getstream/result/call/Call$Companion { 15 | public final fun callCanceledError ()Lio/getstream/result/Result; 16 | public final fun runCatching (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 17 | public static synthetic fun runCatching$default (Lio/getstream/result/call/Call$Companion;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 18 | } 19 | 20 | public final class io/getstream/result/call/Call$DefaultImpls { 21 | public static fun enqueue (Lio/getstream/result/call/Call;)V 22 | } 23 | 24 | public final class io/getstream/result/call/CallKt { 25 | public static final fun doOnResult (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/Call; 26 | public static final fun doOnStart (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 27 | public static final fun enqueue (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V 28 | public static synthetic fun enqueue$default (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 29 | public static final fun flatMap (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 30 | public static final fun forceNewRequest (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 31 | public static final fun launch (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;)V 32 | public static final fun map (Lio/getstream/result/call/Call;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 33 | public static final fun onErrorReturn (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)Lio/getstream/result/call/ReturnOnErrorCall; 34 | public static final fun retry (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lio/getstream/result/call/retry/RetryPolicy;)Lio/getstream/result/call/Call; 35 | public static final fun share (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;)Lio/getstream/result/call/Call; 36 | public static final fun toUnitCall (Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 37 | public static final fun withPrecondition (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/call/Call; 38 | public static final fun zipWith (Lio/getstream/result/call/Call;Lio/getstream/result/call/Call;)Lio/getstream/result/call/Call; 39 | } 40 | 41 | public final class io/getstream/result/call/CoroutineCall : io/getstream/result/call/Call { 42 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 43 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 44 | public fun cancel ()V 45 | public fun enqueue ()V 46 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 47 | public fun execute ()Lio/getstream/result/Result; 48 | } 49 | 50 | public final class io/getstream/result/call/DistinctCall : io/getstream/result/call/Call { 51 | public fun (Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V 52 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 53 | public fun cancel ()V 54 | public fun enqueue ()V 55 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 56 | public fun execute ()Lio/getstream/result/Result; 57 | public final fun originCall ()Lio/getstream/result/call/Call; 58 | } 59 | 60 | public final class io/getstream/result/call/ReturnOnErrorCall : io/getstream/result/call/Call { 61 | public fun (Lio/getstream/result/call/Call;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;)V 62 | public fun await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 63 | public fun cancel ()V 64 | public fun enqueue ()V 65 | public fun enqueue (Lio/getstream/result/call/Call$Callback;)V 66 | public fun execute ()Lio/getstream/result/Result; 67 | } 68 | 69 | public final class io/getstream/result/call/SharedCalls : kotlin/coroutines/CoroutineContext$Element { 70 | public static final field Key Lio/getstream/result/call/SharedCalls$Key; 71 | public fun ()V 72 | public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; 73 | public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; 74 | public fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; 75 | public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; 76 | public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; 77 | } 78 | 79 | public final class io/getstream/result/call/SharedCalls$Key : kotlin/coroutines/CoroutineContext$Key { 80 | } 81 | 82 | public final class io/getstream/result/call/dispatcher/CallDispatcherProvider { 83 | public static final field INSTANCE Lio/getstream/result/call/dispatcher/CallDispatcherProvider; 84 | public final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher; 85 | public final fun getImmediate ()Lkotlinx/coroutines/CoroutineDispatcher; 86 | public final fun getMain ()Lkotlinx/coroutines/CoroutineDispatcher; 87 | public final fun reset ()V 88 | public final fun set (Lkotlinx/coroutines/CoroutineDispatcher;Lkotlinx/coroutines/CoroutineDispatcher;)V 89 | } 90 | 91 | public abstract interface class io/getstream/result/call/retry/RetryPolicy { 92 | public abstract fun retryTimeout (ILio/getstream/result/Error;)I 93 | public abstract fun shouldRetry (ILio/getstream/result/Error;)Z 94 | } 95 | 96 | -------------------------------------------------------------------------------- /stream-result-call/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | import io.getstream.Configurations 19 | 20 | @Suppress("DSL_SCOPE_VIOLATION") 21 | plugins { 22 | id(libs.plugins.android.library.get().pluginId) 23 | id(libs.plugins.kotlin.multiplatform.get().pluginId) 24 | id(libs.plugins.kotlin.atomicfu.get().pluginId) 25 | id(libs.plugins.nexus.plugin.get().pluginId) 26 | id("de.mannodermaus.android-junit5") version "1.11.2.0" 27 | } 28 | 29 | mavenPublishing { 30 | val artifactId = "stream-result-call" 31 | coordinates( 32 | Configurations.artifactGroup, 33 | artifactId, 34 | Configurations.versionName 35 | ) 36 | 37 | pom { 38 | name.set(artifactId) 39 | description.set("Railway-oriented library to easily model and handle success/failure for Kotlin, Android, and Retrofit.") 40 | } 41 | } 42 | 43 | kotlin { 44 | androidTarget { publishLibraryVariants("release") } 45 | jvm() 46 | 47 | iosX64() 48 | iosArm64() 49 | iosSimulatorArm64() 50 | 51 | macosX64() 52 | macosArm64() 53 | 54 | @Suppress("OPT_IN_USAGE") 55 | applyHierarchyTemplate { 56 | common { 57 | group("android") { 58 | withAndroidTarget() 59 | } 60 | group("jvm") { 61 | withJvm() 62 | } 63 | group("skia") { 64 | withJvm() 65 | group("apple") { 66 | group("ios") { 67 | withIosX64() 68 | withIosArm64() 69 | withIosSimulatorArm64() 70 | } 71 | group("macos") { 72 | withMacosX64() 73 | withMacosArm64() 74 | } 75 | } 76 | } 77 | } 78 | } 79 | 80 | sourceSets { 81 | all { 82 | languageSettings.optIn("kotlin.contracts.ExperimentalContracts") 83 | } 84 | commonMain { 85 | dependencies { 86 | api(project(":stream-result")) 87 | 88 | implementation(libs.kotlinx.coroutines) 89 | implementation(libs.stream.log) 90 | 91 | api(libs.atomicfu) 92 | } 93 | } 94 | } 95 | 96 | explicitApi() 97 | } 98 | 99 | android { 100 | compileSdk = Configurations.compileSdk 101 | namespace = "io.getstream.result.call" 102 | defaultConfig { 103 | minSdk = Configurations.minSdk 104 | consumerProguardFiles("consumer-proguard-rules.pro") 105 | } 106 | 107 | compileOptions { 108 | sourceCompatibility = JavaVersion.VERSION_17 109 | targetCompatibility = JavaVersion.VERSION_17 110 | } 111 | } 112 | dependencies { 113 | testImplementation(libs.testing.kluent) 114 | testImplementation(libs.testing.coroutines.test) 115 | testImplementation(libs.testing.mockito) 116 | testImplementation(libs.testing.mockito.kotlin) 117 | testImplementation(libs.testing.mockito.kotlin) 118 | testImplementation(libs.androidx.test.junit) 119 | testImplementation(libs.junit.jupiter.api) 120 | testRuntimeOnly(libs.junit.jupiter.engine) 121 | } -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/BlockedCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import kotlinx.coroutines.CancellationException 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.delay 23 | import kotlinx.coroutines.launch 24 | import kotlinx.coroutines.runBlocking 25 | import kotlinx.coroutines.withContext 26 | import java.util.concurrent.atomic.AtomicBoolean 27 | 28 | internal class BlockedCall(private val result: Result) : Call { 29 | 30 | private val isBlocked = AtomicBoolean(true) 31 | private val started = AtomicBoolean(false) 32 | private val completed = AtomicBoolean(false) 33 | private val cancelled = AtomicBoolean(false) 34 | 35 | fun unblock() { 36 | isBlocked.set(false) 37 | } 38 | 39 | fun block() { 40 | isBlocked.set(true) 41 | } 42 | 43 | private suspend fun awaitResult() = withContext(CallDispatcherProvider.IO) { 44 | try { 45 | started.set(true) 46 | while (isBlocked.get()) { 47 | delay(10) 48 | } 49 | if (!cancelled.get()) { 50 | completed.set(true) 51 | } 52 | result 53 | } catch (e: Throwable) { 54 | if (e is CancellationException) { 55 | cancelled.set(true) 56 | } 57 | throw e 58 | } 59 | } 60 | 61 | fun isStarted(): Boolean = started.get() 62 | fun isCompleted(): Boolean = completed.get() 63 | fun isCanceled(): Boolean = cancelled.get() 64 | 65 | override fun execute(): Result = runBlocking { awaitResult() } 66 | override suspend fun await(): Result = withContext(CallDispatcherProvider.IO) { awaitResult() } 67 | 68 | override fun enqueue(callback: Call.Callback) { 69 | CoroutineScope(CallDispatcherProvider.IO).launch { 70 | callback.onResult(awaitResult()) 71 | } 72 | } 73 | 74 | override fun cancel() { 75 | cancelled.set(true) 76 | } 77 | 78 | fun uncancel() { 79 | cancelled.set(false) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/BlockedTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import kotlinx.coroutines.CoroutineScope 21 | import kotlinx.coroutines.delay 22 | import kotlinx.coroutines.runBlocking 23 | import kotlinx.coroutines.withContext 24 | import java.util.concurrent.atomic.AtomicBoolean 25 | 26 | internal class BlockedTask(private val result: Result) { 27 | 28 | private val isBlocked = AtomicBoolean(true) 29 | private val started = AtomicBoolean(false) 30 | private val completed = AtomicBoolean(false) 31 | 32 | fun unblock() { 33 | isBlocked.set(false) 34 | } 35 | 36 | fun getSyncTask(): () -> Result = { runBlocking { awaitResult() } } 37 | 38 | fun getSuspendTask(): suspend CoroutineScope.() -> Result = { awaitResult() } 39 | 40 | private suspend fun awaitResult() = withContext(CallDispatcherProvider.IO) { 41 | started.set(true) 42 | while (isBlocked.get()) { 43 | delay(10) 44 | } 45 | completed.set(true) 46 | result 47 | } 48 | 49 | fun isStarted(): Boolean = started.get() 50 | fun isCompleted(): Boolean = completed.get() 51 | } 52 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/CallTests.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Error 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.retry.RetryPolicy 21 | import kotlinx.coroutines.ExperimentalCoroutinesApi 22 | import kotlinx.coroutines.test.runTest 23 | import org.amshove.kluent.`should be equal to` 24 | import org.amshove.kluent.shouldBeEqualTo 25 | import org.junit.jupiter.api.Test 26 | import org.junit.jupiter.api.extension.RegisterExtension 27 | 28 | @ExperimentalCoroutinesApi 29 | internal class CallTests { 30 | 31 | companion object { 32 | @JvmField 33 | @RegisterExtension 34 | val testCoroutines = TestCoroutineExtension() 35 | } 36 | 37 | @Test 38 | fun `Should invoke methods in right order`() = runTest { 39 | val mutableList = mutableListOf() 40 | 41 | CoroutineCall(testCoroutines.scope) { 42 | mutableList.add(2) 43 | Result.Success(2) 44 | } 45 | .doOnStart(testCoroutines.scope) { mutableList.add(1) } 46 | .doOnResult(testCoroutines.scope) { mutableList.add(4) } 47 | .enqueue { 48 | mutableList.add(3) 49 | } 50 | 51 | mutableList shouldBeEqualTo listOf(1, 2, 3, 4) 52 | } 53 | 54 | @Test 55 | fun `Should return from onErrorReturn when original call gives error`() = runTest { 56 | val result = CoroutineCall>(testCoroutines.scope) { 57 | Result.Failure(Error.GenericError(message = "Test error")) 58 | }.onErrorReturn(testCoroutines.scope) { 59 | Result.Success(listOf(0, 1)) 60 | }.await() 61 | result shouldBeEqualTo Result.Success(listOf(0, 1)) 62 | } 63 | 64 | @Test 65 | fun `Should return from onErrorReturn when precondition fails`() = runTest { 66 | val result = CoroutineCall(testCoroutines.scope) { 67 | Result.Success(listOf(10, 20, 30)) 68 | }.withPrecondition(testCoroutines.scope) { 69 | Result.Failure(Error.GenericError(message = "Error from precondition")) 70 | }.onErrorReturn(testCoroutines.scope) { 71 | Result.Success(listOf(0, 1)) 72 | }.await() 73 | result shouldBeEqualTo Result.Success(listOf(0, 1)) 74 | } 75 | 76 | @Test 77 | fun `Should not return from onErrorReturn when original call gives success`() = runTest { 78 | val result = CoroutineCall(testCoroutines.scope) { 79 | Result.Success(listOf(10, 20, 30)) 80 | }.onErrorReturn(testCoroutines.scope) { 81 | Result.Success(listOf(0, 1)) 82 | }.await() 83 | result shouldBeEqualTo Result.Success(listOf(10, 20, 30)) 84 | } 85 | 86 | @Test 87 | fun `Should retry a call according to RetryPolicy`() = runTest { 88 | var currentValue = 0 89 | val maxAttempts = 3 90 | val retryPolicy = object : RetryPolicy { 91 | override fun shouldRetry(attempt: Int, error: Error): Boolean = attempt < maxAttempts 92 | 93 | override fun retryTimeout(attempt: Int, error: Error): Int = 0 94 | } 95 | 96 | CoroutineCall(testCoroutines.scope) { 97 | currentValue++ 98 | Result.Failure(Error.GenericError(message = "")) 99 | } 100 | .retry(testCoroutines.scope, retryPolicy) 101 | .doOnStart(testCoroutines.scope) { currentValue++ } 102 | .enqueue { 103 | currentValue++ 104 | } 105 | 106 | currentValue `should be equal to` maxAttempts + 2 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/CoroutineCallTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import kotlinx.coroutines.Job 20 | import kotlinx.coroutines.async 21 | import kotlinx.coroutines.delay 22 | import kotlinx.coroutines.plus 23 | import kotlinx.coroutines.test.runTest 24 | import org.amshove.kluent.`should be equal to` 25 | import org.junit.jupiter.api.Test 26 | import org.junit.jupiter.api.extension.RegisterExtension 27 | import org.mockito.Mockito 28 | import org.mockito.kotlin.any 29 | import org.mockito.kotlin.mock 30 | import org.mockito.kotlin.never 31 | import org.mockito.kotlin.only 32 | import org.mockito.kotlin.spy 33 | import java.util.* 34 | 35 | internal class CoroutineCallTest { 36 | 37 | companion object { 38 | @JvmField 39 | @RegisterExtension 40 | val testCoroutines = TestCoroutineExtension() 41 | } 42 | 43 | val resultValue = randomString() 44 | val validResult: Result = Result.Success(resultValue) 45 | 46 | @Test 47 | fun `Call should be executed and return a valid result`() = runTest { 48 | val blockedTask = BlockedTask(validResult).apply { unblock() } 49 | val call = CoroutineCall(testCoroutines.scope, blockedTask.getSuspendTask()) 50 | 51 | val result = call.execute() 52 | 53 | result `should be equal to` validResult 54 | blockedTask.isStarted() `should be equal to` true 55 | blockedTask.isCompleted() `should be equal to` true 56 | } 57 | 58 | @Test 59 | fun `Call should be enqueued and return a valid result by the callback`() = runTest { 60 | val callback: Call.Callback = mock() 61 | val blockedTask = BlockedTask(validResult).apply { unblock() } 62 | val call = CoroutineCall(testCoroutines.scope, blockedTask.getSuspendTask()) 63 | 64 | call.enqueue(callback) 65 | 66 | Mockito.verify(callback, only()).onResult( 67 | org.mockito.kotlin.check { 68 | it `should be equal to` validResult 69 | } 70 | ) 71 | blockedTask.isStarted() `should be equal to` true 72 | blockedTask.isCompleted() `should be equal to` true 73 | } 74 | 75 | @Test 76 | fun `Canceled Call should be enqueued and shouldn't return value on the callback`() = runTest { 77 | val callback: Call.Callback = spy() 78 | val blockedTask = BlockedTask(validResult) 79 | val call = CoroutineCall(testCoroutines.scope, blockedTask.getSuspendTask()) 80 | 81 | call.enqueue(callback) 82 | call.cancel() 83 | blockedTask.unblock() 84 | 85 | Mockito.verify(callback, never()).onResult(any()) 86 | blockedTask.isStarted() `should be equal to` true 87 | blockedTask.isCompleted() `should be equal to` false 88 | } 89 | 90 | @Test 91 | fun `Call should be executed asynchronous and return a valid result`() = runTest { 92 | val blockedTask = BlockedTask(validResult).apply { unblock() } 93 | val call = CoroutineCall(testCoroutines.scope, blockedTask.getSuspendTask()) 94 | 95 | val result = call.await() 96 | 97 | result `should be equal to` validResult 98 | blockedTask.isStarted() `should be equal to` true 99 | blockedTask.isCompleted() `should be equal to` true 100 | } 101 | 102 | @Test 103 | fun `Canceled Call should be executed asynchronous and return a cancel error`() = runTest { 104 | val blockedTask = BlockedTask(validResult) 105 | val call = CoroutineCall(testCoroutines.scope, blockedTask.getSuspendTask()) 106 | 107 | val localScope = testCoroutines.scope + Job() 108 | val deferredResult = localScope.async { 109 | call.await() 110 | } 111 | delay(10) 112 | call.cancel() 113 | blockedTask.unblock() 114 | val result = deferredResult.await() 115 | 116 | result `should be equal to` Call.callCanceledError() 117 | blockedTask.isStarted() `should be equal to` true 118 | blockedTask.isCompleted() `should be equal to` false 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/DoOnResultCallTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import kotlinx.coroutines.async 20 | import kotlinx.coroutines.delay 21 | import kotlinx.coroutines.test.runTest 22 | import org.amshove.kluent.`should be equal to` 23 | import org.junit.jupiter.api.Test 24 | import org.junit.jupiter.api.extension.RegisterExtension 25 | import org.mockito.Mockito 26 | import org.mockito.kotlin.any 27 | import org.mockito.kotlin.mock 28 | import org.mockito.kotlin.never 29 | import org.mockito.kotlin.only 30 | import org.mockito.kotlin.spy 31 | 32 | internal class DoOnResultCallTest { 33 | 34 | companion object { 35 | @JvmField 36 | @RegisterExtension 37 | val testCoroutines = TestCoroutineExtension() 38 | } 39 | 40 | private val resultValue = randomString() 41 | private val validResult: Result = Result.Success(resultValue) 42 | private val consumer = SpyConsumer() 43 | 44 | @Test 45 | fun `Call should be executed and return a valid result`() = runTest { 46 | val blockedCall = BlockedCall(validResult).apply { unblock() } 47 | val call = blockedCall.doOnResult(testCoroutines.scope, consumer) 48 | 49 | val result = call.execute() 50 | 51 | result `should be equal to` validResult 52 | consumer `should be invoked with` validResult 53 | blockedCall.isStarted() `should be equal to` true 54 | blockedCall.isCompleted() `should be equal to` true 55 | blockedCall.isCanceled() `should be equal to` false 56 | } 57 | 58 | @Test 59 | fun `Call should be enqueued and return a valid result by the callback`() = runTest { 60 | val callback: Call.Callback = mock() 61 | val blockedCall = BlockedCall(validResult).apply { unblock() } 62 | val call = blockedCall.doOnResult(testCoroutines.scope, consumer) 63 | 64 | call.enqueue(callback) 65 | 66 | Mockito.verify(callback, only()).onResult( 67 | org.mockito.kotlin.check { 68 | it `should be equal to` validResult 69 | } 70 | ) 71 | consumer `should be invoked with` validResult 72 | blockedCall.isStarted() `should be equal to` true 73 | blockedCall.isCompleted() `should be equal to` true 74 | blockedCall.isCanceled() `should be equal to` false 75 | } 76 | 77 | @Test 78 | fun `Canceled Call should be enqueued and shouldn't return value on the callback`() = runTest { 79 | val callback: Call.Callback = spy() 80 | val blockedCall = BlockedCall(validResult) 81 | val call = blockedCall.doOnResult(testCoroutines.scope, consumer) 82 | 83 | call.enqueue(callback) 84 | call.cancel() 85 | blockedCall.unblock() 86 | 87 | Mockito.verify(callback, never()).onResult(any()) 88 | consumer.`should not be invoked`() 89 | blockedCall.isStarted() `should be equal to` true 90 | blockedCall.isCompleted() `should be equal to` false 91 | blockedCall.isCanceled() `should be equal to` true 92 | } 93 | 94 | @Test 95 | fun `Call should be executed asynchronous and return a valid result`() = runTest { 96 | val blockedCall = BlockedCall(validResult).apply { unblock() } 97 | val call = blockedCall.doOnResult(testCoroutines.scope, consumer) 98 | 99 | val result = call.await() 100 | 101 | result `should be equal to` validResult 102 | consumer `should be invoked with` validResult 103 | blockedCall.isStarted() `should be equal to` true 104 | blockedCall.isCompleted() `should be equal to` true 105 | blockedCall.isCanceled() `should be equal to` false 106 | } 107 | 108 | @Test 109 | fun `Canceled Call should be executed asynchronous and return a cancel error`() = runTest { 110 | val blockedCall = BlockedCall(validResult) 111 | val call = blockedCall.doOnResult(testCoroutines.scope, consumer) 112 | 113 | val deferedResult = async { call.await() } 114 | delay(10) 115 | call.cancel() 116 | blockedCall.unblock() 117 | val result = deferedResult.await() 118 | 119 | result `should be equal to` Call.callCanceledError() 120 | consumer.`should not be invoked`() 121 | blockedCall.isStarted() `should be equal to` true 122 | blockedCall.isCompleted() `should be equal to` false 123 | blockedCall.isCanceled() `should be equal to` true 124 | } 125 | 126 | private class SpyConsumer : suspend (Result) -> Unit { 127 | private var invocations = 0 128 | private var result: Result? = null 129 | override suspend fun invoke(result: Result) { 130 | invocations++ 131 | this.result = result 132 | } 133 | 134 | infix fun `should be invoked with`(result: Result) { 135 | invocations `should be equal to` 1 136 | this.result `should be equal to` result 137 | } 138 | 139 | fun `should not be invoked`() { 140 | if (invocations > 0) { 141 | throw AssertionError("Consumer never wanted to be invoked but invoked") 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/DoOnStartCallTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import kotlinx.coroutines.async 20 | import kotlinx.coroutines.delay 21 | import kotlinx.coroutines.test.runTest 22 | import org.amshove.kluent.`should be equal to` 23 | import org.junit.jupiter.api.Test 24 | import org.junit.jupiter.api.extension.RegisterExtension 25 | import org.mockito.Mockito 26 | import org.mockito.kotlin.any 27 | import org.mockito.kotlin.mock 28 | import org.mockito.kotlin.never 29 | import org.mockito.kotlin.only 30 | import org.mockito.kotlin.spy 31 | 32 | internal class DoOnStartCallTest { 33 | companion object { 34 | @JvmField 35 | @RegisterExtension 36 | val testCoroutines = TestCoroutineExtension() 37 | } 38 | 39 | private val resultValue = randomString() 40 | private val validResult: Result = Result.Success(resultValue) 41 | private val sideEffect = SpySideEffect() 42 | 43 | @Test 44 | fun `Call should be executed and return a valid result`() = runTest { 45 | val blockedCall = BlockedCall(validResult).apply { unblock() } 46 | val call = blockedCall.doOnStart(testCoroutines.scope, sideEffect) 47 | 48 | val result = call.execute() 49 | 50 | result `should be equal to` validResult 51 | sideEffect.`should be invoked`() 52 | blockedCall.isStarted() `should be equal to` true 53 | blockedCall.isCompleted() `should be equal to` true 54 | blockedCall.isCanceled() `should be equal to` false 55 | } 56 | 57 | @Test 58 | fun `Call should be enqueued and return a valid result by the callback`() = runTest { 59 | val callback: Call.Callback = mock() 60 | val blockedCall = BlockedCall(validResult).apply { unblock() } 61 | val call = blockedCall.doOnStart(testCoroutines.scope, sideEffect) 62 | 63 | call.enqueue(callback) 64 | 65 | Mockito.verify(callback, only()).onResult( 66 | org.mockito.kotlin.check { 67 | it `should be equal to` validResult 68 | } 69 | ) 70 | sideEffect.`should be invoked`() 71 | blockedCall.isStarted() `should be equal to` true 72 | blockedCall.isCompleted() `should be equal to` true 73 | blockedCall.isCanceled() `should be equal to` false 74 | } 75 | 76 | @Test 77 | fun `Canceled Call should be enqueued and shouldn't return value on the callback`() = runTest { 78 | val callback: Call.Callback = spy() 79 | val blockedCall = BlockedCall(validResult) 80 | val call = blockedCall.doOnStart(testCoroutines.scope, sideEffect) 81 | 82 | call.enqueue(callback) 83 | call.cancel() 84 | blockedCall.unblock() 85 | 86 | Mockito.verify(callback, never()).onResult(any()) 87 | sideEffect.`should be invoked`() 88 | blockedCall.isStarted() `should be equal to` true 89 | blockedCall.isCompleted() `should be equal to` false 90 | blockedCall.isCanceled() `should be equal to` true 91 | } 92 | 93 | @Test 94 | fun `Call should be executed asynchronous and return a valid result`() = runTest { 95 | val blockedCall = BlockedCall(validResult).apply { unblock() } 96 | val call = blockedCall.doOnStart(testCoroutines.scope, sideEffect) 97 | 98 | val result = call.await() 99 | 100 | result `should be equal to` validResult 101 | sideEffect.`should be invoked`() 102 | blockedCall.isStarted() `should be equal to` true 103 | blockedCall.isCompleted() `should be equal to` true 104 | blockedCall.isCanceled() `should be equal to` false 105 | } 106 | 107 | @Test 108 | fun `Canceled Call should be executed asynchronous and return a cancel error`() = runTest { 109 | val blockedCall = BlockedCall(validResult) 110 | val call = blockedCall.doOnStart(testCoroutines.scope, sideEffect) 111 | 112 | val deferedResult = async { call.await() } 113 | delay(10) 114 | call.cancel() 115 | blockedCall.unblock() 116 | val result = deferedResult.await() 117 | 118 | result `should be equal to` Call.callCanceledError() 119 | sideEffect.`should be invoked`() 120 | blockedCall.isStarted() `should be equal to` true 121 | blockedCall.isCompleted() `should be equal to` false 122 | blockedCall.isCanceled() `should be equal to` true 123 | } 124 | 125 | private class SpySideEffect : suspend () -> Unit { 126 | private var invocations = 0 127 | override suspend fun invoke() { 128 | invocations++ 129 | } 130 | 131 | fun `should be invoked`() { 132 | invocations `should be equal to` 1 133 | } 134 | 135 | fun `should not be invoked`() { 136 | if (invocations > 0) { 137 | throw AssertionError("SideEffect never wanted to be invoked but invoked") 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/MapCallTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import kotlinx.coroutines.async 20 | import kotlinx.coroutines.test.runTest 21 | import org.amshove.kluent.`should be equal to` 22 | import org.junit.jupiter.api.Test 23 | import org.junit.jupiter.api.extension.ExtendWith 24 | import org.mockito.Mockito 25 | import org.mockito.kotlin.any 26 | import org.mockito.kotlin.mock 27 | import org.mockito.kotlin.never 28 | import org.mockito.kotlin.only 29 | import org.mockito.kotlin.spy 30 | 31 | @ExtendWith(TestCoroutineExtension::class) 32 | internal class MapCallTest { 33 | 34 | private val resultValue = positiveRandomInt() 35 | private val validResult: Result = Result.Success(resultValue) 36 | private val expectedResult: Result = Result.Success("$resultValue") 37 | private val mapper: SpyMapper = SpyMapper { "$it" } 38 | 39 | @Test 40 | fun `Call should be executed and return a valid result`() = runTest { 41 | val blockedCall = BlockedCall(validResult).apply { unblock() } 42 | val call = blockedCall.map(mapper) 43 | 44 | val result = call.execute() 45 | 46 | result `should be equal to` expectedResult 47 | mapper `should be invoked with` resultValue 48 | blockedCall.isStarted() `should be equal to` true 49 | blockedCall.isCompleted() `should be equal to` true 50 | blockedCall.isCanceled() `should be equal to` false 51 | } 52 | 53 | @Test 54 | fun `Canceled Call should be executed and return a cancel error`() = runTest { 55 | val blockedCall = BlockedCall(validResult) 56 | val call = blockedCall.map(mapper) 57 | 58 | val deferedResult = async { call.execute() } 59 | call.cancel() 60 | blockedCall.unblock() 61 | val result = deferedResult.await() 62 | 63 | result `should be equal to` Call.callCanceledError() 64 | mapper.`should not be invoked`() 65 | blockedCall.isStarted() `should be equal to` true 66 | blockedCall.isCompleted() `should be equal to` false 67 | blockedCall.isCanceled() `should be equal to` true 68 | } 69 | 70 | @Test 71 | fun `Call should be enqueued and return a valid result by the callback`() = runTest { 72 | val callback: Call.Callback = mock() 73 | val blockedCall = BlockedCall(validResult).apply { unblock() } 74 | val call = blockedCall.map(mapper) 75 | 76 | call.enqueue(callback) 77 | 78 | Mockito.verify(callback, only()).onResult( 79 | org.mockito.kotlin.check { 80 | it `should be equal to` expectedResult 81 | } 82 | ) 83 | mapper `should be invoked with` resultValue 84 | blockedCall.isStarted() `should be equal to` true 85 | blockedCall.isCompleted() `should be equal to` true 86 | blockedCall.isCanceled() `should be equal to` false 87 | } 88 | 89 | @Test 90 | fun `Canceled Call should be enqueued and shouldn't return value on the callback`() = runTest { 91 | val callback: Call.Callback = spy() 92 | val blockedCall = BlockedCall(validResult) 93 | val call = blockedCall.map(mapper) 94 | 95 | call.enqueue(callback) 96 | call.cancel() 97 | blockedCall.unblock() 98 | 99 | Mockito.verify(callback, never()).onResult(any()) 100 | mapper.`should not be invoked`() 101 | blockedCall.isStarted() `should be equal to` true 102 | blockedCall.isCompleted() `should be equal to` false 103 | blockedCall.isCanceled() `should be equal to` true 104 | } 105 | 106 | @Test 107 | fun `Call should be executed asynchronous and return a valid result`() = runTest { 108 | val blockedCall = BlockedCall(validResult).apply { unblock() } 109 | val call = blockedCall.map(mapper) 110 | 111 | val result = call.await() 112 | 113 | result `should be equal to` expectedResult 114 | mapper `should be invoked with` resultValue 115 | blockedCall.isStarted() `should be equal to` true 116 | blockedCall.isCompleted() `should be equal to` true 117 | blockedCall.isCanceled() `should be equal to` false 118 | } 119 | 120 | @Test 121 | fun `Canceled Call should be executed asynchronous and return a cancel error`() = runTest { 122 | val blockedCall = BlockedCall(validResult) 123 | val call = blockedCall.map(mapper) 124 | 125 | val deferedResult = async { call.await() } 126 | call.cancel() 127 | blockedCall.unblock() 128 | val result = deferedResult.await() 129 | 130 | result `should be equal to` Call.callCanceledError() 131 | mapper.`should not be invoked`() 132 | blockedCall.isStarted() `should be equal to` true 133 | blockedCall.isCompleted() `should be equal to` false 134 | blockedCall.isCanceled() `should be equal to` true 135 | } 136 | 137 | private class SpyMapper(private val mapFunction: (T) -> R) : (T) -> R { 138 | private var invocations = 0 139 | private var input: T? = null 140 | override fun invoke(input: T): R { 141 | invocations++ 142 | this.input = input 143 | return mapFunction(input) 144 | } 145 | 146 | infix fun `should be invoked with`(input: T) { 147 | invocations `should be equal to` 1 148 | this.input `should be equal to` input 149 | } 150 | 151 | fun `should not be invoked`() { 152 | if (invocations > 0) { 153 | throw AssertionError("SpyMapper never wanted to be invoked but invoked") 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/Mother.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import kotlin.random.Random 19 | 20 | private val charPool: CharArray = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toCharArray() 21 | 22 | public fun positiveRandomInt(maxInt: Int = Int.MAX_VALUE - 1): Int = 23 | Random.nextInt(1, maxInt + 1) 24 | 25 | public fun positiveRandomLong(maxLong: Long = Long.MAX_VALUE - 1): Long = 26 | Random.nextLong(1, maxLong + 1) 27 | 28 | public fun randomInt(): Int = Random.nextInt() 29 | public fun randomIntBetween(min: Int, max: Int): Int = Random.nextInt(min, max + 1) 30 | public fun randomLong(): Long = Random.nextLong() 31 | public fun randomLongBetween(min: Long, max: Long = Long.MAX_VALUE - 1): Long = Random.nextLong(min, max + 1) 32 | public fun randomBoolean(): Boolean = Random.nextBoolean() 33 | public fun randomString(size: Int = 20): String = buildString(capacity = size) { 34 | repeat(size) { 35 | append(charPool.random()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /stream-result-call/src/androidTest/kotlin/io/getstream/result/call/TestCoroutineExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("EXPERIMENTAL_API_USAGE") 17 | 18 | package io.getstream.result.call 19 | 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.test.TestCoroutineScheduler 23 | import kotlinx.coroutines.test.TestDispatcher 24 | import kotlinx.coroutines.test.TestScope 25 | import kotlinx.coroutines.test.UnconfinedTestDispatcher 26 | import kotlinx.coroutines.test.resetMain 27 | import kotlinx.coroutines.test.setMain 28 | import org.junit.jupiter.api.extension.AfterAllCallback 29 | import org.junit.jupiter.api.extension.AfterEachCallback 30 | import org.junit.jupiter.api.extension.BeforeAllCallback 31 | import org.junit.jupiter.api.extension.BeforeEachCallback 32 | import org.junit.jupiter.api.extension.ExtensionContext 33 | 34 | public class TestCoroutineExtension : 35 | BeforeEachCallback, 36 | BeforeAllCallback, 37 | AfterEachCallback, 38 | AfterAllCallback { 39 | 40 | private var _scope: TestScope? = null 41 | public val dispatcher: TestDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler()) 42 | public val scope: TestScope 43 | get() = requireNotNull(_scope) 44 | private var beforeAllCalled: Boolean = false 45 | 46 | override fun beforeAll(context: ExtensionContext) { 47 | Dispatchers.setMain(dispatcher) 48 | CallDispatcherProvider.set( 49 | mainDispatcher = dispatcher, 50 | ioDispatcher = dispatcher 51 | ) 52 | beforeAllCalled = true 53 | } 54 | 55 | override fun afterEach(context: ExtensionContext) { 56 | check(beforeAllCalled) { "TestCoroutineExtension field must be static" } 57 | } 58 | 59 | override fun afterAll(context: ExtensionContext) { 60 | Dispatchers.resetMain() 61 | CallDispatcherProvider.reset() 62 | _scope = null 63 | } 64 | 65 | override fun beforeEach(context: ExtensionContext?) { 66 | _scope = TestScope(dispatcher) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/CoroutineCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.log.taggedLogger 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.Job 23 | import kotlinx.coroutines.launch 24 | import kotlinx.coroutines.runBlocking 25 | import kotlinx.coroutines.withContext 26 | import kotlin.coroutines.CoroutineContext 27 | 28 | public class CoroutineCall( 29 | private val scope: CoroutineScope, 30 | private val suspendingTask: suspend CoroutineScope.() -> Result 31 | ) : Call { 32 | 33 | private val logger by taggedLogger("CoroutineCall") 34 | 35 | private val jobs = hashSetOf() 36 | 37 | override fun execute(): Result = runBlocking { await() } 38 | 39 | override suspend fun await(): Result = Call.runCatching { 40 | logger.d { "[await] no args" } 41 | withContext(scope.coroutineContext) { 42 | jobs.addFrom(coroutineContext) 43 | suspendingTask() 44 | } 45 | } 46 | 47 | override fun cancel() { 48 | logger.d { "[cancel] no args" } 49 | jobs.cancelAll() 50 | } 51 | 52 | override fun enqueue(callback: Call.Callback) { 53 | logger.d { "[enqueue] no args" } 54 | scope.launch { 55 | jobs.addFrom(coroutineContext) 56 | val result = suspendingTask() 57 | withContext(CallDispatcherProvider.Main) { 58 | callback.onResult(result) 59 | } 60 | } 61 | } 62 | 63 | private fun HashSet.cancelAll() { 64 | forEach { 65 | it.cancel() 66 | } 67 | clear() 68 | } 69 | 70 | private fun HashSet.addFrom(context: CoroutineContext) { 71 | context[Job]?.also { 72 | add(it) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/DistinctCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import io.getstream.result.call.internal.SynchronizedReference 21 | import kotlinx.atomicfu.atomic 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.Deferred 24 | import kotlinx.coroutines.SupervisorJob 25 | import kotlinx.coroutines.async 26 | import kotlinx.coroutines.cancelChildren 27 | import kotlinx.coroutines.job 28 | import kotlinx.coroutines.launch 29 | import kotlinx.coroutines.plus 30 | import kotlinx.coroutines.runBlocking 31 | import kotlinx.coroutines.withContext 32 | 33 | /** 34 | * Reusable wrapper around [Call] which delivers a single result to all subscribers. 35 | */ 36 | public class DistinctCall( 37 | scope: CoroutineScope, 38 | private val callBuilder: () -> Call, 39 | private val onFinished: () -> Unit 40 | ) : Call { 41 | 42 | private val distinctScope = scope + SupervisorJob(scope.coroutineContext.job) 43 | private val deferred = SynchronizedReference>>() 44 | private val delegateCall = atomic?>(initial = null) 45 | 46 | public fun originCall(): Call = callBuilder() 47 | 48 | override fun execute(): Result = runBlocking { await() } 49 | 50 | override fun enqueue(callback: Call.Callback) { 51 | distinctScope.launch { 52 | await().takeUnless { it.isCanceled }?.also { result -> 53 | withContext(CallDispatcherProvider.Main) { 54 | callback.onResult(result) 55 | } 56 | } 57 | } 58 | } 59 | 60 | override suspend fun await(): Result = Call.runCatching { 61 | deferred.getOrCreate { 62 | distinctScope.async { 63 | callBuilder() 64 | .also { delegateCall.value = it } 65 | .await() 66 | .also { doFinally() } 67 | } 68 | }.await() 69 | } 70 | 71 | override fun cancel() { 72 | delegateCall.value?.cancel() 73 | distinctScope.coroutineContext.cancelChildren() 74 | doFinally() 75 | } 76 | 77 | private fun doFinally() { 78 | if (deferred.reset()) { 79 | onFinished() 80 | } 81 | } 82 | 83 | private val Result.isCanceled get() = this == Call.callCanceledError() 84 | } 85 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/DoOnResultCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import kotlinx.coroutines.CoroutineScope 21 | import kotlinx.coroutines.SupervisorJob 22 | import kotlinx.coroutines.cancelChildren 23 | import kotlinx.coroutines.job 24 | import kotlinx.coroutines.launch 25 | import kotlinx.coroutines.plus 26 | import kotlinx.coroutines.runBlocking 27 | import kotlinx.coroutines.withContext 28 | 29 | internal class DoOnResultCall( 30 | private val originalCall: Call, 31 | scope: CoroutineScope, 32 | private val consumer: suspend (Result) -> Unit 33 | ) : Call { 34 | 35 | private val callScope = scope + SupervisorJob(scope.coroutineContext.job) 36 | 37 | override fun execute(): Result = runBlocking { await() } 38 | 39 | override fun enqueue(callback: Call.Callback) { 40 | callScope.launch { 41 | originalCall.enqueue { result -> 42 | callScope.launch { 43 | withContext(CallDispatcherProvider.Main) { 44 | callback.onResult(result) 45 | } 46 | consumer(result) 47 | } 48 | } 49 | } 50 | } 51 | 52 | override fun cancel() { 53 | originalCall.cancel() 54 | callScope.coroutineContext.cancelChildren() 55 | } 56 | 57 | override suspend fun await(): Result = Call.runCatching { 58 | withContext(callScope.coroutineContext) { 59 | originalCall.await().also { consumer(it) } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/DoOnStartCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import kotlinx.coroutines.CoroutineScope 21 | import kotlinx.coroutines.SupervisorJob 22 | import kotlinx.coroutines.cancelChildren 23 | import kotlinx.coroutines.job 24 | import kotlinx.coroutines.launch 25 | import kotlinx.coroutines.plus 26 | import kotlinx.coroutines.runBlocking 27 | import kotlinx.coroutines.withContext 28 | 29 | internal class DoOnStartCall( 30 | private val originalCall: Call, 31 | scope: CoroutineScope, 32 | private val sideEffect: suspend () -> Unit 33 | ) : Call { 34 | 35 | private val callScope = scope + SupervisorJob(scope.coroutineContext.job) 36 | 37 | override fun execute(): Result = runBlocking { await() } 38 | 39 | override fun enqueue(callback: Call.Callback) { 40 | callScope.launch { 41 | sideEffect() 42 | originalCall.enqueue { 43 | callScope.launch(CallDispatcherProvider.Main) { callback.onResult(it) } 44 | } 45 | } 46 | } 47 | 48 | override fun cancel() { 49 | originalCall.cancel() 50 | callScope.coroutineContext.cancelChildren() 51 | } 52 | 53 | override suspend fun await(): Result = Call.runCatching { 54 | withContext(callScope.coroutineContext) { 55 | sideEffect() 56 | originalCall.await() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/ErrorCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Error 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.launch 23 | import kotlinx.coroutines.withContext 24 | 25 | internal class ErrorCall( 26 | private val scope: CoroutineScope, 27 | private val e: Error 28 | ) : Call { 29 | override fun cancel() { 30 | // Not supported 31 | } 32 | 33 | override fun execute(): Result { 34 | return Result.Failure(e) 35 | } 36 | 37 | override fun enqueue(callback: Call.Callback) { 38 | scope.launch(CallDispatcherProvider.Main) { 39 | callback.onResult(Result.Failure(e)) 40 | } 41 | } 42 | 43 | override suspend fun await(): Result = withContext(scope.coroutineContext) { 44 | Result.Failure(e) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/FlatMapCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.flatMapSuspend 20 | import kotlinx.atomicfu.atomic 21 | import kotlinx.coroutines.runBlocking 22 | 23 | internal class FlatMapCall( 24 | private val call: Call, 25 | private val mapper: (T) -> Call 26 | ) : Call { 27 | private val canceled = atomic(false) 28 | private var mappedCall: Call? = null 29 | 30 | override fun cancel() { 31 | canceled.value = true 32 | call.cancel() 33 | mappedCall?.cancel() 34 | } 35 | 36 | override fun execute(): Result = runBlocking { await() } 37 | 38 | override fun enqueue(callback: Call.Callback) { 39 | call.enqueue { 40 | it.takeUnless { canceled.value } 41 | ?.onError { callback.onResult(Result.Failure(it)) } 42 | ?.onSuccess { value -> 43 | mapper(value) 44 | .also { mappedCall = it } 45 | .enqueue { 46 | it.takeUnless { canceled.value }?.let(callback::onResult) 47 | } 48 | } 49 | } 50 | } 51 | 52 | override suspend fun await(): Result = 53 | call.await() 54 | .takeUnless { canceled.value } 55 | ?.flatMapSuspend { 56 | mapper(it) 57 | .also { mappedCall = it } 58 | .takeUnless { canceled.value } 59 | ?.await() 60 | ?: Call.callCanceledError() 61 | } 62 | ?: Call.callCanceledError() 63 | } 64 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/MapCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.Call.Companion.callCanceledError 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.atomicfu.atomic 22 | import kotlinx.coroutines.runBlocking 23 | import kotlinx.coroutines.withContext 24 | 25 | internal class MapCall( 26 | private val call: Call, 27 | private val mapper: (T) -> K 28 | ) : Call { 29 | 30 | private val canceled = atomic(false) 31 | 32 | override fun cancel() { 33 | canceled.value = true 34 | call.cancel() 35 | } 36 | 37 | override fun execute(): Result = runBlocking { await() } 38 | 39 | override fun enqueue(callback: Call.Callback) { 40 | call.enqueue { 41 | it.takeUnless { canceled.value } 42 | ?.map(mapper) 43 | ?.let(callback::onResult) 44 | } 45 | } 46 | 47 | override suspend fun await(): Result = withContext(CallDispatcherProvider.IO) { 48 | call.await() 49 | .takeUnless { canceled.value } 50 | ?.map(mapper) 51 | .takeUnless { canceled.value } 52 | ?: callCanceledError() 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/ResultCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | 20 | internal class ResultCall( 21 | private val result: Result 22 | ) : Call { 23 | override fun execute(): Result = result 24 | override suspend fun await(): Result = result 25 | override fun enqueue(callback: Call.Callback) = callback.onResult(result) 26 | override fun cancel() { 27 | /* no-op */ 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/ReturnOnErrorCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Error 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.coroutines.CoroutineScope 22 | import kotlinx.coroutines.SupervisorJob 23 | import kotlinx.coroutines.cancelChildren 24 | import kotlinx.coroutines.job 25 | import kotlinx.coroutines.launch 26 | import kotlinx.coroutines.plus 27 | import kotlinx.coroutines.runBlocking 28 | import kotlinx.coroutines.withContext 29 | 30 | /** 31 | * A wrapper around [Call] that swallows the error and emits new data from [onErrorReturn]. 32 | */ 33 | public class ReturnOnErrorCall( 34 | private val originalCall: Call, 35 | scope: CoroutineScope, 36 | private val onErrorReturn: suspend (originalError: Error) -> Result 37 | ) : Call { 38 | 39 | private val callScope = scope + SupervisorJob(scope.coroutineContext.job) 40 | 41 | override fun execute(): Result = runBlocking { await() } 42 | 43 | override fun enqueue(callback: Call.Callback) { 44 | callScope.launch { 45 | originalCall.enqueue { originalResult -> 46 | callScope.launch { 47 | val finalResult = map(originalResult) 48 | withContext(CallDispatcherProvider.Main) { 49 | callback.onResult(finalResult) 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | override fun cancel() { 57 | originalCall.cancel() 58 | callScope.coroutineContext.cancelChildren() 59 | } 60 | 61 | override suspend fun await(): Result = Call.runCatching(::map) { 62 | withContext(callScope.coroutineContext) { 63 | map(originalCall.await()) 64 | } 65 | } 66 | 67 | private suspend fun map(result: Result): Result = when (result) { 68 | is Result.Success -> result 69 | is Result.Failure -> onErrorReturn(result.value) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/SharedCalls.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import kotlinx.atomicfu.atomic 19 | import kotlinx.coroutines.CoroutineScope 20 | import kotlin.coroutines.CoroutineContext 21 | 22 | /** 23 | * Creates a shared [Call] instance. 24 | */ 25 | @Suppress("FunctionName", "UNCHECKED_CAST") 26 | internal fun SharedCall( 27 | origin: Call, 28 | originIdentifier: () -> Int, 29 | scope: CoroutineScope 30 | ): Call { 31 | val sharedCalls = scope.coroutineContext[SharedCalls] ?: return origin 32 | val identifier = originIdentifier() 33 | return sharedCalls[identifier] as? Call 34 | ?: DistinctCall(scope, { origin }) { 35 | sharedCalls.remove(identifier) 36 | }.also { 37 | sharedCalls.put(identifier, it) 38 | } 39 | } 40 | 41 | /** 42 | * The [CoroutineContext.Element] which holds ongoing calls until those get finished. 43 | * 44 | * The purpose of shared calls is to stop side effects for the same call executing multiple times. 45 | */ 46 | public class SharedCalls : CoroutineContext.Element { 47 | 48 | /** 49 | * A key of [SharedCalls] coroutine context element. 50 | */ 51 | public override val key: CoroutineContext.Key = Key 52 | 53 | /** 54 | * A collection of uncompleted calls. 55 | */ 56 | private val calls = atomic>>(initial = mutableMapOf()) 57 | 58 | /** 59 | * Provides a [Call] based of specified [identifier] if available. 60 | */ 61 | internal operator fun get(identifier: Int): Call? { 62 | return calls.value[identifier] 63 | } 64 | 65 | /** 66 | * Puts a [Call] behind of specified [identifier]. 67 | */ 68 | internal fun put(identifier: Int, value: Call) { 69 | calls.value[identifier] = value 70 | } 71 | 72 | /** 73 | * Removes a [Call] based of specified [identifier]. 74 | */ 75 | internal fun remove(identifier: Int) { 76 | calls.value.remove(identifier) 77 | } 78 | 79 | public companion object Key : CoroutineContext.Key 80 | } 81 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/WithPreconditionCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 20 | import io.getstream.result.flatMapSuspend 21 | import io.getstream.result.onErrorSuspend 22 | import kotlinx.coroutines.CoroutineScope 23 | import kotlinx.coroutines.SupervisorJob 24 | import kotlinx.coroutines.cancelChildren 25 | import kotlinx.coroutines.job 26 | import kotlinx.coroutines.launch 27 | import kotlinx.coroutines.plus 28 | import kotlinx.coroutines.runBlocking 29 | import kotlinx.coroutines.withContext 30 | 31 | internal class WithPreconditionCall( 32 | private val originalCall: Call, 33 | scope: CoroutineScope, 34 | private val precondition: suspend () -> Result 35 | ) : Call { 36 | private val callScope = scope + SupervisorJob(scope.coroutineContext.job) 37 | 38 | override fun execute(): Result = runBlocking { await() } 39 | 40 | override fun enqueue(callback: Call.Callback) { 41 | callScope.launch { 42 | val result = precondition() 43 | result 44 | .onSuccess { originalCall.enqueue { callScope.launch { notifyResult(it, callback) } } } 45 | .onErrorSuspend { notifyResult(Result.Failure(it), callback) } 46 | } 47 | } 48 | 49 | private suspend fun notifyResult(result: Result, callback: Call.Callback) = 50 | withContext(CallDispatcherProvider.Main) { 51 | callback.onResult(result) 52 | } 53 | 54 | override fun cancel() { 55 | originalCall.cancel() 56 | callScope.coroutineContext.cancelChildren() 57 | } 58 | 59 | override suspend fun await(): Result = Call.runCatching { 60 | withContext(callScope.coroutineContext) { 61 | precondition() 62 | .flatMapSuspend { 63 | originalCall.await() 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/ZipCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call 17 | 18 | import io.getstream.result.Error 19 | import io.getstream.result.Result 20 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 21 | import kotlinx.atomicfu.atomic 22 | import kotlinx.coroutines.async 23 | import kotlinx.coroutines.runBlocking 24 | import kotlinx.coroutines.withContext 25 | 26 | internal class ZipCall( 27 | private val callA: Call, 28 | private val callB: Call 29 | ) : Call> { 30 | private val canceled = atomic(false) 31 | 32 | override fun cancel() { 33 | canceled.value = true 34 | callA.cancel() 35 | callB.cancel() 36 | } 37 | 38 | override fun execute(): Result> = runBlocking { await() } 39 | 40 | override fun enqueue(callback: Call.Callback>) { 41 | callA.enqueue { resultA -> 42 | when { 43 | canceled.value -> { // no-op 44 | } 45 | 46 | resultA is Result.Success -> callB.enqueue { resultB -> 47 | when { 48 | canceled.value -> null 49 | resultB is Result.Success -> resultA.combine(resultB) 50 | resultB is Result.Failure -> getErrorB(resultB) 51 | else -> null 52 | }?.let(callback::onResult) 53 | } 54 | 55 | resultA is Result.Failure -> callback.onResult(getErrorA(resultA).also { callB.cancel() }) 56 | } 57 | } 58 | } 59 | 60 | private fun getErrorA(resultA: Result.Failure): Result> { 61 | return Result.Failure(resultA.value) 62 | } 63 | 64 | private fun getErrorB(resultB: Result.Failure): Result> { 65 | return Result.Failure(resultB.value) 66 | } 67 | 68 | private fun Result.combine(result: Result): Result> { 69 | return if (this is Result.Success && result is Result.Success) { 70 | Result.Success(Pair(this.value, result.value)) 71 | } else { 72 | Result.Failure(Error.GenericError("Cannot combine results because one of them failed.")) 73 | } 74 | } 75 | 76 | override suspend fun await(): Result> = withContext(CallDispatcherProvider.IO) { 77 | val deferredA = async { callA.await() } 78 | val deferredB = async { callB.await() } 79 | 80 | val resultA = deferredA.await() 81 | if (canceled.value) return@withContext Call.callCanceledError() 82 | if (resultA is Result.Failure) { 83 | deferredB.cancel() 84 | return@withContext getErrorA(resultA) 85 | } 86 | 87 | val resultB = deferredB.await() 88 | if (canceled.value) return@withContext Call.callCanceledError() 89 | if (resultB is Result.Failure) { 90 | return@withContext getErrorB(resultB) 91 | } 92 | 93 | resultA.combine(resultB) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/dispatcher/CallDispatcherProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.dispatcher 17 | 18 | import io.getstream.result.call.dispatcher.CallDispatcherProvider.reset 19 | import io.getstream.result.call.dispatcher.CallDispatcherProvider.set 20 | import kotlinx.coroutines.CoroutineDispatcher 21 | import kotlinx.coroutines.Dispatchers 22 | import kotlinx.coroutines.IO 23 | import kotlinx.coroutines.MainCoroutineDispatcher 24 | 25 | /** 26 | * Coroutine dispatchers used internally by Stream libraries. Should always be used 27 | * instead of directly using [Dispatchers] or creating new dispatchers. 28 | * 29 | * Can be modified using [set] and [reset] for testing purposes. 30 | */ 31 | public object CallDispatcherProvider { 32 | 33 | /** 34 | * Represents the Main coroutine dispatcher, tied to the UI thread. 35 | */ 36 | public var Main: CoroutineDispatcher = Dispatchers.Main 37 | internal set 38 | 39 | /** 40 | * Represents the Immediate coroutine dispatcher, which is usually tied to the UI thread. 41 | * 42 | * Useful for some cases where the UI updates require immediate execution, without dispatching the update events. 43 | */ 44 | public val Immediate: CoroutineDispatcher 45 | get() { 46 | val mainDispatcher = Main 47 | 48 | return if (mainDispatcher is MainCoroutineDispatcher) { 49 | mainDispatcher.immediate 50 | } else { 51 | mainDispatcher 52 | } 53 | } 54 | 55 | /** 56 | * Represents the IO coroutine dispatcher, which is usually tied to background work. 57 | */ 58 | public var IO: CoroutineDispatcher = Dispatchers.IO 59 | internal set 60 | 61 | /** 62 | * Overrides the main (UI thread) and IO dispatcher. For testing purposes only. 63 | */ 64 | public fun set(mainDispatcher: CoroutineDispatcher, ioDispatcher: CoroutineDispatcher) { 65 | Main = mainDispatcher 66 | IO = ioDispatcher 67 | } 68 | 69 | /** 70 | * Resets the dispatchers to their default values. For testing purposes only. 71 | */ 72 | public fun reset() { 73 | Main = Dispatchers.Main 74 | IO = Dispatchers.IO 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/internal/SynchronizedReference.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.internal 17 | 18 | import kotlin.concurrent.Volatile 19 | 20 | /** 21 | * An object reference that may be updated thread-safely. 22 | */ 23 | internal class SynchronizedReference( 24 | @Volatile private var value: T? = null 25 | ) { 26 | 27 | /** 28 | * Provides an existing [T] object reference. 29 | */ 30 | public fun get(): T? = value 31 | 32 | /** 33 | * Provides either an existing [T] object or creates a new one using [builder] function. 34 | * 35 | * This method is **thread-safe** and can be safely invoked without external synchronization. 36 | */ 37 | public fun getOrCreate(builder: () -> T): T { 38 | return value ?: value ?: builder.invoke().also { 39 | value = it 40 | } 41 | } 42 | 43 | /** 44 | * Drops an existing [T] object reference to null. 45 | */ 46 | public fun reset(): Boolean { 47 | return set(null) != null 48 | } 49 | 50 | /** 51 | * Accepts [value] instance of [T] and holds its reference. 52 | * 53 | * This method is **thread-safe** and can be safely invoked without external synchronization. 54 | */ 55 | public fun set(value: T?): T? { 56 | val currentValue = this.value 57 | this.value = value 58 | return currentValue 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/retry/CallRetryService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retry 17 | 18 | import io.getstream.log.taggedLogger 19 | import io.getstream.result.Error 20 | import io.getstream.result.Result 21 | import kotlinx.coroutines.delay 22 | 23 | /** 24 | * Service that allows retrying calls based on [RetryPolicy]. 25 | * 26 | * @param retryPolicy The policy used for determining if the call should be retried. 27 | */ 28 | internal class CallRetryService( 29 | private val retryPolicy: RetryPolicy, 30 | private val isPermanent: (Error) -> Boolean = { false } 31 | ) { 32 | 33 | private val logger by taggedLogger("CallRetryService") 34 | 35 | /** 36 | * Runs the task and retries based on [RetryPolicy]. 37 | * 38 | * @param task The task to be run. 39 | */ 40 | @Suppress("LoopWithTooManyJumpStatements") 41 | suspend fun runAndRetry(task: suspend () -> Result): Result { 42 | var attempt = 1 43 | var result: Result 44 | while (true) { 45 | result = task() 46 | when (result) { 47 | is Result.Success -> break 48 | is Result.Failure -> { 49 | if (isPermanent.invoke(result.value)) { 50 | break 51 | } 52 | val shouldRetry = retryPolicy.shouldRetry(attempt, result.value) 53 | val timeout = retryPolicy.retryTimeout(attempt, result.value) 54 | 55 | if (shouldRetry) { 56 | // temporary failure, continue 57 | logger.i { 58 | "API call failed (attempt $attempt), retrying in $timeout seconds." + 59 | " Error was ${result.value}" 60 | } 61 | delay(timeout.toLong()) 62 | attempt += 1 63 | } else { 64 | logger.i { 65 | "API call failed (attempt $attempt). " + 66 | "Giving up for now, will retry when connection recovers. " + 67 | "Error was ${result.value}" 68 | } 69 | break 70 | } 71 | } 72 | } 73 | } 74 | return result 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/retry/RetryCall.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retry 17 | 18 | import io.getstream.result.Result 19 | import io.getstream.result.call.Call 20 | import io.getstream.result.call.Call.Companion.callCanceledError 21 | import io.getstream.result.call.dispatcher.CallDispatcherProvider 22 | import kotlinx.atomicfu.atomic 23 | import kotlinx.coroutines.CoroutineScope 24 | import kotlinx.coroutines.Job 25 | import kotlinx.coroutines.launch 26 | import kotlinx.coroutines.runBlocking 27 | import kotlinx.coroutines.withContext 28 | import kotlinx.coroutines.yield 29 | 30 | /** 31 | * A wrapper around [Call] that allows retrying the original call based on 32 | * [RetryPolicy]. 33 | * 34 | * @param originalCall The original call. 35 | * @param scope Coroutine scope where the call should be run. 36 | * @param callRetryService A service responsible for retrying calls based on 37 | * [RetryPolicy]. 38 | */ 39 | internal class RetryCall( 40 | private val originalCall: Call, 41 | private val scope: CoroutineScope, 42 | private val callRetryService: CallRetryService 43 | ) : Call { 44 | 45 | private var job: Job? = null 46 | private val canceled = atomic(false) 47 | 48 | override fun execute(): Result = runBlocking { await() } 49 | 50 | override fun enqueue(callback: Call.Callback) { 51 | job = scope.launch { 52 | val result = await() 53 | withContext(CallDispatcherProvider.Main) { 54 | yield() 55 | callback.onResult(result) 56 | } 57 | } 58 | } 59 | 60 | override fun cancel() { 61 | canceled.value = true 62 | originalCall.cancel() 63 | job?.cancel() 64 | } 65 | 66 | override suspend fun await(): Result = withContext(scope.coroutineContext) { 67 | callRetryService.runAndRetry { 68 | originalCall 69 | .takeUnless { canceled.value } 70 | ?.await() 71 | ?: callCanceledError() 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /stream-result-call/src/commonMain/kotlin/io/getstream/result/call/retry/RetryPolicy.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.call.retry 17 | 18 | import io.getstream.result.Error 19 | 20 | /** 21 | * The retry policy is being used to determine if and when the call should be retried if a temporary error occurred. 22 | */ 23 | public interface RetryPolicy { 24 | /** 25 | * Determines whether the call should be retried. 26 | * 27 | * @param attempt Current retry attempt. 28 | * @param error The error returned by the previous attempt. 29 | * 30 | * @return true if the call should be retried, false otherwise. 31 | */ 32 | public fun shouldRetry(attempt: Int, error: Error): Boolean 33 | 34 | /** 35 | * Provides a timeout used to delay the next call. 36 | * 37 | * @param attempt Current retry attempt. 38 | * @param error The error returned by the previous attempt. 39 | * 40 | * @return The timeout in milliseconds before making a retry. 41 | */ 42 | public fun retryTimeout(attempt: Int, error: Error): Int 43 | } 44 | -------------------------------------------------------------------------------- /stream-result/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /stream-result/api/android/stream-result.api: -------------------------------------------------------------------------------- 1 | public abstract class io/getstream/result/Error { 2 | public abstract fun getMessage ()Ljava/lang/String; 3 | } 4 | 5 | public final class io/getstream/result/Error$GenericError : io/getstream/result/Error { 6 | public fun (Ljava/lang/String;)V 7 | public final fun component1 ()Ljava/lang/String; 8 | public final fun copy (Ljava/lang/String;)Lio/getstream/result/Error$GenericError; 9 | public static synthetic fun copy$default (Lio/getstream/result/Error$GenericError;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/result/Error$GenericError; 10 | public fun equals (Ljava/lang/Object;)Z 11 | public fun getMessage ()Ljava/lang/String; 12 | public fun hashCode ()I 13 | public fun toString ()Ljava/lang/String; 14 | } 15 | 16 | public final class io/getstream/result/Error$NetworkError : io/getstream/result/Error { 17 | public static final field Companion Lio/getstream/result/Error$NetworkError$Companion; 18 | public static final field UNKNOWN_STATUS_CODE I 19 | public fun (Ljava/lang/String;IILjava/lang/Throwable;)V 20 | public synthetic fun (Ljava/lang/String;IILjava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V 21 | public final fun component1 ()Ljava/lang/String; 22 | public final fun component2 ()I 23 | public final fun component3 ()I 24 | public final fun component4 ()Ljava/lang/Throwable; 25 | public final fun copy (Ljava/lang/String;IILjava/lang/Throwable;)Lio/getstream/result/Error$NetworkError; 26 | public static synthetic fun copy$default (Lio/getstream/result/Error$NetworkError;Ljava/lang/String;IILjava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$NetworkError; 27 | public fun equals (Ljava/lang/Object;)Z 28 | public final fun getCause ()Ljava/lang/Throwable; 29 | public fun getMessage ()Ljava/lang/String; 30 | public final fun getServerErrorCode ()I 31 | public final fun getStatusCode ()I 32 | public fun hashCode ()I 33 | public fun toString ()Ljava/lang/String; 34 | } 35 | 36 | public final class io/getstream/result/Error$NetworkError$Companion { 37 | } 38 | 39 | public final class io/getstream/result/Error$ThrowableError : io/getstream/result/Error { 40 | public fun (Ljava/lang/String;Ljava/lang/Throwable;)V 41 | public final fun component1 ()Ljava/lang/String; 42 | public final fun component2 ()Ljava/lang/Throwable; 43 | public final fun copy (Ljava/lang/String;Ljava/lang/Throwable;)Lio/getstream/result/Error$ThrowableError; 44 | public static synthetic fun copy$default (Lio/getstream/result/Error$ThrowableError;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$ThrowableError; 45 | public fun equals (Ljava/lang/Object;)Z 46 | public final fun getCause ()Ljava/lang/Throwable; 47 | public fun getMessage ()Ljava/lang/String; 48 | public fun hashCode ()I 49 | public fun toString ()Ljava/lang/String; 50 | } 51 | 52 | public final class io/getstream/result/ErrorKt { 53 | public static final fun copyWithMessage (Lio/getstream/result/Error;Ljava/lang/String;)Lio/getstream/result/Error; 54 | public static final fun extractCause (Lio/getstream/result/Error;)Ljava/lang/Throwable; 55 | } 56 | 57 | public abstract class io/getstream/result/Result { 58 | public final fun errorOrNull ()Lio/getstream/result/Error; 59 | public final fun getOrNull ()Ljava/lang/Object; 60 | public final fun getOrThrow ()Ljava/lang/Object; 61 | public final fun isFailure ()Z 62 | public final fun isSuccess ()Z 63 | public final fun map (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 64 | public final synthetic fun mapSuspend (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 65 | public final fun onError (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 66 | public final fun onSuccess (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 67 | public final fun toUnitResult ()Lio/getstream/result/Result; 68 | } 69 | 70 | public final class io/getstream/result/Result$Failure : io/getstream/result/Result { 71 | public fun (Lio/getstream/result/Error;)V 72 | public final fun component1 ()Lio/getstream/result/Error; 73 | public final fun copy (Lio/getstream/result/Error;)Lio/getstream/result/Result$Failure; 74 | public static synthetic fun copy$default (Lio/getstream/result/Result$Failure;Lio/getstream/result/Error;ILjava/lang/Object;)Lio/getstream/result/Result$Failure; 75 | public fun equals (Ljava/lang/Object;)Z 76 | public final fun getValue ()Lio/getstream/result/Error; 77 | public fun hashCode ()I 78 | public fun toString ()Ljava/lang/String; 79 | } 80 | 81 | public final class io/getstream/result/Result$Success : io/getstream/result/Result { 82 | public fun (Ljava/lang/Object;)V 83 | public final fun component1 ()Ljava/lang/Object; 84 | public final fun copy (Ljava/lang/Object;)Lio/getstream/result/Result$Success; 85 | public static synthetic fun copy$default (Lio/getstream/result/Result$Success;Ljava/lang/Object;ILjava/lang/Object;)Lio/getstream/result/Result$Success; 86 | public fun equals (Ljava/lang/Object;)Z 87 | public final fun getValue ()Ljava/lang/Object; 88 | public fun hashCode ()I 89 | public fun toString ()Ljava/lang/String; 90 | } 91 | 92 | public final class io/getstream/result/ResultKt { 93 | public static final fun flatMap (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 94 | public static final synthetic fun flatMapSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 95 | public static final synthetic fun onErrorSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 96 | public static final synthetic fun onSuccessSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 97 | public static final synthetic fun recover (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result$Success; 98 | public static final synthetic fun recoverSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 99 | public static final synthetic fun then (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 100 | } 101 | 102 | public abstract interface annotation class io/getstream/result/internal/StreamHandsOff : java/lang/annotation/Annotation { 103 | public abstract fun reason ()Ljava/lang/String; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /stream-result/api/jvm/stream-result.api: -------------------------------------------------------------------------------- 1 | public abstract class io/getstream/result/Error { 2 | public abstract fun getMessage ()Ljava/lang/String; 3 | } 4 | 5 | public final class io/getstream/result/Error$GenericError : io/getstream/result/Error { 6 | public fun (Ljava/lang/String;)V 7 | public final fun component1 ()Ljava/lang/String; 8 | public final fun copy (Ljava/lang/String;)Lio/getstream/result/Error$GenericError; 9 | public static synthetic fun copy$default (Lio/getstream/result/Error$GenericError;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/result/Error$GenericError; 10 | public fun equals (Ljava/lang/Object;)Z 11 | public fun getMessage ()Ljava/lang/String; 12 | public fun hashCode ()I 13 | public fun toString ()Ljava/lang/String; 14 | } 15 | 16 | public final class io/getstream/result/Error$NetworkError : io/getstream/result/Error { 17 | public static final field Companion Lio/getstream/result/Error$NetworkError$Companion; 18 | public static final field UNKNOWN_STATUS_CODE I 19 | public fun (Ljava/lang/String;IILjava/lang/Throwable;)V 20 | public synthetic fun (Ljava/lang/String;IILjava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V 21 | public final fun component1 ()Ljava/lang/String; 22 | public final fun component2 ()I 23 | public final fun component3 ()I 24 | public final fun component4 ()Ljava/lang/Throwable; 25 | public final fun copy (Ljava/lang/String;IILjava/lang/Throwable;)Lio/getstream/result/Error$NetworkError; 26 | public static synthetic fun copy$default (Lio/getstream/result/Error$NetworkError;Ljava/lang/String;IILjava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$NetworkError; 27 | public fun equals (Ljava/lang/Object;)Z 28 | public final fun getCause ()Ljava/lang/Throwable; 29 | public fun getMessage ()Ljava/lang/String; 30 | public final fun getServerErrorCode ()I 31 | public final fun getStatusCode ()I 32 | public fun hashCode ()I 33 | public fun toString ()Ljava/lang/String; 34 | } 35 | 36 | public final class io/getstream/result/Error$NetworkError$Companion { 37 | } 38 | 39 | public final class io/getstream/result/Error$ThrowableError : io/getstream/result/Error { 40 | public fun (Ljava/lang/String;Ljava/lang/Throwable;)V 41 | public final fun component1 ()Ljava/lang/String; 42 | public final fun component2 ()Ljava/lang/Throwable; 43 | public final fun copy (Ljava/lang/String;Ljava/lang/Throwable;)Lio/getstream/result/Error$ThrowableError; 44 | public static synthetic fun copy$default (Lio/getstream/result/Error$ThrowableError;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$ThrowableError; 45 | public fun equals (Ljava/lang/Object;)Z 46 | public final fun getCause ()Ljava/lang/Throwable; 47 | public fun getMessage ()Ljava/lang/String; 48 | public fun hashCode ()I 49 | public fun toString ()Ljava/lang/String; 50 | } 51 | 52 | public final class io/getstream/result/ErrorKt { 53 | public static final fun copyWithMessage (Lio/getstream/result/Error;Ljava/lang/String;)Lio/getstream/result/Error; 54 | public static final fun extractCause (Lio/getstream/result/Error;)Ljava/lang/Throwable; 55 | } 56 | 57 | public abstract class io/getstream/result/Result { 58 | public final fun errorOrNull ()Lio/getstream/result/Error; 59 | public final fun getOrNull ()Ljava/lang/Object; 60 | public final fun getOrThrow ()Ljava/lang/Object; 61 | public final fun isFailure ()Z 62 | public final fun isSuccess ()Z 63 | public final fun map (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 64 | public final synthetic fun mapSuspend (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 65 | public final fun onError (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 66 | public final fun onSuccess (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 67 | public final fun toUnitResult ()Lio/getstream/result/Result; 68 | } 69 | 70 | public final class io/getstream/result/Result$Failure : io/getstream/result/Result { 71 | public fun (Lio/getstream/result/Error;)V 72 | public final fun component1 ()Lio/getstream/result/Error; 73 | public final fun copy (Lio/getstream/result/Error;)Lio/getstream/result/Result$Failure; 74 | public static synthetic fun copy$default (Lio/getstream/result/Result$Failure;Lio/getstream/result/Error;ILjava/lang/Object;)Lio/getstream/result/Result$Failure; 75 | public fun equals (Ljava/lang/Object;)Z 76 | public final fun getValue ()Lio/getstream/result/Error; 77 | public fun hashCode ()I 78 | public fun toString ()Ljava/lang/String; 79 | } 80 | 81 | public final class io/getstream/result/Result$Success : io/getstream/result/Result { 82 | public fun (Ljava/lang/Object;)V 83 | public final fun component1 ()Ljava/lang/Object; 84 | public final fun copy (Ljava/lang/Object;)Lio/getstream/result/Result$Success; 85 | public static synthetic fun copy$default (Lio/getstream/result/Result$Success;Ljava/lang/Object;ILjava/lang/Object;)Lio/getstream/result/Result$Success; 86 | public fun equals (Ljava/lang/Object;)Z 87 | public final fun getValue ()Ljava/lang/Object; 88 | public fun hashCode ()I 89 | public fun toString ()Ljava/lang/String; 90 | } 91 | 92 | public final class io/getstream/result/ResultKt { 93 | public static final fun flatMap (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 94 | public static final synthetic fun flatMapSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 95 | public static final synthetic fun onErrorSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 96 | public static final synthetic fun onSuccessSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 97 | public static final synthetic fun recover (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result$Success; 98 | public static final synthetic fun recoverSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 99 | public static final synthetic fun then (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 100 | } 101 | 102 | public abstract interface annotation class io/getstream/result/internal/StreamHandsOff : java/lang/annotation/Annotation { 103 | public abstract fun reason ()Ljava/lang/String; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /stream-result/api/stream-result.api: -------------------------------------------------------------------------------- 1 | public abstract class io/getstream/result/Error { 2 | public abstract fun getMessage ()Ljava/lang/String; 3 | } 4 | 5 | public final class io/getstream/result/Error$GenericError : io/getstream/result/Error { 6 | public fun (Ljava/lang/String;)V 7 | public final fun component1 ()Ljava/lang/String; 8 | public final fun copy (Ljava/lang/String;)Lio/getstream/result/Error$GenericError; 9 | public static synthetic fun copy$default (Lio/getstream/result/Error$GenericError;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/result/Error$GenericError; 10 | public fun equals (Ljava/lang/Object;)Z 11 | public fun getMessage ()Ljava/lang/String; 12 | public fun hashCode ()I 13 | public fun toString ()Ljava/lang/String; 14 | } 15 | 16 | public final class io/getstream/result/Error$NetworkError : io/getstream/result/Error { 17 | public static final field Companion Lio/getstream/result/Error$NetworkError$Companion; 18 | public static final field UNKNOWN_STATUS_CODE I 19 | public fun (Ljava/lang/String;IILjava/lang/Throwable;)V 20 | public synthetic fun (Ljava/lang/String;IILjava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V 21 | public final fun component1 ()Ljava/lang/String; 22 | public final fun component2 ()I 23 | public final fun component3 ()I 24 | public final fun component4 ()Ljava/lang/Throwable; 25 | public final fun copy (Ljava/lang/String;IILjava/lang/Throwable;)Lio/getstream/result/Error$NetworkError; 26 | public static synthetic fun copy$default (Lio/getstream/result/Error$NetworkError;Ljava/lang/String;IILjava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$NetworkError; 27 | public fun equals (Ljava/lang/Object;)Z 28 | public final fun getCause ()Ljava/lang/Throwable; 29 | public fun getMessage ()Ljava/lang/String; 30 | public final fun getServerErrorCode ()I 31 | public final fun getStatusCode ()I 32 | public fun hashCode ()I 33 | public fun toString ()Ljava/lang/String; 34 | } 35 | 36 | public final class io/getstream/result/Error$NetworkError$Companion { 37 | } 38 | 39 | public final class io/getstream/result/Error$ThrowableError : io/getstream/result/Error { 40 | public fun (Ljava/lang/String;Ljava/lang/Throwable;)V 41 | public final fun component1 ()Ljava/lang/String; 42 | public final fun component2 ()Ljava/lang/Throwable; 43 | public final fun copy (Ljava/lang/String;Ljava/lang/Throwable;)Lio/getstream/result/Error$ThrowableError; 44 | public static synthetic fun copy$default (Lio/getstream/result/Error$ThrowableError;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$ThrowableError; 45 | public fun equals (Ljava/lang/Object;)Z 46 | public final fun getCause ()Ljava/lang/Throwable; 47 | public fun getMessage ()Ljava/lang/String; 48 | public fun hashCode ()I 49 | public fun toString ()Ljava/lang/String; 50 | } 51 | 52 | public final class io/getstream/result/ErrorKt { 53 | public static final fun copyWithMessage (Lio/getstream/result/Error;Ljava/lang/String;)Lio/getstream/result/Error; 54 | public static final fun extractCause (Lio/getstream/result/Error;)Ljava/lang/Throwable; 55 | } 56 | 57 | public abstract class io/getstream/result/Result { 58 | public final fun errorOrNull ()Lio/getstream/result/Error; 59 | public final fun getOrNull ()Ljava/lang/Object; 60 | public final fun getOrThrow ()Ljava/lang/Object; 61 | public final fun isFailure ()Z 62 | public final fun isSuccess ()Z 63 | public final fun map (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 64 | public final synthetic fun mapSuspend (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 65 | public final fun onError (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 66 | public final fun onSuccess (Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 67 | public final fun toUnitResult ()Lio/getstream/result/Result; 68 | } 69 | 70 | public final class io/getstream/result/Result$Failure : io/getstream/result/Result { 71 | public fun (Lio/getstream/result/Error;)V 72 | public final fun component1 ()Lio/getstream/result/Error; 73 | public final fun copy (Lio/getstream/result/Error;)Lio/getstream/result/Result$Failure; 74 | public static synthetic fun copy$default (Lio/getstream/result/Result$Failure;Lio/getstream/result/Error;ILjava/lang/Object;)Lio/getstream/result/Result$Failure; 75 | public fun equals (Ljava/lang/Object;)Z 76 | public final fun getValue ()Lio/getstream/result/Error; 77 | public fun hashCode ()I 78 | public fun toString ()Ljava/lang/String; 79 | } 80 | 81 | public final class io/getstream/result/Result$Success : io/getstream/result/Result { 82 | public fun (Ljava/lang/Object;)V 83 | public final fun component1 ()Ljava/lang/Object; 84 | public final fun copy (Ljava/lang/Object;)Lio/getstream/result/Result$Success; 85 | public static synthetic fun copy$default (Lio/getstream/result/Result$Success;Ljava/lang/Object;ILjava/lang/Object;)Lio/getstream/result/Result$Success; 86 | public fun equals (Ljava/lang/Object;)Z 87 | public final fun getValue ()Ljava/lang/Object; 88 | public fun hashCode ()I 89 | public fun toString ()Ljava/lang/String; 90 | } 91 | 92 | public final class io/getstream/result/ResultKt { 93 | public static final fun flatMap (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 94 | public static final synthetic fun flatMapSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 95 | public static final synthetic fun onErrorSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 96 | public static final synthetic fun onSuccessSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 97 | public static final synthetic fun recover (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result$Success; 98 | public static final synthetic fun recoverSuspend (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 99 | public static final synthetic fun then (Lio/getstream/result/Result;Lkotlin/jvm/functions/Function1;)Lio/getstream/result/Result; 100 | } 101 | 102 | public abstract interface annotation class io/getstream/result/internal/StreamHandsOff : java/lang/annotation/Annotation { 103 | public abstract fun reason ()Ljava/lang/String; 104 | } 105 | 106 | -------------------------------------------------------------------------------- /stream-result/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | @file:Suppress("UnstableApiUsage") 17 | 18 | import io.getstream.Configurations 19 | 20 | @Suppress("DSL_SCOPE_VIOLATION") 21 | plugins { 22 | id(libs.plugins.android.library.get().pluginId) 23 | id(libs.plugins.kotlin.multiplatform.get().pluginId) 24 | id(libs.plugins.nexus.plugin.get().pluginId) 25 | } 26 | 27 | mavenPublishing { 28 | val artifactId = "stream-result" 29 | coordinates( 30 | Configurations.artifactGroup, 31 | artifactId, 32 | Configurations.versionName 33 | ) 34 | 35 | pom { 36 | name.set(artifactId) 37 | description.set("Railway-oriented library to easily model and handle success/failure for Kotlin, Android, and Retrofit.") 38 | } 39 | } 40 | 41 | kotlin { 42 | androidTarget { publishLibraryVariants("release") } 43 | jvm() 44 | 45 | iosX64() 46 | iosArm64() 47 | iosSimulatorArm64() 48 | 49 | macosX64() 50 | macosArm64() 51 | 52 | @Suppress("OPT_IN_USAGE") 53 | applyHierarchyTemplate { 54 | common { 55 | group("android") { 56 | withAndroidTarget() 57 | } 58 | group("jvm") { 59 | withJvm() 60 | } 61 | group("skia") { 62 | withJvm() 63 | group("apple") { 64 | group("ios") { 65 | withIosX64() 66 | withIosArm64() 67 | withIosSimulatorArm64() 68 | } 69 | group("macos") { 70 | withMacosX64() 71 | withMacosArm64() 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | sourceSets { 79 | all { 80 | languageSettings.optIn("kotlin.contracts.ExperimentalContracts") 81 | } 82 | } 83 | 84 | explicitApi() 85 | } 86 | 87 | android { 88 | compileSdk = Configurations.compileSdk 89 | namespace = "io.getstream.result" 90 | defaultConfig { 91 | minSdk = Configurations.minSdk 92 | consumerProguardFiles("consumer-proguard-rules.pro") 93 | } 94 | 95 | compileOptions { 96 | sourceCompatibility = JavaVersion.VERSION_17 97 | targetCompatibility = JavaVersion.VERSION_17 98 | } 99 | } 100 | 101 | dependencies { 102 | } -------------------------------------------------------------------------------- /stream-result/src/commonMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /stream-result/src/commonMain/kotlin/io/getstream/result/Error.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2022 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result 17 | 18 | import io.getstream.result.Error.NetworkError.Companion.UNKNOWN_STATUS_CODE 19 | import io.getstream.result.internal.StreamHandsOff 20 | 21 | /** 22 | * Represents the generic error model. 23 | */ 24 | public sealed class Error { 25 | 26 | public abstract val message: String 27 | 28 | /** 29 | * An error that only contains the message. 30 | * 31 | * @param message The message describing the error. 32 | */ 33 | public data class GenericError(override val message: String) : Error() 34 | 35 | /** 36 | * An error that contains a message and cause. 37 | * 38 | * @param message The message describing the error. 39 | * @param cause The [Throwable] associated with the error. 40 | */ 41 | public data class ThrowableError(override val message: String, public val cause: Throwable) : 42 | Error() { 43 | 44 | @StreamHandsOff( 45 | "Throwable doesn't override the equals method;" + 46 | " therefore, it needs custom implementation." 47 | ) 48 | override fun equals(other: Any?): Boolean { 49 | if (this === other) return true 50 | if (other != null && this::class != other::class) return false 51 | 52 | return (other as? Error)?.let { 53 | message == it.message && cause.equalCause(it.extractCause()) 54 | } ?: false 55 | } 56 | 57 | private fun Throwable?.equalCause(other: Throwable?): Boolean { 58 | if ((this == null && other == null) || this === other) return true 59 | return this?.message == other?.message && this?.cause.equalCause(other?.cause) 60 | } 61 | 62 | @StreamHandsOff( 63 | "Throwable doesn't override the hashCode method;" + 64 | " therefore, it needs custom implementation." 65 | ) 66 | override fun hashCode(): Int { 67 | return 31 * message.hashCode() + cause.hashCode() 68 | } 69 | } 70 | 71 | /** 72 | * An error resulting from the network operation. 73 | * 74 | * @param message The message describing the error. 75 | * @param serverErrorCode The error code returned by the backend. 76 | * @param statusCode HTTP status code or [UNKNOWN_STATUS_CODE] if not available. 77 | * @param cause The optional [Throwable] associated with the error. 78 | */ 79 | public data class NetworkError( 80 | override val message: String, 81 | public val serverErrorCode: Int, 82 | public val statusCode: Int = UNKNOWN_STATUS_CODE, 83 | public val cause: Throwable? = null 84 | ) : Error() { 85 | 86 | @StreamHandsOff( 87 | "Throwable doesn't override the equals method;" + 88 | " therefore, it needs custom implementation." 89 | ) 90 | override fun equals(other: Any?): Boolean { 91 | if (this === other) return true 92 | if (other != null && this::class != other::class) return false 93 | 94 | return (other as? Error)?.let { 95 | message == it.message && cause.equalCause(it.extractCause()) 96 | } ?: false 97 | } 98 | 99 | private fun Throwable?.equalCause(other: Throwable?): Boolean { 100 | if ((this == null && other == null) || this === other) return true 101 | return this?.message == other?.message && this?.cause.equalCause(other?.cause) 102 | } 103 | 104 | @StreamHandsOff( 105 | "Throwable doesn't override the hashCode method;" + 106 | " therefore, it needs custom implementation." 107 | ) 108 | override fun hashCode(): Int { 109 | return 31 * message.hashCode() + (cause?.hashCode() ?: 0) 110 | } 111 | 112 | public companion object { 113 | public const val UNKNOWN_STATUS_CODE: Int = -1 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * Copies the original [Error] objects with custom message. 120 | * 121 | * @param message The message to replace. 122 | * 123 | * @return New [Error] instance. 124 | */ 125 | public fun Error.copyWithMessage(message: String): Error { 126 | return when (this) { 127 | is Error.GenericError -> this.copy(message = message) 128 | is Error.NetworkError -> this.copy(message = message) 129 | is Error.ThrowableError -> this.copy(message = message) 130 | } 131 | } 132 | 133 | /** 134 | * Extracts the cause from [Error] object or null if it's not available. 135 | * 136 | * @return The [Throwable] that is the error's cause or null if not available. 137 | */ 138 | public fun Error.extractCause(): Throwable? { 139 | return when (this) { 140 | is Error.GenericError -> null 141 | is Error.NetworkError -> cause 142 | is Error.ThrowableError -> cause 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /stream-result/src/commonMain/kotlin/io/getstream/result/internal/StreamHandsOff.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014-2023 Stream.io Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.getstream.result.internal 17 | 18 | /** 19 | * Indicates that the annotated code should not be modified without consulting the team. 20 | * @param reason 21 | * For example: 22 | * 23 | *
24 |  * @StreamHandsOff(reason = "If you move a card, the house is collapsed")
25 |  * public class HouseOfCards { }
26 | 
* 27 | */ 28 | @MustBeDocumented 29 | @Retention(AnnotationRetention.RUNTIME) 30 | public annotation class StreamHandsOff(val reason: String) 31 | --------------------------------------------------------------------------------