Line breaks in cURL and HTTPie snippets

Closes gh-260
This commit is contained in:
Tomasz Kopczynski
2017-02-12 21:51:17 +01:00
committed by Andy Wilkinson
parent 82164348f3
commit 2cb9d36ef1
10 changed files with 508 additions and 199 deletions

View File

@@ -35,6 +35,8 @@ public abstract class CliDocumentation {
}
private static final CommandFormatter defaultCommandFormatter = multiLineFormat();
/**
* Returns a new {@code Snippet} that will document the curl request for the API
* operation.
@@ -42,7 +44,7 @@ public abstract class CliDocumentation {
* @return the snippet that will document the curl request
*/
public static Snippet curlRequest() {
return new CurlRequestSnippet();
return curlRequest(defaultCommandFormatter);
}
/**
@@ -54,7 +56,31 @@ public abstract class CliDocumentation {
* @return the snippet that will document the curl request
*/
public static Snippet curlRequest(Map<String, Object> attributes) {
return new CurlRequestSnippet(attributes);
return curlRequest(attributes, defaultCommandFormatter);
}
/**
* Returns a new {@code Snippet} that will document the curl request for the API
* operation. The given {@code commandFormatter} will be used for formatting the snippet.
*
* @param commandFormatter the command formatter
* @return the snippet that will document the curl request
*/
public static Snippet curlRequest(CommandFormatter commandFormatter) {
return curlRequest(null, commandFormatter);
}
/**
* Returns a new {@code Snippet} that will document the curl request for the API
* operation. The given {@code attributes} will be available during snippet
* generation. The given {@code commandFormatter} will be used for formatting the snippet.
*
* @param attributes the attributes
* @param commandFormatter the command formatter
* @return the snippet that will document the curl request
*/
public static Snippet curlRequest(Map<String, Object> attributes, CommandFormatter commandFormatter) {
return new CurlRequestSnippet(attributes, commandFormatter);
}
/**
@@ -64,7 +90,7 @@ public abstract class CliDocumentation {
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest() {
return new HttpieRequestSnippet();
return httpieRequest(defaultCommandFormatter);
}
/**
@@ -76,7 +102,47 @@ public abstract class CliDocumentation {
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest(Map<String, Object> attributes) {
return new HttpieRequestSnippet(attributes);
return httpieRequest(attributes, defaultCommandFormatter);
}
/**
* Returns a new {@code Snippet} that will document the HTTPie request for the API
* operation. The given {@code commandFormatter} will be used for formatting the snippet.
*
* @param commandFormatter the command formatter
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest(CommandFormatter commandFormatter) {
return httpieRequest(null, defaultCommandFormatter);
}
/**
* Returns a new {@code Snippet} that will document the HTTPie request for the API
* operation. The given {@code attributes} will be available during snippet
* generation. The given {@code commandFormatter} will be used for formatting the snippet.
*
* @param attributes the attributes
* @param commandFormatter the command formatter
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest(Map<String, Object> attributes, CommandFormatter commandFormatter) {
return new HttpieRequestSnippet(attributes, commandFormatter);
}
/**
* Creates a new {@code CommandFormatter} which formats input to a multi line output.
*
* @return A multi line {@code commandFormatter}
*/
public static CommandFormatter multiLineFormat() {
return new ConcatenatingCommandFormatter(" \\%n ");
}
/**
* Creates a new {@code CommandFormatter} which formats input to a single line output.
*
* @return A single line {@code CommandFormatter}
*/
public static CommandFormatter singleLineFormat() {
return new ConcatenatingCommandFormatter(" ");
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed 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.springframework.restdocs.cli;
import java.util.List;
/**
* Formatter for {@link CurlRequestSnippet} and {@link HttpieRequestSnippet}.
* Its purpose is to format a command snippet from a list of its parts represented
* as {@code String}s.
*
* @author Tomasz Kopczynski
*/
public interface CommandFormatter {
/**
* Formats a list of {@code String}s into a single {@code String}.
*
* @param elements A list of {@code String}s to be formatted
* @return A list of {@code String}s formatted as one {@code String}
*/
String format(List<String> elements);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2014-2017 the original author or authors.
*
* Licensed 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.springframework.restdocs.cli;
import java.util.List;
import org.springframework.util.CollectionUtils;
/**
* {@link CommandFormatter} which concatenates commands with a given {@code separator}.
*
* @author Tomasz Kopczynski
*/
final class ConcatenatingCommandFormatter implements CommandFormatter {
private String separator;
ConcatenatingCommandFormatter(String separator) {
this.separator = separator;
}
/**
* Concatenates a list of {@code String}s with a specified separator.
*
* @param elements A list of {@code String}s to be concatenated
* @return Concatenated list of {@code String}s as one {@code String}
*/
@Override
public String format(List<String> elements) {
if (CollectionUtils.isEmpty(elements)) {
return "";
}
StringBuilder result = new StringBuilder();
for (String element : elements) {
result.append(String.format(this.separator));
result.append(element);
}
return result.toString();
}
}

View File

@@ -16,8 +16,7 @@
package org.springframework.restdocs.cli;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -31,6 +30,7 @@ import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.RequestCookie;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -45,11 +45,23 @@ import org.springframework.util.StringUtils;
*/
public class CurlRequestSnippet extends TemplatedSnippet {
private final CommandFormatter commandFormatter;
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
*/
@Deprecated
protected CurlRequestSnippet() {
this(null);
this(null, null);
}
/**
* Creates a new {@code CurlRequestSnippet} with a given {@link CommandFormatter}.
*
* @param commandFormatter The formatter for generating the snippet
*/
protected CurlRequestSnippet(CommandFormatter commandFormatter) {
this(null, commandFormatter);
}
/**
@@ -58,8 +70,24 @@ public class CurlRequestSnippet extends TemplatedSnippet {
*
* @param attributes The additional attributes
*/
@Deprecated
protected CurlRequestSnippet(Map<String, Object> attributes) {
this(attributes, null);
}
/**
* Creates a new {@code CurlRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering
* and the given {@link CommandFormatter}.
*
* @param attributes The additional attributes
* @param commandFormatter The formatter for generating the snippet
*/
protected CurlRequestSnippet(Map<String, Object> attributes, CommandFormatter commandFormatter) {
super("curl-request", attributes);
Assert.notNull(commandFormatter, "Command formatter must be set");
this.commandFormatter = commandFormatter;
}
@Override
@@ -87,21 +115,25 @@ public class CurlRequestSnippet extends TemplatedSnippet {
}
private String getOptions(Operation operation) {
StringWriter command = new StringWriter();
PrintWriter printer = new PrintWriter(command);
writeIncludeHeadersInOutputOption(printer);
CliOperationRequest request = new CliOperationRequest(operation.getRequest());
writeUserOptionIfNecessary(request, printer);
writeHttpMethodIfNecessary(request, printer);
writeHeaders(request, printer);
writeCookies(request, printer);
writePartsIfNecessary(request, printer);
writeContent(request, printer);
StringBuilder builder = new StringBuilder();
writeIncludeHeadersInOutputOption(builder);
return command.toString();
CliOperationRequest request = new CliOperationRequest(operation.getRequest());
writeUserOptionIfNecessary(request, builder);
writeHttpMethodIfNecessary(request, builder);
List<String> additionaLines = new ArrayList<>();
writeHeaders(request, additionaLines);
writeCookies(request, additionaLines);
writePartsIfNecessary(request, additionaLines);
writeContent(request, additionaLines);
builder.append(this.commandFormatter.format(additionaLines));
return builder.toString();
}
private void writeCookies(CliOperationRequest request, PrintWriter printer) {
private void writeCookies(CliOperationRequest request, List<String> lines) {
if (!CollectionUtils.isEmpty(request.getCookies())) {
StringBuilder cookiesBuilder = new StringBuilder();
for (RequestCookie cookie : request.getCookies()) {
@@ -111,79 +143,82 @@ public class CurlRequestSnippet extends TemplatedSnippet {
cookiesBuilder.append(
String.format("%s=%s", cookie.getName(), cookie.getValue()));
}
printer.print(String.format(" --cookie '%s'", cookiesBuilder.toString()));
lines.add(String.format("--cookie '%s'", cookiesBuilder.toString()));
}
}
private void writeIncludeHeadersInOutputOption(PrintWriter writer) {
writer.print("-i");
private void writeIncludeHeadersInOutputOption(StringBuilder builder) {
builder.append("-i");
}
private void writeUserOptionIfNecessary(CliOperationRequest request,
PrintWriter writer) {
StringBuilder builder) {
String credentials = request.getBasicAuthCredentials();
if (credentials != null) {
writer.print(String.format(" -u '%s'", credentials));
builder.append(String.format(" -u '%s'", credentials));
}
}
private void writeHttpMethodIfNecessary(OperationRequest request,
PrintWriter writer) {
StringBuilder builder) {
if (!HttpMethod.GET.equals(request.getMethod())) {
writer.print(String.format(" -X %s", request.getMethod()));
builder.append(String.format(" -X %s", request.getMethod()));
}
}
private void writeHeaders(CliOperationRequest request, PrintWriter writer) {
private void writeHeaders(CliOperationRequest request, List<String> lines) {
for (Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
for (String header : entry.getValue()) {
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
lines.add(String.format("-H '%s: %s'", entry.getKey(), header));
}
}
}
private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) {
private void writePartsIfNecessary(OperationRequest request, List<String> lines) {
for (OperationRequestPart part : request.getParts()) {
writer.printf(" -F '%s=", part.getName());
StringBuilder oneLine = new StringBuilder();
oneLine.append(String.format("-F '%s=", part.getName()));
if (!StringUtils.hasText(part.getSubmittedFileName())) {
writer.append(part.getContentAsString());
oneLine.append(part.getContentAsString());
}
else {
writer.printf("@%s", part.getSubmittedFileName());
oneLine.append(String.format("@%s", part.getSubmittedFileName()));
}
if (part.getHeaders().getContentType() != null) {
writer.append(";type=")
.append(part.getHeaders().getContentType().toString());
oneLine.append(";type=");
oneLine.append(part.getHeaders().getContentType().toString());
}
writer.append("'");
oneLine.append("'");
lines.add(oneLine.toString());
}
}
private void writeContent(CliOperationRequest request, PrintWriter writer) {
private void writeContent(CliOperationRequest request, List<String> lines) {
String content = request.getContentAsString();
if (StringUtils.hasText(content)) {
writer.print(String.format(" -d '%s'", content));
lines.add(String.format("-d '%s'", content));
}
else if (!request.getParts().isEmpty()) {
for (Entry<String, List<String>> entry : request.getParameters().entrySet()) {
for (String value : entry.getValue()) {
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
lines.add(String.format("-F '%s=%s'", entry.getKey(), value));
}
}
}
else if (request.isPutOrPost()) {
writeContentUsingParameters(request, writer);
writeContentUsingParameters(request, lines);
}
}
private void writeContentUsingParameters(OperationRequest request,
PrintWriter writer) {
List<String> lines) {
Parameters uniqueParameters = request.getParameters()
.getUniqueParameters(request.getUri());
String queryString = uniqueParameters.toQueryString();
if (StringUtils.hasText(queryString)) {
writer.print(String.format(" -d '%s'", queryString));
lines.add(String.format("-d '%s'", queryString));
}
}

View File

@@ -18,6 +18,7 @@ package org.springframework.restdocs.cli;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,6 +34,7 @@ import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.RequestCookie;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -46,11 +48,23 @@ import org.springframework.util.StringUtils;
*/
public class HttpieRequestSnippet extends TemplatedSnippet {
private final CommandFormatter commandFormatter;
/**
* Creates a new {@code HttpieRequestSnippet} with no additional attributes.
*/
@Deprecated
protected HttpieRequestSnippet() {
this(null);
this(null, null);
}
/**
* Creates a new {@code HttpieRequestSnippet} with the given {@link CommandFormatter}.
*
* @param commandFormatter The formatter for generating the snippet
*/
protected HttpieRequestSnippet(CommandFormatter commandFormatter) {
this(null, commandFormatter);
}
/**
@@ -59,8 +73,24 @@ public class HttpieRequestSnippet extends TemplatedSnippet {
*
* @param attributes The additional attributes
*/
@Deprecated
protected HttpieRequestSnippet(Map<String, Object> attributes) {
this(attributes, null);
}
/**
* Creates a new {@code HttpieRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering
* and the given {@link CommandFormatter}.
*
* @param attributes The additional attributes
* @param commandFormatter The formatter for generating the snippet
*/
protected HttpieRequestSnippet(Map<String, Object> attributes, CommandFormatter commandFormatter) {
super("httpie-request", attributes);
Assert.notNull(commandFormatter, "Command formatter must be set");
this.commandFormatter = commandFormatter;
}
@Override
@@ -103,13 +133,14 @@ public class HttpieRequestSnippet extends TemplatedSnippet {
}
private String getRequestItems(CliOperationRequest request) {
StringWriter requestItems = new StringWriter();
PrintWriter printer = new PrintWriter(requestItems);
writeFormDataIfNecessary(request, printer);
writeHeaders(request, printer);
writeCookies(request, printer);
writeParametersIfNecessary(request, printer);
return requestItems.toString();
List<String> lines = new ArrayList<>();
writeFormDataIfNecessary(request, lines);
writeHeaders(request, lines);
writeCookies(request, lines);
writeParametersIfNecessary(request, lines);
return this.commandFormatter.format(lines);
}
private void writeOptions(OperationRequest request, PrintWriter writer) {
@@ -136,20 +167,23 @@ public class HttpieRequestSnippet extends TemplatedSnippet {
writer.print(String.format("%s", request.getMethod().name()));
}
private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) {
private void writeFormDataIfNecessary(OperationRequest request, List<String> lines) {
for (OperationRequestPart part : request.getParts()) {
writer.printf(" \\%n '%s'", part.getName());
StringBuilder oneLine = new StringBuilder();
oneLine.append(String.format("'%s'", part.getName()));
if (!StringUtils.hasText(part.getSubmittedFileName())) {
// https://github.com/jkbrzt/httpie/issues/342
writer.printf("@<(echo '%s')", part.getContentAsString());
oneLine.append(String.format("@<(echo '%s')", part.getContentAsString()));
}
else {
writer.printf("@'%s'", part.getSubmittedFileName());
oneLine.append(String.format("@'%s'", part.getSubmittedFileName()));
}
lines.add(oneLine.toString());
}
}
private void writeHeaders(OperationRequest request, PrintWriter writer) {
private void writeHeaders(OperationRequest request, List<String> lines) {
HttpHeaders headers = request.getHeaders();
for (Entry<String, List<String>> entry : headers.entrySet()) {
for (String header : entry.getValue()) {
@@ -159,41 +193,41 @@ public class HttpieRequestSnippet extends TemplatedSnippet {
&& header.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
continue;
}
writer.print(String.format(" '%s:%s'", entry.getKey(), header));
lines.add(String.format("'%s:%s'", entry.getKey(), header));
}
}
}
private void writeCookies(OperationRequest request, PrintWriter writer) {
private void writeCookies(OperationRequest request, List<String> lines) {
for (RequestCookie cookie : request.getCookies()) {
writer.print(String.format(" 'Cookie:%s=%s'", cookie.getName(),
lines.add(String.format("'Cookie:%s=%s'", cookie.getName(),
cookie.getValue()));
}
}
private void writeParametersIfNecessary(CliOperationRequest request,
PrintWriter writer) {
List<String> lines) {
if (StringUtils.hasText(request.getContentAsString())) {
return;
}
if (!request.getParts().isEmpty()) {
writeContentUsingParameters(request.getParameters(), writer);
writeContentUsingParameters(request.getParameters(), lines);
}
else if (request.isPutOrPost()) {
writeContentUsingParameters(
request.getParameters().getUniqueParameters(request.getUri()),
writer);
lines);
}
}
private void writeContentUsingParameters(Parameters parameters, PrintWriter writer) {
private void writeContentUsingParameters(Parameters parameters, List<String> lines) {
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
if (entry.getValue().isEmpty()) {
writer.append(String.format(" '%s='", entry.getKey()));
lines.add(String.format("'%s='", entry.getKey()));
}
else {
for (String value : entry.getValue()) {
writer.append(String.format(" '%s=%s'", entry.getKey(), value));
lines.add(String.format("'%s=%s'", entry.getKey(), value));
}
}
}