Skip to content

Commit e2d6a62

Browse files
authored
Add MalformedEncodingException type (#217)
1 parent b67a2ea commit e2d6a62

11 files changed

Lines changed: 97 additions & 57 deletions

File tree

library/base16/src/commonMain/kotlin/io/matthewnelson/encoding/base16/Base16.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import io.matthewnelson.encoding.base16.internal.build
2121
import io.matthewnelson.encoding.core.Decoder
2222
import io.matthewnelson.encoding.core.Encoder
2323
import io.matthewnelson.encoding.core.EncoderDecoder
24-
import io.matthewnelson.encoding.core.EncodingException
24+
import io.matthewnelson.encoding.core.MalformedEncodingException
2525
import io.matthewnelson.encoding.core.util.DecoderInput
2626
import io.matthewnelson.encoding.core.util.FeedBuffer
2727
import kotlin.contracts.ExperimentalContracts
@@ -102,7 +102,7 @@ public class Base16: EncoderDecoder<Base16.Config> {
102102
* allowed but ignored) during decoding operations. This is non-compliant with
103103
* `RFC 4648`.
104104
*
105-
* If `false`, an [EncodingException] will be thrown.
105+
* If `false`, a [MalformedEncodingException] will be thrown.
106106
* */
107107
public fun isLenient(enable: Boolean): Builder = apply { _isLenient = enable }
108108

@@ -367,7 +367,7 @@ public class Base16: EncoderDecoder<Base16.Config> {
367367
diff += if (gea + lef == 2) -87 else 0
368368

369369
if (diff == 0) {
370-
throw EncodingException("Char[${input}] is not a valid Base16 character")
370+
throw MalformedEncodingException("Char[${input}] is not a valid $NAME character")
371371
}
372372

373373
if (!hasBuffered) {

library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/Base32.kt

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import io.matthewnelson.encoding.core.Decoder
2626
import io.matthewnelson.encoding.core.Encoder
2727
import io.matthewnelson.encoding.core.EncoderDecoder
2828
import io.matthewnelson.encoding.core.EncodingException
29+
import io.matthewnelson.encoding.core.MalformedEncodingException
2930
import io.matthewnelson.encoding.core.util.DecoderInput
3031
import io.matthewnelson.encoding.core.util.FeedBuffer
3132
import io.matthewnelson.encoding.core.util.LineBreakOutFeed
@@ -122,7 +123,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
122123
* allowed but ignored) during decoding operations. This is non-compliant with
123124
* the Crockford spec.
124125
*
125-
* If `false`, an [EncodingException] will be thrown.
126+
* If `false`, a [MalformedEncodingException] will be thrown.
126127
* */
127128
public fun isLenient(enable: Boolean): Builder = apply { _isLenient = enable }
128129

@@ -260,7 +261,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
260261
} else {
261262
"Missing check symbol. Expected[$checkSymbol]"
262263
}
263-
throw EncodingException(msg)
264+
throw MalformedEncodingException(msg)
264265
} else {
265266
outSize--
266267
}
@@ -525,7 +526,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
525526
* allowed but ignored) during decoding operations. This is non-compliant with
526527
* `RFC 4648`.
527528
*
528-
* If `false`, an [EncodingException] will be thrown.
529+
* If `false`, a [MalformedEncodingException] will be thrown.
529530
* */
530531
public fun isLenient(enable: Boolean): Builder = apply { _isLenient = enable }
531532

@@ -866,7 +867,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
866867
* allowed but ignored) during decoding operations. This is non-compliant with
867868
* `RFC 4648`.
868869
*
869-
* If `false`, an [EncodingException] will be thrown.
870+
* If `false`, a [MalformedEncodingException] will be thrown.
870871
* */
871872
public fun isLenient(enable: Boolean): Builder = apply { _isLenient = enable }
872873

@@ -1143,7 +1144,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
11431144
val code = input.code
11441145
val diff = code.decodeDiff()
11451146
if (diff == 0) {
1146-
throw Diff0EncodingException("Char[$input] is not a valid Base32 character")
1147+
throw MalformedEncodingException("Char[$input] is not a valid ${name()} character")
11471148
}
11481149

