/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 org.apache.kafka.connect.data; import org.apache.kafka.connect.errors.DataException; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; public class StructTest { private static final Schema FLAT_STRUCT_SCHEMA = SchemaBuilder.struct() .field("int8", Schema.INT8_SCHEMA) .field("int16", Schema.INT16_SCHEMA) .field("int32", Schema.INT32_SCHEMA) .field("int64", Schema.INT64_SCHEMA) .field("float32", Schema.FLOAT32_SCHEMA) .field("float64", Schema.FLOAT64_SCHEMA) .field("boolean", Schema.BOOLEAN_SCHEMA) .field("string", Schema.STRING_SCHEMA) .field("bytes", Schema.BYTES_SCHEMA) .build(); private static final Schema ARRAY_SCHEMA = SchemaBuilder.array(Schema.INT8_SCHEMA).build(); private static final Schema MAP_SCHEMA = SchemaBuilder.map( Schema.INT32_SCHEMA, Schema.STRING_SCHEMA ).build(); private static final Schema NESTED_CHILD_SCHEMA = SchemaBuilder.struct() .field("int8", Schema.INT8_SCHEMA) .build(); private static final Schema NESTED_SCHEMA = SchemaBuilder.struct() .field("array", ARRAY_SCHEMA) .field("map", MAP_SCHEMA) .field("nested", NESTED_CHILD_SCHEMA) .build(); private static final Schema REQUIRED_FIELD_SCHEMA = Schema.INT8_SCHEMA; private static final Schema OPTIONAL_FIELD_SCHEMA = SchemaBuilder.int8().optional().build(); private static final Schema DEFAULT_FIELD_SCHEMA = SchemaBuilder.int8().defaultValue((byte) 0).build(); @Test public void testFlatStruct() { Struct struct = new Struct(FLAT_STRUCT_SCHEMA) .put("int8", (byte) 12) .put("int16", (short) 12) .put("int32", 12) .put("int64", (long) 12) .put("float32", 12.f) .put("float64", 12.) .put("boolean", true) .put("string", "foobar") .put("bytes", "foobar".getBytes()); // Test equality, and also the type-specific getters assertEquals((byte) 12, (byte) struct.getInt8("int8")); assertEquals((short) 12, (short) struct.getInt16("int16")); assertEquals(12, (int) struct.getInt32("int32")); assertEquals((long) 12, (long) struct.getInt64("int64")); assertEquals((Float) 12.f, struct.getFloat32("float32")); assertEquals((Double) 12., struct.getFloat64("float64")); assertEquals(true, struct.getBoolean("boolean")); assertEquals("foobar", struct.getString("string")); assertEquals(ByteBuffer.wrap("foobar".getBytes()), ByteBuffer.wrap(struct.getBytes("bytes"))); struct.validate(); } @Test public void testComplexStruct() { List<Byte> array = Arrays.asList((byte) 1, (byte) 2); Map<Integer, String> map = Collections.singletonMap(1, "string"); Struct struct = new Struct(NESTED_SCHEMA) .put("array", array) .put("map", map) .put("nested", new Struct(NESTED_CHILD_SCHEMA).put("int8", (byte) 12)); // Separate the call to get the array and map to validate the typed get methods work properly List<Byte> arrayExtracted = struct.getArray("array"); assertEquals(array, arrayExtracted); Map<Byte, Byte> mapExtracted = struct.getMap("map"); assertEquals(map, mapExtracted); assertEquals((byte) 12, struct.getStruct("nested").get("int8")); struct.validate(); } // These don't test all the ways validation can fail, just one for each element. See more extensive validation // tests in SchemaTest. These are meant to ensure that we are invoking the same code path and that we do deeper // inspection than just checking the class of the object @Test(expected = DataException.class) public void testInvalidFieldType() { new Struct(FLAT_STRUCT_SCHEMA).put("int8", "should fail because this is a string, not int8"); } @Test(expected = DataException.class) public void testInvalidArrayFieldElements() { new Struct(NESTED_SCHEMA).put("array", Arrays.asList("should fail since elements should be int8s")); } @Test(expected = DataException.class) public void testInvalidMapKeyElements() { new Struct(NESTED_SCHEMA).put("map", Collections.singletonMap("should fail because keys should be int8s", (byte) 12)); } @Test(expected = DataException.class) public void testInvalidStructFieldSchema() { new Struct(NESTED_SCHEMA).put("nested", new Struct(MAP_SCHEMA)); } @Test(expected = DataException.class) public void testInvalidStructFieldValue() { new Struct(NESTED_SCHEMA).put("nested", new Struct(NESTED_CHILD_SCHEMA)); } @Test(expected = DataException.class) public void testMissingFieldValidation() { // Required int8 field Schema schema = SchemaBuilder.struct().field("field", REQUIRED_FIELD_SCHEMA).build(); Struct struct = new Struct(schema); struct.validate(); } @Test public void testMissingOptionalFieldValidation() { Schema schema = SchemaBuilder.struct().field("field", OPTIONAL_FIELD_SCHEMA).build(); Struct struct = new Struct(schema); struct.validate(); } @Test public void testMissingFieldWithDefaultValidation() { Schema schema = SchemaBuilder.struct().field("field", DEFAULT_FIELD_SCHEMA).build(); Struct struct = new Struct(schema); struct.validate(); } @Test public void testMissingFieldWithDefaultValue() { Schema schema = SchemaBuilder.struct().field("field", DEFAULT_FIELD_SCHEMA).build(); Struct struct = new Struct(schema); assertEquals((byte) 0, struct.get("field")); } @Test public void testMissingFieldWithoutDefaultValue() { Schema schema = SchemaBuilder.struct().field("field", REQUIRED_FIELD_SCHEMA).build(); Struct struct = new Struct(schema); assertNull(struct.get("field")); } @Test public void testEquals() { Struct struct1 = new Struct(FLAT_STRUCT_SCHEMA) .put("int8", (byte) 12) .put("int16", (short) 12) .put("int32", 12) .put("int64", (long) 12) .put("float32", 12.f) .put("float64", 12.) .put("boolean", true) .put("string", "foobar") .put("bytes", ByteBuffer.wrap("foobar".getBytes())); Struct struct2 = new Struct(FLAT_STRUCT_SCHEMA) .put("int8", (byte) 12) .put("int16", (short) 12) .put("int32", 12) .put("int64", (long) 12) .put("float32", 12.f) .put("float64", 12.) .put("boolean", true) .put("string", "foobar") .put("bytes", ByteBuffer.wrap("foobar".getBytes())); Struct struct3 = new Struct(FLAT_STRUCT_SCHEMA) .put("int8", (byte) 12) .put("int16", (short) 12) .put("int32", 12) .put("int64", (long) 12) .put("float32", 12.f) .put("float64", 12.) .put("boolean", true) .put("string", "mismatching string") .put("bytes", ByteBuffer.wrap("foobar".getBytes())); assertEquals(struct1, struct2); assertNotEquals(struct1, struct3); List<Byte> array = Arrays.asList((byte) 1, (byte) 2); Map<Integer, String> map = Collections.singletonMap(1, "string"); struct1 = new Struct(NESTED_SCHEMA) .put("array", array) .put("map", map) .put("nested", new Struct(NESTED_CHILD_SCHEMA).put("int8", (byte) 12)); List<Byte> array2 = Arrays.asList((byte) 1, (byte) 2); Map<Integer, String> map2 = Collections.singletonMap(1, "string"); struct2 = new Struct(NESTED_SCHEMA) .put("array", array2) .put("map", map2) .put("nested", new Struct(NESTED_CHILD_SCHEMA).put("int8", (byte) 12)); List<Byte> array3 = Arrays.asList((byte) 1, (byte) 2, (byte) 3); Map<Integer, String> map3 = Collections.singletonMap(2, "string"); struct3 = new Struct(NESTED_SCHEMA) .put("array", array3) .put("map", map3) .put("nested", new Struct(NESTED_CHILD_SCHEMA).put("int8", (byte) 13)); assertEquals(struct1, struct2); assertNotEquals(struct1, struct3); } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void testValidateStructWithNullValue() { Schema schema = SchemaBuilder.struct() .field("one", Schema.STRING_SCHEMA) .field("two", Schema.STRING_SCHEMA) .field("three", Schema.STRING_SCHEMA) .build(); Struct struct = new Struct(schema); thrown.expect(DataException.class); thrown.expectMessage("Invalid value: null used for required field: \"one\", schema type: STRING"); struct.validate(); } @Test public void testValidateFieldWithInvalidValueType() { String fieldName = "field"; FakeSchema fakeSchema = new FakeSchema(); thrown.expect(DataException.class); thrown.expectMessage("Invalid Java object for schema type null: class java.lang.Object for field: \"field\""); ConnectSchema.validateValue(fieldName, fakeSchema, new Object()); thrown.expect(DataException.class); thrown.expectMessage("Invalid Java object for schema type INT8: class java.lang.Object for field: \"field\""); ConnectSchema.validateValue(fieldName, Schema.INT8_SCHEMA, new Object()); } }