From 13de314e72f4cfd0bc9926a375721417ccfd0746 Mon Sep 17 00:00:00 2001 From: vbhat6 Date: Thu, 5 Oct 2023 18:30:58 +0530 Subject: [PATCH 1/6] feat: Customizing Cloudevents validation Signed-off-by: vbhat6 --- .../core/v1/CloudEventBuilder.java | 27 +++++++++++- .../core/validator/CloudEventValidator.java | 16 +++++++ .../core/v1/CloudEventBuilderTest.java | 44 +++++++++++++++++++ .../core/v1/CustomCloudEventValidator.java | 11 +++++ .../v1/CustomCloudEventValidatorImpl.java | 15 +++++++ 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java create mode 100644 core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java create mode 100644 core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java diff --git a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java index 9209c7123..e21bad4d6 100644 --- a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java +++ b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java @@ -21,6 +21,7 @@ import io.cloudevents.SpecVersion; import io.cloudevents.core.CloudEventUtils; import io.cloudevents.core.impl.BaseCloudEventBuilder; +import io.cloudevents.core.validator.CloudEventValidator; import io.cloudevents.rw.CloudEventContextReader; import io.cloudevents.rw.CloudEventContextWriter; import io.cloudevents.rw.CloudEventRWException; @@ -119,7 +120,31 @@ public CloudEvent build() { throw createMissingAttributeException(TYPE); } - return new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions); + CloudEvent cloudEvent = new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions); + + String name = null; + try{ + // check if the header.validator.class is set as a system property, + if ((name = System.getProperty("header.validator.class")) != null){ + Class dynamicClass = Class.forName(name); + Object dynamicObject = dynamicClass.newInstance(); + + if (dynamicObject instanceof CloudEventValidator) { + // pluggable implementation of validation implementation + CloudEventValidator cloudEventValidator = (CloudEventValidator) dynamicObject; + + cloudEventValidator.validate(cloudEvent); + } + else { + throw new IllegalArgumentException("Passed class is not an instance of CloudEventValidator"); + } + } + } + catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { + throw new IllegalArgumentException("Unable to load the header.validator.class passed as vm argument = " + name + ". Please check the classpath", e); + } + + return cloudEvent; } @Override diff --git a/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java b/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java new file mode 100644 index 000000000..f1b01a7fc --- /dev/null +++ b/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java @@ -0,0 +1,16 @@ +package io.cloudevents.core.validator; + +import io.cloudevents.CloudEvent; + +/** + * Interface which defines validation for CloudEvents attributes and extensions. + */ +public interface CloudEventValidator { + + /** + * Validate the attributes of a CloudEvent. + * + * @param cloudEvent the CloudEvent to validate + */ + void validate(CloudEvent cloudEvent); +} diff --git a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java index a0c6cc6ef..a3aaeb686 100644 --- a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java +++ b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java @@ -142,4 +142,48 @@ void testMissingType() { ).hasMessageContaining("Attribute 'type' cannot be null"); } + @Test + void testClassNotFoundExceptionForValidator(){ + + System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidatorTest"); + assertThatCode(() -> CloudEventBuilder + .v1() + .withId("000") + .withSource(URI.create("http://localhost")) + .withType("aaa") + .withExtension("astring", 10) + .build() + ).hasMessageContaining("Unable to load the header.validator.class passed as vm argument"); + } + + @Test + void testClassCastExceptionForValidator(){ + + System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidator"); + assertThatCode(() -> CloudEventBuilder + .v1() + .withId("000") + .withSource(URI.create("http://localhost")) + .withType("aaa") + .withExtension("astring", 10) + .build() + ).hasMessageContaining("Passed class is not an instance of CloudEventValidator"); + } + + /** + * This test is to check for the mandatory extension 'namespace' as per Organization need + */ + @Test + void testMissingNamespaceExtension(){ + + System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidatorImpl"); + assertThatCode(() -> CloudEventBuilder + .v1() + .withId("000") + .withSource(URI.create("http://localhost")) + .withType("aaa") + .build() + ).hasMessageContaining("Extension 'namespace' cannot be null"); + } + } diff --git a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java new file mode 100644 index 000000000..f0878ac6e --- /dev/null +++ b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java @@ -0,0 +1,11 @@ +package io.cloudevents.core.v1; + +/** + * Dummy validation class which does not implement CloudEventValidator class. + */ +public class CustomCloudEventValidator { + + void validate(){ + + } +} diff --git a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java new file mode 100644 index 000000000..89c968bd4 --- /dev/null +++ b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java @@ -0,0 +1,15 @@ +package io.cloudevents.core.v1; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.validator.CloudEventValidator; + +public class CustomCloudEventValidatorImpl implements CloudEventValidator { + @Override + public void validate(CloudEvent cloudEvent) { + + if (cloudEvent.getExtension("namespace") == null){ + throw new IllegalStateException("Extension 'namespace' cannot be null"); + } + + } +} From 4588166aff473e29e95b47b3d7453cf52d79a6ba Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Mon, 16 Oct 2023 09:26:53 -0400 Subject: [PATCH 2/6] Add link to CloudEvent security mailing list (#599) Signed-off-by: Doug Davis Signed-off-by: vbhat6 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9b2b16d6d..aa0a1bd25 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,10 @@ for how PR reviews and approval, and our [Code of Conduct](https://github.com/cloudevents/spec/blob/main/docs/GOVERNANCE.md#additional-information) information. +If there is a security concern with one of the CloudEvents specifications, or +with one of the project's SDKs, please send an email to +[cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io). + ## Additional SDK Resources - [List of current active maintainers](MAINTAINERS.md) From 977e846f4102846f2e97a7922549d64ce84400d7 Mon Sep 17 00:00:00 2001 From: vbhat6 Date: Wed, 31 Jan 2024 09:58:14 +0530 Subject: [PATCH 3/6] feat: Customizing Cloudevents validation Signed-off-by: vbhat6 --- .../provider/CloudEventValidatorProvider.java | 70 +++++++++++++++++++ .../core/v1/CloudEventBuilder.java | 24 +------ .../core/validator/CloudEventValidator.java | 17 +++++ .../core/v1/CloudEventBuilderTest.java | 44 ------------ .../core/v1/CustomCloudEventValidator.java | 11 --- .../v1/CustomCloudEventValidatorImpl.java | 15 ---- 6 files changed, 90 insertions(+), 91 deletions(-) create mode 100644 core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java delete mode 100644 core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java delete mode 100644 core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java diff --git a/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java b/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java new file mode 100644 index 000000000..c96245256 --- /dev/null +++ b/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java @@ -0,0 +1,70 @@ +/* + * Copyright 2018-Present The CloudEvents Authors + *

+ * Licensed 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 io.cloudevents.core.provider; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.validator.CloudEventValidator; + +import java.util.Iterator; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; + +/** + * CloudEventValidatorProvider is a singleton class which loads and access CE Validator service providers on behalf of service clients. + */ +public class CloudEventValidatorProvider { + + private static CloudEventValidatorProvider cloudEventValidatorProvider; + + private ServiceLoader loader; + + private CloudEventValidatorProvider(){ + loader = ServiceLoader.load(CloudEventValidator.class); + } + + public static synchronized CloudEventValidatorProvider getInstance(){ + if(cloudEventValidatorProvider == null){ + cloudEventValidatorProvider = new CloudEventValidatorProvider(); + } + return cloudEventValidatorProvider; + } + + /** + * iterates through available Cloudevent validators. + * @param cloudEvent + */ + public void validate(CloudEvent cloudEvent){ + try{ + // + Iterator validatorIterator = loader.iterator(); + while (validatorIterator.hasNext()){ + CloudEventValidator validator = validatorIterator.next(); + validator.validate(cloudEvent); + + } + } catch (ServiceConfigurationError serviceError) { + + serviceError.printStackTrace(); + } + + } + + + + + +} diff --git a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java index e21bad4d6..bf2ac7a85 100644 --- a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java +++ b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java @@ -21,6 +21,7 @@ import io.cloudevents.SpecVersion; import io.cloudevents.core.CloudEventUtils; import io.cloudevents.core.impl.BaseCloudEventBuilder; +import io.cloudevents.core.provider.CloudEventValidatorProvider; import io.cloudevents.core.validator.CloudEventValidator; import io.cloudevents.rw.CloudEventContextReader; import io.cloudevents.rw.CloudEventContextWriter; @@ -122,27 +123,8 @@ public CloudEvent build() { CloudEvent cloudEvent = new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions); - String name = null; - try{ - // check if the header.validator.class is set as a system property, - if ((name = System.getProperty("header.validator.class")) != null){ - Class dynamicClass = Class.forName(name); - Object dynamicObject = dynamicClass.newInstance(); - - if (dynamicObject instanceof CloudEventValidator) { - // pluggable implementation of validation implementation - CloudEventValidator cloudEventValidator = (CloudEventValidator) dynamicObject; - - cloudEventValidator.validate(cloudEvent); - } - else { - throw new IllegalArgumentException("Passed class is not an instance of CloudEventValidator"); - } - } - } - catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException("Unable to load the header.validator.class passed as vm argument = " + name + ". Please check the classpath", e); - } + CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance(); + validator.validate(cloudEvent); return cloudEvent; } diff --git a/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java b/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java index f1b01a7fc..bc6cec5c9 100644 --- a/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java +++ b/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java @@ -1,8 +1,25 @@ +/* + * Copyright 2018-Present The CloudEvents Authors + *

+ * Licensed 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 io.cloudevents.core.validator; import io.cloudevents.CloudEvent; /** + * @author Vinay Bhat * Interface which defines validation for CloudEvents attributes and extensions. */ public interface CloudEventValidator { diff --git a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java index a3aaeb686..a0c6cc6ef 100644 --- a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java +++ b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java @@ -142,48 +142,4 @@ void testMissingType() { ).hasMessageContaining("Attribute 'type' cannot be null"); } - @Test - void testClassNotFoundExceptionForValidator(){ - - System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidatorTest"); - assertThatCode(() -> CloudEventBuilder - .v1() - .withId("000") - .withSource(URI.create("http://localhost")) - .withType("aaa") - .withExtension("astring", 10) - .build() - ).hasMessageContaining("Unable to load the header.validator.class passed as vm argument"); - } - - @Test - void testClassCastExceptionForValidator(){ - - System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidator"); - assertThatCode(() -> CloudEventBuilder - .v1() - .withId("000") - .withSource(URI.create("http://localhost")) - .withType("aaa") - .withExtension("astring", 10) - .build() - ).hasMessageContaining("Passed class is not an instance of CloudEventValidator"); - } - - /** - * This test is to check for the mandatory extension 'namespace' as per Organization need - */ - @Test - void testMissingNamespaceExtension(){ - - System.setProperty("header.validator.class", "io.cloudevents.core.v1.CustomCloudEventValidatorImpl"); - assertThatCode(() -> CloudEventBuilder - .v1() - .withId("000") - .withSource(URI.create("http://localhost")) - .withType("aaa") - .build() - ).hasMessageContaining("Extension 'namespace' cannot be null"); - } - } diff --git a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java deleted file mode 100644 index f0878ac6e..000000000 --- a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidator.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.cloudevents.core.v1; - -/** - * Dummy validation class which does not implement CloudEventValidator class. - */ -public class CustomCloudEventValidator { - - void validate(){ - - } -} diff --git a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java b/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java deleted file mode 100644 index 89c968bd4..000000000 --- a/core/src/test/java/io/cloudevents/core/v1/CustomCloudEventValidatorImpl.java +++ /dev/null @@ -1,15 +0,0 @@ -package io.cloudevents.core.v1; - -import io.cloudevents.CloudEvent; -import io.cloudevents.core.validator.CloudEventValidator; - -public class CustomCloudEventValidatorImpl implements CloudEventValidator { - @Override - public void validate(CloudEvent cloudEvent) { - - if (cloudEvent.getExtension("namespace") == null){ - throw new IllegalStateException("Extension 'namespace' cannot be null"); - } - - } -} From c92553c0d5db5e426b9704541109141a85ab49b1 Mon Sep 17 00:00:00 2001 From: vbhat6 Date: Wed, 7 Feb 2024 10:12:33 +0530 Subject: [PATCH 4/6] feat: Customizing Cloudevents validation Signed-off-by: vbhat6 --- .../provider/CloudEventValidatorProvider.java | 28 ++++--------------- .../core/v03/CloudEventBuilder.java | 9 +++++- .../core/v1/CloudEventBuilder.java | 2 +- .../core/v1/CloudEventBuilderTest.java | 12 ++++++++ 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java b/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java index c96245256..2f6afd616 100644 --- a/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java +++ b/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java @@ -28,18 +28,15 @@ */ public class CloudEventValidatorProvider { - private static CloudEventValidatorProvider cloudEventValidatorProvider; + private static final CloudEventValidatorProvider cloudEventValidatorProvider = new CloudEventValidatorProvider(); - private ServiceLoader loader; + private final ServiceLoader loader; private CloudEventValidatorProvider(){ loader = ServiceLoader.load(CloudEventValidator.class); } - public static synchronized CloudEventValidatorProvider getInstance(){ - if(cloudEventValidatorProvider == null){ - cloudEventValidatorProvider = new CloudEventValidatorProvider(); - } + public static CloudEventValidatorProvider getInstance() { return cloudEventValidatorProvider; } @@ -48,23 +45,8 @@ public static synchronized CloudEventValidatorProvider getInstance(){ * @param cloudEvent */ public void validate(CloudEvent cloudEvent){ - try{ - // - Iterator validatorIterator = loader.iterator(); - while (validatorIterator.hasNext()){ - CloudEventValidator validator = validatorIterator.next(); - validator.validate(cloudEvent); - - } - } catch (ServiceConfigurationError serviceError) { - - serviceError.printStackTrace(); + for (final CloudEventValidator validator : loader) { + validator.validate(cloudEvent); } - } - - - - - } diff --git a/core/src/main/java/io/cloudevents/core/v03/CloudEventBuilder.java b/core/src/main/java/io/cloudevents/core/v03/CloudEventBuilder.java index 7f043ce7e..152498cc3 100644 --- a/core/src/main/java/io/cloudevents/core/v03/CloudEventBuilder.java +++ b/core/src/main/java/io/cloudevents/core/v03/CloudEventBuilder.java @@ -16,9 +16,12 @@ */ package io.cloudevents.core.v03; +import io.cloudevents.CloudEvent; import io.cloudevents.SpecVersion; import io.cloudevents.core.CloudEventUtils; import io.cloudevents.core.impl.BaseCloudEventBuilder; +import io.cloudevents.core.provider.CloudEventValidatorProvider; +import io.cloudevents.core.v1.CloudEventV1; import io.cloudevents.rw.CloudEventContextReader; import io.cloudevents.rw.CloudEventContextWriter; import io.cloudevents.rw.CloudEventRWException; @@ -122,7 +125,11 @@ public CloudEventV03 build() { throw createMissingAttributeException("type"); } - return new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions); + CloudEventV03 cloudEvent = new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions); + final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance(); + validator.validate(cloudEvent); + + return cloudEvent; } @Override diff --git a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java index bf2ac7a85..060fa65df 100644 --- a/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java +++ b/core/src/main/java/io/cloudevents/core/v1/CloudEventBuilder.java @@ -123,7 +123,7 @@ public CloudEvent build() { CloudEvent cloudEvent = new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions); - CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance(); + final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance(); validator.validate(cloudEvent); return cloudEvent; diff --git a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java index a0c6cc6ef..1f7c9fcc9 100644 --- a/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java +++ b/core/src/test/java/io/cloudevents/core/v1/CloudEventBuilderTest.java @@ -142,4 +142,16 @@ void testMissingType() { ).hasMessageContaining("Attribute 'type' cannot be null"); } + @Test + void testValidatorProvider(){ + assertThatCode(() -> CloudEventBuilder + .v1() + .withId("000") + .withSource(URI.create("http://localhost")) + .withType(TYPE) + .withExtension("namespace", "order") + .build() + ).hasMessageContaining("Expecting sales in namespace extension"); + } + } From 62c55e702c8120425539ca94c3689cea7e744bca Mon Sep 17 00:00:00 2001 From: Calum Murray Date: Wed, 31 Jan 2024 05:51:03 -0500 Subject: [PATCH 5/6] Update cesql TCK tests (#603) Signed-off-by: Calum Murray Signed-off-by: vbhat6 --- .../java/io/cloudevents/sql/TCKTestSuite.java | 3 +- sql/src/test/resources/tck/README.md | 10 +- .../test/resources/tck/like_expression.yaml | 23 +++ .../tck/subscriptions_api_recreations.yaml | 168 ++++++++++++++++++ 4 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 sql/src/test/resources/tck/subscriptions_api_recreations.yaml diff --git a/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java b/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java index f122c0354..d48b7a3c4 100644 --- a/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java +++ b/sql/src/test/java/io/cloudevents/sql/TCKTestSuite.java @@ -115,7 +115,8 @@ public Stream> tckTestCases() { "parse_errors", "spec_examples", "string_builtin_functions", - "sub_expression" + "sub_expression", + "subscriptions_api_recreations" ).map(fileName -> "/tck/" + fileName + ".yaml"); return tckFiles diff --git a/sql/src/test/resources/tck/README.md b/sql/src/test/resources/tck/README.md index 9b024099a..5852abcfe 100644 --- a/sql/src/test/resources/tck/README.md +++ b/sql/src/test/resources/tck/README.md @@ -11,12 +11,12 @@ Each test definition includes: * `name`: Name of the test case * `expression`: Expression to test. -* `result`: Expected result (optional). Can be a boolean, an integer or a string. -* `error`: Expected error (optional). If absent, no error is expected. -* `event`: Input event (optional). If present, this is a valid event serialized in JSON format. If absent, when testing +* `result`: Expected result (OPTIONAL). Can be a boolean, an integer or a string. +* `error`: Expected error (OPTIONAL). If absent, no error is expected. +* `event`: Input event (OPTIONAL). If present, this is a valid event serialized in JSON format. If absent, when testing the expression, any valid event can be passed. -* `eventOverrides`: Overrides to the input event (optional). This might be used when `event` is missing, in order to - define only some specific values, while the other (required) attributes can be any value. +* `eventOverrides`: Overrides to the input event (OPTIONAL). This might be used when `event` is missing, in order to + define only some specific values, while the other (REQUIRED) attributes can be any value. The `error` values could be any of the following: diff --git a/sql/src/test/resources/tck/like_expression.yaml b/sql/src/test/resources/tck/like_expression.yaml index 9704cafb2..b6bc5a18b 100644 --- a/sql/src/test/resources/tck/like_expression.yaml +++ b/sql/src/test/resources/tck/like_expression.yaml @@ -93,3 +93,26 @@ tests: eventOverrides: myext: "abc123123%456_dzf" result: false + + - name: With type coercion from int (1) + expression: "234 LIKE '23_'" + result: true + - name: With type coercion from int (2) + expression: "2344 LIKE '23%'" + result: true + - name: With type coercion from int (3) + expression: "2344 LIKE '23_'" + result: false + + - name: With type coercion from bool (1) + expression: "TRUE LIKE 'tr%'" + result: true + - name: With type coercion from bool (2) + expression: "TRUE LIKE '%ue'" + result: true + - name: With type coercion from bool (3) + expression: "FALSE LIKE 'tr%'" + result: false + - name: With type coercion from bool (4) + expression: "FALSE LIKE 'fal%'" + result: true diff --git a/sql/src/test/resources/tck/subscriptions_api_recreations.yaml b/sql/src/test/resources/tck/subscriptions_api_recreations.yaml new file mode 100644 index 000000000..9bd8659d3 --- /dev/null +++ b/sql/src/test/resources/tck/subscriptions_api_recreations.yaml @@ -0,0 +1,168 @@ +name: SubscriptionsAPI Recreations +tests: + - name: Prefix filter (1) + expression: "source LIKE 'https://%'" + result: true + eventOverrides: + source: "https://example.com" + - name: Prefix filter (2) + expression: "source LIKE 'https://%'" + result: false + eventOverrides: + source: "http://example.com" + - name: Prefix filter on string extension + expression: "myext LIKE 'custom%'" + result: true + eventOverrides: + myext: "customext" + - name: Prefix filter on missing string extension + expression: "myext LIKE 'custom%'" + error: missingAttribute + + - name: Suffix filter (1) + expression: "type like '%.error'" + result: true + eventOverrides: + type: "com.github.error" + - name: Suffix filter (2) + expression: "type like '%.error'" + result: false + eventOverrides: + type: "com.github.success" + - name: Suffix filter on string extension + expression: "myext LIKE '%ext'" + result: true + eventOverrides: + myext: "customext" + - name: Suffix filter on missing string extension + expression: "myext LIKE '%ext'" + error: missingAttribute + + - name: Exact filter (1) + expression: "id = 'myId'" + result: true + eventOverrides: + id: "myId" + - name: Exact filter (2) + expression: "id = 'myId'" + result: false + eventOverrides: + id: "notmyId" + - name: Exact filter on string extension + expression: "myext = 'customext'" + result: true + eventOverrides: + myext: "customext" + - name: Exact filter on missing string extension + expression: "myext = 'customext'" + error: missingAttribute + + - name: Prefix filter AND Suffix filter (1) + expression: "id LIKE 'my%' AND source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.ca" + - name: Prefix filter AND Suffix filter (2) + expression: "id LIKE 'my%' AND source LIKE '%.ca'" + result: false + eventOverrides: + id: "myId" + source: "http://www.some-website.com" + - name: Prefix filter AND Suffix filter (3) + expression: "myext LIKE 'custom%' AND type LIKE '%.error'" + result: true + eventOverrides: + myext: "customext" + type: "com.github.error" + - name: Prefix AND Suffix filter (4) + expression: "type LIKE 'example.%' AND myext LIKE 'custom%'" + error: missingAttribute + eventOverrides: + type: "example.event.type" + + - name: Prefix OR Suffix filter (1) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.ca" + - name: Prefix OR Suffix filter (2) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "myId" + source: "http://www.some-website.com" + - name: Prefix OR Suffix filter (3) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: true + eventOverrides: + id: "notmyId" + source: "http://www.some-website.ca" + - name: Prefix OR Suffix filter (4) + expression: "id LIKE 'my%' OR source LIKE '%.ca'" + result: false + eventOverrides: + id: "notmyId" + source: "http://www.some-website.com" + + - name: Disjunctive Normal Form (1) + expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: true + eventOverrides: + id: "myId" + type: "example.event.success" + - name: Disjunctive Normal Form (2) + expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: true + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Disjunctive Normal Form (3) + expresion: "(id = 'myId' AND type LIKE '%.success') OR (id = 'notmyId' AND source LIKE 'http://%' AND type LIKE '%.warning')" + result: false + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "https://localhost.localdomain" + + - name: Conjunctive Normal Form (1) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: true + eventOverrides: + id: "myId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (2) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: true + eventOverrides: + id: "notmyId" + type: "example.event.success" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (3) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: false + eventOverrides: + id: "notmyId" + type: "example.event.warning" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (4) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning')" + result: true + eventOverrides: + id: "myId" + type: "example.event.success" + source: "http://localhost.localdomain" + - name: Conjunctive Normal Form (5) + expression: "(id = 'myId' OR type LIKE '%.success') AND (id = 'notmyId' OR source LIKE 'https://%' OR type LIKE '%.warning') AND (myext = 'customext')" + error: missingAttribute + eventOverrides: + id: "myId" + type: "example.event.success" + source: "http://localhost.localdomain" + + + + From 8c66b1bdaa2236144d62de35ad222962e5431cc6 Mon Sep 17 00:00:00 2001 From: vbhat6 Date: Thu, 15 Feb 2024 11:09:01 +0530 Subject: [PATCH 6/6] feat: Customizing Cloudevents validation Signed-off-by: vbhat6 --- .../core/test/CloudEventCustomValidator.java | 17 +++++++++++++++++ ...oudevents.core.validator.CloudEventValidator | 1 + 2 files changed, 18 insertions(+) create mode 100644 core/src/test/java/io/cloudevents/core/test/CloudEventCustomValidator.java create mode 100644 core/src/test/resources/META-INF/services/io.cloudevents.core.validator.CloudEventValidator diff --git a/core/src/test/java/io/cloudevents/core/test/CloudEventCustomValidator.java b/core/src/test/java/io/cloudevents/core/test/CloudEventCustomValidator.java new file mode 100644 index 000000000..55fe0a024 --- /dev/null +++ b/core/src/test/java/io/cloudevents/core/test/CloudEventCustomValidator.java @@ -0,0 +1,17 @@ +package io.cloudevents.core.test; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.validator.CloudEventValidator; + +public class CloudEventCustomValidator implements CloudEventValidator { + + @Override + public void validate(CloudEvent cloudEvent) { + String namespace = null; + if ((namespace = (String) cloudEvent.getExtension("namespace")) != null && + !namespace.equals("sales")){ + throw new IllegalStateException("Expecting sales in namespace extension"); + } + } + +} diff --git a/core/src/test/resources/META-INF/services/io.cloudevents.core.validator.CloudEventValidator b/core/src/test/resources/META-INF/services/io.cloudevents.core.validator.CloudEventValidator new file mode 100644 index 000000000..c265df28c --- /dev/null +++ b/core/src/test/resources/META-INF/services/io.cloudevents.core.validator.CloudEventValidator @@ -0,0 +1 @@ +io.cloudevents.core.test.CloudEventCustomValidator