From ec2f1a68d8396b9c7a12517f77d1814fffb1f643 Mon Sep 17 00:00:00 2001 From: Tristan Tarrant Date: Wed, 15 May 2024 12:28:50 +0200 Subject: [PATCH] IPROTO-334 Don't throw NPE when proto.lock contains a message with no fields * Also introduce a ProtoLock.compatibilityCheck() method --- .../protostream/FileDescriptorSource.java | 5 ++ .../protostream/descriptors/ProtoLock.java | 63 +++++++++++++------ 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/infinispan/protostream/FileDescriptorSource.java b/core/src/main/java/org/infinispan/protostream/FileDescriptorSource.java index d52b9df86..d7ad999b9 100644 --- a/core/src/main/java/org/infinispan/protostream/FileDescriptorSource.java +++ b/core/src/main/java/org/infinispan/protostream/FileDescriptorSource.java @@ -160,6 +160,11 @@ public FileDescriptorSource addProtoFile(String name, File protoFile) throws IOE return this; } + + public FileDescriptorSource addProtoFile(File protoFile) throws IOException { + return addProtoFile(protoFile.getName(), protoFile); + } + public static FileDescriptorSource fromResources(ClassLoader userClassLoader, String... classPathResources) throws IOException { return new FileDescriptorSource().addProtoFiles(userClassLoader, classPathResources); } diff --git a/core/src/main/java/org/infinispan/protostream/descriptors/ProtoLock.java b/core/src/main/java/org/infinispan/protostream/descriptors/ProtoLock.java index 1817f9503..b4a2ba603 100644 --- a/core/src/main/java/org/infinispan/protostream/descriptors/ProtoLock.java +++ b/core/src/main/java/org/infinispan/protostream/descriptors/ProtoLock.java @@ -12,6 +12,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import org.infinispan.protostream.impl.Log; + import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; @@ -35,6 +37,25 @@ public Map descriptors() { return descriptors; } + /** + * Checks for compatibility between all the descriptors in this ProtoLock instance against those in the supplied one + * + * @param that + * @param strict + */ + public void checkCompatibility(ProtoLock that, boolean strict) { + List errors = new ArrayList<>(); + for (Map.Entry descriptor : descriptors.entrySet()) { + FileDescriptor d2 = that.descriptors.get(descriptor.getKey()); + if (d2 != null) { + descriptor.getValue().checkCompatibility(d2, strict, errors); + } + } + if (!errors.isEmpty()) { + throw Log.LOG.incompatibleSchemaChanges(String.join("\n", errors)); + } + } + public static ProtoLock readLockFile(InputStream is) throws IOException { JsonFactory jsonFactory = new JsonFactory(); jsonFactory.setCodec(new ObjectMapper()); @@ -108,26 +129,28 @@ private static Map readMessages(JsonNode json, Strin JsonNode message = messages.get(m); String name = message.get("name").asText(); Descriptor.Builder mb = messageBuilders.computeIfAbsent(name, n -> new Descriptor.Builder().withName(n).withFullName(namePrefix + n)); - ArrayNode fields = (ArrayNode) message.get("fields"); - Map oneOfBuilders = new HashMap<>(); - for (int f = 0; f < fields.size(); f++) { - JsonNode field = fields.get(f); - FieldDescriptor.Builder fb = new FieldDescriptor.Builder(); - fb.withName(field.get("name").asText()); - fb.withNumber(field.get("id").asInt()); - fb.withTypeName(field.get("type").asText()); - if (field.has("is_repeated")) { - fb.withLabel(Label.REPEATED); - } - if (field.has("optional")) { - fb.withLabel(Label.OPTIONAL); - } - readOptions(field, fb); - if (message.has("oneof_parent")) { - String oneOf = message.get("oneof_parent").asText(); - oneOfBuilders.computeIfAbsent(oneOf, n -> new OneOfDescriptor.Builder().withName(n)).addField(fb); - } else { - mb.addField(fb); + if (message.has("fields")) { + ArrayNode fields = (ArrayNode) message.get("fields"); + Map oneOfBuilders = new HashMap<>(); + for (int f = 0; f < fields.size(); f++) { + JsonNode field = fields.get(f); + FieldDescriptor.Builder fb = new FieldDescriptor.Builder(); + fb.withName(field.get("name").asText()); + fb.withNumber(field.get("id").asInt()); + fb.withTypeName(field.get("type").asText()); + if (field.has("is_repeated")) { + fb.withLabel(Label.REPEATED); + } + if (field.has("optional")) { + fb.withLabel(Label.OPTIONAL); + } + readOptions(field, fb); + if (message.has("oneof_parent")) { + String oneOf = message.get("oneof_parent").asText(); + oneOfBuilders.computeIfAbsent(oneOf, n -> new OneOfDescriptor.Builder().withName(n)).addField(fb); + } else { + mb.addField(fb); + } } } if (message.has("maps")) {