1 /*
2  * Copyright 2017-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.serialization
6 
7 import kotlinx.serialization.encoding.*
8 import kotlinx.serialization.descriptors.*
9 
10 /**
11  * A generic exception indicating the problem in serialization or deserialization process.
12  *
13  * This is a generic exception type that can be thrown during problems at any stage of the serialization,
14  * including encoding, decoding, serialization, deserialization, and validation.
15  * [SerialFormat] implementors should throw subclasses of this exception at any unexpected event,
16  * whether it is a malformed input or unsupported class layout.
17  *
18  * [SerializationException] is a subclass of [IllegalArgumentException] for the sake of consistency and user-defined validation:
19  * Any serialization exception is triggered by the illegal input, whether
20  * it is a serializer that does not support specific structure or an invalid input.
21  *
22  * It is also an established pattern to validate input in user's classes in the following manner:
23  * ```
24  * @Serializable
25  * class Foo(...) {
26  *     init {
27  *         required(age > 0) { ... }
28  *         require(name.isNotBlank()) { ... }
29  *     }
30  * }
31  * ```
32  * While clearly being serialization error (when compromised data was deserialized),
33  * Kotlin way is to throw `IllegalArgumentException` here instead of using library-specific `SerializationException`.
34  *
35  * For general "catch-all" patterns around deserialization of potentially
36  * untrusted/invalid/corrupted data it is recommended to catch `IllegalArgumentException` type
37  * to avoid catching irrelevant to serialization errors such as `OutOfMemoryError` or domain-specific ones.
38  */
39 public open class SerializationException : IllegalArgumentException {
40 
41     /**
42      * Creates an instance of [SerializationException] without any details.
43      */
44     public constructor()
45 
46     /**
47      * Creates an instance of [SerializationException] with the specified detail [message].
48      */
49     public constructor(message: String?) : super(message)
50 
51     /**
52      * Creates an instance of [SerializationException] with the specified detail [message], and the given [cause].
53      */
54     public constructor(message: String?, cause: Throwable?) : super(message, cause)
55 
56     /**
57      * Creates an instance of [SerializationException] with the specified [cause].
58      */
59     public constructor(cause: Throwable?) : super(cause)
60 }
61 
62 /**
63  * Thrown when [KSerializer] did not receive a non-optional property from [CompositeDecoder] and [CompositeDecoder.decodeElementIndex]
64  * had already returned [CompositeDecoder.DECODE_DONE].
65  *
66  * [MissingFieldException] is thrown on missing field from all [auto-generated][Serializable] serializers and it
67  * is recommended to throw this exception from user-defined serializers.
68  *
69  * [MissingFieldException] is constructed from the following properties:
70  * - [missingFields] -- fields that were required for the deserialization but have not been found.
71  *   They are always non-empty and their names match the corresponding names in [SerialDescriptor.elementNames]
72  * - Optional `serialName` -- serial name of the enclosing class that failed to get deserialized.
73  *   Matches the corresponding [SerialDescriptor.serialName].
74  *
75  * @see SerializationException
76  * @see KSerializer
77  */
78 @ExperimentalSerializationApi
79 public class MissingFieldException(
80     missingFields: List<String>, message: String?, cause: Throwable?
81 ) : SerializationException(message, cause) {
82 
83     /**
84      * List of fields that were required but not found during deserialization.
85      * Contains at least one element.
86      */
87     public val missingFields: List<String> = missingFields
88 
89     /**
90      * Creates an instance of [MissingFieldException] for the given [missingFields] and [serialName] of
91      * the corresponding serializer.
92      */
93     public constructor(
94         missingFields: List<String>,
95         serialName: String
96     ) : this(
97         missingFields,
98         if (missingFields.size == 1) "Field '${missingFields[0]}' is required for type with serial name '$serialName', but it was missing"
99         else "Fields $missingFields are required for type with serial name '$serialName', but they were missing",
100         null
101     )
102 
103     /**
104      * Creates an instance of [MissingFieldException] for the given [missingField] and [serialName] of
105      * the corresponding serializer.
106      */
107     public constructor(
108         missingField: String,
109         serialName: String
110     ) : this(
111         listOf(missingField),
112         "Field '$missingField' is required for type with serial name '$serialName', but it was missing",
113         null
114     )
115 
116     @PublishedApi // Constructor used by the generated serializers
117     internal constructor(missingField: String) : this(
118         listOf(missingField),
119         "Field '$missingField' is required, but it was missing",
120         null
121     )
122 }
123 
124 /**
125  * Thrown when [KSerializer] received unknown property index from [CompositeDecoder.decodeElementIndex].
126  *
127  * This exception means that data schema has changed in backwards-incompatible way.
128  */
129 @PublishedApi
130 internal class UnknownFieldException
131 // This constructor is used by coroutines exception recovery
132 internal constructor(message: String?) : SerializationException(message) {
133     // This constructor is used by the generated serializers
134     constructor(index: Int) : this("An unknown field for index $index")
135 }
136