diff --git a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java index 5813ae9f3d3..86f4cf42ea1 100644 --- a/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java +++ b/rewrite-core/src/main/java/org/openrewrite/config/YamlResourceLoader.java @@ -131,6 +131,7 @@ public YamlResourceLoader(InputStream yamlInput, * @param source Declarative recipe source * @param properties Placeholder properties * @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used. + * @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies * @throws UncheckedIOException On unexpected IOException */ public YamlResourceLoader(InputStream yamlInput, @@ -138,6 +139,26 @@ public YamlResourceLoader(InputStream yamlInput, Properties properties, @Nullable ClassLoader classLoader, Collection dependencyResourceLoaders) throws UncheckedIOException { + this(yamlInput, source, properties, classLoader, dependencyResourceLoaders, jsonMapper -> { + }); + } + + /** + * Load a declarative recipe, optionally using the specified classloader and optionally including resource loaders + * for recipes from dependencies. + * + * @param yamlInput Declarative recipe yaml input stream + * @param source Declarative recipe source + * @param properties Placeholder properties + * @param classLoader Optional classloader to use with jackson. If not specified, the runtime classloader will be used. + * @param dependencyResourceLoaders Optional resource loaders for recipes from dependencies + * @param mapperCustomizer Customizer for the ObjectMapper + * @throws UncheckedIOException On unexpected IOException + */ + public YamlResourceLoader(InputStream yamlInput, URI source, Properties properties, + @Nullable ClassLoader classLoader, + Collection dependencyResourceLoaders, + Consumer mapperCustomizer) { this.source = source; this.dependencyResourceLoaders = dependencyResourceLoaders; @@ -148,6 +169,8 @@ public YamlResourceLoader(InputStream yamlInput, .build() .registerModule(new ParameterNamesModule()) .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + mapperCustomizer.accept(mapper); maybeAddKotlinModule(mapper); this.classLoader = classLoader; @@ -482,7 +505,7 @@ public Collection listCategoryDescriptors() { @Language("markdown") String packageName = (String) c.get("packageName"); if (packageName.endsWith("." + CategoryTree.CORE) || - packageName.contains("." + CategoryTree.CORE + ".")) { + packageName.contains("." + CategoryTree.CORE + ".")) { throw new IllegalArgumentException("The package name 'core' is reserved."); } diff --git a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java index 0752bbb78e2..828c4612b8f 100644 --- a/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java +++ b/rewrite-core/src/test/java/org/openrewrite/RecipeLifecycleTest.java @@ -41,8 +41,7 @@ import java.util.UUID; import static java.util.Objects.requireNonNull; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.assertj.core.api.Assertions.*; import static org.openrewrite.Recipe.noop; import static org.openrewrite.test.RewriteTest.toRecipe; import static org.openrewrite.test.SourceSpecs.text; @@ -276,47 +275,48 @@ public PlainText visitText(PlainText text, ExecutionContext ctx) { @Test void canCallImperativeRecipeWithoutArgsFromDeclarative() { rewriteRun(spec -> spec.recipeFromYaml(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe - displayName: Test Recipe - description: Test Recipe. - recipeList: - - org.openrewrite.NoArgRecipe - """, - "test.recipe" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + description: Test Recipe. + recipeList: + - org.openrewrite.NoArgRecipe + """, + "test.recipe" ), text("Hi", "NoArgRecipeHi")); } @Test - void canCallImperativeRecipeWithUnnecessaryArgsFromDeclarative() { - rewriteRun(spec -> spec.recipeFromYaml(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe - displayName: Test Recipe - description: Test Recipe. - recipeList: - - org.openrewrite.NoArgRecipe: - foo: bar - """, - "test.recipe" - ), - text("Hi", "NoArgRecipeHi")); + void canNotCallImperativeRecipeWithUnnecessaryArgsFromDeclarativeInTests() { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + rewriteRun(spec -> spec.recipeFromYaml(""" + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + description: Test Recipe. + recipeList: + - org.openrewrite.NoArgRecipe: + foo: bar + """, + "test.recipe" + ), + text("Hi", "NoArgRecipeHi"))); } @Test void canCallRecipeWithNoExplicitConstructor() { rewriteRun(spec -> spec.recipeFromYaml(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe - displayName: Test Recipe - description: Test Recipe. - recipeList: - - org.openrewrite.DefaultConstructorRecipe - """, + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe + displayName: Test Recipe + description: Test Recipe. + recipeList: + - org.openrewrite.DefaultConstructorRecipe + """, "test.recipe" ), text("Hi", "DefaultConstructorRecipeHi")); @@ -325,28 +325,28 @@ void canCallRecipeWithNoExplicitConstructor() { @Test void declarativeRecipeChain() { rewriteRun(spec -> spec.recipeFromYaml(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.a - displayName: Test Recipe - description: Test Recipe. - recipeList: - - test.recipe.b - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.b - displayName: Test Recipe - description: Test Recipe. - recipeList: - - test.recipe.c - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.c - displayName: Test Recipe - description: Test Recipe. - recipeList: - - org.openrewrite.NoArgRecipe - """, + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.a + displayName: Test Recipe + description: Test Recipe. + recipeList: + - test.recipe.b + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.b + displayName: Test Recipe + description: Test Recipe. + recipeList: + - test.recipe.c + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.c + displayName: Test Recipe + description: Test Recipe. + recipeList: + - org.openrewrite.NoArgRecipe + """, "test.recipe.a" ), text("Hi", "NoArgRecipeHi")); @@ -356,34 +356,34 @@ void declarativeRecipeChain() { void declarativeRecipeChainAcrossFiles() { rewriteRun(spec -> spec.recipe(Environment.builder() .load(new YamlResourceLoader(new ByteArrayInputStream(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.c - displayName: Test Recipe - description: Test Recipe. - recipeList: - - org.openrewrite.NoArgRecipe - """.getBytes()), + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.c + displayName: Test Recipe + description: Test Recipe. + recipeList: + - org.openrewrite.NoArgRecipe + """.getBytes()), URI.create("rewrite.yml"), new Properties())) .load(new YamlResourceLoader(new ByteArrayInputStream(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.b - displayName: Test Recipe - description: Test Recipe. - recipeList: - - test.recipe.c - """.getBytes()), + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.b + displayName: Test Recipe + description: Test Recipe. + recipeList: + - test.recipe.c + """.getBytes()), URI.create("rewrite.yml"), new Properties())) .load(new YamlResourceLoader(new ByteArrayInputStream(""" - --- - type: specs.openrewrite.org/v1beta/recipe - name: test.recipe.a - displayName: Test Recipe - description: Test Recipe. - recipeList: - - test.recipe.b - """.getBytes()), + --- + type: specs.openrewrite.org/v1beta/recipe + name: test.recipe.a + displayName: Test Recipe + description: Test Recipe. + recipeList: + - test.recipe.b + """.getBytes()), URI.create("rewrite.yml"), new Properties())) .build() .activateRecipes("test.recipe.a")), diff --git a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java index b00d6f6c4b8..0ff2bb80f28 100644 --- a/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java +++ b/rewrite-test/src/main/java/org/openrewrite/test/RecipeSpec.java @@ -15,6 +15,7 @@ */ package org.openrewrite.test; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.dataformat.csv.CsvMapper; import com.fasterxml.jackson.dataformat.csv.CsvSchema; @@ -145,7 +146,8 @@ public RecipeSpec recipeFromResources(String... activeRecipes) { private static Recipe recipeFromInputStream(InputStream yaml, String... activeRecipes) { return Environment.builder() - .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties())) + .load(new YamlResourceLoader(yaml, URI.create("rewrite.yml"), new Properties(), null, Collections.emptyList(), + mapper -> mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES))) .build() .activateRecipes(activeRecipes); }