11491150
if (iBuf < 7) {
@@ -1463,7 +1464,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
14631464
if (isCheckSymbolSet) {
14641465
// If the set checkSymbol was not intended, it's only valid as the
14651466
// very last character and the previous update call was invalid.
1466-
throw EncodingException("CheckSymbol[${_config.checkSymbol}] was set")
1467+
throw MalformedEncodingException("CheckSymbol[${_config.checkSymbol}] was set")
14671468
}
14681469

14691470
// Crockford allows for insertion of hyphens, which are to be ignored.
@@ -1472,7 +1473,7 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
14721473
try {
14731474
super.consumeProtected(input)
14741475
hadInput = true
1475-
} catch (e: Diff0EncodingException) {
1476+
} catch (e: MalformedEncodingException) {
14761477
// decodeDiff returned 0. See if it's a check symbol.
14771478
if (!input.isCheckSymbol()) throw e
14781479

@@ -1483,14 +1484,14 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
14831484
}
14841485

14851486
// Have the wrong check symbol
1486-
throw EncodingException("Char[$input] IS a check symbol, but did not match config's CheckSymbol[${_config.checkSymbol}]", e)
1487+
throw MalformedEncodingException("Char[$input] IS a check symbol, but did not match config's CheckSymbol[${_config.checkSymbol}]", e)
14871488
}
14881489
}
14891490

14901491
override fun doFinalProtected() {
14911492
if (isClosed() || _config.finalizeWhenFlushed) {
14921493
if (hadInput && _config.checkSymbol != null && !isCheckSymbolSet) {
1493-
throw EncodingException("Missing check symbol. Expected[${_config.checkSymbol}]")
1494+
throw MalformedEncodingException("Missing check symbol. Expected[${_config.checkSymbol}]")
14941495
}
14951496
isCheckSymbolSet = false
14961497
hadInput = false
@@ -1559,10 +1560,6 @@ public sealed class Base32<C: EncoderDecoder.Config>(config: C): EncoderDecoder<
15591560
}
15601561
}
15611562

