From dfb2a64e63b88a7d762149dca6d1e79edba45b67 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 09:37:39 +0000 Subject: [PATCH 1/8] Take the sequence number check out, to show the new tests failing The JSON Builder specification says the element MUST fail with an error and MUST NOT set its JSON value when no Sequence Element has produced a sequence number. JsonBuilderElement already does that, and nothing tested it, so the regression audit for this release read it as missing. This commit adds the tests and takes the check out, so the build shows them failing. The next commit puts the check back, unchanged, and the tests pass. What reaches rc is the tests alone. --- .../flowelements/JsonBuilderElement.java | 3 +- .../jsonbuilder/JsonBuilderSequenceTests.java | 228 ++++++++++++++++++ 2 files changed, 229 insertions(+), 2 deletions(-) create mode 100644 pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java index fcf92033..9a933716 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java @@ -225,8 +225,7 @@ private int getSequenceNumber(FlowData data) throws Exception { "query.sequence", Integer.class); if(sequence.hasValue() == false) { - throw new Exception("Sequence number not present in evidence. " + - "this is mandatory."); + return 1; } return sequence.getValue(); } diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java new file mode 100644 index 00000000..2fc70421 --- /dev/null +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -0,0 +1,228 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +package fiftyone.pipeline.jsonbuilder; + +import fiftyone.common.testhelpers.TestLoggerFactory; +import fiftyone.pipeline.core.data.FlowData; +import fiftyone.pipeline.core.data.FlowError; +import fiftyone.pipeline.core.flowelements.Pipeline; +import fiftyone.pipeline.core.flowelements.PipelineBuilder; +import fiftyone.pipeline.engines.fiftyone.flowelements.SequenceElement; +import fiftyone.pipeline.engines.fiftyone.flowelements.SequenceElementBuilder; +import fiftyone.pipeline.engines.testhelpers.flowelements.EmptyEngine; +import fiftyone.pipeline.engines.testhelpers.flowelements.EmptyEngineBuilder; +import fiftyone.pipeline.exceptions.AggregateException; +import fiftyone.pipeline.jsonbuilder.data.JsonBuilderData; +import fiftyone.pipeline.jsonbuilder.flowelements.JsonBuilderElement; +import fiftyone.pipeline.jsonbuilder.flowelements.JsonBuilderElementBuilder; + +import org.junit.jupiter.api.Test; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; + +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * The JsonBuilderElement needs a sequence number, and fails without one. + *

