/* * Copyright (C) Contributors to the Suwayomi project * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ package suwayomi.tachidesk.graphql.server import com.expediagroup.graphql.server.execution.GraphQLRequestParser import com.expediagroup.graphql.server.types.GraphQLBatchRequest import com.expediagroup.graphql.server.types.GraphQLRequest import com.expediagroup.graphql.server.types.GraphQLServerRequest import io.github.oshai.kotlinlogging.KotlinLogging import io.javalin.http.Context import io.javalin.http.UploadedFile import io.javalin.json.fromJsonStream import io.javalin.json.fromJsonString import java.io.IOException class JavalinGraphQLRequestParser : GraphQLRequestParser { private val logger = KotlinLogging.logger {} @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") override suspend fun parseRequest(context: Context): GraphQLServerRequest? { return try { val jsonMapper = context.jsonMapper() val contentType = context.contentType() val formParam = if ( contentType?.contains("application/x-www-form-urlencoded") == true || contentType?.contains("multipart/form-data") == true ) { context.formParam("operations") ?: throw IllegalArgumentException("Cannot find 'operations' body") } else { return context.bodyInputStream().use { jsonMapper.fromJsonStream(it) } } val request = jsonMapper.fromJsonString(formParam) val map = context .formParam("map") ?.let { jsonMapper.fromJsonString>>(it) }.orEmpty() val mapItems = map .flatMap { (key, variables) -> val file = context.uploadedFile(key) variables.map { fullVariable -> val variable = fullVariable.removePrefix("variables.").substringBefore('.') val listIndex = fullVariable.substringAfterLast('.').toIntOrNull() MapItem( variable, listIndex, file, ) } }.groupBy { it.variable } when (request) { is GraphQLRequest -> { request.copy(variables = request.variables?.modifyFiles(mapItems)) } is GraphQLBatchRequest -> { request.copy( requests = request.requests.map { it.copy( variables = it.variables?.modifyFiles(mapItems), ) }, ) } } } catch (e: IOException) { logger.error(e) { "Error when parsing request" } null } } data class MapItem( val variable: String, val listIndex: Int?, val file: UploadedFile?, ) /** * Example [this]: { "file": null } * Example: '{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) { id } }", "variables": { "file": null } }' * Example map "{ "0": ["variables.file"] }" * TODO nested objects */ private fun Map.modifyFiles(map: Map>): Map = mapValues { (name, value) -> if (map.containsKey(name)) { val items = map[name].orEmpty() if (items.size > 1) { if (value is List<*>) { value.mapIndexed { index, any -> any ?: items.firstOrNull { it.listIndex == index }?.file } } else { value } } else { value ?: items.firstOrNull()?.file } } else { value } } }