Skip to content

Commit 0e19c06

Browse files
authored
feat!: use kotlin nullability for null documents instead of explicit subclass (#664)
1 parent 18f0524 commit 0e19c06

File tree

13 files changed

+62
-64
lines changed

13 files changed

+62
-64
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "1a759771-971f-4354-838f-ee7b0364e379",
3+
"type": "feature",
4+
"description": "(breaking) Use kotlin nullability to represent null Documents instead of an explicit subclass."
5+
}

docs/design/document-type.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,16 @@ sealed class Document {
3838
data class Number(val value: kotlin.Number) : Document()
3939
data class String(val value: kotlin.String) : Document()
4040
data class Boolean(val value: kotlin.Boolean) : Document()
41-
data class List(val value: kotlin.collections.List<Document>) :
42-
Document(), kotlin.collections.List<Document> by value
43-
data class Map(val value: kotlin.collections.Map<kotlin.String, Document>) :
44-
Document(), kotlin.collections.Map<kotlin.String, Document> by value
45-
object Null : Document()
41+
data class List(val value: kotlin.collections.List<Document?>) :
42+
Document(), kotlin.collections.List<Document?> by value
43+
data class Map(val value: kotlin.collections.Map<kotlin.String, Document?>) :
44+
Document(), kotlin.collections.Map<kotlin.String, Document?> by value
4645

4746
fun asString(): kotlin.String
4847
fun asStringOrNull(): kotlin.String?
4948
fun asInt(): Int
5049
fun asIntOrNull(): Int?
5150
// etc...
52-
val isNull: kotlin.Boolean
53-
get() = this == Null
5451
}
5552
```
5653

@@ -64,12 +61,14 @@ class DocumentBuilder internal constructor() {
6461
infix fun String.to(value: String?)
6562
infix fun String.to(value: Boolean?)
6663
infix fun String.to(value: Document?)
64+
infix fun String.to(value: Nothing?)
6765

6866
class ListBuilder internal constructor() {
6967
fun add(value: Number?)
7068
fun add(value: String?)
7169
fun add(value: Boolean?)
7270
fun add(value: Document?)
71+
fun add(value: Nothing?)
7372

7473
fun addAll(value: List<Number?>)
7574
fun addAll(value: List<String?>)
@@ -106,4 +105,5 @@ fun main() {
106105
# Revision history
107106

108107
* 5/27/2021 - Initial upload
109-
* 5/22/2022 - Refine/extend structure and use of interface
108+
* 5/22/2022 - Refine/extend structure and use of interface
109+
* 6/16/2022 - Refactor nullability to use kotlin instead of explicit subclass

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ kotlin.native.ignoreDisabledTargets=true
88
kotlin.mpp.hierarchicalStructureSupport=false
99

1010
# SDK
11-
sdkVersion=0.10.3-SNAPSHOT
11+
sdkVersion=0.11.0-SNAPSHOT
1212

1313
# kotlin
1414
kotlinVersion=1.6.21

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smithy/Document.kt

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ sealed class Document {
4242
/**
4343
* Wraps a [kotlin.collections.List].
4444
*/
45-
data class List(val value: kotlin.collections.List<Document>) :
46-
Document(), kotlin.collections.List<Document> by value {
45+
data class List(val value: kotlin.collections.List<Document?>) :
46+
Document(), kotlin.collections.List<Document?> by value {
4747
override fun toString() = value.joinToString(separator = ",", prefix = "[", postfix = "]")
4848
}
4949

5050
/**
5151
* Wraps a [kotlin.collections.Map].
5252
*/
53-
data class Map(val value: kotlin.collections.Map<kotlin.String, Document>) :
54-
Document(), kotlin.collections.Map<kotlin.String, Document> by value {
53+
data class Map(val value: kotlin.collections.Map<kotlin.String, Document?>) :
54+
Document(), kotlin.collections.Map<kotlin.String, Document?> by value {
5555
override fun toString() = value
5656
.entries
5757
.joinToString(
@@ -62,13 +62,6 @@ sealed class Document {
6262
)
6363
}
6464

65-
/**
66-
* Represents a `null` value.
67-
*/
68-
object Null : Document() {
69-
override fun toString() = "null"
70-
}
71-
7265
private fun asNumber(): kotlin.Number = (this as Number).value
7366
private fun asNumberOrNull(): kotlin.Number? = (this as? Number)?.value
7467

@@ -78,11 +71,11 @@ sealed class Document {
7871
fun asBoolean(): kotlin.Boolean = (this as Boolean).value
7972
fun asBooleanOrNull(): kotlin.Boolean? = (this as? Boolean)?.value
8073

81-
fun asList(): kotlin.collections.List<Document> = (this as List).value
82-
fun asListOrNull(): kotlin.collections.List<Document>? = (this as? List)?.value
74+
fun asList(): kotlin.collections.List<Document?> = (this as List).value
75+
fun asListOrNull(): kotlin.collections.List<Document?>? = (this as? List)?.value
8376

84-
fun asMap(): kotlin.collections.Map<kotlin.String, Document> = (this as Map).value
85-
fun asMapOrNull(): kotlin.collections.Map<kotlin.String, Document>? = (this as? Map)?.value
77+
fun asMap(): kotlin.collections.Map<kotlin.String, Document?> = (this as Map).value
78+
fun asMapOrNull(): kotlin.collections.Map<kotlin.String, Document?>? = (this as? Map)?.value
8679

8780
fun asInt(): Int = asNumber().toInt()
8881
fun asIntOrNull(): Int? = asNumberOrNull()?.toInt()
@@ -101,9 +94,6 @@ sealed class Document {
10194

10295
fun asDouble(): Double = asNumber().toDouble()
10396
fun asDoubleOrNull(): Double? = asNumberOrNull()?.toDouble()
104-
105-
val isNull: kotlin.Boolean
106-
get() = this == Null
10797
}
10898

10999
/**
@@ -124,9 +114,9 @@ fun Document(value: Boolean): Document = Document.Boolean(value)
124114
/**
125115
* Construct a [Document] from a [List].
126116
*/
127-
fun Document(value: List<Document>): Document = Document.List(value)
117+
fun Document(value: List<Document?>): Document = Document.List(value)
128118

129119
/**
130120
* Construct a [Document] from a [Map].
131121
*/
132-
fun Document(value: Map<String, Document>): Document = Document.Map(value)
122+
fun Document(value: Map<String, Document?>): Document = Document.Map(value)

runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/smithy/DocumentBuilder.kt

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,46 @@ package aws.smithy.kotlin.runtime.smithy
77
import kotlin.jvm.JvmName
88

99
class DocumentBuilder internal constructor() {
10-
val content: MutableMap<String, Document> = linkedMapOf()
10+
val content: MutableMap<String, Document?> = linkedMapOf()
1111

1212
infix fun String.to(value: Number?) {
13-
require(content[this] == null) { "Key $this is already registered in builder" }
14-
content[this] = if (value != null) Document(value) else Document.Null
13+
require(this !in content) { "Key $this is already registered in builder" }
14+
content[this] = if (value != null) Document(value) else null
1515
}
1616

1717
infix fun String.to(value: String?) {
18-
require(content[this] == null) { "Key $this is already registered in builder" }
19-
content[this] = if (value != null) Document(value) else Document.Null
18+
require(this !in content) { "Key $this is already registered in builder" }
19+
content[this] = if (value != null) Document(value) else null
2020
}
2121

2222
infix fun String.to(value: Boolean?) {
23-
require(content[this] == null) { "Key $this is already registered in builder" }
24-
content[this] = if (value != null) Document(value) else Document.Null
23+
require(this !in content) { "Key $this is already registered in builder" }
24+
content[this] = if (value != null) Document(value) else null
2525
}
2626

2727
infix fun String.to(value: Document?) {
28-
require(content[this] == null) { "Key $this is already registered in builder" }
29-
content[this] = value ?: Document.Null
28+
require(this !in content) { "Key $this is already registered in builder" }
29+
content[this] = value
3030
}
3131

3232
infix fun String.to(@Suppress("UNUSED_PARAMETER") value: Nothing?) {
33-
require(content[this] == null) { "Key $this is already registered in builder" }
34-
content[this] = Document.Null
33+
require(this !in content) { "Key $this is already registered in builder" }
34+
content[this] = null
3535
}
3636

3737
class ListBuilder internal constructor() {
38-
val content: MutableList<Document> = mutableListOf()
38+
val content: MutableList<Document?> = mutableListOf()
3939

4040
fun add(value: Number?): Boolean =
41-
content.add(if (value != null) Document(value) else Document.Null)
41+
content.add(if (value != null) Document(value) else null)
4242
fun add(value: String?): Boolean =
43-
content.add(if (value != null) Document(value) else Document.Null)
43+
content.add(if (value != null) Document(value) else null)
4444
fun add(value: Boolean?): Boolean =
45-
content.add(if (value != null) Document(value) else Document.Null)
45+
content.add(if (value != null) Document(value) else null)
4646
fun add(value: Document?): Boolean =
47-
content.add(value ?: Document.Null)
47+
content.add(value)
4848
fun add(@Suppress("UNUSED_PARAMETER") value: Nothing?): Boolean =
49-
content.add(Document.Null)
49+
content.add(null)
5050

5151
@JvmName("addAllNumbers") fun addAll(value: List<Number?>) = value.forEach(::add)
5252
@JvmName("addAllStrings") fun addAll(value: List<String?>) = value.forEach(::add)

runtime/runtime-core/common/test/aws/smithy/kotlin/runtime/DocumentBuilderTest.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ class DocumentBuilderTest {
1919
add(12)
2020
add(true)
2121
add("blah")
22-
add(Document.Null)
22+
add(null)
2323
addAll(listOf(9, 10, 12))
2424
}
25-
"qux" to Document.Null
25+
"qux" to null
2626
}
2727

2828
val expected = """{"foo":1,"baz":[202,12,true,"blah",null,9,10,12],"qux":null}"""

runtime/serde/common/src/aws/smithy/kotlin/runtime/serde/Serializer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ interface StructSerializer : PrimitiveSerializer {
137137
* @param descriptor
138138
* @param value
139139
*/
140-
fun field(descriptor: SdkFieldDescriptor, value: Document)
140+
fun field(descriptor: SdkFieldDescriptor, value: Document?)
141141

142142
/**
143143
* Writes the field name given in the descriptor, and then

runtime/serde/serde-form-url/common/src/aws/smithy/kotlin/runtime/serde/formurl/FormUrlSerializer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private class FormUrlStructSerializer(
131131
serializeInstant(value, format)
132132
}
133133

134-
override fun field(descriptor: SdkFieldDescriptor, value: Document) {
134+
override fun field(descriptor: SdkFieldDescriptor, value: Document?) {
135135
throw SerializationException(
136136
"cannot serialize field ${descriptor.serialName}; Document type is not supported by form-url encoding"
137137
)

runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonDeserializer.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,27 +64,30 @@ class JsonDeserializer(payload: ByteArray) : Deserializer, Deserializer.ElementI
6464
}
6565

6666
override fun deserializeDocument(): Document =
67+
checkNotNull(deserializeDocumentImpl()) { "expected non-null document field" }
68+
69+
private fun deserializeDocumentImpl(): Document? =
6770
when (val token = reader.peek()) {
6871
is JsonToken.Number -> Document(deserializeNumber())
6972
is JsonToken.String -> Document(deserializeString())
7073
is JsonToken.Bool -> Document(deserializeBoolean())
7174
JsonToken.Null -> {
7275
reader.nextToken()
73-
Document.Null
76+
null
7477
}
7578
JsonToken.BeginArray ->
7679
deserializeList(SdkFieldDescriptor(SerialKind.Document)) {
77-
val values = mutableListOf<Document>()
80+
val values = mutableListOf<Document?>()
7881
while (hasNextElement()) {
79-
values.add(deserializeDocument())
82+
values.add(deserializeDocumentImpl())
8083
}
8184
Document.List(values)
8285
}
8386
JsonToken.BeginObject ->
8487
deserializeMap(SdkFieldDescriptor(SerialKind.Document)) {
85-
val values = mutableMapOf<String, Document>()
88+
val values = mutableMapOf<String, Document?>()
8689
while (hasNextEntry()) {
87-
values[key()] = deserializeDocument()
90+
values[key()] = deserializeDocumentImpl()
8891
}
8992
Document.Map(values)
9093
}

runtime/serde/serde-json/common/src/aws/smithy/kotlin/runtime/serde/json/JsonSerializer.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class JsonSerializer : Serializer, ListSerializer, MapSerializer, StructSerializ
102102
serializeInstant(value, format)
103103
}
104104

105-
override fun field(descriptor: SdkFieldDescriptor, value: Document) {
105+
override fun field(descriptor: SdkFieldDescriptor, value: Document?) {
106106
jsonWriter.writeName(descriptor.serialName)
107107
serializeDocument(value)
108108
}
@@ -259,12 +259,12 @@ class JsonSerializer : Serializer, ListSerializer, MapSerializer, StructSerializ
259259
}
260260
}
261261

262-
fun serializeDocument(value: Document) {
262+
fun serializeDocument(value: Document?) {
263263
when (value) {
264264
is Document.Number -> jsonWriter.writeValue(value.value)
265265
is Document.String -> jsonWriter.writeValue(value.value)
266266
is Document.Boolean -> jsonWriter.writeValue(value.value)
267-
is Document.Null -> jsonWriter.writeNull()
267+
null -> jsonWriter.writeNull()
268268
is Document.List -> {
269269
jsonWriter.beginArray()
270270
value.value.forEach(::serializeDocument)

0 commit comments

Comments
 (0)