1562-
// Thrown by AbstractDecoderFeed when decodeDiff returns 0.
1563-
// Is for Crockford in order to check input for a check symbol.
1564-
private class Diff0EncodingException(message: String): EncodingException(message)
1565-
15661563
private class HyphenOutFeed(
15671564
private val interval: Byte,
15681565
private val out: Encoder.OutFeed,

library/base32/src/commonMain/kotlin/io/matthewnelson/encoding/base32/internal/-Config.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package io.matthewnelson.encoding.base32.internal
1919

2020
import io.matthewnelson.encoding.base32.Base32
2121
import io.matthewnelson.encoding.core.EncoderDecoder
22+
import io.matthewnelson.encoding.core.EncodingSizeException
2223

2324
internal inline fun ((Boolean, Boolean, Byte, Char?, Boolean, Boolean) -> Base32.Crockford.Config).build(
2425
b: Base32.Crockford.Builder,
@@ -110,6 +111,7 @@ internal inline fun EncoderDecoder.Config.Companion.decodeOutMaxSize32(encodedSi
110111
return (encodedSize.toLong() * 5L / 8L).toInt()
111112
}
112113

114+
@Throws(EncodingSizeException::class)
113115
internal inline fun EncoderDecoder.Config.Companion.encodeOutSize64(unEncodedSize: Long, willBePadded: Boolean): Long {
114116
if (unEncodedSize > MAX_UNENCODED_SIZE) {
115117
throw outSizeExceedsMaxEncodingSizeException(unEncodedSize, Long.MAX_VALUE)

library/base64/src/commonMain/kotlin/io/matthewnelson/encoding/base64/Base64.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import io.matthewnelson.encoding.base64.internal.build
2121
import io.matthewnelson.encoding.core.Decoder
2222
import io.matthewnelson.encoding.core.Encoder
2323
import io.matthewnelson.encoding.core.EncoderDecoder
24-
import io.matthewnelson.encoding.core.EncodingException
24+
import io.matthewnelson.encoding.core.MalformedEncodingException
2525
import io.matthewnelson.encoding.core.util.DecoderInput
2626
import io.matthewnelson.encoding.core.util.FeedBuffer
2727
import kotlin.contracts.ExperimentalContracts
@@ -142,7 +142,7 @@ public class Base64: EncoderDecoder<Base64.Config> {
142142
* allowed but ignored) during decoding operations. This is non-compliant with
143143
* `RFC 4648`.
144144
*
145-
* If `false`, an [EncodingException] will be thrown.
145+
* If `false`, a [MalformedEncodingException] will be thrown.
146146
* */
147147
public fun isLenient(enable: Boolean): Builder = apply { _isLenient = enable }
148148

@@ -456,7 +456,7 @@ public class Base64: EncoderDecoder<Base64.Config> {
456456
diff += if (eqSla + eqUSc == 1) k else 0
457457

458458
if (diff == 0) {
459-
throw EncodingException("Char[$input] is not a valid Base64 character")
459+
throw MalformedEncodingException("Char[$input] is not a valid $NAME character")
460460
}
461461

462462
if (iBuf < 3) {

library/core/api/core.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ public class io/matthewnelson/encoding/core/EncodingSizeException : io/matthewne
180180
public abstract interface annotation class io/matthewnelson/encoding/core/ExperimentalEncodingApi : java/lang/annotation/Annotation {
181181
}
182182

183+
public class io/matthewnelson/encoding/core/MalformedEncodingException : io/matthewnelson/encoding/core/EncodingException {
184+
public fun <init> (Ljava/lang/String;)V
185+
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
186+
}
187+
183188
public final class io/matthewnelson/encoding/core/_FeedKt {
184189
public static final fun use (Lio/matthewnelson/encoding/core/EncoderDecoder$Feed;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object;
185190
public static final fun useFinally (Lio/matthewnelson/encoding/core/EncoderDecoder$Feed;Ljava/lang/Throwable;)V

library/core/api/core.klib.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,11 @@ open class io.matthewnelson.encoding.core/EncodingSizeException : io.matthewnels
175175
constructor <init>(kotlin/String, kotlin/Throwable?) // io.matthewnelson.encoding.core/EncodingSizeException.<init>|<init>(kotlin.String;kotlin.Throwable?){}[0]
176176
}
177177

178+
open class io.matthewnelson.encoding.core/MalformedEncodingException : io.matthewnelson.encoding.core/EncodingException { // io.matthewnelson.encoding.core/MalformedEncodingException|null[0]
179+
constructor <init>(kotlin/String) // io.matthewnelson.encoding.core/MalformedEncodingException.<init>|<init>(kotlin.String){}[0]
180+
constructor <init>(kotlin/String, kotlin/Throwable?) // io.matthewnelson.encoding.core/MalformedEncodingException.<init>|<init>(kotlin.String;kotlin.Throwable?){}[0]
181+
}
182+
178183
sealed class <#A: io.matthewnelson.encoding.core/EncoderDecoder.Config> io.matthewnelson.encoding.core/Decoder { // io.matthewnelson.encoding.core/Decoder|null[0]
179184
final val config // io.matthewnelson.encoding.core/Decoder.config|{}config[0]
180185
final fun <get-config>(): #A // io.matthewnelson.encoding.core/Decoder.config.<get-config>|<get-config>(){}[0]

library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Decoder.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ public sealed class Decoder<C: EncoderDecoder.Config>(public val config: C) {
118118
if (config.isLenient) {
119119
return
120120
} else {
121-
throw EncodingException("Spaces and new lines are forbidden when isLenient[false]")
121+
throw MalformedEncodingException("Spaces and new lines are forbidden when isLenient[false]")
122122
}
123123
}
124124

@@ -132,7 +132,7 @@ public sealed class Decoder<C: EncoderDecoder.Config>(public val config: C) {
132132
if (_isPaddingSet) {
133133
// Trying to decode something else that is not
134134
// a space, new line, or padding. Fail.
135-
throw EncodingException(
135+
throw MalformedEncodingException(
136136
"Padding[${config.paddingChar}] was previously passed, " +
137137
"but decoding operations are still being attempted."
138138
)

library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/EncoderDecoder.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
5151

5252
/**
5353
* If `true`, the characters ('\n', '\r', ' ', '\t') will be skipped over (i.e.
54-
* allowed but ignored) during decoding operations. If `false`, an [EncodingException]
54+
* allowed but ignored) during decoding operations. If `false`, a [MalformedEncodingException]
5555
* will be thrown when those characters are encountered. If `null`, those characters
5656
* are passed along to the [Decoder.Feed] implementation as input.
5757
* */
@@ -85,7 +85,8 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
8585
/**
8686
* The character that is used when padding encoded output. This is used by [Decoder.Feed]
8787
* to mark input as "completing" such that further non-padding input can be exceptionally
88-
* rejected. If the encoding specification does not use padding, `null` may be specified.
88+
* rejected with a [MalformedEncodingException]. If the encoding specification does not
89+
* use padding, `null` may be specified.
8990
*
9091
* **NOTE:** [Decoder.Feed] will not pass along padding characters to the [Decoder.Feed]
9192
* implementation; they will be automatically dropped. If this is undesirable, consider
@@ -312,7 +313,7 @@ public abstract class EncoderDecoder<C: EncoderDecoder.Config>(config: C): Encod
312313
lastRelevantChar--
313314
continue
314315
} else {
315-
throw EncodingException("Spaces and new lines are forbidden when isLenient[false]")
316+
throw MalformedEncodingException("Spaces and new lines are forbidden when isLenient[false]")
316317
}
317318
}
318319

library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/Exceptions.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
**/
1616
package io.matthewnelson.encoding.core
1717

18+
import io.matthewnelson.encoding.core.util.FeedBuffer
19+
20+
/**
21+
* The `EncodingException` class is a generic encoding exception class that provide
22+
* type safety for all the encoding related exception classes that extend from it.
23+
*
24+
* @see [EncodingSizeException]
25+
* @see [MalformedEncodingException]
26+
* */
1827
public open class EncodingException: RuntimeException {
1928

2029
final override val message: String
@@ -25,7 +34,24 @@ public open class EncodingException: RuntimeException {
2534
}
2635
}
2736

37+
/**
38+
* This exception is thrown to indicate that an [EncoderDecoder.Config] pre-calculation
39+
* step for an operation failed.
40+
*
41+
* @see [EncoderDecoder.Config.outSizeExceedsMaxEncodingSizeException]
42+
* */
2843
public open class EncodingSizeException: EncodingException {
2944
public constructor(message: String): this(message, null)
3045
public constructor(message: String, cause: Throwable?): super(message, cause)
3146
}
47+
48+
/**
49+
* This exception is thrown to indicate an encoding/decoding operation failed due to
50+
* malformed input, such as an invalid character or byte sequence.
51+
*
52+
* @see [FeedBuffer.truncatedInputEncodingException]
53+
* */
54+
public open class MalformedEncodingException: EncodingException {
55+
public constructor(message: String): this(message, null)
56+
public constructor(message: String, cause: Throwable?): super(message, cause)
57+
}

library/core/src/commonMain/kotlin/io/matthewnelson/encoding/core/util/FeedBuffer.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package io.matthewnelson.encoding.core.util
2020
import io.matthewnelson.encoding.core.Decoder
2121
import io.matthewnelson.encoding.core.Encoder
2222
import io.matthewnelson.encoding.core.EncodingException
23+
import io.matthewnelson.encoding.core.MalformedEncodingException
2324
import kotlin.jvm.JvmField
2425
import kotlin.jvm.JvmName
2526
import kotlin.jvm.JvmStatic
@@ -29,6 +30,9 @@ import kotlin.jvm.JvmStatic
2930
* buffer their input until ready to output data via their
3031
* supplied [Decoder.OutFeed]/[Encoder.OutFeed].
3132
*
33+
* **NOTE:** This adds significant overhead to operations.
34+
* It is far better, performance wise, to not use.
35+
*
3236
* @see [Flush]
3337
* @see [Finalize]
3438
* @see [truncatedInputEncodingException]
@@ -104,12 +108,12 @@ constructor(
104108
public companion object {
105109

106110
/**
107-
* Helper for generating a standard [EncodingException] when
111+
* Helper for generating a standard [MalformedEncodingException] when
108112
* an illegal modulus is encountered while decoding.
109113
* */
110114
@JvmStatic
111115
public fun truncatedInputEncodingException(modulus: Int): EncodingException {
112-
return EncodingException("Truncated input. Illegal Modulus[$modulus]")
116+
return MalformedEncodingException("Truncated input. Illegal Modulus[$modulus]")
113117
}
114118
}
115119
}

0 commit comments

Comments
 (0)