From 6578f4273069357d21d6138f2b1b97aa207763ea Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 8 Feb 2016 13:41:24 +0000 Subject: [PATCH] Allow custom templates to be provided for a specific template format Previously, custom snippet templates were loaded from org/springframework/restdocs/templates and the default templates were loaded from org/springframework/restdocs/templates/{$formatId}. Without relying on the ordering of the classpath, this made it impossible to provide a custom template for a specific format. This commit updates the locations that are checked for snippet templates. The following locations are now checked in order: 1. org/springframework/restdocs/templates/${formatId}/${name}.snippet 2. org/springframework/restdocs/templates/${name}.snippet 3. org/springframework/restdocs/templates/${formatId}/default-${name}.snippet The second location is provided largely for backwards compatibility with 1.0. Users are expected to use the first location to provide any custom templates, with Spring REST Docs provided templates for all of the built-in snippets in the third location. Closes gh-196 Closes gh-197 --- .../docs/asciidoc/documenting-your-api.adoc | 10 ++- .../StandardTemplateResourceResolver.java | 57 +++++++++---- ...t.snippet => default-curl-request.snippet} | 0 ...t.snippet => default-http-request.snippet} | 0 ....snippet => default-http-response.snippet} | 0 .../{links.snippet => default-links.snippet} | 0 ...nippet => default-path-parameters.snippet} | 0 ...snippet => default-request-fields.snippet} | 0 ...nippet => default-request-headers.snippet} | 0 ...pet => default-request-parameters.snippet} | 0 ...nippet => default-response-fields.snippet} | 0 ...ippet => default-response-headers.snippet} | 0 ...t.snippet => default-curl-request.snippet} | 0 ...t.snippet => default-http-request.snippet} | 0 ....snippet => default-http-response.snippet} | 0 .../{links.snippet => default-links.snippet} | 0 ...nippet => default-path-parameters.snippet} | 0 ...snippet => default-request-fields.snippet} | 0 ...nippet => default-request-headers.snippet} | 0 ...pet => default-request-parameters.snippet} | 0 ...nippet => default-response-fields.snippet} | 0 ...ippet => default-response-headers.snippet} | 0 ...StandardTemplateResourceResolverTests.java | 82 +++++++++++++------ .../{test.snippet => test-custom.snippet} | 0 .../restdocs/templates/test-default.snippet | 0 .../test-format-specific-custom.snippet | 0 26 files changed, 108 insertions(+), 41 deletions(-) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{curl-request.snippet => default-curl-request.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{http-request.snippet => default-http-request.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{http-response.snippet => default-http-response.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{links.snippet => default-links.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{path-parameters.snippet => default-path-parameters.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{request-fields.snippet => default-request-fields.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{request-headers.snippet => default-request-headers.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{request-parameters.snippet => default-request-parameters.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{response-fields.snippet => default-response-fields.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/{response-headers.snippet => default-response-headers.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{curl-request.snippet => default-curl-request.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{http-request.snippet => default-http-request.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{http-response.snippet => default-http-response.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{links.snippet => default-links.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{path-parameters.snippet => default-path-parameters.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{request-fields.snippet => default-request-fields.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{request-headers.snippet => default-request-headers.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{request-parameters.snippet => default-request-parameters.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{response-fields.snippet => default-response-fields.snippet} (100%) rename spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/{response-headers.snippet => default-response-headers.snippet} (100%) rename spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/{test.snippet => test-custom.snippet} (100%) create mode 100644 spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-default.snippet create mode 100644 spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-format-specific-custom.snippet diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index c68b9d43..fb9977c8 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -627,10 +627,14 @@ generated snippets. templates] are provided for each of the snippets that Spring REST Docs can produce. To customize a snippet's content, you can provide your own template. -Templates are loaded from the classpath in the `org.springframework.restdocs.templates` -package and each template is named after the snippet that it will produce. For example, to +Templates are loaded from the classpath from an `org.springframework.restdocs.templates` +subpackage. The name of the subpackage is determined by the ID of the template format +that is in use. The default template format, Asciidoctor, has the ID `asciidoctor` so +snippets are loaded from `org.springframework.restdocs.templates.asciidoctor`. Each +template is named after the snippet that it will produce. For example, to override the template for the `curl-request.adoc` snippet, create a template named -`curl-request.snippet` in `src/test/resources/org/springframework/restdocs/templates`. +`curl-request.snippet` in +`src/test/resources/org/springframework/restdocs/templates/asciidoctor`. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/StandardTemplateResourceResolver.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/StandardTemplateResourceResolver.java index 22912eb3..7e0b38ac 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/StandardTemplateResourceResolver.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/templates/StandardTemplateResourceResolver.java @@ -22,12 +22,20 @@ import org.springframework.core.io.Resource; /** * Standard implementation of {@link TemplateResourceResolver}. *

- * Templates are resolved by looking for a resource on the classpath named - * {@code org/springframework/restdocs/templates/{name}.snippet}. If no such - * resource exists an attempt is made to return a default resource that is appropriate for - * the configured snippet format. + * Templates are resolved by looking for resources on the classpath. The following + * locations are checked in order: + *

    + *
  1. + * org/springframework/restdocs/templates/${templateFormatId}/${name}.snippet + *
  2. + *
  3. org/springframework/restdocs/templates/${name}.snippet
  4. + *
  5. + * org/springframework/restdocs/templates/${templateFormatId}/default-${name}.snippet + *
  6. + *
* * @author Andy Wilkinson + * @see TemplateFormat#getId() */ public class StandardTemplateResourceResolver implements TemplateResourceResolver { @@ -57,18 +65,37 @@ public class StandardTemplateResourceResolver implements TemplateResourceResolve @Override public Resource resolveTemplateResource(String name) { - ClassPathResource classPathResource = new ClassPathResource( - "org/springframework/restdocs/templates/" + name + ".snippet"); - if (!classPathResource.exists()) { - classPathResource = new ClassPathResource( - "org/springframework/restdocs/templates/" - + this.templateFormat.getId() + "/" + name + ".snippet"); - if (!classPathResource.exists()) { - throw new IllegalStateException("Template named '" + name - + "' could not be resolved"); - } + Resource formatSpecificCustomTemplate = getFormatSpecificCustomTemplate(name); + if (formatSpecificCustomTemplate.exists()) { + return formatSpecificCustomTemplate; } - return classPathResource; + Resource customTemplate = getCustomTemplate(name); + if (customTemplate.exists()) { + return customTemplate; + } + Resource defaultTemplate = getDefaultTemplate(name); + if (defaultTemplate.exists()) { + return defaultTemplate; + } + throw new IllegalStateException("Template named '" + name + + "' could not be resolved"); + } + + private Resource getFormatSpecificCustomTemplate(String name) { + return new ClassPathResource(String.format( + "org/springframework/restdocs/templates/%s/%s.snippet", + this.templateFormat.getId(), name)); + } + + private Resource getCustomTemplate(String name) { + return new ClassPathResource(String.format( + "org/springframework/restdocs/templates/%s.snippet", name)); + } + + private Resource getDefaultTemplate(String name) { + return new ClassPathResource(String.format( + "org/springframework/restdocs/templates/%s/default-%s.snippet", + this.templateFormat.getId(), name)); } } diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/curl-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-curl-request.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/curl-request.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-curl-request.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/http-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-http-request.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/http-request.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-http-request.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/http-response.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-http-response.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/http-response.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-http-response.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/links.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-links.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/links.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-links.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/path-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-path-parameters.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/path-parameters.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-path-parameters.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-fields.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-fields.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-fields.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-headers.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-headers.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-headers.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-parameters.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/request-parameters.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-request-parameters.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/response-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-response-fields.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/response-fields.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-response-fields.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/response-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-response-headers.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/response-headers.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-response-headers.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/curl-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-curl-request.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/curl-request.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-curl-request.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/http-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-http-request.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/http-request.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-http-request.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/http-response.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-http-response.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/http-response.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-http-response.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/links.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-links.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/links.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-links.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/path-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-path-parameters.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/path-parameters.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-path-parameters.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-fields.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-fields.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-fields.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-headers.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-headers.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-headers.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-parameters.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-parameters.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/request-parameters.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-request-parameters.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/response-fields.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-response-fields.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/response-fields.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-response-fields.snippet diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/response-headers.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-response-headers.snippet similarity index 100% rename from spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/response-headers.snippet rename to spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-response-headers.snippet diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java index 8d8ef578..1d152335 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/templates/StandardTemplateResourceResolverTests.java @@ -47,29 +47,17 @@ public class StandardTemplateResourceResolverTests { private final TestClassLoader classLoader = new TestClassLoader(); @Test - public void customSnippetResolution() throws Exception { - this.classLoader.addResource( - "org/springframework/restdocs/templates/test.snippet", getClass() - .getResource("test.snippet")); - Resource snippet = doWithThreadContextClassLoader(this.classLoader, - new Callable() { - - @Override - public Resource call() { - return StandardTemplateResourceResolverTests.this.resolver - .resolveTemplateResource("test"); - } - - }); - - assertThat(snippet.getURL(), is(equalTo(getClass().getResource("test.snippet")))); - } - - @Test - public void fallsBackToDefaultSnippet() throws Exception { + public void formatSpecificCustomSnippetHasHighestPrecedence() throws Exception { this.classLoader.addResource( "org/springframework/restdocs/templates/asciidoctor/test.snippet", - getClass().getResource("test.snippet")); + getClass().getResource("test-format-specific-custom.snippet")); + this.classLoader.addResource( + "org/springframework/restdocs/templates/test.snippet", getClass() + .getResource("test-custom.snippet")); + this.classLoader + .addResource( + "org/springframework/restdocs/templates/asciidoctor/default-test.snippet", + getClass().getResource("test-default.snippet")); Resource snippet = doWithThreadContextClassLoader(this.classLoader, new Callable() { @@ -81,11 +69,59 @@ public class StandardTemplateResourceResolverTests { }); - assertThat(snippet.getURL(), is(equalTo(getClass().getResource("test.snippet")))); + assertThat( + snippet.getURL(), + is(equalTo(getClass().getResource("test-format-specific-custom.snippet")))); } @Test - public void failsIfCustomAndDefaultSnippetDoNotExist() throws Exception { + public void generalCustomSnippetIsUsedInAbsenceOfFormatSpecificCustomSnippet() + throws Exception { + this.classLoader.addResource( + "org/springframework/restdocs/templates/test.snippet", getClass() + .getResource("test-custom.snippet")); + this.classLoader + .addResource( + "org/springframework/restdocs/templates/asciidoctor/default-test.snippet", + getClass().getResource("test-default.snippet")); + Resource snippet = doWithThreadContextClassLoader(this.classLoader, + new Callable() { + + @Override + public Resource call() { + return StandardTemplateResourceResolverTests.this.resolver + .resolveTemplateResource("test"); + } + + }); + + assertThat(snippet.getURL(), + is(equalTo(getClass().getResource("test-custom.snippet")))); + } + + @Test + public void defaultSnippetIsUsedInAbsenceOfCustomSnippets() throws Exception { + this.classLoader + .addResource( + "org/springframework/restdocs/templates/asciidoctor/default-test.snippet", + getClass().getResource("test-default.snippet")); + Resource snippet = doWithThreadContextClassLoader(this.classLoader, + new Callable() { + + @Override + public Resource call() { + return StandardTemplateResourceResolverTests.this.resolver + .resolveTemplateResource("test"); + } + + }); + + assertThat(snippet.getURL(), + is(equalTo(getClass().getResource("test-default.snippet")))); + } + + @Test + public void failsIfCustomAndDefaultSnippetsDoNotExist() throws Exception { this.thrown.expect(IllegalStateException.class); this.thrown.expectMessage(equalTo("Template named 'test' could not be resolved")); doWithThreadContextClassLoader(this.classLoader, new Callable() { diff --git a/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test.snippet b/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-custom.snippet similarity index 100% rename from spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test.snippet rename to spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-custom.snippet diff --git a/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-default.snippet b/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-default.snippet new file mode 100644 index 00000000..e69de29b diff --git a/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-format-specific-custom.snippet b/spring-restdocs-core/src/test/resources/org/springframework/restdocs/templates/test-format-specific-custom.snippet new file mode 100644 index 00000000..e69de29b