-
Notifications
You must be signed in to change notification settings - Fork 108
/
SerializationTools.kt
227 lines (203 loc) · 7.98 KB
/
SerializationTools.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amplifyframework.auth.cognito.featuretest.generators
import aws.sdk.kotlin.services.cognitoidentity.model.CognitoIdentityException
import aws.sdk.kotlin.services.cognitoidentityprovider.model.CognitoIdentityProviderException
import aws.smithy.kotlin.runtime.time.Instant
import com.amplifyframework.auth.AuthException
import com.amplifyframework.auth.cognito.featuretest.FeatureTestCase
import com.amplifyframework.auth.cognito.featuretest.serializers.CognitoIdentityExceptionSerializer
import com.amplifyframework.auth.cognito.featuretest.serializers.CognitoIdentityProviderExceptionSerializer
import com.amplifyframework.auth.cognito.featuretest.serializers.deserializeToAuthState
import com.amplifyframework.auth.cognito.featuretest.serializers.serialize
import com.amplifyframework.auth.cognito.options.AWSCognitoAuthSignInOptions
import com.amplifyframework.auth.result.AuthSessionResult
import com.amplifyframework.statemachine.codegen.states.AuthState
import com.google.gson.Gson
import java.io.BufferedWriter
import java.io.File
import java.io.FileOutputStream
import java.io.FileWriter
import kotlin.reflect.KClass
import kotlin.reflect.KVisibility
import kotlin.reflect.full.declaredMemberProperties
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
const val basePath = "aws-auth-cognito/src/test/resources/feature-test"
fun writeFile(json: String, dirName: String, fileName: String) {
val directory = File("$basePath/$dirName")
directory.mkdirs()
val filePath = "${directory.path}/$fileName"
val fileWriter = FileWriter(filePath)
fileWriter.write(json)
fileWriter.close()
println("File written in ${directory.absolutePath}")
}
fun cleanDirectory() {
val directory = File(basePath)
if (directory.exists()) {
directory.deleteRecursively()
}
}
internal fun FeatureTestCase.exportJson() {
val format = Json {
prettyPrint = true
}
val result = format.encodeToString(this)
val dirName = "testsuites/" + api.name.name
val fileName = description.replace(" ", "_").plus(".json")
writeFile(result, dirName, fileName)
println("Json exported:\n $result")
}
internal fun AuthState.exportJson() {
val result = this.serialize()
val reverse = result.deserializeToAuthState()
val dirName = "states"
val fileName = "${authNState?.javaClass?.simpleName}_${authZState?.javaClass?.simpleName}.json"
writeFile(result, dirName, fileName)
println("Json exported:\n $result")
println("Serialized can be reversed = ${reverse.serialize() == result}")
}
/**
* Generates a md file with all the test cases formatted.
*/
internal fun List<FeatureTestCase>.exportToMd() {
val outputStream = FileOutputStream("testSuite.md")
val writer = outputStream.bufferedWriter()
val jsonFormat = Json { prettyPrint = true }
groupBy { it.api.name }
.forEach {
with(writer) {
newLine()
write("# ${it.key.name}") // Header 1
newLine()
it.value.forEach {
newLine()
write("## Case: *${it.description}*")
newLine()
// Preconditions (GIVEN)
write("### Preconditions") // Header 2
newLine()
write("- **Amplify Configuration**: ${it.preConditions.`amplify-configuration`}")
newLine()
write("- **Initial State:** ${it.preConditions.state}")
newLine()
write("- **Mock Responses:** ")
printCodeBlock {
if (it.preConditions.mockedResponses.isEmpty()) "[]" else
jsonFormat.encodeToString(it.preConditions.mockedResponses)
}
// Parameters (WHEN)
write("### Input")
newLine()
write("- **params:**")
printCodeBlock {
jsonFormat.encodeToString(it.api.params)
}
write("- **options:**")
printCodeBlock { jsonFormat.encodeToString(it.api.options) }
// Then
write("### Validations")
newLine()
it.validations.forEach {
printCodeBlock { jsonFormat.encodeToString(it) }
}
}
}
}
writer.flush()
}
private fun BufferedWriter.printCodeBlock(blob: () -> String) {
newLine()
write("```json")
newLine()
write(blob.invoke())
newLine()
write("```")
newLine()
}
/**
* Extension class to convert primitives and collections
* from [https://github.com/Kotlin/kotlinx.serialization/issues/296#issuecomment-1132714147]
*/
fun Map<*, *>.toJsonElement(): JsonElement {
return JsonObject(
mapNotNull {
(it.key as? String ?: return@mapNotNull null) to it.value.toJsonElement()
}.toMap()
)
}
fun Collection<*>.toJsonElement(): JsonElement = JsonArray(mapNotNull { it.toJsonElement() })
fun Any?.toJsonElement(): JsonElement {
return when (this) {
null -> JsonNull
is Map<*, *> -> toJsonElement()
is Collection<*> -> toJsonElement()
is Boolean -> JsonPrimitive(this)
is Number -> JsonPrimitive(this)
is String -> JsonPrimitive(this)
is Instant -> JsonPrimitive(this.epochSeconds)
is AuthException -> toJsonElement()
is AWSCognitoAuthSignInOptions -> toJsonElement()
is CognitoIdentityProviderException -> Json.encodeToJsonElement(
CognitoIdentityProviderExceptionSerializer,
this
)
is AuthSessionResult<*> -> toJsonElement()
is CognitoIdentityException -> Json.encodeToJsonElement(CognitoIdentityExceptionSerializer, this)
else -> gsonBasedSerializer(this)
}
}
fun AuthException.toJsonElement(): JsonElement {
val responseMap = mutableMapOf<String, Any?>(
"errorType" to this::class.simpleName,
"errorMessage" to message,
"recoverySuggestion" to recoverySuggestion,
"cause" to cause
)
return responseMap.toJsonElement()
}
fun AuthSessionResult<*>.toJsonElement(): JsonElement {
return (if (type == AuthSessionResult.Type.SUCCESS) value else error).toJsonElement()
}
/**
* Uses Gson to convert objects which cannot be serialized,
* tries to convert to map of params to vals
*/
fun gsonBasedSerializer(value: Any): JsonElement {
val gson = Gson()
return try {
gson.fromJson(gson.toJson(value).toString(), Map::class.java).toJsonElement()
} catch (ex: Exception) {
reflectionBasedSerializer(value)
}
}
/**
* Final fallback to serialize by using reflection, traversing the object members and converting it to Map.
* Note that this method is similar to what Gson does. But Gson fails when there is name collision in parent and child
* classes.
*/
fun reflectionBasedSerializer(value: Any): JsonElement {
return (value::class as KClass<*>).declaredMemberProperties.filter {
it.visibility == KVisibility.PUBLIC
}.associate {
it.name to it.getter.call(value)
}.toMap().toJsonElement()
}