Skip to content

Commit

Permalink
Merge pull request #6 from Infomaniak/i02-config-network-module
Browse files Browse the repository at this point in the history
[i02] network: Config network module
  • Loading branch information
sirambd authored Jul 11, 2024
2 parents 0eb8428 + 5a62201 commit aee8a27
Show file tree
Hide file tree
Showing 27 changed files with 994 additions and 8 deletions.
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(this@ApiClientProvider.json)
}
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

0 comments on commit aee8a27

Please sign in to comment.