Skip to content

Commit 761f59d

Browse files
committed
fix: allow to deserialize and serialize multi-resource json docs (#855)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent 899bcc2 commit 761f59d

File tree

2 files changed

+97
-55
lines changed

2 files changed

+97
-55
lines changed

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/EditorResourceSerialization.kt

+77-37
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
******************************************************************************/
1111
package com.redhat.devtools.intellij.kubernetes.editor
1212

13+
import com.fasterxml.jackson.databind.ObjectMapper
14+
import com.fasterxml.jackson.databind.node.ArrayNode
1315
import com.intellij.json.JsonFileType
1416
import com.intellij.openapi.fileTypes.FileType
1517
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
@@ -22,7 +24,6 @@ import org.jetbrains.yaml.YAMLFileType
2224
object EditorResourceSerialization {
2325

2426
const val RESOURCE_SEPARATOR_YAML = "\n---"
25-
private const val RESOURCE_SEPARATOR_JSON = ",\n"
2627

2728
/**
2829
* Returns a list of [HasMetadata] for a given yaml or json string.
@@ -41,60 +42,99 @@ object EditorResourceSerialization {
4142
* @see JsonFileType.INSTANCE
4243
*/
4344
fun deserialize(jsonYaml: String?, fileType: FileType?, currentNamespace: String?): List<HasMetadata> {
44-
return if (jsonYaml == null
45-
|| !isSupported(fileType)) {
46-
emptyList()
47-
} else {
48-
val resources = jsonYaml
49-
.split(RESOURCE_SEPARATOR_YAML)
50-
.filter { jsonYaml -> jsonYaml.isNotBlank() }
51-
if (resources.size > 1
52-
&& YAMLFileType.YML != fileType) {
53-
throw ResourceException(
54-
"${fileType?.name ?: "File type"} is not supported for multi-resource documents. Only ${YAMLFileType.YML.name} is.")
45+
return try {
46+
when {
47+
jsonYaml == null ->
48+
emptyList()
49+
50+
YAMLFileType.YML == fileType ->
51+
yaml2Resources(jsonYaml, currentNamespace)
52+
53+
JsonFileType.INSTANCE == fileType ->
54+
json2Resources(jsonYaml, currentNamespace)
55+
56+
else ->
57+
emptyList()
5558
}
56-
try {
57-
resources
58-
.map { jsonYaml ->
59-
setMissingNamespace(currentNamespace, createResource<GenericKubernetesResource>(jsonYaml))
59+
} catch (e: RuntimeException) {
60+
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
61+
}
62+
}
63+
64+
private fun yaml2Resources(yaml: String, currentNamespace: String?): List<HasMetadata> {
65+
val resources = yaml
66+
.split(RESOURCE_SEPARATOR_YAML)
67+
.filter { yaml ->
68+
yaml.isNotBlank()
69+
}
70+
return resources
71+
.map { yaml ->
72+
setMissingNamespace(currentNamespace, createResource<GenericKubernetesResource>(yaml))
73+
}
74+
.toList()
75+
}
76+
77+
private fun json2Resources(json: String?, currentNamespace: String?): List<HasMetadata> {
78+
val mapper = ObjectMapper()
79+
val rootNode = mapper.readTree(json)
80+
return when {
81+
rootNode.isArray ->
82+
(rootNode as ArrayNode)
83+
.mapNotNull { node ->
84+
setMissingNamespace(currentNamespace, mapper.treeToValue(node, GenericKubernetesResource::class.java))
6085
}
6186
.toList()
62-
} catch (e: RuntimeException) {
63-
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
64-
}
87+
rootNode.isObject ->
88+
listOf(
89+
setMissingNamespace(currentNamespace,
90+
mapper.treeToValue(rootNode, GenericKubernetesResource::class.java)
91+
)
92+
)
93+
else ->
94+
emptyList()
6595
}
6696
}
6797

6898
private fun setMissingNamespace(namespace: String?, resource: HasMetadata): HasMetadata {
69-
if (resource.metadata.namespace.isNullOrEmpty()
99+
if (resource.metadata != null
100+
&& resource.metadata.namespace.isNullOrEmpty()
70101
&& namespace != null) {
71102
resource.metadata.namespace = namespace
72103
}
73104
return resource
74105
}
75106

76107
fun serialize(resources: List<HasMetadata>, fileType: FileType?): String? {
77-
if (fileType == null) {
78-
return null
79-
}
80-
if (resources.size >= 2
81-
&& fileType != YAMLFileType.YML) {
82-
throw UnsupportedOperationException(
83-
"${fileType.name} is not supported for multi-resource documents. Only ${YAMLFileType.YML.name} is.")
108+
return try {
109+
when {
110+
fileType == null ->
111+
null
112+
113+
YAMLFileType.YML == fileType ->
114+
resources2yaml(resources)
115+
116+
JsonFileType.INSTANCE == fileType ->
117+
resources2json(resources)
118+
119+
else ->
120+
""
121+
}
122+
} catch (e: RuntimeException) {
123+
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
84124
}
85-
return resources
86-
.mapNotNull { resource -> serialize(resource, fileType) }
87-
.joinToString("\n")
125+
}
88126

127+
private fun resources2yaml(resources: List<HasMetadata>): String {
128+
return resources.joinToString("\n") { resource ->
129+
Serialization.asYaml(resource).trim()
130+
}
89131
}
90132

91-
private fun serialize(resource: HasMetadata, fileType: FileType): String? {
92-
return when(fileType) {
93-
YAMLFileType.YML ->
94-
Serialization.asYaml(resource).trim()
95-
JsonFileType.INSTANCE ->
96-
Serialization.asJson(resource).trim()
97-
else -> null
133+
private fun resources2json(resources: List<HasMetadata>): String {
134+
return if (resources.size == 1) {
135+
Serialization.asJson(resources.first()).trim()
136+
} else {
137+
Serialization.asJson(resources).trim()
98138
}
99139
}
100140

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/editor/EditorResourceSerializationTest.kt

+20-18
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import com.intellij.json.JsonFileType
1414
import com.intellij.openapi.fileTypes.PlainTextFileType
1515
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.resource
1616
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
17+
import io.fabric8.kubernetes.api.model.HasMetadata
1718
import io.fabric8.kubernetes.api.model.Pod
1819
import io.fabric8.kubernetes.client.utils.Serialization
1920
import org.assertj.core.api.Assertions.assertThat
2021
import org.assertj.core.api.Assertions.assertThatThrownBy
22+
import org.assertj.core.api.Assertions.tuple
2123
import org.jetbrains.yaml.YAMLFileType
2224
import org.junit.Test
2325

@@ -57,19 +59,20 @@ class EditorResourceSerializationTest {
5759
}
5860

5961
@Test
60-
fun `#deserialize throws if given multiple json resources`() {
62+
fun `#deserialize returns list of resources if given multiple JSON resources`() {
6163
// given
6264
val json = """
63-
{"apiVersion": "v1", "kind": "Pod"}
64-
---
65-
{"apiVersion": "v1", "kind": "Service"}
65+
[
66+
{"apiVersion": "v1", "kind": "Pod"},
67+
{"apiVersion": "v1", "kind": "Service"}
68+
]
6669
""".trimIndent()
67-
68-
assertThatThrownBy {
69-
// when
70-
EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, null)
71-
// then
72-
}.isInstanceOf(ResourceException::class.java)
70+
val deserialized = EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, null)
71+
assertThat(deserialized)
72+
.extracting(HasMetadata::getKind, HasMetadata::getApiVersion)
73+
.containsExactlyInAnyOrder(
74+
tuple("Pod", "v1"),
75+
tuple("Service", "v1"))
7376
}
7477

7578
@Test
@@ -203,17 +206,17 @@ class EditorResourceSerializationTest {
203206
}
204207

205208
@Test
206-
fun `#serialize throws if given multiple resources and non-YAML file type`() {
209+
fun `#serialize returns json array for given multiple resources and JSON file type`() {
207210
// given
208211
val resources = listOf(
209212
resource<Pod>("darth vader"),
210213
resource<Pod>("emperor")
211214
)
212-
assertThatThrownBy {
213-
// when
214-
EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
215-
// then
216-
}.isInstanceOf(UnsupportedOperationException::class.java)
215+
val expected = Serialization.asJson(resources).trim()
216+
// when
217+
val serialized = EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
218+
// then
219+
assertThat(serialized).isEqualTo(expected)
217220
}
218221

219222
@Test
@@ -259,7 +262,7 @@ class EditorResourceSerializationTest {
259262
}
260263

261264
@Test
262-
fun `#serialize returns null if given unsupported file type`() {
265+
fun `#serialize returns '' if given unsupported file type`() {
263266
// given
264267
val resource = resource<Pod>("leia")
265268
// when
@@ -268,5 +271,4 @@ class EditorResourceSerializationTest {
268271
assertThat(serialized)
269272
.isEqualTo("")
270273
}
271-
272274
}

0 commit comments

Comments
 (0)