
HTTP mocking toolkit for registering API mocks and resolving requests via a concise DSL; supports lazy responses, persistent registry, Ktor/OkHttp plugins, JSON helpers and editor UI.
MolApi is a Kotlin Multiplatform toolkit for registering API mocks and resolving incoming requests against them. The core idea is intentionally small:
The repository includes project-specific ai skills in ai-skills/. They provide
implementation guidance for each MolApi library module
request -> registry.find(request) -> mock.matcher.matches(request) -> mock.response
The project currently targets Android and iOS for the common library modules, with additional Android-only adapters where the underlying stack is Android/JVM-only.
MolApi artifacts are published to Maven Central under the io.github.je-dog group. Add
mavenCentral() to your repositories:
repositories {
mavenCentral()
}Then add only the modules your project needs. For a Kotlin Multiplatform project, keep common
modules in commonMain and Android-only adapters in androidMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.je-dog:molapi-http:<latest_version>")
implementation("io.github.je-dog:molapi-http-ktor:<latest_version>")
implementation("io.github.je-dog:molapi-http-serialization:<latest_version>")
// Optional: persistent mocks and Compose Multiplatform editor UI.
implementation("io.github.je-dog:molapi-room:<latest_version>")
implementation("io.github.je-dog:molapi-http-editor:<latest_version>")
}
androidMain.dependencies {
implementation("io.github.je-dog:molapi-http-retrofit:<latest_version>")
implementation("io.github.je-dog:molapi-http-gson:<latest_version>")
implementation("io.github.je-dog:molapi-http-android-assets:<latest_version>")
}
}
}For a regular Android Gradle module, add dependencies through the usual dependencies block:
dependencies {
implementation("io.github.je-dog:molapi-http:<latest_version>")
implementation("io.github.je-dog:molapi-http-retrofit:<latest_version>")
}| Module | Maven Central | Purpose | Targets |
|---|---|---|---|
:molapi-core |
Generic request, matcher, response and registry contracts. | Android, iOS | |
:molapi-http |
HTTP request/response model, matchers, in-memory registry aliases and DSL. | Android, iOS | |
:molapi-http-ktor |
Ktor Client plugin that returns a mock response before a real request is sent. | Android, iOS | |
:molapi-http-serialization |
kotlinx.serialization helpers for JSON HTTP bodies. |
Android, iOS | |
:molapi-room |
Room-backed persistent registry foundation. | Android, iOS | |
:molapi-http-editor |
Compose Multiplatform screen for viewing, searching and editing HTTP mocks. | Android, iOS | |
:molapi-http-android-assets |
Helps create JSON HTTP bodies from Android project assets. | Android | |
:molapi-http-gson |
Gson helpers for JSON HTTP bodies. | Android | |
:molapi-http-retrofit |
OkHttp/Retrofit interceptor integration. | Android | |
:sample |
Not published | Shared sample app code. | Android, iOS |
:sampleAndroidApp |
Not published | Android sample application entry point. | Android |
SampleIosApp/ |
Not published | iOS sample application entry point. | iOS |
Create an HTTP registry and register mocks through the DSL. The main response overloads are shown
below; get, post, put, patch, head, and delete have these response forms, while
generic http also accepts an explicit HttpMethod:
import dag.khinkal.molapi.http.dsl.get
import dag.khinkal.molapi.http.dsl.http
import dag.khinkal.molapi.http.dsl.patch
import dag.khinkal.molapi.http.dsl.post
import dag.khinkal.molapi.http.dsl.put
import dag.khinkal.molapi.http.model.Headers
import dag.khinkal.molapi.http.model.HttpMethod
import dag.khinkal.molapi.http.model.HttpResponse
import dag.khinkal.molapi.http.model.HttpUrl
import dag.khinkal.molapi.http.model.JsonBody
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
val registry = HttpInMemoryApiMockRegistry().apply {
// A JSON string shortcut.
get(
path = "/todos",
json = """[{"id":1,"title":"from molapi","completed":false}]""",
)
// A full response, including custom headers and status.
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = JsonBody("""{"title":"new"}"""),
response = HttpResponse(
body = JsonBody("""{"id":2,"title":"new","completed":false}"""),
headers = Headers.jsonContent(),
statusCode = 201,
),
)
// An arbitrary response body with its metadata.
put(
path = "/todos/2",
response = JsonBody("""{"completed":true}"""),
responseHeaders = Headers.jsonContent(),
statusCode = 202,
)
// A response produced lazily from the request registration code.
patch("/todos/2") {
HttpResponse(body = JsonBody("""{"completed":false}"""))
}
// Without selected http method
http(
method = HttpMethod.DELETE,
path = "/todos/2",
response = HttpResponse(statusCode = 204),
)
}The HTTP DSL supports get, post, put, patch, head, delete, and generic http mocks.
Each registered mock stores a matcher and a response:
HttpUrl components: scheme, host, effective port, encoded path and
order-independent query parameters.null, that field is ignored.Install MolApiKtorPlugin into a Ktor HttpClient. If a registered mock matches the outgoing
request, the plugin returns the mock response. If no mock matches, the request continues normally.
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
import io.ktor.client.HttpClient
val registry = HttpInMemoryApiMockRegistry()
val client = HttpClient {
install(MolApiKtorPlugin) {
this.registry = registry
}
}When using the editor integration, initialize MolApiEditor once at application startup and pass
its shared registry into the plugin:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import io.ktor.client.HttpClient
val client = HttpClient {
install(MolApiKtorPlugin) {
registry = MolApiEditor.registry
}
}sample/src/commonMain/kotlin/dag/khinkal/molapi/TodoApi.kt shows this pattern in the sample client
configuration.
On Android, add MolApiRetrofitInterceptor through the OkHttp builder used by Retrofit:
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
import dag.khinkal.molapi.http.retrofit.interceptor.addMolApiInterceptor
import okhttp3.OkHttpClient
val registry = HttpInMemoryApiMockRegistry()
val okHttpClient = OkHttpClient.Builder()
.addMolApiInterceptor(registry)
.build()The interceptor follows the same fallback rule as the Ktor plugin: mock match first, real request otherwise.
When the editor is enabled, use the same shared registry:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.retrofit.interceptor.addMolApiInterceptor
import okhttp3.OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.addMolApiInterceptor(MolApiEditor.registry)
.build()Use molapi-http-serialization in multiplatform code:
import dag.khinkal.molapi.http.serializable.util.JsonHttpResponse
import kotlinx.serialization.Serializable
@Serializable
data class TodoDto(
val id: Int,
val title: String,
)
val response = JsonHttpResponse(
serializableBody = TodoDto(id = 1, title = "from molapi"),
)Use molapi-http-gson in Android code:
import dag.khinkal.molapi.http.gson.util.GsonHttpResponse
import dag.khinkal.molapi.http.model.Headers
val response = GsonHttpResponse(
headers = Headers.jsonContent(),
body = TodoDto(id = 1, title = "from molapi"),
)Use molapi-http-android-assets when mock bodies are stored in Android project assets. The module
only creates JsonBody values from asset files; request matching, response status and headers stay
configured through the regular molapi-http API.
import android.content.Context
import android.content.res.AssetManager
import dag.khinkal.molapi.http.assets.AssetJsonBody
import dag.khinkal.molapi.http.dsl.post
import dag.khinkal.molapi.http.model.Headers
import dag.khinkal.molapi.http.model.HttpUrl
import dag.khinkal.molapi.http.model.JsonBody
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
fun createRegistry(
context: Context,
assetManager: AssetManager,
): HttpInMemoryApiMockRegistry =
HttpInMemoryApiMockRegistry().apply {
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = AssetJsonBody(context, "requests/create-todo.json"),
responseHeaders = Headers.jsonContent(),
statusCode = 201,
) {
JsonBody.fromAssets(context, "responses/todo-created.json")
}
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = AssetJsonBody(assetManager, "requests/create-todo.json"),
responseHeaders = Headers.jsonContent(),
statusCode = 201,
) {
JsonBody.fromAssets(assetManager, "responses/todo-created.json")
}
}AssetJsonBody(context, path) and JsonBody.fromAssets(context, path) read the asset text as UTF-8
by default. Pass a Charset explicitly if an asset uses another encoding.
:molapi-room provides RoomApiMockRegistry, which stores mocks in Room and restores them through
an ApiMockParser. The parser owns serialization for matcher and response types, so a feature can
decide which mock shapes are safe to persist.
Mocks use String ids. The default registry id generator is UuidApiMockIdGenerator, so new
records get random UUID string ids unless a custom ApiMockIdGenerator is supplied.
For HTTP mocks, the editor module already ships the Room parser used by MolApiEditor.
:molapi-http-editor exposes a Compose Multiplatform MolApiHttpMockEditorScreen and a
MolApiEditor facade. The facade owns one shared HttpApiMockRegistry so the editor UI and the
network interceptor/plugin see the same mocks.
Initialize the editor once at application startup.
Android:
import android.app.Application
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.editor.init
class App : Application() {
override fun onCreate() {
super.onCreate()
MolApiEditor.init(this)
}
}iOS:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.editor.init
fun initializeMolApiEditor() {
MolApiEditor.init()
}Call the exported initializeMolApiEditor() from the Swift App.init() or another iOS application
startup point before creating screens or HTTP clients.
import androidx.compose.runtime.Composable
import dag.khinkal.molapi.http.editor.MolApiHttpMockEditorScreen
@Composable
fun MockEditor() {
MolApiHttpMockEditorScreen()
}For tests or custom dependency injection, pass an explicit registry provider:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import io.ktor.client.HttpClient
val customRegistry: HttpApiMockRegistry = CustomHttpApiMockRegistry()
val client = HttpClient {
install(MolApiKtorPlugin) {
registry = customRegistry
}
}
@Composable
fun TestScreen() {
MolApiHttpMockEditorScreen(registry = { customRegistry })
}The screen can list registered HTTP mocks, filter them, add new JSON mocks and clear or remove entries from the registry. Request and response JSON bodies can be typed inline or loaded through the platform document picker.
Build the Android sample:
./gradlew :sampleAndroidApp:assembleDebugRun focused library checks:
./gradlew :molapi-core:testAndroidHostTest :molapi-http:testAndroidHostTest
./gradlew :molapi-http-ktor:testAndroidHostTest
./gradlew :molapi-http-android-assets:testAndroidHostTest
./gradlew :sample:testAndroidHostTestRun iOS simulator checks for common modules:
./gradlew :molapi-core:iosSimulatorArm64Test :molapi-http:iosSimulatorArm64Test
./gradlew :molapi-http-ktor:iosSimulatorArm64Test
./gradlew :sample:iosSimulatorArm64TestThe iOS app entry point is in SampleIosApp/; open that directory in Xcode to run the sample on an
iOS
simulator or device.
commonMain.:molapi-http depends on :molapi-core as API because HTTP public types expose core contracts.:molapi-http-ktor and :molapi-http-retrofit) accept a registry from
the caller; they do not depend on :molapi-http-editor.Copyright 2026 MolApi Contributors
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
https://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.
MolApi is a Kotlin Multiplatform toolkit for registering API mocks and resolving incoming requests against them. The core idea is intentionally small:
The repository includes project-specific ai skills in ai-skills/. They provide
implementation guidance for each MolApi library module
request -> registry.find(request) -> mock.matcher.matches(request) -> mock.response
The project currently targets Android and iOS for the common library modules, with additional Android-only adapters where the underlying stack is Android/JVM-only.
MolApi artifacts are published to Maven Central under the io.github.je-dog group. Add
mavenCentral() to your repositories:
repositories {
mavenCentral()
}Then add only the modules your project needs. For a Kotlin Multiplatform project, keep common
modules in commonMain and Android-only adapters in androidMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.je-dog:molapi-http:<latest_version>")
implementation("io.github.je-dog:molapi-http-ktor:<latest_version>")
implementation("io.github.je-dog:molapi-http-serialization:<latest_version>")
// Optional: persistent mocks and Compose Multiplatform editor UI.
implementation("io.github.je-dog:molapi-room:<latest_version>")
implementation("io.github.je-dog:molapi-http-editor:<latest_version>")
}
androidMain.dependencies {
implementation("io.github.je-dog:molapi-http-retrofit:<latest_version>")
implementation("io.github.je-dog:molapi-http-gson:<latest_version>")
implementation("io.github.je-dog:molapi-http-android-assets:<latest_version>")
}
}
}For a regular Android Gradle module, add dependencies through the usual dependencies block:
dependencies {
implementation("io.github.je-dog:molapi-http:<latest_version>")
implementation("io.github.je-dog:molapi-http-retrofit:<latest_version>")
}| Module | Maven Central | Purpose | Targets |
|---|---|---|---|
:molapi-core |
Generic request, matcher, response and registry contracts. | Android, iOS | |
:molapi-http |
HTTP request/response model, matchers, in-memory registry aliases and DSL. | Android, iOS | |
:molapi-http-ktor |
Ktor Client plugin that returns a mock response before a real request is sent. | Android, iOS | |
:molapi-http-serialization |
kotlinx.serialization helpers for JSON HTTP bodies. |
Android, iOS | |
:molapi-room |
Room-backed persistent registry foundation. | Android, iOS | |
:molapi-http-editor |
Compose Multiplatform screen for viewing, searching and editing HTTP mocks. | Android, iOS | |
:molapi-http-android-assets |
Helps create JSON HTTP bodies from Android project assets. | Android | |
:molapi-http-gson |
Gson helpers for JSON HTTP bodies. | Android | |
:molapi-http-retrofit |
OkHttp/Retrofit interceptor integration. | Android | |
:sample |
Not published | Shared sample app code. | Android, iOS |
:sampleAndroidApp |
Not published | Android sample application entry point. | Android |
SampleIosApp/ |
Not published | iOS sample application entry point. | iOS |
Create an HTTP registry and register mocks through the DSL. The main response overloads are shown
below; get, post, put, patch, head, and delete have these response forms, while
generic http also accepts an explicit HttpMethod:
import dag.khinkal.molapi.http.dsl.get
import dag.khinkal.molapi.http.dsl.http
import dag.khinkal.molapi.http.dsl.patch
import dag.khinkal.molapi.http.dsl.post
import dag.khinkal.molapi.http.dsl.put
import dag.khinkal.molapi.http.model.Headers
import dag.khinkal.molapi.http.model.HttpMethod
import dag.khinkal.molapi.http.model.HttpResponse
import dag.khinkal.molapi.http.model.HttpUrl
import dag.khinkal.molapi.http.model.JsonBody
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
val registry = HttpInMemoryApiMockRegistry().apply {
// A JSON string shortcut.
get(
path = "/todos",
json = """[{"id":1,"title":"from molapi","completed":false}]""",
)
// A full response, including custom headers and status.
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = JsonBody("""{"title":"new"}"""),
response = HttpResponse(
body = JsonBody("""{"id":2,"title":"new","completed":false}"""),
headers = Headers.jsonContent(),
statusCode = 201,
),
)
// An arbitrary response body with its metadata.
put(
path = "/todos/2",
response = JsonBody("""{"completed":true}"""),
responseHeaders = Headers.jsonContent(),
statusCode = 202,
)
// A response produced lazily from the request registration code.
patch("/todos/2") {
HttpResponse(body = JsonBody("""{"completed":false}"""))
}
// Without selected http method
http(
method = HttpMethod.DELETE,
path = "/todos/2",
response = HttpResponse(statusCode = 204),
)
}The HTTP DSL supports get, post, put, patch, head, delete, and generic http mocks.
Each registered mock stores a matcher and a response:
HttpUrl components: scheme, host, effective port, encoded path and
order-independent query parameters.null, that field is ignored.Install MolApiKtorPlugin into a Ktor HttpClient. If a registered mock matches the outgoing
request, the plugin returns the mock response. If no mock matches, the request continues normally.
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
import io.ktor.client.HttpClient
val registry = HttpInMemoryApiMockRegistry()
val client = HttpClient {
install(MolApiKtorPlugin) {
this.registry = registry
}
}When using the editor integration, initialize MolApiEditor once at application startup and pass
its shared registry into the plugin:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import io.ktor.client.HttpClient
val client = HttpClient {
install(MolApiKtorPlugin) {
registry = MolApiEditor.registry
}
}sample/src/commonMain/kotlin/dag/khinkal/molapi/TodoApi.kt shows this pattern in the sample client
configuration.
On Android, add MolApiRetrofitInterceptor through the OkHttp builder used by Retrofit:
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
import dag.khinkal.molapi.http.retrofit.interceptor.addMolApiInterceptor
import okhttp3.OkHttpClient
val registry = HttpInMemoryApiMockRegistry()
val okHttpClient = OkHttpClient.Builder()
.addMolApiInterceptor(registry)
.build()The interceptor follows the same fallback rule as the Ktor plugin: mock match first, real request otherwise.
When the editor is enabled, use the same shared registry:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.retrofit.interceptor.addMolApiInterceptor
import okhttp3.OkHttpClient
val okHttpClient = OkHttpClient.Builder()
.addMolApiInterceptor(MolApiEditor.registry)
.build()Use molapi-http-serialization in multiplatform code:
import dag.khinkal.molapi.http.serializable.util.JsonHttpResponse
import kotlinx.serialization.Serializable
@Serializable
data class TodoDto(
val id: Int,
val title: String,
)
val response = JsonHttpResponse(
serializableBody = TodoDto(id = 1, title = "from molapi"),
)Use molapi-http-gson in Android code:
import dag.khinkal.molapi.http.gson.util.GsonHttpResponse
import dag.khinkal.molapi.http.model.Headers
val response = GsonHttpResponse(
headers = Headers.jsonContent(),
body = TodoDto(id = 1, title = "from molapi"),
)Use molapi-http-android-assets when mock bodies are stored in Android project assets. The module
only creates JsonBody values from asset files; request matching, response status and headers stay
configured through the regular molapi-http API.
import android.content.Context
import android.content.res.AssetManager
import dag.khinkal.molapi.http.assets.AssetJsonBody
import dag.khinkal.molapi.http.dsl.post
import dag.khinkal.molapi.http.model.Headers
import dag.khinkal.molapi.http.model.HttpUrl
import dag.khinkal.molapi.http.model.JsonBody
import dag.khinkal.molapi.http.registry.HttpInMemoryApiMockRegistry
fun createRegistry(
context: Context,
assetManager: AssetManager,
): HttpInMemoryApiMockRegistry =
HttpInMemoryApiMockRegistry().apply {
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = AssetJsonBody(context, "requests/create-todo.json"),
responseHeaders = Headers.jsonContent(),
statusCode = 201,
) {
JsonBody.fromAssets(context, "responses/todo-created.json")
}
post(
url = HttpUrl(path = "/todos"),
headers = Headers.jsonContent(),
body = AssetJsonBody(assetManager, "requests/create-todo.json"),
responseHeaders = Headers.jsonContent(),
statusCode = 201,
) {
JsonBody.fromAssets(assetManager, "responses/todo-created.json")
}
}AssetJsonBody(context, path) and JsonBody.fromAssets(context, path) read the asset text as UTF-8
by default. Pass a Charset explicitly if an asset uses another encoding.
:molapi-room provides RoomApiMockRegistry, which stores mocks in Room and restores them through
an ApiMockParser. The parser owns serialization for matcher and response types, so a feature can
decide which mock shapes are safe to persist.
Mocks use String ids. The default registry id generator is UuidApiMockIdGenerator, so new
records get random UUID string ids unless a custom ApiMockIdGenerator is supplied.
For HTTP mocks, the editor module already ships the Room parser used by MolApiEditor.
:molapi-http-editor exposes a Compose Multiplatform MolApiHttpMockEditorScreen and a
MolApiEditor facade. The facade owns one shared HttpApiMockRegistry so the editor UI and the
network interceptor/plugin see the same mocks.
Initialize the editor once at application startup.
Android:
import android.app.Application
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.editor.init
class App : Application() {
override fun onCreate() {
super.onCreate()
MolApiEditor.init(this)
}
}iOS:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.editor.init
fun initializeMolApiEditor() {
MolApiEditor.init()
}Call the exported initializeMolApiEditor() from the Swift App.init() or another iOS application
startup point before creating screens or HTTP clients.
import androidx.compose.runtime.Composable
import dag.khinkal.molapi.http.editor.MolApiHttpMockEditorScreen
@Composable
fun MockEditor() {
MolApiHttpMockEditorScreen()
}For tests or custom dependency injection, pass an explicit registry provider:
import dag.khinkal.molapi.http.editor.MolApiEditor
import dag.khinkal.molapi.http.ktor.plugin.MolApiKtorPlugin
import io.ktor.client.HttpClient
val customRegistry: HttpApiMockRegistry = CustomHttpApiMockRegistry()
val client = HttpClient {
install(MolApiKtorPlugin) {
registry = customRegistry
}
}
@Composable
fun TestScreen() {
MolApiHttpMockEditorScreen(registry = { customRegistry })
}The screen can list registered HTTP mocks, filter them, add new JSON mocks and clear or remove entries from the registry. Request and response JSON bodies can be typed inline or loaded through the platform document picker.
Build the Android sample:
./gradlew :sampleAndroidApp:assembleDebugRun focused library checks:
./gradlew :molapi-core:testAndroidHostTest :molapi-http:testAndroidHostTest
./gradlew :molapi-http-ktor:testAndroidHostTest
./gradlew :molapi-http-android-assets:testAndroidHostTest
./gradlew :sample:testAndroidHostTestRun iOS simulator checks for common modules:
./gradlew :molapi-core:iosSimulatorArm64Test :molapi-http:iosSimulatorArm64Test
./gradlew :molapi-http-ktor:iosSimulatorArm64Test
./gradlew :sample:iosSimulatorArm64TestThe iOS app entry point is in SampleIosApp/; open that directory in Xcode to run the sample on an
iOS
simulator or device.
commonMain.:molapi-http depends on :molapi-core as API because HTTP public types expose core contracts.:molapi-http-ktor and :molapi-http-retrofit) accept a registry from
the caller; they do not depend on :molapi-http-editor.Copyright 2026 MolApi Contributors
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
https://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.