summaryrefslogtreecommitdiff
path: root/formats/json/commonMain/src/kotlinx/serialization/json/internal/JsonExceptions.kt
blob: c6098dd4a1674e8941b1c62687a1d1dcff93d5b9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/*
 * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
 */

@file:Suppress("FunctionName")

package kotlinx.serialization.json.internal

import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
import kotlinx.serialization.json.*

/**
 * Generic exception indicating a problem with JSON serialization and deserialization.
 */
internal open class JsonException(message: String) : SerializationException(message)

/**
 * Thrown when [Json] has failed to parse the given JSON string or deserialize it to a target class.
 */
internal class JsonDecodingException(message: String) : JsonException(message)

internal fun JsonDecodingException(offset: Int, message: String) =
    JsonDecodingException(if (offset >= 0) "Unexpected JSON token at offset $offset: $message" else message)

/**
 * Thrown when [Json] has failed to create a JSON string from the given value.
 */
internal class JsonEncodingException(message: String) : JsonException(message)

internal fun JsonDecodingException(offset: Int, message: String, input: CharSequence) =
    JsonDecodingException(offset, "$message\nJSON input: ${input.minify(offset)}")

internal fun InvalidFloatingPointEncoded(value: Number, output: String) = JsonEncodingException(
    "Unexpected special floating-point value $value. By default, " +
            "non-finite floating point values are prohibited because they do not conform JSON specification. " +
            "$specialFlowingValuesHint\n" +
            "Current output: ${output.minify()}"
)


// Extension on JSON reader and fail immediately
internal fun AbstractJsonLexer.throwInvalidFloatingPointDecoded(result: Number): Nothing {
    fail("Unexpected special floating-point value $result. By default, " +
            "non-finite floating point values are prohibited because they do not conform JSON specification",
        hint = specialFlowingValuesHint)
}

internal fun AbstractJsonLexer.invalidTrailingComma(entity: String = "object"): Nothing {
    fail("Trailing comma before the end of JSON $entity",
        position = currentPosition - 1,
        hint = "Trailing commas are non-complaint JSON and not allowed by default. Use 'allowTrailingCommas = true' in 'Json {}' builder to support them."
    )
}

@OptIn(ExperimentalSerializationApi::class)
internal fun InvalidKeyKindException(keyDescriptor: SerialDescriptor) = JsonEncodingException(
    "Value of type '${keyDescriptor.serialName}' can't be used in JSON as a key in the map. " +
            "It should have either primitive or enum kind, but its kind is '${keyDescriptor.kind}'.\n" +
            allowStructuredMapKeysHint
)

// Exceptions for tree-based decoder

internal fun InvalidFloatingPointEncoded(value: Number, key: String, output: String) =
    JsonEncodingException(unexpectedFpErrorMessage(value, key, output))

internal fun InvalidFloatingPointDecoded(value: Number, key: String, output: String) =
    JsonDecodingException(-1, unexpectedFpErrorMessage(value, key, output))

private fun unexpectedFpErrorMessage(value: Number, key: String, output: String): String {
    return "Unexpected special floating-point value $value with key $key. By default, " +
            "non-finite floating point values are prohibited because they do not conform JSON specification. " +
            "$specialFlowingValuesHint\n" +
            "Current output: ${output.minify()}"
}

internal fun UnknownKeyException(key: String, input: String) = JsonDecodingException(
    -1,
    "Encountered an unknown key '$key'.\n" +
            "$ignoreUnknownKeysHint\n" +
            "Current input: ${input.minify()}"
)

internal fun CharSequence.minify(offset: Int = -1): CharSequence {
    if (length < 200) return this
    if (offset == -1) {
        val start = this.length - 60
        if (start <= 0) return this
        return "....." + substring(start)
    }

    val start = offset - 30
    val end = offset + 30
    val prefix = if (start <= 0) "" else "....."
    val suffix = if (end >= length) "" else "....."
    return prefix + substring(start.coerceAtLeast(0), end.coerceAtMost(length)) + suffix
}