Repository: ktorio/ktor
File: ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/ContentNegotiation.kt (Kotlin)

Code before:
/**
 * A plugin that serves two primary purposes:
 * - Negotiating media types between the client and server. For this, it uses the `Accept` and `Content-Type` headers.
 * - Serializing/deserializing the content in a specific format when sending requests and receiving responses.
 *    Ktor supports the following formats out-of-the-box: `JSON`, `XML`, and `CBOR`.
 *
 * You can learn more from [Content negotiation and serialization](https://ktor.io/docs/serialization-client.html).
 */
@OptIn(InternalAPI::class)
public val ContentNegotiation: ClientPlugin<ContentNegotiationConfig> = createClientPlugin(
    "ContentNegotiation",
    ::ContentNegotiationConfig
) {
    val registrations: List<ContentNegotiationConfig.ConverterRegistration> = pluginConfig.registrations
    val ignoredTypes: Set<KClass<*>> = pluginConfig.ignoredTypes

    suspend fun convertRequest(request: HttpRequestBuilder, body: Any): OutgoingContent? {
        val requestRegistrations = if (request.attributes.contains(ExcludeContentTypes)) {
            val excluded = request.attributes[ExcludeContentTypes]
            registrations.filter { registration -> excluded.none { registration.contentTypeToSend.match(it) } }
        } else {
            registrations
        }

        requestRegistrations.forEach {
            val acceptHeaders = request.headers.getAll(HttpHeaders.Accept).orEmpty()
            if (acceptHeaders.none { h -> ContentType.parse(h).match(it.contentTypeToSend) }) {
                LOGGER.trace("Adding Accept=${it.contentTypeToSend.contentType} header with q=0.8 for ${request.url}")
                // automatically added headers get a lower content type priority, so user-specified accept headers
                //  with higher q or implicit q=1 will take precedence
                val contentTypeToSend = when (val qValue = pluginConfig.defaultAcceptHeaderQValue) {
                  null -> it.contentTypeToSend
                  else -> it.contentTypeToSend.withParameter("q", qValue)
                }
                request.accept(contentTypeToSend)
            }
        }

        if (body is OutgoingContent || ignoredTypes.any { it.isInstance(body) }) {
            LOGGER.trace(
                "Body type ${body::class} is in ignored types. " +
                    "Skipping ContentNegotiation for ${request.url}."
            )
            return null
        }
        val contentType = request.contentType() ?: run {
            LOGGER.trace("Request doesn't have Content-Type header. Skipping ContentNegotiation for ${request.url}.")
            return null
        }

        if (body is Unit) {

Review comment (suggestion):
We can move `val acceptHeaders =` before `requestRegistrations.forEach {`

Code after:

/**
 * A plugin that serves two primary purposes:
 * - Negotiating media types between the client and server. For this, it uses the `Accept` and `Content-Type` headers.
 * - Serializing/deserializing the content in a specific format when sending requests and receiving responses.
 *    Ktor supports the following formats out-of-the-box: `JSON`, `XML`, and `CBOR`.
 *
 * You can learn more from [Content negotiation and serialization](https://ktor.io/docs/serialization-client.html).
 */
@OptIn(InternalAPI::class)
public val ContentNegotiation: ClientPlugin<ContentNegotiationConfig> = createClientPlugin(
    "ContentNegotiation",
    ::ContentNegotiationConfig
) {
    val registrations: List<ContentNegotiationConfig.ConverterRegistration> = pluginConfig.registrations
    val ignoredTypes: Set<KClass<*>> = pluginConfig.ignoredTypes

    suspend fun convertRequest(request: HttpRequestBuilder, body: Any): OutgoingContent? {
        val requestRegistrations = if (request.attributes.contains(ExcludedContentTypes)) {
            val excluded = request.attributes[ExcludedContentTypes]
            registrations.filter { registration -> excluded.none { registration.contentTypeToSend.match(it) } }
        } else {
            registrations
        }

        val acceptHeaders = request.headers.getAll(HttpHeaders.Accept).orEmpty()
        requestRegistrations.forEach {
            if (acceptHeaders.none { h -> ContentType.parse(h).match(it.contentTypeToSend) }) {
                // automatically added headers get a lower content type priority, so user-specified accept headers
                //  with higher q or implicit q=1 will take precedence
                val contentTypeToSend = when (val qValue = pluginConfig.defaultAcceptHeaderQValue) {
                    null -> it.contentTypeToSend
                    else -> it.contentTypeToSend.withParameter("q", qValue.toString())
                }
                LOGGER.trace("Adding Accept=$contentTypeToSend header for ${request.url}")
                request.accept(contentTypeToSend)
            }
        }

        if (body is OutgoingContent || ignoredTypes.any { it.isInstance(body) }) {
            LOGGER.trace(
                "Body type ${body::class} is in ignored types. " +
                    "Skipping ContentNegotiation for ${request.url}."
            )
            return null
        }
        val contentType = request.contentType() ?: run {
            LOGGER.trace("Request doesn't have Content-Type header. Skipping ContentNegotiation for ${request.url}.")
            return null
        }