aglassman
aglassman

Reputation: 2653

Document Image response in Spring REST Docs

I have a REST endpoint that generates random images. I'm using Spring REST Docs, but the Response is all garbled in the http-response.adoc file. Is there an easy way for Mock MVC and REST Docs to store the file somewhere so my .adoc files can reference it?

Upvotes: 1

Views: 1717

Answers (3)

Wim Deblauwe
Wim Deblauwe

Reputation: 26878

An alternative way if you don't need/want to show the images in the docs: Use a ContentModifyingOperationPreprocessor to replace the bytes with some string that makes it clear to the readers of the documentation that there will be some image bytes in the response.

For example:

        mockMvc.perform(get("/api/users/{id}/avatar", user.getId().asString())
                                .with(createCustomerAuth()))
               .andExpect(status().isOk())
               .andDo(document("get-user-avatar-example",
                               null,
                               Preprocessors.preprocessResponse(new ContentModifyingOperationPreprocessor(new ContentModifier() {
                                   @Override
                                   public byte[] modifyContent(byte[] originalContent, MediaType contentType) {
                                       return "<< IMAGE BODY HERE >>".getBytes(StandardCharsets.UTF_8);
                                   }
                               }))));

This generates an adoc file like this:

[source,http,options="nowrap"]
----
HTTP/1.1 200 OK
Content-Type: image/png
Content-Length: 15
Cache-Control: max-age=3600

<< IMAGE BODY HERE >>
----

Upvotes: 0

Philipp Gayret
Philipp Gayret

Reputation: 5070

What you can do is implement a custom Snippet which saves the resulting response. You can use the RestDocumentationContext attribute of the operation you receive to obtain the output directory.

    mockMvc.perform(get("/example"))
            .andDo(document("some-example", operation -> {
                var context = (RestDocumentationContext) operation.getAttributes().get(RestDocumentationContext.class.getName());
                var path = Paths.get(context.getOutputDirectory().getAbsolutePath(), operation.getName(), "response-file.png");
                Files.createDirectories(path.getParent());
                Files.write(path, operation.getResponse().getContent());
            }));

However this will create a .png file in your output directory which generally isn't really useful if you have Asciidoc in a source directory that needs to embed it. So what you can instead do is create an Asciidoc file which contains custom HTML of an image tag with its source being a base64 representation of the response.

    mockMvc.perform(get("/example"))
            .andDo(document("some-example", operation -> {
                var context = (RestDocumentationContext) operation.getAttributes().get(RestDocumentationContext.class.getName());
                var path = Paths.get(context.getOutputDirectory().getAbsolutePath(), operation.getName(), "response-file.adoc");
                var outputStream = new ByteArrayOutputStream();
                outputStream.write("++++\n".getBytes());
                outputStream.write("<img src=\"data:image/png;base64,".getBytes());
                outputStream.write(Base64.getEncoder().encode(operation.getResponse().getContent()));
                outputStream.write("\"/>\n".getBytes());
                outputStream.write("++++\n".getBytes());
                Files.createDirectories(path.getParent());
                Files.write(path, outputStream.toByteArray());
            }));

Although a bit more overhead in terms of space, if you use this, you don't need to mess with referencing build files from your sources.

Upvotes: 1

Max Farsikov
Max Farsikov

Reputation: 2763

Not the perfect but working solution:

class ImageSnippet implements Snippet {

    private final String filePath;

    public ImageSnippet(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public void document(Operation operation) throws IOException {
        byte[] picture = operation.getResponse().getContent();

        Path path = Paths.get(filePath);
        Files.deleteIfExists(path);
        Files.createDirectories(path.getParent());
        Files.createFile(path);

        try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
            fos.write(picture);
        }
    }
}

And usage in MockMvc test (image folder path is important):

    mockMvc.perform(get("/my-profile/barcode")
                      .accept(MediaType.IMAGE_PNG))
            .andExpect(status().isOk())
            .andDo(document("my-profile/barcode",
                      new ImageSnippet("build/asciidoc/html5/images/barcode.png")));

In .adoc template:

 image::barcode.png[]

And here is my build.gradle Asciidoctor configuration (imagesdir is important):

asciidoctor {
    dependsOn test
    backends = ['html5']
    options doctype: 'book'

    attributes = [
            'source-highlighter': 'highlightjs',
            'imagesdir'         : './images',
            'toc'               : 'left',
            'toclevels'         : 3,
            'numbered'          : '',
            'icons'             : 'font',
            'setanchors'        : '',
            'idprefix'          : '',
            'idseparator'       : '-',
            'docinfo1'          : '',
            'safe-mode-unsafe'  : '',
            'allow-uri-read'    : '',
            'snippets'          : snippetsDir,
            linkattrs           : true,
            encoding            : 'utf-8'
    ]

    inputs.dir snippetsDir
    outputDir 'build/asciidoc'
    sourceDir 'src/docs/asciidoc'
    sources {
        include 'index.adoc'
    }
}

Upvotes: 1

Related Questions