Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[i02] network: Config network module #6

Merged
merged 14 commits into from
Jul 11, 2024
9 changes: 2 additions & 7 deletions .github/workflows/multiplatform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,11 @@ jobs:
token: ${{ github.token }}
submodules: recursive

# Setup Gradle and run Build
# Setup Gradle and clean
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew clean
./gradlew :Common:build
./gradlew :Core:build
./gradlew :DB:build
./gradlew :Network:build
run: ./gradlew clean

# Run tests
- name: Run Unit tests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.common.exceptions

/**
* Represents an unknown exception that can occur during the execution of the application.
*
* This exception is used to encapsulate unexpected or unknown errors that are not covered
* by other specific exception types.
*
* @property message The detailed message describing the error.
* @property cause The underlying cause of this exception, if any.
*
* @constructor Creates an instance of `UnknownException` with a detailed error message and an optional cause.
*
* @param cause The underlying exception that caused this exception.
*/
class UnknownException(cause: Throwable) : Exception(cause) {
override val message: String = cause.message ?: cause.toString()
}
5 changes: 4 additions & 1 deletion Network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,11 @@ kotlin {

sourceSets {
commonMain.dependencies {
implementation(project(":Common"))
api(project(":Common"))
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.content.negociation)
implementation(libs.ktor.client.json)
implementation(libs.ktor.client.encoding)
implementation(libs.kotlinx.serialization.json)
}
commonTest.dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network

import com.infomaniak.multiplatform_swisstransfer.common.exceptions.UnknownException
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.ApiException
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.NetworkException
import com.infomaniak.multiplatform_swisstransfer.network.exceptions.UnknownApiException
import com.infomaniak.multiplatform_swisstransfer.network.models.ApiError
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.HttpClientEngineFactory
import io.ktor.client.plugins.HttpRequestRetry
import io.ktor.client.plugins.HttpResponseValidator
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.compression.ContentEncoding
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.statement.HttpResponse
import io.ktor.client.statement.bodyAsText
import io.ktor.serialization.kotlinx.json.json
import io.ktor.utils.io.errors.IOException
import kotlinx.serialization.json.Json

class ApiClientProvider internal constructor(engine: HttpClientEngineFactory<*>? = null) {

constructor() : this(null)

val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
isLenient = true
useAlternativeNames = false
}

val httpClient = createHttpClient(engine)

fun createHttpClient(engine: HttpClientEngineFactory<*>?): HttpClient {
val block: HttpClientConfig<*>.() -> Unit = {
install(ContentNegotiation) {
json([email protected])
}
install(ContentEncoding) {
gzip()
}
install(HttpTimeout) {
requestTimeoutMillis = REQUEST_TIMEOUT
connectTimeoutMillis = REQUEST_TIMEOUT
socketTimeoutMillis = REQUEST_TIMEOUT
}
install(HttpRequestRetry) {
retryOnExceptionIf(maxRetries = MAX_RETRY) { _, cause ->
cause.isNetworkException()
}
delayMillis { retry ->
retry * 500L
}
}
HttpResponseValidator {
validateResponse { response: HttpResponse ->
val statusCode = response.status.value
val bodyResponse = response.bodyAsText()
if (statusCode >= 300) {
runCatching {
val apiError = json.decodeFromString<ApiError>(bodyResponse)
throw ApiException(apiError.errorCode, apiError.message)
}.onFailure {
throw UnknownApiException(statusCode, bodyResponse)
}
}
}
handleResponseExceptionWithRequest { cause, _ ->
when (cause) {
is IOException -> throw NetworkException("Network error: ${cause.message}")
is ApiException, is UnknownApiException -> throw cause
else -> throw UnknownException(cause)
}
}
}
}

return if (engine != null) HttpClient(engine, block) else HttpClient(block)
}

private fun Throwable.isNetworkException() = this is IOException

private companion object {
const val REQUEST_TIMEOUT = 10_000L
const val MAX_RETRY = 3
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.exceptions

class ApiException(val errorCode: Int, errorMessage: String) : Exception(errorMessage)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.exceptions

import io.ktor.utils.io.errors.IOException

class NetworkException(message: String) : IOException(message)

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.exceptions

class UnknownApiException(val statusCode: Int, val bodyResponse: String) : Exception(bodyResponse)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.models

import kotlinx.serialization.Serializable

@Serializable
data class ApiError(
val errorCode: Int,
val message: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.models

import kotlinx.serialization.Serializable

@Serializable
data class ApiResponse<T>(
val result: ApiResponseStatus = ApiResponseStatus.UNKNOWN,
val data: T? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.infomaniak.multiplatform_swisstransfer.network.models

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
enum class ApiResponseStatus {

@SerialName("error")
ERROR,

@SerialName("success")
SUCCESS,

@SerialName("asynchronous")
ASYNCHRONOUS,

@SerialName("unknown")
UNKNOWN;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Infomaniak SwissTransfer - Multiplatform
* Copyright (C) 2024 Infomaniak Network SA
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package com.infomaniak.multiplatform_swisstransfer.network.models.transfer

import com.infomaniak.multiplatform_swisstransfer.common.interfaces.Container
import com.infomaniak.multiplatform_swisstransfer.network.serializers.DateToTimestampSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
class ContainerApi : Container<List<FileApi>> {
@SerialName("UUID")
override var uuid: String = ""

override var duration: Long = 0

@SerialName("createdDate")
@Serializable(DateToTimestampSerializer::class)
override var createdDateTimestamp: Long = 0

@SerialName("expiredDate")
@Serializable(DateToTimestampSerializer::class)
override var expiredDateTimestamp: Long = 0
override var numberOfFile: Long = 0
override var message: String? = null
override var needPassword: Long = 0
override var lang: String = ""
override var sizeUploaded: Long = 0

@SerialName("deletedDate")
@Serializable(DateToTimestampSerializer::class)
override var deletedDateTimestamp: Long? = null
override var swiftVersion: Long = 0
override var downloadLimit: Long = 0
override var source: String = "ST"

// @SerialName("WSUser") TODO: What's it ?
// var wsUser: JsonElement? = null

override var files: List<FileApi> = emptyList()
}
Loading
Loading