Repository: provectus/kafka-ui
Issue: Produce message is not working for topics with enums in schema
Labels: type/bug
scope/backend

**Describe the bug**
Produce message is not working for topics with enums in schema. If you'll try to produce a message for a topic with schema and enum in it. Frontend will fail:

```
 Type key is missing
    at Object.a.default (index.ts:112)
    at m (index.ts:69)
    at Object.a.build [as default] (index.ts:218)
    at a.default (index.ts:15)
    at SendMessage.tsx:67
    at Rl (react-dom.production.min.js:262)
    at a.unstable_runWithPriority (scheduler.production.min.js:18)
    at Gt (react-dom.production.min.js:122)
    at Ll (react-dom.production.min.js:261)
    at react-dom.production.min.js:261
```

The problem is In json schema generator which skip type property for enum fields.


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverter.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverter.java
index 707b11dbf68..e6cff30893d 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverter.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverter.java
@@ -67,6 +67,10 @@ private FieldSchema convertSchema(String name, Schema schema,
   }
 
   private FieldSchema createUnionSchema(Schema schema, Map<String, FieldSchema> definitions) {
+
+    final boolean nullable = schema.getTypes().stream()
+        .anyMatch(t -> t.getType().equals(Schema.Type.NULL));
+
     final Map<String, FieldSchema> fields = schema.getTypes().stream()
         .filter(t -> !t.getType().equals(Schema.Type.NULL))
         .map(f -> Tuples.of(
@@ -80,7 +84,16 @@ private FieldSchema createUnionSchema(Schema schema, Map<String, FieldSchema> de
             Tuple2::getT2
         ));
 
-    return new ObjectFieldSchema(fields, Collections.emptyList(), true);
+    if (nullable) {
+      return new OneOfFieldSchema(
+          List.of(
+             new SimpleFieldSchema(new SimpleJsonType(JsonType.Type.NULL)),
+             new ObjectFieldSchema(fields, Collections.emptyList())
+         )
+      );
+    } else {
+      return new ObjectFieldSchema(fields, Collections.emptyList());
+    }
   }
 
   private FieldSchema createObjectSchema(String name, Schema schema,
diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java
index 3fe1f26fcd0..296c5e0715a 100644
--- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java
+++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/jsonschema/ObjectFieldSchema.java
@@ -15,18 +15,11 @@
 public class ObjectFieldSchema implements FieldSchema {
   private final Map<String, FieldSchema> properties;
   private final List<String> required;
-  private final boolean nullable;
 
   public ObjectFieldSchema(Map<String, FieldSchema> properties,
                            List<String> required) {
-    this(properties, required, false);
-  }
-
-  public ObjectFieldSchema(Map<String, FieldSchema> properties,
-                           List<String> required, boolean nullable) {
     this.properties = properties;
     this.required = required;
-    this.nullable = nullable;
   }
 
   public Map<String, FieldSchema> getProperties() {
@@ -46,18 +39,9 @@ public JsonNode toJsonNode(ObjectMapper mapper) {
             Tuple2::getT2
         ));
     final ObjectNode objectNode = mapper.createObjectNode();
-    if (this.nullable) {
-      objectNode.set(
-          "type",
-          mapper.createArrayNode()
-              .add(JsonType.Type.OBJECT.getName())
-              .add(JsonType.Type.NULL.getName())
-      );
-    } else {
-      objectNode.setAll(
-          new SimpleJsonType(JsonType.Type.OBJECT).toJsonNode(mapper)
-      );
-    }
+    objectNode.setAll(
+        new SimpleJsonType(JsonType.Type.OBJECT).toJsonNode(mapper)
+    );
     objectNode.set("properties", mapper.valueToTree(nodes));
     if (!required.isEmpty()) {
       objectNode.set("required", mapper.valueToTree(required));


Test patch:
diff --git a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverterTest.java b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverterTest.java
index 58daa739cf7..edcc9c559b4 100644
--- a/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverterTest.java
+++ b/kafka-ui-api/src/test/java/com/provectus/kafka/ui/util/jsonschema/AvroJsonSchemaConverterTest.java
@@ -87,13 +87,14 @@ public void avroConvertTest() throws URISyntaxException, JsonProcessingException
         + "{\"$ref\":\"#/definitions/RecordInnerMessage\"}},"
         + "\"required\":[\"record\"],\"definitions\":"
         + "{\"RecordInnerMessage\":{\"type\":\"object\",\""
-        + "properties\":{\"long_text\":{\"type\":[\"object\", \"null\"],"
-        + "\"properties\":{\"string\":{\"type\":\"string\"}}},"
-        + "\"array\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}},"
-        + "\"id\":{\"type\":\"integer\"},\"text\":{\"type\":\"string\"},"
-        + "\"map\":{\"type\":\"object\",\"additionalProperties\":"
-        + "{\"type\":\"integer\"}},\"order\":{\"type\":\"string\","
-        + "\"enum\":[\"SPADES\",\"HEARTS\",\"DIAMONDS\",\"CLUBS\"]}},"
+        + "properties\":{\"long_text\":{\"oneOf\":[{\"type\":\"null\"},"
+        + "{\"type\":\"object\",\"properties\":{\"string\":"
+        + "{\"type\":\"string\"}}}]},\"array\":{\"type\":\"array\",\"items\":"
+        + "{\"type\":\"string\"}},\"id\":{\"type\":\"integer\"},\"text\":"
+        + "{\"type\":\"string\"},\"map\":{\"type\":\"object\","
+        + "\"additionalProperties\":{\"type\":\"integer\"}},"
+        + "\"order\":{\"enum\":[\"SPADES\",\"HEARTS\",\"DIAMONDS\",\"CLUBS\"],"
+        + "\"type\":\"string\"}},"
         + "\"required\":[\"id\",\"text\",\"order\",\"array\",\"map\"]}}}";
 
     final JsonSchema convertRecord = converter.convert(basePath, recordSchema);
@@ -151,10 +152,10 @@ public void testNullableUnions() throws URISyntaxException, IOException, Process
         "{\"$id\":\"http://example.com/Message\","
         + "\"$schema\":\"https://json-schema.org/draft/2020-12/schema\","
         + "\"type\":\"object\",\"properties\":{\"text\":"
-        + "{\"type\":[\"object\", \"null\"],\"properties\":{\"string\":"
-        + "{\"type\":\"string\"}}},\"value\":{\"type\":[\"object\", \"null\"],"
-        + "\"properties\":{\"string\":{\"type\":\"string\"},"
-        + "\"long\":{\"type\":\"integer\"}}}}}";
+        + "{\"oneOf\":[{\"type\":\"null\"},{\"type\":\"object\","
+        + "\"properties\":{\"string\":{\"type\":\"string\"}}}]},\"value\":"
+        + "{\"oneOf\":[{\"type\":\"null\"},{\"type\":\"object\","
+        + "\"properties\":{\"string\":{\"type\":\"string\"},\"long\":{\"type\":\"integer\"}}}]}}}";
 
     final JsonSchema convert = converter.convert(basePath, recordSchema);
     Assertions.assertEquals(