diff --git a/FloconAndroid/flocon-base/src/main/java/io/github/openflocon/flocon/plugins/network/model/BadQualityConfig.kt b/FloconAndroid/flocon-base/src/main/java/io/github/openflocon/flocon/plugins/network/model/BadQualityConfig.kt index d517050a..80c99e19 100644 --- a/FloconAndroid/flocon-base/src/main/java/io/github/openflocon/flocon/plugins/network/model/BadQualityConfig.kt +++ b/FloconAndroid/flocon-base/src/main/java/io/github/openflocon/flocon/plugins/network/model/BadQualityConfig.kt @@ -12,8 +12,17 @@ data class BadQualityConfig( ) class Error( val weight: Float, // increase the probability of being triggered vs all others errors - val errorCode: Int, - val errorBody: String, - val errorContentType: String, // "application/json" - ) + val type: Type, + ) { + sealed interface Type { + data class Body( + val errorCode: Int, + val errorBody: String, + val errorContentType: String, // "application/json" + ) : Type + data class ErrorThrow( + val classPath: String, + ) : Type + } + } } \ No newline at end of file diff --git a/FloconAndroid/flocon/src/main/java/io/github/openflocon/flocon/plugins/network/mapper/BadQualityToJson.kt b/FloconAndroid/flocon/src/main/java/io/github/openflocon/flocon/plugins/network/mapper/BadQualityToJson.kt index 1eae8582..80fae5a6 100644 --- a/FloconAndroid/flocon/src/main/java/io/github/openflocon/flocon/plugins/network/mapper/BadQualityToJson.kt +++ b/FloconAndroid/flocon/src/main/java/io/github/openflocon/flocon/plugins/network/mapper/BadQualityToJson.kt @@ -23,9 +23,16 @@ fun toJsonObject(config: BadQualityConfig): JSONObject { config.errors.forEach { error -> val errorObject = JSONObject() errorObject.put("weight", error.weight) - errorObject.put("errorCode", error.errorCode) - errorObject.put("errorBody", error.errorBody) - errorObject.put("errorContentType", error.errorContentType) + when(val t = error.type) { + is BadQualityConfig.Error.Type.Body -> { + errorObject.put("errorCode", t.errorCode) + errorObject.put("errorBody", t.errorBody) + errorObject.put("errorContentType", t.errorContentType) + } + is BadQualityConfig.Error.Type.ErrorThrow -> { + errorObject.put("errorException", t.classPath) + } + } errorsArray.put(errorObject) } jsonObject.put("errors", errorsArray) @@ -54,13 +61,27 @@ fun parseBadQualityConfig(jsonString: String): BadQualityConfig? { val errorsList = mutableListOf() for (i in 0 until errorsArray.length()) { val errorObject = errorsArray.getJSONObject(i) - val error = BadQualityConfig.Error( - weight = errorObject.getDouble("weight").toFloat(), - errorCode = errorObject.getInt("errorCode"), - errorBody = errorObject.getString("errorBody"), - errorContentType = errorObject.getString("errorContentType") - ) - errorsList.add(error) + try { + val errorException = errorObject.optString("errorException") + val errorCode = errorObject.optInt("errorCode") + val errorBody = errorObject.optString("errorBody") + val errorContentType = errorObject.optString("errorContentType") + val error = BadQualityConfig.Error( + weight = errorObject.getDouble("weight").toFloat(), + type = if (errorException.isNotEmpty()) { + BadQualityConfig.Error.Type.ErrorThrow(errorException) + } else { + BadQualityConfig.Error.Type.Body( + errorCode = errorCode, + errorBody = errorBody, + errorContentType = errorContentType + ) + } + ) + errorsList.add(error) + } catch (t: Throwable) { + FloconLogger.logError(t.message ?: "bad connection network parsing issue", t) + } } BadQualityConfig( diff --git a/FloconAndroid/okhttp-interceptor/src/main/java/io/github/openflocon/flocon/okhttp/OkHttpInterceptor.kt b/FloconAndroid/okhttp-interceptor/src/main/java/io/github/openflocon/flocon/okhttp/OkHttpInterceptor.kt index 628d2f38..15b18f83 100644 --- a/FloconAndroid/okhttp-interceptor/src/main/java/io/github/openflocon/flocon/okhttp/OkHttpInterceptor.kt +++ b/FloconAndroid/okhttp-interceptor/src/main/java/io/github/openflocon/flocon/okhttp/OkHttpInterceptor.kt @@ -84,80 +84,112 @@ class FloconOkhttpInterceptor() : Interceptor { ) ) - val response = if(isMocked) { - executeMock(request = request, mock = mockConfig) - } else { - val badQualityConfig = floconNetworkPlugin.badQualityConfig // Supposons que cette propriété existe - - if (badQualityConfig != null) { - val latencyProbability = badQualityConfig.latency.latencyTriggerProbability - val shouldSimulateLatency = latencyProbability > 0f && (latencyProbability == 1f || Math.random() < latencyProbability) - if (shouldSimulateLatency) { - try { - val latencyMs = Random.nextLong(badQualityConfig.latency.minLatencyMs, badQualityConfig.latency.maxLatencyMs + 1) - Thread.sleep(latencyMs) - } catch (e: InterruptedException) { - Thread.currentThread().interrupt() - } - } - - failResponseIfNeeded( - badQualityConfig = badQualityConfig, - request = request, - ) ?: chain.proceed(request) // if no need to trigger an error + try { + val response = if (isMocked) { + executeMock(request = request, mock = mockConfig) } else { - chain.proceed(request) + val badQualityConfig = + floconNetworkPlugin.badQualityConfig // Supposons que cette propriété existe + + if (badQualityConfig != null) { + val latencyProbability = badQualityConfig.latency.latencyTriggerProbability + val shouldSimulateLatency = + latencyProbability > 0f && (latencyProbability == 1f || Math.random() < latencyProbability) + if (shouldSimulateLatency) { + try { + val latencyMs = Random.nextLong( + badQualityConfig.latency.minLatencyMs, + badQualityConfig.latency.maxLatencyMs + 1 + ) + Thread.sleep(latencyMs) + } catch (e: InterruptedException) { + Thread.currentThread().interrupt() + } + } + + failResponseIfNeeded( + badQualityConfig = badQualityConfig, + request = request, + ) ?: chain.proceed(request) // if no need to trigger an error + } else { + chain.proceed(request) + } } - } - val endTime = System.nanoTime() + val endTime = System.nanoTime() - val durationMs: Double = (endTime - startTime) / 1e6 + val durationMs: Double = (endTime - startTime) / 1e6 - // To get the response body, be careful - // because the body can only be read once. - // It must be duplicated so that the chain can continue normally. - val responseBody = response.body - var responseBodyString: String? = null - var responseSize: Long? = null - val responseContentType: MediaType? = responseBody?.contentType() + // To get the response body, be careful + // because the body can only be read once. + // It must be duplicated so that the chain can continue normally. + val responseBody = response.body + var responseBodyString: String? = null + var responseSize: Long? = null + val responseContentType: MediaType? = responseBody?.contentType() - if (responseBody != null) { - // Use response.peekBody() to safely read the body without consuming it - // Note: peekBody has a max size, adjust as needed. - responseBodyString = response.peekBody(Long.MAX_VALUE).string() // Reads the body safely - responseSize = responseBody.contentLength().let { - if (it != -1L) it else responseBodyString?.toByteArray(StandardCharsets.UTF_8)?.size?.toLong() + if (responseBody != null) { + // Use response.peekBody() to safely read the body without consuming it + // Note: peekBody has a max size, adjust as needed. + responseBodyString = + response.peekBody(Long.MAX_VALUE).string() // Reads the body safely + responseSize = responseBody.contentLength().let { + if (it != -1L) it else responseBodyString?.toByteArray(StandardCharsets.UTF_8)?.size?.toLong() + } } - } - val responseHeadersMap = - response.headers.toMultimap().mapValues { it.value.joinToString(",") } + val responseHeadersMap = + response.headers.toMultimap().mapValues { it.value.joinToString(",") } - val isImage = responseContentType?.toString()?.startsWith("image/") == true + val isImage = responseContentType?.toString()?.startsWith("image/") == true - val floconCallResponse = FloconNetworkResponse( - httpCode = response.code, - contentType = responseContentType?.toString(), - body = responseBodyString.takeUnless { isImage }, // dont send images responses bytes - headers = responseHeadersMap, - size = responseSize, - grpcStatus = null, - ) - - floconNetworkPlugin.logResponse( - FloconNetworkCallResponse( - floconCallId = floconCallId, - durationMs = durationMs, - floconNetworkType = floconNetworkType, - isMocked = isMocked, - response = floconCallResponse, + val floconCallResponse = FloconNetworkResponse( + httpCode = response.code, + contentType = responseContentType?.toString(), + body = responseBodyString.takeUnless { isImage }, // dont send images responses bytes + headers = responseHeadersMap, + size = responseSize, + grpcStatus = null, ) - ) - // Rebuild the response with a new body so that the chain can continue - // The original response body is already consumed by peekBody, so no need to rebuild with it. - // Just return the original response if you don't modify the body itself. - return response + floconNetworkPlugin.logResponse( + FloconNetworkCallResponse( + floconCallId = floconCallId, + durationMs = durationMs, + floconNetworkType = floconNetworkType, + isMocked = isMocked, + response = floconCallResponse, + ) + ) + + // Rebuild the response with a new body so that the chain can continue + // The original response body is already consumed by peekBody, so no need to rebuild with it. + // Just return the original response if you don't modify the body itself. + return response + } catch (e: IOException) { + val endTime = System.nanoTime() + + val durationMs: Double = (endTime - startTime) / 1e6 + + val floconCallResponse = FloconNetworkResponse( + httpCode = 0, + contentType = "error", + body = e.message, // TODO better handle of errors + headers = emptyMap(), + size = null, + grpcStatus = null, + ) + + floconNetworkPlugin.logResponse( + FloconNetworkCallResponse( + floconCallId = floconCallId, + durationMs = durationMs, + floconNetworkType = floconNetworkType, + isMocked = isMocked, + response = floconCallResponse, + ) + ) + throw e + } } private fun failResponseIfNeeded( @@ -168,15 +200,28 @@ class FloconOkhttpInterceptor() : Interceptor { if (shouldFail) { val selectedError = selectRandomError(badQualityConfig.errors) if (selectedError != null) { - val errorBody = selectedError.errorBody.toResponseBody(selectedError.errorContentType.toMediaTypeOrNull()) + when(val t = selectedError.type) { + is BadQualityConfig.Error.Type.Body -> { + val errorBody = t.errorBody.toResponseBody(t.errorContentType.toMediaTypeOrNull()) - return Response.Builder() - .request(request) - .protocol(Protocol.HTTP_1_1) - .code(selectedError.errorCode) // Utiliser le code d'erreur configuré - .message(getHttpMessage(selectedError.errorCode)) - .body(errorBody) - .build() + return Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(t.errorCode) // Utiliser le code d'erreur configuré + .message(getHttpMessage(t.errorCode)) + .body(errorBody) + .build() + } + is BadQualityConfig.Error.Type.ErrorThrow -> { + val errorClass = Class.forName(t.classPath) + val error = errorClass.newInstance() as? Throwable + if(error is IOException) { + throw error //okhttp accepts only IOException + } else if(error is Throwable){ + throw IOException(error) + } + } + } } } return null diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/mapper/BadQualityMapper.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/mapper/BadQualityMapper.kt index 42dd6c2a..335a0e72 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/mapper/BadQualityMapper.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/mapper/BadQualityMapper.kt @@ -18,9 +18,16 @@ fun BadQualityConfigDomainModel.toUi() = BadQualityConfigUiModel( private fun BadQualityConfigDomainModel.Error.toUi() = BadQualityConfigUiModel.Error( weight = weight, - httpCode = httpCode, - body = body, - contentType = contentType, + type = when(val t = type) { + is BadQualityConfigDomainModel.Error.Type.Body -> BadQualityConfigUiModel.Error.Type.Body( + httpCode = t.httpCode, + body = t.body, + contentType = t.contentType, + ) + is BadQualityConfigDomainModel.Error.Type.Exception -> BadQualityConfigUiModel.Error.Type.Exception( + classPath = t.classPath, + ) + } ) private fun BadQualityConfigDomainModel.LatencyConfig.toUi() = BadQualityConfigUiModel.LatencyConfig( @@ -43,9 +50,16 @@ fun BadQualityConfigUiModel.toDomain() = BadQualityConfigDomainModel( private fun BadQualityConfigUiModel.Error.toDomain() = BadQualityConfigDomainModel.Error( weight = weight, - httpCode = httpCode, - body = body, - contentType = contentType, + type = when(val t = type) { + is BadQualityConfigUiModel.Error.Type.Body -> BadQualityConfigDomainModel.Error.Type.Body( + httpCode = t.httpCode, + body = t.body, + contentType = t.contentType, + ) + is BadQualityConfigUiModel.Error.Type.Exception -> BadQualityConfigDomainModel.Error.Type.Exception( + classPath = t.classPath, + ) + } ) private fun BadQualityConfigUiModel.LatencyConfig.toDomain() = BadQualityConfigDomainModel.LatencyConfig( diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/BadQualityConfigUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/BadQualityConfigUiModel.kt index 20ac95ac..84ad4b6d 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/BadQualityConfigUiModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/BadQualityConfigUiModel.kt @@ -19,10 +19,19 @@ data class BadQualityConfigUiModel( data class Error( val uuid: String = UUID.randomUUID().toString(), val weight: Float, // increase the probability of being triggered vs all others errors - val httpCode: Int, - val body: String, - val contentType: String, // "application/json" - ) + val type: Type, + ) { + sealed interface Type { + data class Body( + val httpCode: Int, + val body: String, + val contentType: String, // "application/json" + ) : Type + data class Exception( + val classPath: String, + ) : Type + } + } } fun previewBadQualityConfigUiModel(errorCount: Int) = BadQualityConfigUiModel( @@ -39,9 +48,11 @@ fun previewBadQualityConfigUiModel(errorCount: Int) = BadQualityConfigUiModel( errors = List(errorCount) { BadQualityConfigUiModel.Error( weight = 1f, - httpCode = 500, - body = "{\"error\":\"...\"}", - contentType = "application/json", + type = BadQualityConfigUiModel.Error.Type.Body( + httpCode = 500, + body = "{\"error\":\"...\"}", + contentType = "application/json", + ) ) }, ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/PossibleExceptionUiModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/PossibleExceptionUiModel.kt new file mode 100644 index 00000000..fe63883c --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/model/PossibleExceptionUiModel.kt @@ -0,0 +1,30 @@ +package io.github.openflocon.flocondesktop.features.network.badquality.edition.model + +data class PossibleExceptionUiModel( + val classPath : String, + val description: String, +) + +// warning : be sure it's only IOExceptions +val possibleExceptions = listOf( + PossibleExceptionUiModel( + "java.net.SocketTimeoutException", + "The network operation timed out (connection, read, or write)." + ), + PossibleExceptionUiModel( + "java.net.UnknownHostException", + "Unable to resolve the host name or server address." + ), + PossibleExceptionUiModel( + "java.net.ConnectException", + "Connection to the server was refused or couldn't be established." + ), + PossibleExceptionUiModel( + "javax.net.ssl.SSLHandshakeException", + "Failed to complete the TLS/SSL handshake (often a certificate issue)." + ), + PossibleExceptionUiModel( + "java.io.IOException", + "A generic I/O error; an unspecified network problem." + ) +) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/view/BadQualityEditionWindow.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/view/BadQualityEditionWindow.kt index b889bd6e..6fcb7bab 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/view/BadQualityEditionWindow.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/network/badquality/edition/view/BadQualityEditionWindow.kt @@ -35,8 +35,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.BadQualityConfigUiModel import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.SelectedBadQualityUiModel +import io.github.openflocon.flocondesktop.features.network.badquality.edition.model.possibleExceptions +import io.github.openflocon.flocondesktop.features.network.list.view.components.getMethodBackground +import io.github.openflocon.flocondesktop.features.network.list.view.components.getMethodText +import io.github.openflocon.flocondesktop.features.network.list.view.components.postMethodBackground +import io.github.openflocon.flocondesktop.features.network.list.view.components.postMethodText import io.github.openflocon.flocondesktop.features.network.mock.edition.view.NetworkMockFieldView import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconButton @@ -179,21 +185,45 @@ fun BadNetworkQualityEditionContent( }, ) { FloconSurface { - ErrorsEditor( - error = selectedError, - onErrorsChange = { error -> - errors = if (errors.any { it.uuid == selectedError.uuid }) { - errors.map { - if (it.uuid == selectedError.uuid) { - error - } else it - } - } else { - errors + error - } - selectedErrorToEdit = null - }, - ) + when (val t = selectedError.type) { + is BadQualityConfigUiModel.Error.Type.Body -> { + ErrorsEditor( + error = selectedError, + httpType = t, + onErrorsChange = { error -> + errors = if (errors.any { it.uuid == selectedError.uuid }) { + errors.map { + if (it.uuid == selectedError.uuid) { + error + } else it + } + } else { + errors + error + } + selectedErrorToEdit = null + }, + ) + } + + is BadQualityConfigUiModel.Error.Type.Exception -> { + ErrorExceptionEditor( + error = selectedError, + errorException = t, + onErrorsChange = { error -> + errors = if (errors.any { it.uuid == selectedError.uuid }) { + errors.map { + if (it.uuid == selectedError.uuid) { + error + } else it + } + } else { + errors + error + } + selectedErrorToEdit = null + }, + ) + } + } } } } @@ -209,7 +239,8 @@ fun BadNetworkQualityEditionContent( id = config?.id ?: UUID.randomUUID().toString(), // generate a new one name = name, isEnabled = config?.isEnabled ?: false, // disabled by default - createdAt = config?.createdAt ?: System.currentTimeMillis(), // generate a new date + createdAt = config?.createdAt + ?: System.currentTimeMillis(), // generate a new date latency = BadQualityConfigUiModel.LatencyConfig( triggerProbability = triggerProbability .toDoubleOrNull() @@ -257,15 +288,34 @@ fun ErrorsListView( BadQualityConfigUiModel.Error( // new error weight = 1f, - httpCode = 500, - body = "", - contentType = "application/json", + type = BadQualityConfigUiModel.Error.Type.Body( + httpCode = 500, + body = "{\"error\":\"...\"}", + contentType = "application/json", + ), ), ) }, ) { Text( - text = "Add", + text = "Add http error", + ) + } + FloconButton( + onClick = { + onErrorsClicked( + BadQualityConfigUiModel.Error( + // new error + weight = 1f, + type = BadQualityConfigUiModel.Error.Type.Exception( + classPath = possibleExceptions.first().classPath, + ), + ), + ) + }, + ) { + Text( + text = "Add Exception", ) } } @@ -290,9 +340,24 @@ fun ErrorsListView( ) Text("Weight : ${error.weight}", style = textStyle) - Text("HttpCode : ${error.httpCode}", style = textStyle) // or throwable ? - Text(error.contentType, style = textStyle) - Text("Body : ${error.body}", maxLines = 2, style = textStyle) + when (val t = error.type) { + is BadQualityConfigUiModel.Error.Type.Body -> { + Text("HttpCode : ${t.httpCode}", style = textStyle) // or throwable ? + Text(t.contentType, style = textStyle) + Text("Body : ${t.body}", maxLines = 2, style = textStyle) + } + + is BadQualityConfigUiModel.Error.Type.Exception -> { + Text( + "Exception", + style = textStyle + ) + Text( + t.classPath, + style = textStyle + ) + } + } // bouton supprimer Row( @@ -318,15 +383,70 @@ fun ErrorsListView( } } +@Composable +fun ErrorExceptionEditor( + error: BadQualityConfigUiModel.Error, + errorException: BadQualityConfigUiModel.Error.Type.Exception, + onErrorsChange: (BadQualityConfigUiModel.Error) -> Unit, +) { + Column( + modifier = Modifier.padding(all = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + possibleExceptions.fastForEach { exception -> + val (backgroundColor, textColor) = if (exception.classPath == errorException.classPath) { + FloconTheme.colorPalette.onSurface to FloconTheme.colorPalette.panel + } else { + FloconTheme.colorPalette.panel to FloconTheme.colorPalette.onSurface + } + Column( + modifier = Modifier + .clip(RoundedCornerShape(8.dp)) + .background( + color = backgroundColor, + ) + .then( + Modifier.clickable( + onClick = { + onErrorsChange( + error.copy( + weight = 1f, + type = errorException.copy( + classPath = exception.classPath + ) + ) + ) + }, + ) + ).padding(all = 8.dp) + ) { + Text( + text = exception.description, + style = FloconTheme.typography.bodySmall, + color = textColor, + ) + Text( + text = exception.classPath, + style = FloconTheme.typography.bodyMedium.copy( + fontWeight = FontWeight.Bold, + ), + color = textColor, + ) + } + } + } +} + @Composable fun ErrorsEditor( error: BadQualityConfigUiModel.Error, + httpType: BadQualityConfigUiModel.Error.Type.Body, onErrorsChange: (BadQualityConfigUiModel.Error) -> Unit, ) { var weight by remember(error) { mutableStateOf(error.weight.toString()) } - var httpCode by remember(error) { mutableStateOf(error.httpCode.toString()) } - var contentType by remember { mutableStateOf(error.contentType) } - var body by remember(error) { mutableStateOf(error.body) } + var httpCode by remember(error) { mutableStateOf(httpType.httpCode.toString()) } + var contentType by remember { mutableStateOf(httpType.contentType) } + var body by remember(error) { mutableStateOf(httpType.body) } Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { // bouton ajouter @@ -338,9 +458,11 @@ fun ErrorsEditor( onErrorsChange( error.copy( weight = weight.toFloatOrNull() ?: error.weight, - httpCode = httpCode.toIntOrNull() ?: error.httpCode, - contentType = contentType, - body = body, + type = httpType.copy( + httpCode = httpCode.toIntOrNull() ?: httpType.httpCode, + contentType = contentType, + body = body, + ) ), ) } diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/BadQualityConfigLocalDataSourceImpl.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/BadQualityConfigLocalDataSourceImpl.kt index 52c1d370..254455ce 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/BadQualityConfigLocalDataSourceImpl.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/datasource/BadQualityConfigLocalDataSourceImpl.kt @@ -22,9 +22,8 @@ class BadQualityConfigLocalDataSourceImpl( config: BadQualityConfigDomainModel ) { networkBadQualityConfigDao.save( - toEntity( + config.toEntity( json = json, - config = config, deviceIdAndPackageName = deviceIdAndPackageName ) ) @@ -39,9 +38,8 @@ class BadQualityConfigLocalDataSourceImpl( packageName = deviceIdAndPackageName.packageName, configId = configId )?.let { - toDomain( - json = json, - entity = it + it.toDomain( + json = json ) } } @@ -56,9 +54,8 @@ class BadQualityConfigLocalDataSourceImpl( configId = configId, ).map { it?.let { - toDomain( - json = json, - entity = it + it.toDomain( + json = json ) } }.distinctUntilChanged() @@ -73,9 +70,8 @@ class BadQualityConfigLocalDataSourceImpl( packageName = deviceIdAndPackageName.packageName, ).map { list -> list.map { - toDomain( - json = json, - entity = it + it.toDomain( + json = json ) } }.distinctUntilChanged() @@ -99,9 +95,8 @@ class BadQualityConfigLocalDataSourceImpl( deviceId = deviceIdAndPackageName.deviceId, packageName = deviceIdAndPackageName.packageName, )?.let { - toDomain( - json = json, - entity = it + it.toDomain( + json = json ) } } diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/BadQualityMapper.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/BadQualityMapper.kt index 86102705..f8d8a278 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/BadQualityMapper.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/mapper/BadQualityMapper.kt @@ -8,46 +8,57 @@ import io.github.openflocon.domain.network.models.BadQualityConfigDomainModel import kotlinx.serialization.json.Json import kotlin.time.Instant -fun toDomain(json: Json, entity: BadQualityConfigEntity): BadQualityConfigDomainModel { +fun BadQualityConfigEntity.toDomain(json: Json): BadQualityConfigDomainModel { val errors = try { - json.decodeFromString>(entity.errors) - .map { toDomain(it) } + json.decodeFromString>(errors) + .map { it.toDomain() } } catch (t: Throwable) { t.printStackTrace() emptyList() } return BadQualityConfigDomainModel( - id = entity.id, - name = entity.name, - createdAt = Instant.fromEpochMilliseconds(entity.createdAt), - isEnabled = entity.isEnabled, + id = id, + name = name, + createdAt = Instant.fromEpochMilliseconds(createdAt), + isEnabled = isEnabled, latency = BadQualityConfigDomainModel.LatencyConfig( - triggerProbability = entity.latency.triggerProbability, - minLatencyMs = entity.latency.minLatencyMs, - maxLatencyMs = entity.latency.maxLatencyMs, + triggerProbability = latency.triggerProbability, + minLatencyMs = latency.minLatencyMs, + maxLatencyMs = latency.maxLatencyMs, ), - errorProbability = entity.errorProbability, + errorProbability = errorProbability, errors = errors, ) } -fun toDomain(error: ErrorEmbedded): BadQualityConfigDomainModel.Error { +fun ErrorEmbedded.toDomain(): BadQualityConfigDomainModel.Error { return BadQualityConfigDomainModel.Error( - weight = error.weight, - httpCode = error.httpCode, - body = error.body, - contentType = error.contentType, + weight = weight, + type = type.toDomain(), ) } +private fun ErrorEmbedded.Type.toDomain(): BadQualityConfigDomainModel.Error.Type { + return when (this) { + is ErrorEmbedded.Type.Body -> BadQualityConfigDomainModel.Error.Type.Body( + httpCode = httpCode, + body = body, + contentType = contentType, + ) -fun toEntity( + is ErrorEmbedded.Type.Exception -> BadQualityConfigDomainModel.Error.Type.Exception( + classPath = classPath, + ) + } +} + + +fun BadQualityConfigDomainModel.toEntity( json: Json, - config: BadQualityConfigDomainModel, deviceIdAndPackageName: DeviceIdAndPackageNameDomainModel ): BadQualityConfigEntity { - val errorsEmbedded = config.errors.map { - toEntity(it) + val errorsEmbedded = errors.map { + it.toEntity() } val errors = try { json.encodeToString>(errorsEmbedded) @@ -56,25 +67,35 @@ fun toEntity( "[]" } return BadQualityConfigEntity( - id = config.id, - name = config.name, - createdAt = config.createdAt.toEpochMilliseconds(), + id = id, + name = name, + createdAt = createdAt.toEpochMilliseconds(), deviceId = deviceIdAndPackageName.deviceId, packageName = deviceIdAndPackageName.packageName, - isEnabled = config.isEnabled, + isEnabled = isEnabled, latency = LatencyConfigEmbedded( - triggerProbability = config.latency.triggerProbability, - minLatencyMs = config.latency.minLatencyMs, - maxLatencyMs = config.latency.maxLatencyMs, + triggerProbability = latency.triggerProbability, + minLatencyMs = latency.minLatencyMs, + maxLatencyMs = latency.maxLatencyMs, ), - errorProbability = config.errorProbability, + errorProbability = errorProbability, errors = errors, ) } -private fun toEntity(error: BadQualityConfigDomainModel.Error): ErrorEmbedded = ErrorEmbedded( - weight = error.weight, - httpCode = error.httpCode, - body = error.body, - contentType = error.contentType, +private fun BadQualityConfigDomainModel.Error.toEntity(): ErrorEmbedded = ErrorEmbedded( + weight = weight, + type = this.type.toEntity(), ) + +private fun BadQualityConfigDomainModel.Error.Type.toEntity() = when (this) { + is BadQualityConfigDomainModel.Error.Type.Body -> ErrorEmbedded.Type.Body( + httpCode = httpCode, + body = body, + contentType = contentType, + ) + + is BadQualityConfigDomainModel.Error.Type.Exception -> ErrorEmbedded.Type.Exception( + classPath = classPath, + ) +} diff --git a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/badquality/ErrorEmbedded.kt b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/badquality/ErrorEmbedded.kt index 59131ee3..b1ba40f0 100644 --- a/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/badquality/ErrorEmbedded.kt +++ b/FloconDesktop/data/local/src/commonMain/kotlin/io/github/openflocon/data/local/network/models/badquality/ErrorEmbedded.kt @@ -5,7 +5,19 @@ import kotlinx.serialization.Serializable @Serializable data class ErrorEmbedded( val weight: Float, - val httpCode: Int, - val body: String, - val contentType: String, -) + val type: Type, +) { + @Serializable + sealed interface Type { + @Serializable + data class Body( + val httpCode: Int, + val body: String, + val contentType: String, + ) : Type + @Serializable + data class Exception( + val classPath: String, + ) : Type + } +} diff --git a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/mapper/Mapper.kt b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/mapper/Mapper.kt index 1f806fd7..f93ae790 100644 --- a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/mapper/Mapper.kt +++ b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/mapper/Mapper.kt @@ -134,11 +134,21 @@ fun toRemote(domain: BadQualityConfigDomainModel): BadQualityConfigDataModel = B }, errorProbability = domain.errorProbability, errors = domain.errors.map { - BadQualityConfigDataModel.Error( - weight = it.weight, - errorCode = it.httpCode, - errorBody = it.body, - errorContentType = it.contentType, - ) + when(val t = it.type) { + is BadQualityConfigDomainModel.Error.Type.Body -> BadQualityConfigDataModel.Error( + weight = it.weight, + errorCode = t.httpCode, + errorBody = t.body, + errorContentType = t.contentType, + errorException = null, + ) + is BadQualityConfigDomainModel.Error.Type.Exception -> BadQualityConfigDataModel.Error( + weight = it.weight, + errorException = t.classPath, + errorCode = null, + errorBody = null, + errorContentType = null, + ) + } }, ) diff --git a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/models/BadQualityConfigDataModel.kt b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/models/BadQualityConfigDataModel.kt index cd1c811e..223c1667 100644 --- a/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/models/BadQualityConfigDataModel.kt +++ b/FloconDesktop/data/remote/src/commonMain/kotlin/com/flocon/data/remote/network/models/BadQualityConfigDataModel.kt @@ -18,8 +18,9 @@ data class BadQualityConfigDataModel( @Serializable data class Error( val weight: Float, // increase the probability of being triggered vs all others errors - val errorCode: Int, - val errorBody: String, - val errorContentType: String, // "application/json" + val errorException: String?, + val errorCode: Int?, + val errorBody: String?, + val errorContentType: String?, // "application/json" ) } diff --git a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/BadQualityConfigDomainModel.kt b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/BadQualityConfigDomainModel.kt index b0c6a89c..4ad82c99 100644 --- a/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/BadQualityConfigDomainModel.kt +++ b/FloconDesktop/domain/src/commonMain/kotlin/io/github/openflocon/domain/network/models/BadQualityConfigDomainModel.kt @@ -20,8 +20,17 @@ data class BadQualityConfigDomainModel( ) data class Error( val weight: Float, // increase the probability of being triggered vs all others errors - val httpCode: Int, - val body: String, - val contentType: String, // "application/json" - ) + val type: Type, + ) { + sealed interface Type { + data class Body( + val httpCode: Int, + val body: String, + val contentType: String, + ) : Type + data class Exception( + val classPath: String, + ) : Type + } + } }