1 /*
2 * Copyright 2017-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 */
4
5 @file:Suppress("FunctionName")
6
7 package kotlinx.serialization.json.internal
8
9 import kotlinx.serialization.*
10 import kotlinx.serialization.descriptors.*
11 import kotlinx.serialization.json.*
12
13 /**
14 * Generic exception indicating a problem with JSON serialization and deserialization.
15 */
16 internal open class JsonException(message: String) : SerializationException(message)
17
18 /**
19 * Thrown when [Json] has failed to parse the given JSON string or deserialize it to a target class.
20 */
21 internal class JsonDecodingException(message: String) : JsonException(message)
22
JsonDecodingExceptionnull23 internal fun JsonDecodingException(offset: Int, message: String) =
24 JsonDecodingException(if (offset >= 0) "Unexpected JSON token at offset $offset: $message" else message)
25
26 /**
27 * Thrown when [Json] has failed to create a JSON string from the given value.
28 */
29 internal class JsonEncodingException(message: String) : JsonException(message)
30
31 internal fun JsonDecodingException(offset: Int, message: String, input: CharSequence) =
32 JsonDecodingException(offset, "$message\nJSON input: ${input.minify(offset)}")
33
34 internal fun InvalidFloatingPointEncoded(value: Number, output: String) = JsonEncodingException(
35 "Unexpected special floating-point value $value. By default, " +
36 "non-finite floating point values are prohibited because they do not conform JSON specification. " +
37 "$specialFlowingValuesHint\n" +
38 "Current output: ${output.minify()}"
39 )
40
41
42 // Extension on JSON reader and fail immediately
43 internal fun AbstractJsonLexer.throwInvalidFloatingPointDecoded(result: Number): Nothing {
44 fail("Unexpected special floating-point value $result. By default, " +
45 "non-finite floating point values are prohibited because they do not conform JSON specification",
46 hint = specialFlowingValuesHint)
47 }
48
invalidTrailingCommanull49 internal fun AbstractJsonLexer.invalidTrailingComma(entity: String = "object"): Nothing {
50 fail("Trailing comma before the end of JSON $entity",
51 position = currentPosition - 1,
52 hint = "Trailing commas are non-complaint JSON and not allowed by default. Use 'allowTrailingCommas = true' in 'Json {}' builder to support them."
53 )
54 }
55
56 @OptIn(ExperimentalSerializationApi::class)
InvalidKeyKindExceptionnull57 internal fun InvalidKeyKindException(keyDescriptor: SerialDescriptor) = JsonEncodingException(
58 "Value of type '${keyDescriptor.serialName}' can't be used in JSON as a key in the map. " +
59 "It should have either primitive or enum kind, but its kind is '${keyDescriptor.kind}'.\n" +
60 allowStructuredMapKeysHint
61 )
62
63 // Exceptions for tree-based decoder
64
65 internal fun InvalidFloatingPointEncoded(value: Number, key: String, output: String) =
66 JsonEncodingException(unexpectedFpErrorMessage(value, key, output))
67
68 internal fun InvalidFloatingPointDecoded(value: Number, key: String, output: String) =
69 JsonDecodingException(-1, unexpectedFpErrorMessage(value, key, output))
70
71 private fun unexpectedFpErrorMessage(value: Number, key: String, output: String): String {
72 return "Unexpected special floating-point value $value with key $key. By default, " +
73 "non-finite floating point values are prohibited because they do not conform JSON specification. " +
74 "$specialFlowingValuesHint\n" +
75 "Current output: ${output.minify()}"
76 }
77
UnknownKeyExceptionnull78 internal fun UnknownKeyException(key: String, input: String) = JsonDecodingException(
79 -1,
80 "Encountered an unknown key '$key'.\n" +
81 "$ignoreUnknownKeysHint\n" +
82 "Current input: ${input.minify()}"
83 )
84
85 internal fun CharSequence.minify(offset: Int = -1): CharSequence {
86 if (length < 200) return this
87 if (offset == -1) {
88 val start = this.length - 60
89 if (start <= 0) return this
90 return "....." + substring(start)
91 }
92
93 val start = offset - 30
94 val end = offset + 30
95 val prefix = if (start <= 0) "" else "....."
96 val suffix = if (end >= length) "" else "....."
97 return prefix + substring(start.coerceAtLeast(0), end.coerceAtMost(length)) + suffix
98 }
99