Project Icon

EitherNet

多平台网络API响应处理框架

EitherNet是一个多平台网络API响应处理框架,主要用于Retrofit但可扩展到其他平台。它使用Kotlin密封类型提供类型安全的单一返回点,简化了错误处理流程。该框架支持自定义错误类型、结果处理、重试机制和测试功能,为开发者提供了灵活且强大的网络请求处理方案。

EitherNet

EitherNet 是一个多平台、可插拔且密封的 API 结果类型,用于建模网络 API 响应。目前,它仅在 JVM 上为 Retrofit 实现,但核心 API 定义在公共代码中,可以为其他平台实现。

以下 README 内容主要关注 Retrofit 实现。

使用方法

默认情况下,Retrofit 使用异常来传播错误。本库利用 Kotlin 密封类型来更好地模型化这些响应,提供类型安全的单一返回点,无需异常处理!

核心类型是 ApiResult<out T, out E>,其中 T 是成功类型,E 是可能的错误类型。

ApiResult 有两个密封子类型:SuccessFailureSuccess 类型为 T,没有错误类型;Failure 类型为 E,没有成功类型。Failure 又有四个密封子类型:Failure.NetworkFailureFailure.ApiFailureFailure.HttpFailureFailure.UnknownFailure。这允许通过一致的、非异常流程使用密封 when 分支简单处理结果。

when (val result = myApi.someEndpoint()) {
  is Success -> doSomethingWith(result.response)
  is Failure -> when (result) {
    is NetworkFailure -> showError(result.error)
    is HttpFailure -> showError(result.code)
    is ApiFailure -> showError(result.error)
    is UnknownFailure -> showError(result.error)
  }
}

通常,用户代码只需为 Failure 情况显示一个通用错误消息,但密封子类型也允许更具体的错误消息或错误类型的可插拔性。

只需将端点返回类型更改为类型化的 ApiResult,并包含我们的调用适配器和委托转换器工厂。

interface TestApi {
  @GET("/")
  suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>
}

val api = Retrofit.Builder()
  .addConverterFactory(ApiResultConverterFactory)
  .addCallAdapterFactory(ApiResultCallAdapterFactory)
  .build()
  .create<TestApi>()

如果没有自定义错误返回类型,只需将错误类型设为 Unit

解码错误主体

如果要解码 HttpFailure 中的错误类型,请使用 @DecodeErrorBody 注解标记端点:

interface TestApi {
  @DecodeErrorBody
  @GET("/")
  suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>
}

现在,4xx 或 5xx 响应将尝试将其错误主体(如果有)解码为 ErrorResponse。如果要根据状态码上下文解码错误主体,可以在自定义 Retrofit Converter 中从注解中检索 @StatusCode 注解。

// 在您自己的转换器工厂中
override fun responseBodyConverter(
  type: Type,
  annotations: Array<out Annotation>,
  retrofit: Retrofit
): Converter<ResponseBody, *>? {
  val (statusCode, nextAnnotations) = annotations.statusCode()
    ?: return null
  val errorType = when (statusCode.value) {
    401 -> Unauthorized::class.java
    404 -> NotFound::class.java
    // ...
  }
  val errorDelegate = retrofit.nextResponseBodyConverter<Any>(this, errorType.toType(), nextAnnotations)
  return MyCustomBodyConverter(errorDelegate)
}

注意,内容长度为 0 的错误主体将被跳过。

可插拔性

某些 API 的常见模式是返回多态的 200 响应,其中数据需要动态解析。考虑以下示例:

{
  "ok": true,
  "data": {
    ...
  }
}

同一 API 在错误事件中可能返回这种结构:

{
  "ok": false,
  "error_message": "请重试。"
}

这很难用单一具体类型建模,但使用 ApiResult 很容易处理。只需在自定义 Retrofit Converter 中抛出带有解码错误类型的 ApiException,它将自动作为带有该错误实例的 Failure.ApiFailure 类型呈现。

