{"id":18458816,"url":"https://github.com/agoda-com/ninjato","last_synced_at":"2025-04-08T05:34:39.555Z","repository":{"id":49565228,"uuid":"180314827","full_name":"agoda-com/ninjato","owner":"agoda-com","description":"Flexible and type-safe inline HTTP client for Android and Kotlin","archived":false,"fork":false,"pushed_at":"2021-06-14T11:45:41.000Z","size":447,"stargazers_count":111,"open_issues_count":3,"forks_count":9,"subscribers_count":30,"default_branch":"master","last_synced_at":"2025-03-23T06:51:10.776Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agoda-com.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-04-09T07:56:05.000Z","updated_at":"2024-09-25T03:18:19.000Z","dependencies_parsed_at":"2022-08-24T01:41:10.267Z","dependency_job_id":null,"html_url":"https://github.com/agoda-com/ninjato","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fninjato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fninjato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fninjato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fninjato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agoda-com","download_url":"https://codeload.github.com/agoda-com/ninjato/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247785919,"owners_count":20995641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-06T08:20:16.813Z","updated_at":"2025-04-08T05:34:34.543Z","avatar_url":"https://github.com/agoda-com.png","language":"Kotlin","funding_links":[],"categories":["网络编程"],"sub_categories":["Spring Cloud框架"],"readme":"# Ninjato\n[![Bintray version](https://api.bintray.com/packages/agoda/maven/ninjato/images/download.svg)](https://bintray.com/agoda/maven/ninjato)\n[![Kotlin version badge](https://img.shields.io/badge/kotlin-1.3.41-blue.svg)](http://kotlinlang.org/)\n[![codecov](https://codecov.io/gh/agoda-com/ninjato/branch/master/graph/badge.svg)](https://codecov.io/gh/agoda-com/ninjato)\n\nFlexible and type-safe inline HTTP client for Android and Kotlin\n\n## What is Ninjato?\nNinjato is a library that lets you write simple yet powerful remote HTTP calls, whether it's RESTful services or any other.\nBut at the same time the library gives you the complete control of the flow.\n\nThe library is written in Kotlin and tries to rely on reflection as low as possible. Most of the functionality\nis implemented with the help of inlining and reified types inference during compile time.\n\n## Why not Retrofit?\nRetrofit is a great library, and people at Square do amazing and very important stuff.\nBut for us at Agoda, after a long time with Retrofit we started to feel limited with the way how\nthe library gives us opportunities to customize, intercept and implement more flexible behavior.\n\n## How to use?\n\n### Basic syntax\nIn a nutshell, Ninjato gives a possibility to execute any type of HTTP call through `Api` class.\nAs such, you can have several approaches to define and use your remote services:\n\nExtend the `Api` class directly:\n```kotlin\nclass YourApi(client: HttpClient, config: Api.() -\u003e Unit) : Api(client, config) {\n    override val baseUrl = \"https://yourApi.com\"\n    \n    fun search(query: String): SearchResult = get {\n        endpointUrl = \"/search\"\n        parameters { \"query\" to query }\n    }\n}\n```\nHide implementation behind the interface:\n```kotlin\ninterface YourApi {\n    fun updateArticle(article: Article)\n}\n\nclass YourApiImpl(client: HttpClient, config: Api.() -\u003e Unit) : Api(client, config), YourApi {\n    override val baseUrl = \"https://yourApi.com\"\n    \n    fun updateArticle(article: Article) = post {\n        endpointUrl = \"/updateArticle\"\n        body = article\n    }\n}\n```\nOr implement your endpoints right in the interface:\n```kotlin\ninterface YourApi {\n    val api: Api\n    \n    fun putListing(listing: Listing) = api.put {\n        endpointUrl = \"/putListing\"\n        body = listing\n    }\n}\n```\n\nThere are a lot more ways how you can organize your remote services' code, but we're not going to cover it here.\n\n### Main features\nAs you already understood from the examples below, Ninjato can infer the type of body and what is expected\nto be returned from the request. But there are a lot more features. All of them will be explained in the\nsections below with code examples.\n\n#### DSL cascade: `HttpClient`, `Api` and `Request`\nNinjato is structured in a way that allows it's users to aggregate properties and/or behavior of their\nremote calls through the DSL cascade. This means that all main entities of library: `HttpClient`, `Api` and `Request`\nhave the same configuration interface where you can configure your headers, parameters, interceptors, etc.\n\nDuring the actual call configuration, all of these configurations will be aggregated from **bottom** to **top**,\nor replaced, if only one value is allowed.\n\n#### Request body and return type inference\nThanks to Kotlin capabilities, Ninjato infers the type of body being set (if allowed) and expected return type.\n\nDefault types of `body` property include:\n - `Body`\n - `String`\n - `ByteArray`\n\nDefault types of return value include:\n - `Unit`\n - `Response`\n - `Body`\n - `Stirng`\n - `ByteArray`\n\nFor all custom types library has support of `BodyConverter`.\n\nIMPORTANT NOTE: there is an issue with the Kotlin compiler and inlining of delegates with reified types\nis not working as expected. That means that current syntax of passing your body is inconsistent.\nRight now if you will pass an instance of generic class to a body property, type arguments will be lost:\n\n```kotlin\nfun foo(generic: Generic\u003cString\u003e) = post {\n    body = generic // It will capture only class of the generic via generic.javaClass\n}\n```\n\nThat puts the responsibility of inferring the type arguments on your serializing library. Gson is doing this fine.\nIf you want library to capture actual type and forward it to your `BodyConverter.Factory`, please consider using\nthe newly introduced extension function:\n\n```kotlin\nfun foo(generic: Generic\u003cStirng\u003e) = post {\n    body = convert(generic)\n}\n```\n\n#### BodyConverter\n`BodyConverter` is a simple interface with a `convert` function from one to another.\nLibrary uses the provided `BodyConverter.Factory` instances to get the converter for a specific type.\nYou can provide supplied or your own factory to any level of the DSL cascade:\n\n```kotlin\nval client = NinjatoOkHttpClient(okHttpClient) {\n    converterFactories += GsonConverterFactory(Gson())\n}\n\nval api = YourApi(client) {\n    converterFactories += MoshiConverterFactory()\n}\n\nval result: SearchResult = api.get {\n    endpointUrl = \"/get\"\n    converterFactories += JacksonConverterFactory()\n}\n```\n\nList of body converters provided via extension artifacts can be found in **Setup** section.\n\n#### Interceptors\nInterceptors are instances that are executed prior (`RequestInterceptor`) the request is served by\nthe HTTP client and after (`ResponseInterceptor`) the response was acquired. Interceptors have the ability\nto modify given instance of `Request`/`Response` or even provide completely another instances.\n\nInterceptors can be added to any level of the DSL cascade. The following code can be used also in `Api` classes\nand in any call configuration:\n\n```kotlin\nval client = NinjatoOkHttpClient(okHttpClient) {\n    interceptors += MyRequestInterceptor()\n    interceptors += MyResponseInterceptor()\n    \n    interceptors {\n        request { r -\u003e\n            Log.d(r)\n            r\n        }\n        \n        response { r -\u003e\n            Log.d(r)\n            r\n        }\n    }\n}\n```\n\n#### RetryPolicy\nSimply put, `RetryPolicy` is a class that can allow you to decide, whether or not given request should\ntry again and be executed. You get the `Request` and `Throwable` as input and should return one of the\n`Retry` sealed class children: `DoNotRetry`, `WithoutDelay` or `WithDelay`.\n\nRetry policy can be added to any level of the DSL cascade. The following code can be used also in `Api` classes\nand in any call configuration:\n\n```kotlin\nval client = NinjatoOkHttpClient(okHttpClient) {\n    retryPolicy = MyRetryPolicy()\n    \n    retryPolicy { request, throwable -\u003e\n        if (request.retries \u003e 3) Retry.DoNotRetry else Retry.WithoutDelay\n    }\n}\n```\n\n#### FallbackPolicy\nIn case your `RetryPolicy` allowed a request to be retried, `FallbackPolicy` can help you with modifying\nyou request before a retry attempt. For example, change the `baseUrl` of your request.\n\nFallback policy can be added to any level of the DSL cascade. The following code can be used also in `Api` classes\nand in any call configuration:\n\n```kotlin\nval client = NinjatoOkHttpClient(okHttpClient) {\n    fallbackPolicy = MyFallbackPolicy()\n    \n    fallbackPolicy { request, throwable -\u003e \n        request.also { it.baseUrl = \"https://anotherServer.com\" }\n    }\n}\n```\n\n#### Headers and query parameters\nHeaders and url query parameters are also part of the DSL cascade.\n\nHeaders and query parameters can be added to any level of the DSL cascade. \nThe following code can be used also in `HttpClient` and `Api` classes:\n\n```kotlin\nval result: SearchResult = get {\n    endpointUrl = \"/search\"\n    headers += \"A\" to \"B\"\n    \n    headers {\n        \"B\" to \"C\"\n        \n        cookie {\n            \"C\" to \"D\"\n            expires = 3600\n            isSecure = true      \n        }\n    }\n    \n    parameters {\n        \"query\" to query\n    }\n}\n```\n\n#### Request.Factory\nTo add possibility to extend `Request` entity and enrich it with use-case specific properties and/or logic, Ninjato\nhas a factory that will return instances of `Request` which can be manipulated further in interceptors:\n\n```kotlin\nclass YourRequestFactory : Request.Factory() {\n    override fun create() = YourRequest()\n}\n\nclass YourRequestInterceptor : RequestInterceptor() {\n    fun intercept(request: Request): Request {\n        if (request is YourRequest) {\n            request.headers[\"request_id\"] = request.id\n        }\n    }\n}\n\nval client = NinjatoOkHttpClient(okHttpClient, YourRequestFactory()) {\n    interceptors += YourRequestInterceptor()\n}\n```\n\n#### Response.Factory\nTo add possibility to extend `Resposne` entity and enrich it with use-case specific properties and/or logic, Ninjato\nhas a factory that will return instances of `Response` which can be manipulated further in interceptors:\n\n```kotlin\nclass YourResponseFactory : Response.Factory() {\n    override fun create() = YourResponse()\n}\n\n// usage example\nclass YourResponseInterceptor {\n    fun intercept(response: Response): Response {\n        if (response is YourResponse) {\n            response.token = response.headers[\"auth_token\"]\n        }\n    }\n}\n\nval client = NinjatoOkHttpClient(okHttpClient, YourRequestFactory(), YourResponseFactory()) {\n    interceptors += YourRequestInterceptor()\n    interceptors += YourResponseInterceptor()\n}\n```\n\n#### Wrappers\nIn case you prefer to use some sealed class type wrapper, or RxJava, for example, Ninjato has you covered as well.\nThere are extension artifacts available that are providing extension wrapping functions for your `Api` classes.\nHere are few examples:\n\n```kotlin\ninterface YourApi {\n    val api: Api\n    \n    fun search(query: String): Call\u003cSearchResult\u003e = api.call {\n        get {\n            endpointUrl = \"/serach\"\n            params { \"query\" to query }\n        }\n    }\n    \n    fun updateArticle(article: Article): Completable = api.completable {\n        post {\n            endpointUrl = \"/articles\"\n            body = article\n        }\n    }\n    \n    fun putListing(listing: Listing): Flowable\u003cResponse\u003e = api.flowable {\n        put {\n            endpointUrl = \"/listings\"\n            body = listing\n        }\n    }\n}\n```\n\nList of available extension artifacts can be found in **Setup** section.\n\n#### More\nFor any additional information please refer to library's documentation.\n\n### Setup\nMaven\n```xml\n\u003c!-- Core library !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003eninjato-core\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- OkHttp 3 client !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003eclient-okhttp\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Gson body converter !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003econverter-gson\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Call wrapper !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003eextension-call\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- RxJava wrappers !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003eextension-rxjava\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- RxJava2 wrappers !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.ninjato\u003c/groupId\u003e\n  \u003cartifactId\u003eextension-rxjava2\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n```\nor Gradle:\n```groovy\nrepositories {\n    jcenter()\n}\n\ndependencies {\n    // Core library\n    implementation 'com.agoda.ninjato:ninjato-core:LATEST_VERSION'\n\n    // OkHttp 3 client\n    implementation 'com.agoda.ninjato:client-okhttp:LATEST_VERSION'\n\n    // Gson body converter\n    implementation 'com.agoda.ninjato:converter-gson:LATEST_VERSION'\n\n    // Call wrapper\n    implementation 'com.agoda.ninjato:extension-call:LATEST_VERSION'\n\n    // RxJava wrappers\n    implementation 'com.agoda.ninjato:extension-rxjava:LATEST_VERSION'\n\n    // RxJava 2 wrappers\n    implementation 'com.agoda.ninjato:extension-rxjava2:LATEST_VERSION'\n}\n```\n\n### Contribution Policy\n\nNinjato is an open source project, and depends on its users to improve it. We are more than happy\nto find you interested in taking the project forward.\n\nKindly refer to the [Contribution Guidelines](https://github.com/agoda-com/ninjato/blob/master/CONTRIBUTING.md) for detailed information.\n\n### Code of Conduct\n\nPlease refer to [Code of Conduct](https://github.com/agoda-com/ninjato/blob/master/CODE_OF_CONDUCT.md) document.\n\n### License\n\nNinjato is available under the [Apache License, Version 2.0](https://github.com/agoda-com/ninjato/blob/master/LICENSE).\n\n### Thanks to\n\n* [Unlimity](https://github.com/unlimity) - **Ilya Lim**\n* [Yundom](https://github.com/yundom) - **Dennis Hsieh**","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagoda-com%2Fninjato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagoda-com%2Fninjato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagoda-com%2Fninjato/lists"}