From f87a0dcb77219ee82ea469421c57e971f26b755b Mon Sep 17 00:00:00 2001 From: touchkey <41332000+touchkey@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:18:55 +0530 Subject: [PATCH] feat: Customizing Cloudevents validation (#594) Add SPI for custom CloudEvent validation. Signed-off-by: vbhat6 --- .../provider/CloudEventValidatorProvider.java | 52 +++++++++++++++++++ .../core/v03/CloudEventBuilder.java | 9 +++- .../core/v1/CloudEventBuilder.java | 9 +++- .../core/validator/CloudEventValidator.java | 33 ++++++++++++ .../core/test/CloudEventCustomValidator.java | 17 ++++++ .../core/v1/CloudEventBuilderTest.java | 12 +++++ ...devents.core.validator.CloudEventValidator | 1 + 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java create mode 100644 core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java 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/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..2f6afd616 --- /dev/null +++ b/core/src/main/java/io/cloudevents/core/provider/CloudEventValidatorProvider.java @@ -0,0 +1,52 @@ +/* + * 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 final CloudEventValidatorProvider cloudEventValidatorProvider = new CloudEventValidatorProvider(); + + private final ServiceLoader loader; + + private CloudEventValidatorProvider(){ + loader = ServiceLoader.load(CloudEventValidator.class); + } + + public static CloudEventValidatorProvider getInstance() { + return cloudEventValidatorProvider; + } + + /** + * iterates through available Cloudevent validators. + * @param cloudEvent + */ + public void validate(CloudEvent cloudEvent){ + 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 9209c7123..060fa65df 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,8 @@ 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; import io.cloudevents.rw.CloudEventRWException; @@ -119,7 +121,12 @@ 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); + + final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance(); + validator.validate(cloudEvent); + + 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..bc6cec5c9 --- /dev/null +++ b/core/src/main/java/io/cloudevents/core/validator/CloudEventValidator.java @@ -0,0 +1,33 @@ +/* + * 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 { + + /** + * 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/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/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"); + } + } 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