@GET("/")
suspend fun getData(): ApiResult<SuccessResponse, ErrorResponse>

// 在您自己的转换器工厂中
class ErrorConverterFactory : Converter.Factory() {
  override fun responseBodyConverter(
    type: Type,
    annotations: Array<out Annotation>,
    retrofit: Retrofit
  ): Converter<ResponseBody, *>? {
    // 这返回一个 `@ResultType` 实例,可用于通过 toType() 获取错误类型
    val (errorType, nextAnnotations) = annotations.errorType() ?: return null
    return ResponseBodyConverter(errorType.toType())
  }

  class ResponseBodyConverter(
    private val errorType: Type
  ) : Converter<ResponseBody, *> {
    override fun convert(value: ResponseBody): String {
      if (value.isErrorType()) {
        val errorResponse = ...
        throw ApiException(errorResponse)
      } else {
        return SuccessResponse(...)
      }
    }
  }
}

重试

网络请求的一个常见模式是使用指数退避进行重试。EitherNet 提供了一个高度可配置的 retryWithExponentialBackoff() 函数用于这种情况。

// 默认值供参考
val result = retryWithExponentialBackoff(
  maxAttempts = 3,
  initialDelay = 500.milliseconds,
  delayFactor = 2.0,
  maxDelay = 10.seconds,
  jitterFactor = 0.25,
  onFailure = null, // 可选的失败回调,用于日志记录
) {
    api.getData()
}

测试

EitherNet 提供了一个 测试夹具 工件,其中包含 EitherNetController API,允许轻松测试 EitherNet API。这类似于 OkHttp 的 MockWebServer,可以为特定端点排队结果。

只需在测试中使用 newEitherNetController() 函数之一创建一个新的控制器实例。

val controller = newEitherNetController<PandaApi>() // 具体化类型

然后,您可以从中访问底层模拟的 api 属性,并将其传递给被测试的对象。

// 从控制器获取 api 实例并传递给被测试的对象
val provider = PandaDataProvider(controller.api)

最后,根据需要为端点排队结果。

// 在测试中,您可以为特定端点排队结果
controller.enqueue(PandaApi::getPandas, ApiResult.success("Po"))

您还可以选择传入完整的挂起函数,如果需要动态行为:

controller.enqueue(PandaApi::getPandas) {
  // 这是一个挂起函数!
  delay(1000)
  ApiResult.success("Po")
}

在使用依赖注入的集成测试中,您可以在测试模块中提供控制器及其底层 API,并替换标准模块。这与 Anvil 配合得特别好。

@ContributesTo(
  scope = UserScope::class,
  replaces = [PandaApiModule::class] // 替换标准模块
)
@Module
object TestPandaApiModule {
  @Provides
  fun providePandaApiController(): EitherNetController<PandaApi> = newEitherNetController()

  @Provides
  fun providePandaApi(
    controller: EitherNetController<PandaApi>
  ): PandaApi = controller.api
}

然后,您可以在测试中注入控制器,而 PandaApi 的用户将获得您的测试实例。

Java 互操作性

对于 Java 互操作性,JavaEitherNetControllers.enqueueFromJava 提供了有限的 API。

验证

EitherNetController 会在后台对 API 端点进行一些小型验证。如果您想在此基础上添加自己的验证,可以通过 ServiceLoader 提供 ApiValidator 的实现。有关更多信息,请参阅 ApiValidator 的文档。

安装

Maven Central

dependencies {
  implementation("com.slack.eithernet:eithernet:<版本>")
  implementation("com.slack.eithernet:eithernet-integration-retrofit:<版本>")

  // 测试夹具
  testImplementation(testFixtures("com.slack.eithernet:eithernet:<版本>"))
}

开发版本的快照可在 Sonatype 的 snapshots 仓库中获得。

许可证

Copyright 2020 Slack Technologies, LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号