+ * The + * JSON + * Builder specification says the element MUST fail with an error and MUST + * NOT set its JSON value when no Sequence Element has produced a sequence + * number, that a query.sequence value taken from a query string is text and so + * does not count, and that an integer a caller added to query.sequence does + * count in Java because the element reads the integer in Evidence whoever + * added it. + */ +public class JsonBuilderSequenceTests { + + private final TestLoggerFactory loggerFactory; + + public JsonBuilderSequenceTests() { + ILoggerFactory internalLogger = mock(ILoggerFactory.class); + when(internalLogger.getLogger(anyString())).thenReturn(mock(Logger.class)); + loggerFactory = new TestLoggerFactory(internalLogger); + } + + /** + * Build a pipeline of an engine and the JSON builder, with a sequence + * element in front when asked for. + * @param withSequenceElement whether to place a sequence element first + * @param suppress whether the pipeline suppresses processing exceptions + * @return the built pipeline, which closes its elements + * @throws Exception if the engine or the pipeline cannot be built + */ + private Pipeline buildPipeline( + boolean withSequenceElement, + boolean suppress) throws Exception { + EmptyEngine engine = new EmptyEngineBuilder(loggerFactory).build(); + JsonBuilderElement jsonBuilder = + new JsonBuilderElementBuilder(loggerFactory).build(); + PipelineBuilder builder = new PipelineBuilder(loggerFactory); + if (withSequenceElement) { + SequenceElement sequenceElement = + new SequenceElementBuilder(loggerFactory).build(); + builder = builder.addFlowElement(sequenceElement); + } + return builder + .addFlowElement(engine) + .addFlowElement(jsonBuilder) + .setAutoCloseElements(true) + .setSuppressProcessException(suppress) + .build(); + } + + /** + * Process one request with the pipeline's exceptions suppressed, and + * assert that the element failed, said a sequence element is needed and + * set no JSON. + * @param sequence the query.sequence Evidence to add, or null for none + * @throws Exception if the pipeline cannot be built + */ + private void assertFailsWithoutASequenceNumber(Object sequence) + throws Exception { + try (Pipeline pipeline = buildPipeline(false, true); + FlowData flowData = pipeline.createFlowData()) { + if (sequence != null) { + flowData.addEvidence("query.sequence", sequence); + } + flowData.process(); + + Collection errors = flowData.getErrors(); + assertEquals(1, errors.size(), + "Expected the JsonBuilderElement to record one error."); + String message = errors.iterator().next().getThrowable().getMessage(); + assertNotNull(message, "Expected the error to carry a message."); + assertTrue(message.contains("SequenceElement"), + "Expected the error to say a SequenceElement is needed. " + + "Complete message: " + message); + + JsonBuilderData result = flowData.get(JsonBuilderData.class); + assertNull(result == null ? null : result.getJson(), + "Expected no JSON to have been set."); + } + } + + /** + * With no sequence element and no Evidence there is no sequence number, + * so the element fails and sets no JSON. + */ + @Test + public void JsonBuilder_NoSequenceElement_Fails() throws Exception { + assertFailsWithoutASequenceNumber(null); + } + + /** + * A query string carries text. Text is not a sequence number, so the + * element fails as it does with no Evidence at all. + */ + @Test + public void JsonBuilder_SequenceFromQueryString_Fails() throws Exception { + assertFailsWithoutASequenceNumber("4"); + } + + /** + * Text that is not a number at all fails the same way. + */ + @Test + public void JsonBuilder_SequenceThatIsNotANumber_Fails() throws Exception { + assertFailsWithoutASequenceNumber("abc"); + } + + /** + * The pipeline's own exception handling decides whether the error is + * thrown or recorded. With exceptions not suppressed the failure reaches + * the caller, and still no JSON is set. + */ + @Test + public void JsonBuilder_NoSequenceElement_ThrowsWhenNotSuppressed() + throws Exception { + try (Pipeline pipeline = buildPipeline(false, false); + FlowData flowData = pipeline.createFlowData()) { + AggregateException thrown = assertThrows( + AggregateException.class, + flowData::process); + // The aggregate carries each element's error as a suppressed + // exception. + assertEquals(1, thrown.getSuppressed().length, + "Expected one suppressed exception in the aggregate."); + String message = thrown.getSuppressed()[0].getMessage(); + assertTrue(message.contains("SequenceElement"), + "Expected the error to say a SequenceElement is needed. " + + "Complete message: " + message); + + JsonBuilderData result = flowData.get(JsonBuilderData.class); + assertNull(result == null ? null : result.getJson(), + "Expected no JSON to have been set."); + } + } + + /** + * An integer a caller added to query.sequence is a sequence number, even + * with no sequence element, because the element reads the integer in + * Evidence whoever added it. + */ + @Test + public void JsonBuilder_IntegerFromTheCaller_BuildsTheJson() + throws Exception { + try (Pipeline pipeline = buildPipeline(false, false); + FlowData flowData = pipeline.createFlowData()) { + flowData.addEvidence("query.sequence", 3); + flowData.process(); + + assertEquals(0, flowData.getErrors().size(), + "Expected no error with an integer sequence number."); + JsonBuilderData result = flowData.get(JsonBuilderData.class); + assertNotNull(result, "Expected the JsonBuilderData to be present."); + assertNotNull(result.getJson(), "Expected the JSON to be set."); + } + } + + /** + * With a sequence element in front the JSON is built, which is the order + * the web integration assembles. + */ + @Test + public void JsonBuilder_WithSequenceElement_BuildsTheJson() + throws Exception { + try (Pipeline pipeline = buildPipeline(true, false); + FlowData flowData = pipeline.createFlowData()) { + flowData.process(); + + assertEquals(0, flowData.getErrors().size(), + "Expected no error with a sequence element in the pipeline."); + JsonBuilderData result = flowData.get(JsonBuilderData.class); + assertNotNull(result, "Expected the JsonBuilderData to be present."); + assertNotNull(result.getJson(), "Expected the JSON to be set."); + } + } +} From 6563209c56b4ce6f69f791d1d667c47e4b0743dc Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 09:46:53 +0000 Subject: [PATCH 2/8] Put the sequence number check back The check is restored exactly as it is on rc, so what this draft adds to rc is the tests alone. The previous commit took it out only to show the build failing without it. --- .../pipeline/jsonbuilder/flowelements/JsonBuilderElement.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java index 9a933716..fcf92033 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java @@ -225,7 +225,8 @@ private int getSequenceNumber(FlowData data) throws Exception { "query.sequence", Integer.class); if(sequence.hasValue() == false) { - return 1; + throw new Exception("Sequence number not present in evidence. " + + "this is mandatory."); } return sequence.getValue(); } From 68b399bf9114e41a76d2f206b81dafeec8f696b0 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 11:54:46 +0000 Subject: [PATCH 3/8] Test that the cause says a Sequence Element has to come first The JSON Builder specification says the error SHOULD say that a Sequence Element needs to be placed before the JSON Builder Element in the Pipeline. The exception the pipeline records says so, and the cause it carries does not, reading "Sequence number not present in evidence. this is mandatory." This test pins the cause's wording, and fails until the message changes. --- .../jsonbuilder/JsonBuilderSequenceTests.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java index 2fc70421..d71cadc7 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -186,6 +186,35 @@ public void JsonBuilder_NoSequenceElement_ThrowsWhenNotSuppressed() } } + /** + * The error the element records carries the one getSequenceNumber threw + * as its cause, and the cause has to say what to do as well, because it + * is the line a caller meets at the top of the stack trace. + */ + @Test + public void JsonBuilder_NoSequenceElement_TheCauseSaysWhatIsNeeded() + throws Exception { + try (Pipeline pipeline = buildPipeline(false, true); + FlowData flowData = pipeline.createFlowData()) { + flowData.process(); + + Collection errors = flowData.getErrors(); + assertEquals(1, errors.size(), + "Expected the JsonBuilderElement to record one error."); + Throwable cause = + errors.iterator().next().getThrowable().getCause(); + assertNotNull(cause, "Expected the error to carry a cause."); + String message = cause.getMessage(); + assertNotNull(message, "Expected the cause to carry a message."); + assertTrue( + message.contains("a Sequence Element needs to be placed " + + "before it in the Pipeline"), + "Expected the cause to say that a Sequence Element needs to " + + "be placed before the JSON Builder Element in the " + + "Pipeline. Complete message: " + message); + } + } + /** * An integer a caller added to query.sequence is a sequence number, even * with no sequence element, because the element reads the integer in From 3f98ec73e4f57e0016a416d9b4ad606ab31574e3 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 11:56:37 +0000 Subject: [PATCH 4/8] Say in the cause that a Sequence Element has to come before the JSON builder The cause read "Sequence number not present in evidence. this is mandatory.", which neither says what to do nor reads as one sentence. It now carries the wording 51degrees-go and 51degrees-rust use, so the same misconfiguration reads the same whichever language a deployment uses. The exception the pipeline records is unchanged. --- .../jsonbuilder/flowelements/JsonBuilderElement.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java index fcf92033..6c50d34d 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java @@ -225,8 +225,9 @@ private int getSequenceNumber(FlowData data) throws Exception { "query.sequence", Integer.class); if(sequence.hasValue() == false) { - throw new Exception("Sequence number not present in evidence. " + - "this is mandatory."); + throw new Exception("The JSON Builder Element found no sequence " + + "number, so a Sequence Element needs to be placed before it " + + "in the Pipeline."); } return sequence.getValue(); } From 38900cbc41907db20e67720abf39296fac94fada Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 12:18:54 +0000 Subject: [PATCH 5/8] Pin the error message the JSON Builder specification gives specifications#139 turns the SHOULD into a MUST and gives the exact text, so that the message reads the same in every language. It also says that where an implementation wraps the error in another, the one the caller reads carries the message, which in Java is the PipelineConfigurationException rather than its cause. The constant holds the text and the tests assert it word for word on both the recorded error and the cause. They fail until the element uses it. --- .../pipeline/jsonbuilder/Constants.java | 14 +++++++- .../jsonbuilder/JsonBuilderSequenceTests.java | 34 +++++++++---------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java index e1c422ee..abeb61f7 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java @@ -25,5 +25,17 @@ public class Constants { public static final Integer MAX_JAVASCRIPT_ITERATIONS = 10; - + + /** + * What the element says when no Sequence Element has produced a sequence + * number. The + * JSON + * Builder specification gives this text and every language gives the + * same, so that the message reads the same whichever one a deployment + * uses. + */ + public static final String NO_SEQUENCE_NUMBER_MESSAGE = + "The JSON Builder needs a sequence number. Place a Sequence Element " + + "before the JSON Builder in the Pipeline."; + } diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java index d71cadc7..8230d706 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -46,7 +46,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,6 +61,10 @@ * does not count, and that an integer a caller added to query.sequence does * count in Java because the element reads the integer in Evidence whoever * added it. + *

+ * The error MUST carry the message the page gives, which is + * {@link Constants#NO_SEQUENCE_NUMBER_MESSAGE}, and where the error is wrapped + * in another the one the caller reads carries it. */ public class JsonBuilderSequenceTests { @@ -121,10 +124,9 @@ private void assertFailsWithoutASequenceNumber(Object sequence) assertEquals(1, errors.size(), "Expected the JsonBuilderElement to record one error."); String message = errors.iterator().next().getThrowable().getMessage(); - assertNotNull(message, "Expected the error to carry a message."); - assertTrue(message.contains("SequenceElement"), - "Expected the error to say a SequenceElement is needed. " + - "Complete message: " + message); + assertEquals(Constants.NO_SEQUENCE_NUMBER_MESSAGE, message, + "Expected the error the caller reads to carry the message " + + "the specification gives, word for word."); JsonBuilderData result = flowData.get(JsonBuilderData.class); assertNull(result == null ? null : result.getJson(), @@ -176,9 +178,9 @@ public void JsonBuilder_NoSequenceElement_ThrowsWhenNotSuppressed() assertEquals(1, thrown.getSuppressed().length, "Expected one suppressed exception in the aggregate."); String message = thrown.getSuppressed()[0].getMessage(); - assertTrue(message.contains("SequenceElement"), - "Expected the error to say a SequenceElement is needed. " + - "Complete message: " + message); + assertEquals(Constants.NO_SEQUENCE_NUMBER_MESSAGE, message, + "Expected the error the caller reads to carry the message " + + "the specification gives, word for word."); JsonBuilderData result = flowData.get(JsonBuilderData.class); assertNull(result == null ? null : result.getJson(), @@ -188,8 +190,8 @@ public void JsonBuilder_NoSequenceElement_ThrowsWhenNotSuppressed() /** * The error the element records carries the one getSequenceNumber threw - * as its cause, and the cause has to say what to do as well, because it - * is the line a caller meets at the top of the stack trace. + * as its cause, and the cause says the same thing, because it is the line + * a caller meets at the top of the stack trace. */ @Test public void JsonBuilder_NoSequenceElement_TheCauseSaysWhatIsNeeded() @@ -204,14 +206,10 @@ public void JsonBuilder_NoSequenceElement_TheCauseSaysWhatIsNeeded() Throwable cause = errors.iterator().next().getThrowable().getCause(); assertNotNull(cause, "Expected the error to carry a cause."); - String message = cause.getMessage(); - assertNotNull(message, "Expected the cause to carry a message."); - assertTrue( - message.contains("a Sequence Element needs to be placed " + - "before it in the Pipeline"), - "Expected the cause to say that a Sequence Element needs to " + - "be placed before the JSON Builder Element in the " + - "Pipeline. Complete message: " + message); + assertEquals(Constants.NO_SEQUENCE_NUMBER_MESSAGE, + cause.getMessage(), + "Expected the cause to carry the same message as the error " + + "the caller reads."); } } From 66bedcbd1a084dbf5e17e93a7150f6fbc17557b8 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 12:29:04 +0000 Subject: [PATCH 6/8] Give the specification's message when there is no sequence number The PipelineConfigurationException the caller reads said "Make sure there is a SequenceElement placed before this JsonBuilderElement in the pipeline" and its cause said something else again. Both now carry the text the specification gives, which every language carries. One more test writes that text out beside the constant, so that changing the constant fails rather than moving the other assertions with it. --- .../flowelements/JsonBuilderElement.java | 11 +++++------ .../jsonbuilder/JsonBuilderSequenceTests.java | 13 +++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java index 6c50d34d..72f03704 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/flowelements/JsonBuilderElement.java @@ -203,9 +203,10 @@ private String buildJson(FlowData data, PipelineConfig config) throws Exception try { sequenceNumber = getSequenceNumber(data); } catch (Exception e) { - throw new PipelineConfigurationException("Make sure there is a " - + "SequenceElement placed before this JsonBuilderElement " - + "in the pipeline", e); + // The caller reads this one, so it carries the message the + // specification gives, and the cause carries the same. + throw new PipelineConfigurationException( + Constants.NO_SEQUENCE_NUMBER_MESSAGE, e); } Map allProperties = getAllProperties(data, config); @@ -225,9 +226,7 @@ private int getSequenceNumber(FlowData data) throws Exception { "query.sequence", Integer.class); if(sequence.hasValue() == false) { - throw new Exception("The JSON Builder Element found no sequence " + - "number, so a Sequence Element needs to be placed before it " + - "in the Pipeline."); + throw new Exception(Constants.NO_SEQUENCE_NUMBER_MESSAGE); } return sequence.getValue(); } diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java index 8230d706..40d6a67d 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -134,6 +134,19 @@ private void assertFailsWithoutASequenceNumber(Object sequence) } } + /** + * The message is written out here as well as in the element, so that + * changing the element's constant fails this test rather than moving the + * other assertions along with it. + */ + @Test + public void JsonBuilder_TheMessage_IsTheOneTheSpecificationGives() { + assertEquals( + "The JSON Builder needs a sequence number. Place a Sequence " + + "Element before the JSON Builder in the Pipeline.", + Constants.NO_SEQUENCE_NUMBER_MESSAGE); + } + /** * With no sequence element and no Evidence there is no sequence number, * so the element fails and sets no JSON. From 996c25cd4a1f5881c25cc1901cb04edddeb4d075 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 13:35:26 +0000 Subject: [PATCH 7/8] Pin the JSON Builder links at the specification commit that merged specifications#139 merged at 13:19 as d72eb074, with one more commit saying that the message is the whole text of the error and that a prefix a language's error type writes is not part of it. The links moved from the draft head and from 67ead698, which predates the message. No code changes. --- .../src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java | 2 +- .../fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java index abeb61f7..f72d41ee 100644 --- a/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java +++ b/pipeline/pipeline.jsonbuilder/src/main/java/fiftyone/pipeline/jsonbuilder/Constants.java @@ -29,7 +29,7 @@ public class Constants { /** * What the element says when no Sequence Element has produced a sequence * number. The - * JSON + * JSON * Builder specification gives this text and every language gives the * same, so that the message reads the same whichever one a deployment * uses. diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java index 40d6a67d..4ffc3b8c 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -54,7 +54,7 @@ * The JsonBuilderElement needs a sequence number, and fails without one. *

* The - * JSON + * JSON * Builder specification says the element MUST fail with an error and MUST * NOT set its JSON value when no Sequence Element has produced a sequence * number, that a query.sequence value taken from a query string is text and so From fd7573c7b4e53512d864f3c405c515440305f571 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Wed, 11 Nov 2026 14:00:01 +0000 Subject: [PATCH 8/8] Say in the helper what it now asserts The helper checked that the message named a sequence element and now checks the whole message, so its note says so. --- .../pipeline/jsonbuilder/JsonBuilderSequenceTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java index 4ffc3b8c..0a0329e1 100644 --- a/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java +++ b/pipeline/pipeline.jsonbuilder/src/test/java/fiftyone/pipeline/jsonbuilder/JsonBuilderSequenceTests.java @@ -106,8 +106,8 @@ private Pipeline buildPipeline( /** * Process one request with the pipeline's exceptions suppressed, and - * assert that the element failed, said a sequence element is needed and - * set no JSON. + * assert that the element recorded one error carrying the specification's + * message and set no JSON. * @param sequence the query.sequence Evidence to add, or null for none * @throws Exception if the pipeline cannot be built */