spring-restdocs
spring-restdocs copied to clipboard
Provide an option to switch off the curl snippet's use of -i
Situation
In case that a REST Endpoint provide a binary content ex. application/zip
. The generated curl request snippet should not contains the curl options -i
. Because it include HTTP headers in the curl response stream and processing of that content will fail.
HINT
Most user will copy that curl command and save the binary content with curl option -o export.zip
.
@RestController
@RequestMapping("/foo")
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class FooResource {
@GetMapping(path = "/export", produces = "application/zip")
public ResponseEntity<byte[]> export ()
throws IOException {
ZipFile zipFile = ....
return ResponseEntity.ok(toByteArray(zipFile.asInputStream()));
}
}
curl "https://application/foo/export" -i -X GET \
-H 'Accept: application/zip'
Proposal
In case the response payload is not text based the curl option -i
will not added in the snippet
Workaround Create a custom CurlRequestSnippet which remove the options -i and register it
private static class CustomCurlRequestSnippet extends CurlRequestSnippet {
protected CustomCurlRequestSnippet (CommandFormatter commandFormatter) {
super(commandFormatter);
}
@Override
protected Map<String, Object> createModel(Operation operation) {
Map<String, Object> model = super.createModel(operation);
MediaType responseContentType = operation.getResponse().getHeaders().getContentType();
if (responseContentType != null
&& operation.getResponse().getContent().length > 0
&& !responseContentType.isCompatibleWith(APPLICATION_JSON)) {
String options = (String)model.get("options");
model.put("options", options.replace("-i ", ""));
}
return model;
}
}
MockMvcRestDocumentation.documentationConfiguration(restDocumentation)
.uris()
.withScheme("https")
.withHost("foo")
.withPort(443)
.and()
.snippets()
.withAdditionalDefaults(new CustomCurlRequestSnippet(CliDocumentation.multiLineFormat()))
.and()
.operationPreprocessors()
.withRequestDefaults(prettyPrint(), replaceBinaryContentWithBashDataVariable())
.withResponseDefaults(prettyPrint(), replaceBinaryContentWithBashDataVariable());
It's an interesting problem, but I think it applies to more than just binary content. For example, it's quite common to use curl and then pipe the response into jq
. There are really two conflicting use cases here:
- Exploring an API and examining the full details of the responses
- Using the API with curl and piping the response body to a file or another command
The current situation hinders 2 and the proposal here will hinder 1. I can't think of an alternative that will enable both. A compromise would be to make it easier to configure whether or not -i
is included and allow users to decide. If it were easier to configure, changing the default could also be considered. This is a compromise though. If anyone has an alternative suggestion that even just comes closer to enabling both use cases, I'd love to hear it.
The HTTPie snippets do not have this problem. HTTPie's default behaviour is to show the full response (headers and body) when output is going to the terminal. When output has been redirected it changes its defaults so that only the body is output and binary data isn't suppressed. The problem described in this issue is largely a usability problem with curl. There's an argument to be made that the best solution is to use HTTPie rather than curl.
@wilkinsona
First i would like to thank you for the constructive critique. The jq
use case is a valid objection and i don't see at the moment a solution which solve both requirements (exploring and using the API).
A compromise would be to make it easier to configure whether or not -i is included and allow users to decide.
I agree the only one who probably know the customers respective the concrete integration is the REST API writer.
There's an argument to be made that the best solution is to use HTTPie rather than curl.
hmmm...it depends...in my case the customers are system administrators which prefer the curl snippets.
This worked perfectly for me. And, for anyone interested, I used this class from Kotlin, and this worked for me after a few tweaks to what IntelliJ did to convert it for me:
package com.concur.sca
import org.springframework.http.MediaType
import org.springframework.http.MediaType.APPLICATION_JSON
import org.springframework.restdocs.cli.CommandFormatter
import org.springframework.restdocs.cli.CurlRequestSnippet
import org.springframework.restdocs.operation.Operation
class CustomCurlRequestSnippet(commandFormatter: CommandFormatter?) :
CurlRequestSnippet(commandFormatter) {
override fun createModel(operation: Operation): Map<String, Any> {
val model = super.createModel(operation)
val responseContentType : MediaType? = operation.response.headers!!.contentType
if (responseContentType != null &&
operation.response.content.isNotEmpty() && !responseContentType.isCompatibleWith(APPLICATION_JSON)
) {
val options = model["options"] as String?
model["options"] = options!!.replace("-i ", "")
}
return model
}
}
I am now able to literally copy-paste-run examples from my API doc in our testing environment and pipe the output to jq
for beautifying without any issue at all.
Thanks again!