This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch yv in repository https://gitbox.apache.org/repos/asf/camel.git
commit c7e2700215563b7128c4eeb3bb148cfb7af2444f Author: Claus Ibsen <[email protected]> AuthorDate: Wed Feb 11 12:56:09 2026 +0100 CAMEL-22931: Maven Plugin for YAML DSL validator --- bom/camel-bom/pom.xml | 5 + .../maven/dsl/yaml/GenerateYamlSchemaMojo.java | 5 +- .../camel-yaml-dsl-validator-maven-plugin/pom.xml | 117 +++++++++ .../camel-yaml-dsl-validator-maven-plugin.adoc | 119 +++++++++ .../camel/dsl/yaml/validator/ValidateMojo.java | 291 +++++++++++++++++++++ .../camel-yaml-dsl-validator/pom.xml | 80 ++++++ .../camel/dsl/yaml/validator/YamlValidator.java | 82 ++++++ .../dsl/yaml/validator/YamlValidatorTest.java | 48 ++++ .../src/test/resources/bad.yaml | 26 ++ .../src/test/resources/foo.yaml | 26 ++ .../src/test/resources/log4j2.properties | 31 +++ dsl/camel-yaml-dsl/pom.xml | 2 + parent/pom.xml | 5 + 13 files changed, 836 insertions(+), 1 deletion(-) diff --git a/bom/camel-bom/pom.xml b/bom/camel-bom/pom.xml index 13bb1c80c79c..dda32ab0f532 100644 --- a/bom/camel-bom/pom.xml +++ b/bom/camel-bom/pom.xml @@ -2567,6 +2567,11 @@ <artifactId>camel-yaml-dsl-deserializers</artifactId> <version>4.18.0-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-validator</artifactId> + <version>4.18.0-SNAPSHOT</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-yaml-io</artifactId> diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java index 5f1389eb43f3..46c3f031f91a 100644 --- a/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-maven-plugin/src/main/java/org/apache/camel/maven/dsl/yaml/GenerateYamlSchemaMojo.java @@ -66,6 +66,9 @@ import org.jboss.jandex.DotName; threadSafe = true, requiresProject = false) public class GenerateYamlSchemaMojo extends GenerateYamlSupportMojo { + + private static final String DRAFT = "http://json-schema.org/draft-04/schema#"; + @Parameter(required = true) private File outputFile; @Parameter(defaultValue = "true") @@ -84,7 +87,7 @@ public class GenerateYamlSchemaMojo extends GenerateYamlSupportMojo { final ObjectNode root = mapper.createObjectNode(); - root.put("$schema", "http://json-schema.org/draft-04/schema#"); + root.put("$schema", DRAFT); root.put("type", "array"); items = root.putObject("items"); diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/pom.xml new file mode 100644 index 000000000000..09a91b578249 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/pom.xml @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-parent</artifactId> + <version>4.18.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-yaml-dsl-validator-maven-plugin</artifactId> + <packaging>maven-plugin</packaging> + + <name>Camel :: YAML DSL :: Validator :: Maven Plugins</name> + <description>Maven plugin to validate YAML DSL</description> + + <properties> + <firstVersion>4.18.0</firstVersion> + <camel-prepare-component>false</camel-prepare-component> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-validator</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-plugin-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-compat</artifactId> + </dependency> + <dependency> + <groupId>org.apache.maven.plugin-tools</groupId> + <artifactId>maven-plugin-annotations</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-settings-builder</artifactId> + <version>${maven-version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-builder-support</artifactId> + <version>${maven-version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-model-builder</artifactId> + <version>${maven-version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.maven</groupId> + <artifactId>maven-resolver-provider</artifactId> + <version>${maven-version}</version> + <scope>provided</scope> + </dependency> + + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-plugin-plugin</artifactId> + <configuration> + <goalPrefix>camel-yaml-dsl-validator</goalPrefix> + <mojoDependencies> + <dep>org.codehaus.mojo:exec-maven-plugin</dep> + <dep>org.apache.maven:maven-plugin-api</dep> + </mojoDependencies> + </configuration> + </plugin> + <plugin> + <groupId>org.eclipse.sisu</groupId> + <artifactId>sisu-maven-plugin</artifactId> + <executions> + <execution> + <id>generate-index</id> + <goals> + <goal>main-index</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/docs/camel-yaml-dsl-validator-maven-plugin.adoc b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/docs/camel-yaml-dsl-validator-maven-plugin.adoc new file mode 100644 index 000000000000..99678fafadfd --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/docs/camel-yaml-dsl-validator-maven-plugin.adoc @@ -0,0 +1,119 @@ += Camel YAML DSL Validator Maven Plugin + +The Camel YAML DSL Validator Maven Plugin supports the following goals + + - camel-yaml-dsl-validator:validate - To validate YAML routes are correct according to spec + +== camel-yaml-dsl-validator:validate + +For validating the YAML routes for syntax errors according to the spec. + +Then you can run the `validate` goal from the command line or from within your Java editor such as IDEA or Eclipse. + +[source,bash] +---- +mvn camel-yaml-dsl-validator:validate +---- + +You can also enable the plugin to run automatically as part of the build to catch these errors. + +[source,xml] +---- +<plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-validate-maven-plugin</artifactId> + <executions> + <execution> + <phase>process-classes</phase> + <goals> + <goal>validate</goal> + </goals> + </execution> + </executions> +</plugin> +---- + +The phase determines when the plugin runs. In the sample above the phase is `process-classes` which runs after +the compilation of the main source code. + +The maven plugin can also be configured to validate the test source code, which means that the phase should be +changed accordingly to `process-test-classes` as shown below: + +[source,xml] +---- +<plugin> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-validate-maven-plugin</artifactId> + <executions> + <execution> + <configuration> + <includeTest>true</includeTest> + </configuration> + <phase>process-test-classes</phase> + <goals> + <goal>validate</goal> + </goals> + </execution> + </executions> +</plugin> +---- + +=== Running the goal on any Maven project + +You can also run the validate goal on any Maven project without having to add the plugin to the `pom.xml` file. +Doing so requires to specify the plugin using its fully qualified name. For example to run the goal on +the `main-yaml` from Apache Camel you can run + +[source,bash] +---- +$cd main-yaml +$mvn org.apache.camel:camel-yaml-dsl-validator-maven-plugin:4.18.0:validate +---- + +Which for example outputs (with a forced error) +[source,text] +---- +[INFO] --- camel-yaml-dsl-validator:4.18.0:validate (default-cli) @ camel-example-main-yaml --- +[INFO] Found [/Users/davsclaus/workspace/camel-examples/main-yaml/src/main/resources/routes/my-route.camel.yaml] YAML files ... +[INFO] Validating 1 YAML files ... +[WARNING] + +Validation error detected in 1 files + + File: my-route.camel.yaml + /0/route/from: property 'step' is not defined in the schema and the schema does not allow additional properties + /0/route/from: required property 'steps' not found +---- + +=== Options + +The maven plugin *validate* goal supports the following options which can be configured from the command line (use `-D` syntax), or defined in the `pom.xml` file in the `<configuration>` tag. + +|=== +| Parameter | Default Value | Description +| skip | false | Skip the validation execution. +| failOnError | false | Whether to fail if invalid Camel endpoints was found. By default the plugin logs the errors at WARN level. +| includeTest | false | Whether to include test source code. +| includes | | To filter the names of YAML files to only include files matching any of the given list of patterns (wildcard and regular expression). Multiple values can be separated by comma. +| excludes | | To filter the names of YAML files to exclude files matching any of the given list of patterns (wildcard and regular expression). Multiple values can be separated by comma. +| onlyCamelYamlExt | false | Whether to only accept files with xxx.camel.yaml as file name. By default, all .yaml files are accepted. +|=== + +For example to excludes a specific file: + +[source,bash] +---- +$mvn camel-yaml-dsl-validator:validate -Dcamel.excludes=cheese.yaml +---- + +Notice that you must prefix the `-D` command argument with `camel.`, eg `camel.excludes` as the option name. + +=== Validating include test + +If you have a Maven project then you can run the plugin to validate the endpoints in the unit test source code as well. +You can pass in the options using `-D` style as shown: + +---- +$cd myproject +$mvn org.apache.camel:camel-yaml-dsl-validator:4.18.0:validate -Dcamel.includeTest=true +---- diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/ValidateMojo.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/ValidateMojo.java new file mode 100644 index 000000000000..bea0fe93e0bb --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator-maven-plugin/src/main/java/org/apache/camel/dsl/yaml/validator/ValidateMojo.java @@ -0,0 +1,291 @@ +/* + * 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.camel.dsl.yaml.validator; + +import java.io.File; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.inject.Inject; + +import com.networknt.schema.ValidationMessage; +import org.apache.camel.support.PatternHelper; +import org.apache.camel.util.FileUtil; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.model.Resource; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; + +@Mojo(name = "validate", threadSafe = true) +public class ValidateMojo extends AbstractMojo { + + private final YamlValidator validator = new YamlValidator(); + + private static final String IGNORE_FILE = "application.yml"; + + /** + * The maven project. + */ + @Parameter(property = "project", required = true, readonly = true) + protected MavenProject project; + + @Parameter(defaultValue = "${session}", readonly = true, required = true) + private MavenSession session; + + /** + * Skip the validation execution. + */ + @Parameter(property = "camel.skipValidation", defaultValue = "false") + private boolean skip; + + /** + * Whether to fail if validation reported errors. By default, the plugin logs the errors at WARN level + */ + @Parameter(property = "camel.failOnError", defaultValue = "false") + private boolean failOnError; + + @Parameter(defaultValue = "${project.build.directory}") + private String projectBuildDir; + + /** + * Whether to only accept files with xxx.camel.yaml as file name. By default, all .yaml files are accepted. + */ + @Parameter(property = "camel.onlyCamelYamlExt") + private boolean onlyCamelYamlExt; + + /** + * Whether to include test source code + */ + @Parameter(property = "camel.includeTest", defaultValue = "false") + private boolean includeTest; + + /** + * To filter the names of YAML files to only include files matching any of the given list of patterns (wildcard and + * regular expression). Multiple values can be separated by comma. + */ + @Parameter(property = "camel.includes") + private String includes; + + /** + * To filter the names of YAML files to exclude files matching any of the given pattern in the list (wildcard and + * regular expression). Multiple values can be separated by comma. + */ + @Parameter(property = "camel.excludes") + private String excludes; + + /** + * yamlFiles in memory cache, useful for multi modules maven projects + */ + private static final Set<File> yamlFiles = new LinkedHashSet<>(); + + private final RepositorySystem repositorySystem; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true) + private RepositorySystemSession repositorySystemSession; + + @Inject + public ValidateMojo(RepositorySystem repositorySystem) { + this.repositorySystem = repositorySystem; + } + + @Override + public void execute() throws MojoExecutionException { + if (skip) { + getLog().info("skipping YAML DSL validation as per configuration"); + return; + } + + // find all XML routes + String ext = onlyCamelYamlExt ? ".camel.yaml" : ".yaml"; + findYamlRouters(yamlFiles, includeTest, ext, project); + getLog().debug("Found " + yamlFiles.size() + " YAML files ..."); + + Map<File, List<ValidationMessage>> reports = new LinkedHashMap<>(); + List<File> matched = new ArrayList<>(); + for (File file : yamlFiles) { + if (matchFile(file)) { + matched.add(file); + } + } + if (!matched.isEmpty()) { + getLog().info("Validating " + matched.size() + " YAML files ..."); + try { + validator.init(); + for (File file : matched) { + var report = validateYamlRoute(file); + reports.put(file, report); + } + } catch (Exception e) { + throw new MojoExecutionException(e); + } + } + + validateResults(reports); + } + + private void validateResults(Map<File, List<ValidationMessage>> reports) throws MojoExecutionException { + int count = errorCounts(reports); + if (count == 0) { + getLog().info("Validation success"); + return; + } + StringBuilder sb = new StringBuilder(); + sb.append("Validation error detected in ").append(count).append(" files\n\n"); + + for (var e : reports.entrySet()) { + String name = e.getKey().getName(); + var report = e.getValue(); + + sb.append("\tFile: ").append(name).append("\n"); + for (var r : report) { + sb.append("\t\t").append(r.toString()).append("\n"); + } + sb.append("\n"); + } + getLog().warn("\n\n" + sb + "\n\n"); + + if (failOnError) { + throw new MojoExecutionException(sb.toString()); + } + } + + private int errorCounts(Map<File, List<ValidationMessage>> reports) { + int count = 0; + for (List<ValidationMessage> list : reports.values()) { + if (!list.isEmpty()) { + count++; + } + } + return count; + } + + private List<ValidationMessage> validateYamlRoute(File file) throws Exception { + getLog().debug("Validating YAML DSL in file: " + file); + return validator.validate(file); + } + + private boolean matchFile(File file) { + String no = FileUtil.onlyName(file.getName()); + if (IGNORE_FILE.equals(no)) { + return false; + } + return matchRouteFile(file, excludes, includes, project); + } + + public static boolean matchRouteFile(File file, String excludes, String includes, MavenProject project) { + if (excludes == null && includes == null) { + return true; + } else if (excludes != null && fileListMatchesPattern(excludes, file, project)) { + return false; + } else { + return includes != null ? fileListMatchesPattern(includes, file, project) : true; + } + } + + public static boolean fileListMatchesPattern(String fileList, File file, MavenProject project) { + for (String fileName : fileList.split(",")) { + fileName = fileName.trim(); + String fqn = stripRootPath(asRelativeFile(file.getAbsolutePath(), project), project); + boolean match = PatternHelper.matchPattern(fqn, fileName) || PatternHelper.matchPattern(file.getName(), fileName); + if (match) { + return true; + } + } + return false; + } + + private static String stripRootPath(String name, MavenProject project) { + for (String dir : project.getCompileSourceRoots()) { + dir = asRelativeFile(dir, project); + if (name.startsWith(dir)) { + return name.substring(dir.length() + 1); + } + } + + for (String dir : project.getTestCompileSourceRoots()) { + dir = asRelativeFile(dir, project); + if (name.startsWith(dir)) { + return name.substring(dir.length() + 1); + } + } + + for (Resource resource : project.getResources()) { + String dir = asRelativeFile(resource.getDirectory(), project); + if (name.startsWith(dir)) { + return name.substring(dir.length() + 1); + } + } + + for (Resource resource : project.getTestResources()) { + String dir = asRelativeFile(resource.getDirectory(), project); + if (name.startsWith(dir)) { + return name.substring(dir.length() + 1); + } + } + + return name; + } + + private static String asRelativeFile(String name, MavenProject project) { + String answer = name; + String base = project.getBasedir().getAbsolutePath(); + if (name.startsWith(base)) { + answer = name.substring(base.length()); + if (answer.startsWith(File.separator)) { + answer = answer.substring(1); + } + } + + return answer; + } + + private static void findYamlRouters(Set<File> yamlFiles, boolean includeTest, String ext, MavenProject project) { + for (Resource dir : project.getResources()) { + finYamlFiles(new File(dir.getDirectory()), ext, yamlFiles); + } + if (includeTest) { + for (Resource dir : project.getTestResources()) { + finYamlFiles(new File(dir.getDirectory()), ext, yamlFiles); + } + } + } + + private static void finYamlFiles(File dir, String ext, Set<File> yamlFiles) { + File[] files = dir.isDirectory() ? dir.listFiles() : null; + if (files != null) { + for (File file : files) { + String name = file.getName().toLowerCase(Locale.ROOT); + if (name.endsWith(ext)) { + yamlFiles.add(file); + } else if (file.isDirectory()) { + finYamlFiles(file, ext, yamlFiles); + } + } + } + } + +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/pom.xml new file mode 100644 index 000000000000..d508387a7358 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/pom.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + 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. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-parent</artifactId> + <version>4.18.0-SNAPSHOT</version> + </parent> + + <artifactId>camel-yaml-dsl-validator</artifactId> + <packaging>jar</packaging> + <name>Camel :: YAML DSL :: Validator</name> + <description>Camel DSL with YAML Validator</description> + + <properties> + <firstVersion>4.18.0</firstVersion> + <camel-prepare-component>false</camel-prepare-component> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl</artifactId> + </dependency> + <dependency> + <groupId>com.networknt</groupId> + <artifactId>json-schema-validator</artifactId> + <version>${networknt-json-schema-validator-version}</version> + </dependency> + + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.assertj</groupId> + <artifactId>assertj-core</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-slf4j2-impl</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-jcl</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> + +</project> diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java new file mode 100644 index 000000000000..c28edeef540b --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/main/java/org/apache/camel/dsl/yaml/validator/YamlValidator.java @@ -0,0 +1,82 @@ +/* + * 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.camel.dsl.yaml.validator; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.networknt.schema.JsonMetaSchema; +import com.networknt.schema.JsonSchema; +import com.networknt.schema.JsonSchemaFactory; +import com.networknt.schema.NonValidationKeyword; +import com.networknt.schema.SchemaValidatorsConfig; +import com.networknt.schema.SpecVersionDetector; +import com.networknt.schema.ValidationMessage; + +public class YamlValidator { + + private static final String DRAFT = "http://json-schema.org/draft-04/schema#"; + + // TODO: yaml-dsl-parser to see if its validate camel + + private ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + private JsonNode model; + private JsonSchemaFactory factory; + private SchemaValidatorsConfig config; + private JsonSchema schema; + + private String jsonSchema = "/schema/camelYamlDsl.json"; + + public String getJsonSchema() { + return jsonSchema; + } + + /** + * The schema to use for validating + */ + public void setJsonSchema(String jsonSchema) { + this.jsonSchema = jsonSchema; + } + + public List<ValidationMessage> validate(File file) throws Exception { + if (schema == null) { + init(); + } + try (InputStream is = new FileInputStream(file)) { + var target = mapper.readTree(is); + return new ArrayList<>(schema.validate(target)); + } + } + + public void init() throws Exception { + model = mapper.readTree(YamlValidator.class.getResourceAsStream(jsonSchema)); + factory = JsonSchemaFactory.getInstance(SpecVersionDetector.detect(model)); + config = SchemaValidatorsConfig.builder().locale(Locale.ENGLISH).build(); + // include deprecated as an unknown keyword so the validator does not WARN log about this + JsonMetaSchema jms = factory.getMetaSchema(DRAFT, null); + jms.getKeywords().put("deprecated", new NonValidationKeyword("deprecated")); + schema = factory.getSchema(model, config); + } + +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java new file mode 100644 index 000000000000..d53e7a107dc3 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/java/org/apache/camel/dsl/yaml/validator/YamlValidatorTest.java @@ -0,0 +1,48 @@ +/* + * 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.camel.dsl.yaml.validator; + +import java.io.File; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class YamlValidatorTest { + + private static YamlValidator validator; + + @BeforeAll + public static void setup() throws Exception { + validator = new YamlValidator(); + validator.init(); + } + + @Test + public void testValidateOk() throws Exception { + Assertions.assertTrue(validator.validate(new File("src/test/resources/foo.yaml")).isEmpty()); + } + + @Test + public void testValidateBad() throws Exception { + var report = validator.validate(new File("src/test/resources/bad.yaml")); + Assertions.assertFalse(report.isEmpty()); + Assertions.assertEquals(1, report.size()); + Assertions.assertTrue(report.get(0).getMessage().contains("setCheese")); + System.out.println(report); + } +} diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/bad.yaml b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/bad.yaml new file mode 100644 index 000000000000..5e660e31edd0 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/bad.yaml @@ -0,0 +1,26 @@ +# +# 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. +# + +- route: + from: + uri: timer:yaml + parameters: + period: "1000" + steps: + - setCheese: + simple: Hello Camel from ${routeId} + - log: ${body} \ No newline at end of file diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/foo.yaml b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/foo.yaml new file mode 100644 index 000000000000..d239b6ba3fc1 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/foo.yaml @@ -0,0 +1,26 @@ +# +# 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. +# + +- route: + from: + uri: timer:yaml + parameters: + period: "1000" + steps: + - setBody: + simple: Hello Camel from ${routeId} + - log: ${body} \ No newline at end of file diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/log4j2.properties b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/log4j2.properties new file mode 100644 index 000000000000..492c47dd3f71 --- /dev/null +++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-validator/src/test/resources/log4j2.properties @@ -0,0 +1,31 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- + +appender.file.type = File +appender.file.name = file +appender.file.fileName = target/camel-yaml-dsl-validator-test.log +appender.file.layout.type = PatternLayout +appender.file.layout.pattern = %d [%-15.15t] %-5p %-30.30c{1} - %m%n + +appender.out.type = Console +appender.out.name = out +appender.out.layout.type = PatternLayout +appender.out.layout.pattern = [%30.30t] %-30.30c{1} %-5p %m%n + +rootLogger.level = INFO +rootLogger.appenderRef.file.ref = file +#rootLogger.appenderRef.out.ref = out \ No newline at end of file diff --git a/dsl/camel-yaml-dsl/pom.xml b/dsl/camel-yaml-dsl/pom.xml index f449b5f0d6c2..5becaa137159 100644 --- a/dsl/camel-yaml-dsl/pom.xml +++ b/dsl/camel-yaml-dsl/pom.xml @@ -37,6 +37,8 @@ <module>camel-yaml-dsl-common</module> <module>camel-yaml-dsl-deserializers</module> <module>camel-yaml-dsl</module> + <module>camel-yaml-dsl-validator</module> + <module>camel-yaml-dsl-validator-maven-plugin</module> </modules> </project> \ No newline at end of file diff --git a/parent/pom.xml b/parent/pom.xml index 4c6c8c48fd43..812ea05f3edf 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -3081,6 +3081,11 @@ <artifactId>camel-yaml-dsl-deserializers</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-yaml-dsl-validator</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-jbang-core</artifactId>
