Skip to content

Commit afab308

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

File tree

2 files changed

+99
-55
lines changed

2 files changed

+99
-55
lines changed

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

+78-37
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,21 @@
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
1618
import com.redhat.devtools.intellij.kubernetes.model.util.createResource
1719
import io.fabric8.kubernetes.api.model.GenericKubernetesResource
1820
import io.fabric8.kubernetes.api.model.HasMetadata
21+
import io.fabric8.kubernetes.api.model.KubernetesListBuilder
1922
import io.fabric8.kubernetes.client.utils.Serialization
2023
import org.jetbrains.yaml.YAMLFileType
2124

2225
object EditorResourceSerialization {
2326

2427
const val RESOURCE_SEPARATOR_YAML = "\n---"
25-
private const val RESOURCE_SEPARATOR_JSON = ",\n"
2628

2729
/**
2830
* Returns a list of [HasMetadata] for a given yaml or json string.
@@ -41,60 +43,99 @@ object EditorResourceSerialization {
4143
* @see JsonFileType.INSTANCE
4244
*/
4345
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.")
46+
return try {
47+
when {
48+
jsonYaml == null ->
49+
emptyList()
50+
51+
YAMLFileType.YML == fileType ->
52+
yaml2Resources(jsonYaml, currentNamespace)
53+
54+
JsonFileType.INSTANCE == fileType ->
55+
json2Resources(jsonYaml, currentNamespace)
56+
57+
else ->
58+
emptyList()
5559
}
56-
try {
57-
resources
58-
.map { jsonYaml ->
59-
setMissingNamespace(currentNamespace, createResource<GenericKubernetesResource>(jsonYaml))
60+
} catch (e: RuntimeException) {
61+
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
62+
}
63+
}
64+
65+
private fun yaml2Resources(yaml: String, currentNamespace: String?): List<HasMetadata> {
66+
val resources = yaml
67+
.split(RESOURCE_SEPARATOR_YAML)
68+
.filter { yaml ->
69+
yaml.isNotBlank()
70+
}
71+
return resources
72+
.map { yaml ->
73+
setMissingNamespace(currentNamespace, createResource<GenericKubernetesResource>(yaml))
74+
}
75+
.toList()
76+
}
77+
78+
private fun json2Resources(json: String?, currentNamespace: String?): List<HasMetadata> {
79+
val mapper = ObjectMapper()
80+
val rootNode = mapper.readTree(json)
81+
return when {
82+
rootNode.isArray ->
83+
(rootNode as ArrayNode)
84+
.mapNotNull { node ->
85+
setMissingNamespace(currentNamespace, mapper.treeToValue(node, GenericKubernetesResource::class.java))
6086
}
6187
.toList()
62-
} catch (e: RuntimeException) {
63-
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
64-
}
88+
rootNode.isObject ->
89+
listOf(
90+
setMissingNamespace(currentNamespace,
91+
mapper.treeToValue(rootNode, GenericKubernetesResource::class.java)
92+
)
93+
)
94+
else ->
95+
emptyList()
6596
}
6697
}
6798

6899
private fun setMissingNamespace(namespace: String?, resource: HasMetadata): HasMetadata {
69-
if (resource.metadata.namespace.isNullOrEmpty()
100+
if (resource.metadata != null
101+
&& resource.metadata.namespace.isNullOrEmpty()
70102
&& namespace != null) {
71103
resource.metadata.namespace = namespace
72104
}
73105
return resource
74106
}
75107

76108
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.")
109+
return try {
110+
when {
111+
fileType == null ->
112+
null
113+
114+
YAMLFileType.YML == fileType ->
115+
resources2yaml(resources)
116+
117+
JsonFileType.INSTANCE == fileType ->
118+
resources2json(resources)
119+
120+
else ->
121+
""
122+
}
123+
} catch (e: RuntimeException) {
124+
throw ResourceException("Invalid kubernetes yaml/json", e.cause ?: e)
84125
}
85-
return resources
86-
.mapNotNull { resource -> serialize(resource, fileType) }
87-
.joinToString("\n")
126+
}
88127

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

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
134+
private fun resources2json(resources: List<HasMetadata>): String {
135+
return if (resources.size == 1) {
136+
Serialization.asJson(resources.first()).trim()
137+
} else {
138+
Serialization.asJson(resources).trim()
98139
}
99140
}
100141

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

+21-18
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
package com.redhat.devtools.intellij.kubernetes.editor
1212

1313
import com.intellij.json.JsonFileType
14+
import com.intellij.openapi.fileTypes.MockLanguageFileType
1415
import com.intellij.openapi.fileTypes.PlainTextFileType
1516
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.resource
1617
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
18+
import io.fabric8.kubernetes.api.model.HasMetadata
1719
import io.fabric8.kubernetes.api.model.Pod
1820
import io.fabric8.kubernetes.client.utils.Serialization
1921
import org.assertj.core.api.Assertions.assertThat
2022
import org.assertj.core.api.Assertions.assertThatThrownBy
23+
import org.assertj.core.api.Assertions.tuple
2124
import org.jetbrains.yaml.YAMLFileType
2225
import org.junit.Test
2326

@@ -57,19 +60,20 @@ class EditorResourceSerializationTest {
5760
}
5861

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

7579
@Test
@@ -203,17 +207,17 @@ class EditorResourceSerializationTest {
203207
}
204208

205209
@Test
206-
fun `#serialize throws if given multiple resources and non-YAML file type`() {
210+
fun `#serialize returns json array for given multiple resources and JSON file type`() {
207211
// given
208212
val resources = listOf(
209213
resource<Pod>("darth vader"),
210214
resource<Pod>("emperor")
211215
)
212-
assertThatThrownBy {
213-
// when
214-
EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
215-
// then
216-
}.isInstanceOf(UnsupportedOperationException::class.java)
216+
val expected = Serialization.asJson(resources).trim()
217+
// when
218+
val serialized = EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
219+
// then
220+
assertThat(serialized).isEqualTo(expected)
217221
}
218222

219223
@Test
@@ -259,7 +263,7 @@ class EditorResourceSerializationTest {
259263
}
260264

261265
@Test
262-
fun `#serialize returns null if given unsupported file type`() {
266+
fun `#serialize returns '' if given unsupported file type`() {
263267
// given
264268
val resource = resource<Pod>("leia")
265269
// when
@@ -268,5 +272,4 @@ class EditorResourceSerializationTest {
268272
assertThat(serialized)
269273
.isEqualTo("")
270274
}
271-
272275
}

0 commit comments

Comments
 (0)