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