From 7be1dd2192242faf89f881c8f49634f3200350e0 Mon Sep 17 00:00:00 2001 From: Oleksandr Dzhychko Date: Fri, 8 Sep 2023 13:00:39 +0200 Subject: [PATCH] fix(model-client): use Java Closable ModelClientV2 * Previously the io.ktor.utils.io.core.Closeable was used. * Previously IModelClientV2 implemented Closeable. That was wrong, because IModelClientV2 is only intended to expose the usage of the client --- .../org/modelix/model/client2/Closable.kt | 21 ++++++++ .../modelix/model/client2/IModelClientV2.kt | 18 +++++-- .../modelix/model/client2/ModelClientV2.kt | 2 +- .../org/modelix/model/client2/Closable.js.kt | 21 ++++++++ .../org/modelix/model/client2/Closable.jvm.kt | 21 ++++++++ .../model/client2/ModelClientV2JvmTest.kt | 52 +++++++++++++++++++ 6 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 model-client/src/commonMain/kotlin/org/modelix/model/client2/Closable.kt create mode 100644 model-client/src/jsMain/kotlin/org/modelix/model/client2/Closable.js.kt create mode 100644 model-client/src/jvmMain/kotlin/org/modelix/model/client2/Closable.jvm.kt create mode 100644 model-client/src/jvmTest/kotlin/org/modelix/model/client2/ModelClientV2JvmTest.kt diff --git a/model-client/src/commonMain/kotlin/org/modelix/model/client2/Closable.kt b/model-client/src/commonMain/kotlin/org/modelix/model/client2/Closable.kt new file mode 100644 index 0000000000..29dfdae70d --- /dev/null +++ b/model-client/src/commonMain/kotlin/org/modelix/model/client2/Closable.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.modelix.model.client2 + +internal expect interface Closable { + fun close() +} diff --git a/model-client/src/commonMain/kotlin/org/modelix/model/client2/IModelClientV2.kt b/model-client/src/commonMain/kotlin/org/modelix/model/client2/IModelClientV2.kt index b9ff6c2203..84951b18c9 100644 --- a/model-client/src/commonMain/kotlin/org/modelix/model/client2/IModelClientV2.kt +++ b/model-client/src/commonMain/kotlin/org/modelix/model/client2/IModelClientV2.kt @@ -13,14 +13,26 @@ */ package org.modelix.model.client2 -import io.ktor.utils.io.core.Closeable import org.modelix.model.IVersion import org.modelix.model.api.IIdGenerator import org.modelix.model.lazy.BranchReference import org.modelix.model.lazy.RepositoryId import org.modelix.model.server.api.ModelQuery -interface IModelClientV2 : Closeable { +/** + * This interface is meant exclusively for model client usage. + * + * It is designed to ensure decoupling between model client usage operations and other aspects, + * such as lifecycle management. + * Users of this interface cannot incidentally depend on non-usage functionality. + * See also: [Interface segregation principle](https://en.wikipedia.org/wiki/Interface_segregation_principle) + * + * Specifically, this interface should not be used for managing the client's lifecycle, + * as the lifecycle management may vary depending on the specific implementation. + * If you need to manage the client's lifecycle, use the methods in the class interface of the concrete implementations, + * such as [ModelClientV2]. + */ +interface IModelClientV2 { fun getClientId(): Int fun getIdGenerator(): IIdGenerator fun getUserId(): String? @@ -45,6 +57,4 @@ interface IModelClientV2 : Closeable { suspend fun poll(branch: BranchReference, lastKnownVersion: IVersion?): IVersion suspend fun poll(branch: BranchReference, lastKnownVersion: IVersion?, filter: ModelQuery): IVersion - - override fun close() } diff --git a/model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt b/model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt index 255e2ecd75..d3e48571f5 100644 --- a/model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt +++ b/model-client/src/commonMain/kotlin/org/modelix/model/client2/ModelClientV2.kt @@ -50,7 +50,7 @@ import kotlin.time.Duration.Companion.seconds class ModelClientV2( private val httpClient: HttpClient, val baseUrl: String, -) : IModelClientV2 { +) : IModelClientV2, Closable { private var clientId: Int = 0 private var idGenerator: IIdGenerator = IdGeneratorDummy() private var userId: String? = null diff --git a/model-client/src/jsMain/kotlin/org/modelix/model/client2/Closable.js.kt b/model-client/src/jsMain/kotlin/org/modelix/model/client2/Closable.js.kt new file mode 100644 index 0000000000..a918cb7af0 --- /dev/null +++ b/model-client/src/jsMain/kotlin/org/modelix/model/client2/Closable.js.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.modelix.model.client2 + +internal actual interface Closable { + actual fun close() +} diff --git a/model-client/src/jvmMain/kotlin/org/modelix/model/client2/Closable.jvm.kt b/model-client/src/jvmMain/kotlin/org/modelix/model/client2/Closable.jvm.kt new file mode 100644 index 0000000000..34d328817e --- /dev/null +++ b/model-client/src/jvmMain/kotlin/org/modelix/model/client2/Closable.jvm.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.modelix.model.client2 + +internal actual interface Closable : java.io.Closeable { + actual override fun close() +} diff --git a/model-client/src/jvmTest/kotlin/org/modelix/model/client2/ModelClientV2JvmTest.kt b/model-client/src/jvmTest/kotlin/org/modelix/model/client2/ModelClientV2JvmTest.kt new file mode 100644 index 0000000000..f6c8fd7e99 --- /dev/null +++ b/model-client/src/jvmTest/kotlin/org/modelix/model/client2/ModelClientV2JvmTest.kt @@ -0,0 +1,52 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.modelix.model.client2 + +import io.ktor.client.HttpClient +import io.ktor.client.engine.mock.MockEngine +import io.ktor.client.engine.mock.respondError +import io.ktor.http.HttpStatusCode +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class ModelClientV2JvmTest { + + @Test + fun `Java implementation implements closable functionality`() = runTest { + val url = "http://localhost/v2" + val mockEngine = MockEngine { + respondError(HttpStatusCode.NotFound) + } + val httpClient = HttpClient(mockEngine) + val modelClient = ModelClientV2.builder() + .client(httpClient) + .url(url) + .build() + // Implementing `close` allow to use `.use` method. + modelClient.use { + } + assertFailsWith("Parent job is Completed") { + modelClient.init() + } + // `Closable` implies, that `.close` method is idempotent. + modelClient.close() + assertFailsWith("Parent job is Completed") { + modelClient.init() + } + } +}