From b26d8c085ddcab7af6c62991839e787cc4d4f577 Mon Sep 17 00:00:00 2001 From: Raman Gupta Date: Tue, 1 Mar 2016 16:35:32 -0500 Subject: [PATCH 1/2] Add support for generating an HTTPie snippet This commit adds support for generating a snippet that contains the HTTPie command for the request. As the snippet does not require any additional configuration, it has added to the existing default snippets. Httpie does not currently support setting the content type for each part in a multipart form -- these multipart types are currently ignored. See: https://github.com/jkbrzt/httpie/issues/199 https://github.com/jkbrzt/httpie/issues/271 https://github.com/jkbrzt/httpie/pull/285 https://github.com/jkbrzt/httpie/pull/398 There is an issue with specifying piped input for multipart form data. There is no way currently to specify the data without specifying a filename parameter in the Content-Disposition. For now, this is ignored. See: https://github.com/jkbrzt/httpie/issues/342 See gh-207 --- config/checkstyle/checkstyle.xml | 2 +- .../docs/asciidoc/documenting-your-api.adoc | 4 + docs/src/docs/asciidoc/getting-started.adoc | 3 +- .../mockmvc/CustomDefaultSnippets.java | 3 +- .../restassured/CustomDefaultSnippets.java | 3 +- .../restdocs/cli/AbstractCliSnippet.java | 182 ++++++++++ .../{curl => cli}/QueryStringParser.java | 2 +- .../restdocs/cli/curl/CurlDocumentation.java | 60 ++++ .../restdocs/cli/curl/CurlRequestSnippet.java | 161 +++++++++ .../restdocs/{ => cli}/curl/package-info.java | 2 +- .../cli/httpie/HttpieDocumentation.java | 59 ++++ .../cli/httpie/HttpieRequestSnippet.java | 183 ++++++++++ .../restdocs/cli/httpie/package-info.java | 20 ++ .../restdocs/config/SnippetConfigurer.java | 11 +- .../restdocs/curl/CurlDocumentation.java | 9 +- .../restdocs/curl/CurlRequestSnippet.java | 230 +----------- .../default-httpie-request.snippet | 4 + .../markdown/default-httpie-request.snippet | 3 + .../{curl => cli}/QueryStringParserTests.java | 2 +- .../curl/CurlRequestSnippetTests.java | 2 +- .../cli/httpie/HttpieRequestSnippetTests.java | 330 ++++++++++++++++++ .../RestDocumentationConfigurerTests.java | 6 +- .../restdocs/test/ExpectedSnippet.java | 5 + .../httpie-request-with-title.snippet | 5 + .../httpie-request-with-title.snippet | 4 + ...kMvcRestDocumentationIntegrationTests.java | 2 +- 26 files changed, 1061 insertions(+), 236 deletions(-) create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java rename spring-restdocs-core/src/main/java/org/springframework/restdocs/{curl => cli}/QueryStringParser.java (98%) create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java rename spring-restdocs-core/src/main/java/org/springframework/restdocs/{ => cli}/curl/package-info.java (93%) create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java create mode 100644 spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet create mode 100644 spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet rename spring-restdocs-core/src/test/java/org/springframework/restdocs/{curl => cli}/QueryStringParserTests.java (98%) rename spring-restdocs-core/src/test/java/org/springframework/restdocs/{ => cli}/curl/CurlRequestSnippetTests.java (99%) create mode 100644 spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java create mode 100644 spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet create mode 100644 spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 36c8db5b..86947f99 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -73,7 +73,7 @@ + value="com.jayway.restassured.RestAssured.*, org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.cli.curl.CurlDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.templates.TemplateFormats.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" /> diff --git a/docs/src/docs/asciidoc/documenting-your-api.adoc b/docs/src/docs/asciidoc/documenting-your-api.adoc index fb9977c8..f036275c 100644 --- a/docs/src/docs/asciidoc/documenting-your-api.adoc +++ b/docs/src/docs/asciidoc/documenting-your-api.adoc @@ -542,6 +542,10 @@ A number of snippets are produced automatically when you document a request and | Contains the http://curl.haxx.se[`curl`] command that is equivalent to the `MockMvc` call that is being documented +| `httpie-request.adoc` +| Contains the http://httpie.org[`HTTPie`] command that is equivalent to the `MockMvc` +call that is being documented + | `http-request.adoc` | Contains the HTTP request that is equivalent to the `MockMvc` call that is being documented diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc index 67d7dc17..2e2cc2e3 100644 --- a/docs/src/docs/asciidoc/getting-started.adoc +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -371,9 +371,10 @@ static `document` method on <4> Invoke the root (`/`) of the service. <5> Assert that the service produce the expected response. -By default, three snippets are written: +By default, four snippets are written: * `/index/curl-request.adoc` + * `/index/httpie-request.adoc` * `/index/http-request.adoc` * `/index/http-response.adoc` diff --git a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java index 4be61c37..7164e058 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java @@ -20,12 +20,11 @@ import org.junit.Before; import org.junit.Rule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.restdocs.JUnitRestDocumentation; -import org.springframework.restdocs.RestDocumentation; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; +import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; public class CustomDefaultSnippets { diff --git a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java index ae581cd0..632507c6 100644 --- a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java @@ -19,12 +19,11 @@ package com.example.restassured; import org.junit.Before; import org.junit.Rule; import org.springframework.restdocs.JUnitRestDocumentation; -import org.springframework.restdocs.RestDocumentation; import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.specification.RequestSpecification; -import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; +import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; public class CustomDefaultSnippets { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java new file mode 100644 index 00000000..ef00f0ba --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java @@ -0,0 +1,182 @@ +/* + * Copyright 2014-2016 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.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.util.Base64Utils; + +/** + * An abstract {@link Snippet} that for CLI requests. + * + * @author Andy Wilkinson + * @author Paul-Christian Volkmer + * @author Raman Gupta + */ +public abstract class AbstractCliSnippet extends TemplatedSnippet { + + private static final Set HEADER_FILTERS; + + static { + Set headerFilters = new HashSet<>(); + headerFilters.add(new NamedHeaderFilter(HttpHeaders.HOST)); + headerFilters.add(new NamedHeaderFilter(HttpHeaders.CONTENT_LENGTH)); + headerFilters.add(new BasicAuthHeaderFilter()); + HEADER_FILTERS = Collections.unmodifiableSet(headerFilters); + } + + /** + * Create a new abstract cli snippet with the given name and attributes. + * @param snippetName The snippet name. + * @param attributes The snippet attributes. + */ + protected AbstractCliSnippet(String snippetName, Map attributes) { + super(snippetName, attributes); + } + + /** + * Create the model which will be passed to the template for rendering. + * @param operation The operation + * @return The model. + */ + protected abstract Map createModel(Operation operation); + + /** + * Gets the unique parameters given a request. + * @param request The operation request. + * @return The unique parameters. + */ + protected Parameters getUniqueParameters(OperationRequest request) { + Parameters queryStringParameters = new QueryStringParser() + .parse(request.getUri()); + Parameters uniqueParameters = new Parameters(); + + for (Map.Entry> parameter : request.getParameters().entrySet()) { + addIfUnique(parameter, queryStringParameters, uniqueParameters); + } + return uniqueParameters; + } + + private void addIfUnique(Map.Entry> parameter, + Parameters queryStringParameters, Parameters uniqueParameters) { + if (!queryStringParameters.containsKey(parameter.getKey())) { + uniqueParameters.put(parameter.getKey(), parameter.getValue()); + } + else { + List candidates = parameter.getValue(); + List existing = queryStringParameters.get(parameter.getKey()); + for (String candidate : candidates) { + if (!existing.contains(candidate)) { + uniqueParameters.add(parameter.getKey(), candidate); + } + } + } + } + + /** + * Whether the request operation is a PUT or a POST. + * @param request The request. + * @return boolean + */ + protected boolean isPutOrPost(OperationRequest request) { + return HttpMethod.PUT.equals(request.getMethod()) + || HttpMethod.POST.equals(request.getMethod()); + } + + /** + * Whether the passed header is allowed according to the configured + * header filters. + * @param header The header to test. + * @return boolean + */ + protected boolean allowedHeader(Map.Entry> header) { + for (HeaderFilter headerFilter : HEADER_FILTERS) { + if (!headerFilter.allow(header.getKey(), header.getValue())) { + return false; + } + } + return true; + } + + /** + * Determine if the header passed is a basic auth header. + * @param headerValue The header to test. + * @return boolean + */ + protected boolean isBasicAuthHeader(List headerValue) { + return BasicAuthHeaderFilter.isBasicAuthHeader(headerValue); + } + + /** + * Decodes a basic auth header into name:password credentials. + * @param headerValue The encoded header value. + * @return name:password credentials. + */ + protected String decodeBasicAuthHeader(List headerValue) { + return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue); + } + + private interface HeaderFilter { + + boolean allow(String name, List value); + } + + private static final class BasicAuthHeaderFilter implements HeaderFilter { + + @Override + public boolean allow(String name, List value) { + return !(HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value)); + } + + static boolean isBasicAuthHeader(List value) { + return value != null && (!value.isEmpty()) + && value.get(0).startsWith("Basic "); + } + + static String decodeBasicAuthHeader(List value) { + return new String(Base64Utils.decodeFromString(value.get(0).substring(6))); + } + + } + + private static final class NamedHeaderFilter implements HeaderFilter { + + private final String name; + + NamedHeaderFilter(String name) { + this.name = name; + } + + @Override + public boolean allow(String name, List value) { + return !this.name.equalsIgnoreCase(name); + } + + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/QueryStringParser.java similarity index 98% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/QueryStringParser.java index ea5f3148..4a3ab8ca 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/QueryStringParser.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/QueryStringParser.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.curl; +package org.springframework.restdocs.cli; import java.io.UnsupportedEncodingException; import java.net.URI; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java new file mode 100644 index 00000000..73ef5fe4 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014-2015 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.curl; + +import java.util.Map; + +import org.springframework.restdocs.snippet.Snippet; + +/** + * Static factory methods for documenting a RESTful API as if it were being driven using + * the cURL command-line utility. + * + * @author Andy Wilkinson + * @author Yann Le Guern + * @author Dmitriy Mayboroda + * @author Jonathan Pearlin + */ +public abstract class CurlDocumentation { + + private CurlDocumentation() { + + } + + /** + * Returns a new {@code Snippet} that will document the curl request for the API + * operation. + * + * @return the snippet that will document the curl request + */ + public static Snippet curlRequest() { + return new CurlRequestSnippet(); + } + + /** + * 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. + * + * @param attributes the attributes + * @return the snippet that will document the curl request + */ + public static Snippet curlRequest(Map attributes) { + return new CurlRequestSnippet(attributes); + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java new file mode 100644 index 00000000..af4c253c --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java @@ -0,0 +1,161 @@ +/* + * Copyright 2014-2016 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.curl; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.restdocs.cli.AbstractCliSnippet; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.util.StringUtils; + +/** + * A {@link Snippet} that documents the curl command for a request. + * + * @author Andy Wilkinson + * @author Paul-Christian Volkmer + * @see CurlDocumentation#curlRequest() + * @see CurlDocumentation#curlRequest(Map) + */ +public class CurlRequestSnippet extends AbstractCliSnippet { + + /** + * Creates a new {@code CurlRequestSnippet} with no additional attributes. + */ + protected CurlRequestSnippet() { + this(null); + } + + /** + * Creates a new {@code CurlRequestSnippet} with the given additional + * {@code attributes} that will be included in the model during template rendering. + * + * @param attributes The additional attributes + */ + protected CurlRequestSnippet(Map attributes) { + super("curl-request", attributes); + } + + @Override + protected Map createModel(Operation operation) { + Map model = new HashMap<>(); + model.put("url", getUrl(operation)); + model.put("options", getOptions(operation)); + return model; + } + + private String getUrl(Operation operation) { + return String.format("'%s'", operation.getRequest().getUri()); + } + + private String getOptions(Operation operation) { + StringWriter command = new StringWriter(); + PrintWriter printer = new PrintWriter(command); + writeIncludeHeadersInOutputOption(printer); + writeUserOptionIfNecessary(operation.getRequest(), printer); + writeHttpMethodIfNecessary(operation.getRequest(), printer); + writeHeaders(operation.getRequest().getHeaders(), printer); + writePartsIfNecessary(operation.getRequest(), printer); + writeContent(operation.getRequest(), printer); + + return command.toString(); + } + + private void writeIncludeHeadersInOutputOption(PrintWriter writer) { + writer.print("-i"); + } + + private void writeUserOptionIfNecessary(OperationRequest request, + PrintWriter writer) { + List headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION); + if (isBasicAuthHeader(headerValue)) { + String credentials = decodeBasicAuthHeader(headerValue); + writer.print(String.format(" -u '%s'", credentials)); + } + } + + private void writeHttpMethodIfNecessary(OperationRequest request, + PrintWriter writer) { + if (!HttpMethod.GET.equals(request.getMethod())) { + writer.print(String.format(" -X %s", request.getMethod())); + } + } + + private void writeHeaders(HttpHeaders headers, PrintWriter writer) { + for (Entry> entry : headers.entrySet()) { + if (allowedHeader(entry)) { + for (String header : entry.getValue()) { + writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); + } + } + } + } + + private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) { + for (OperationRequestPart part : request.getParts()) { + writer.printf(" -F '%s=", part.getName()); + if (!StringUtils.hasText(part.getSubmittedFileName())) { + writer.append(part.getContentAsString()); + } + else { + writer.printf("@%s", part.getSubmittedFileName()); + } + if (part.getHeaders().getContentType() != null) { + writer.append(";type=") + .append(part.getHeaders().getContentType().toString()); + } + + writer.append("'"); + } + } + + private void writeContent(OperationRequest request, PrintWriter writer) { + String content = request.getContentAsString(); + if (StringUtils.hasText(content)) { + writer.print(String.format(" -d '%s'", content)); + } + else if (!request.getParts().isEmpty()) { + for (Entry> entry : request.getParameters().entrySet()) { + for (String value : entry.getValue()) { + writer.print(String.format(" -F '%s=%s'", entry.getKey(), value)); + } + } + } + else if (isPutOrPost(request)) { + writeContentUsingParameters(request, writer); + } + } + + private void writeContentUsingParameters(OperationRequest request, + PrintWriter writer) { + Parameters uniqueParameters = getUniqueParameters(request); + String queryString = uniqueParameters.toQueryString(); + if (StringUtils.hasText(queryString)) { + writer.print(String.format(" -d '%s'", queryString)); + } + } +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java similarity index 93% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java index 7c07b91e..02ebe62c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java @@ -17,4 +17,4 @@ /** * Documenting the curl command required to make a request to a RESTful API. */ -package org.springframework.restdocs.curl; +package org.springframework.restdocs.cli.curl; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java new file mode 100644 index 00000000..0313ae02 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014-2015 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.httpie; + +import java.util.Map; + +import org.springframework.restdocs.snippet.Snippet; + +/** + * Static factory methods for documenting a RESTful API as if it were being driven using + * the httpie command-line utility. + * + * @author Andy Wilkinson + * @author Paul-Christian Volkmer + * @author Raman Gupta + */ +public abstract class HttpieDocumentation { + + private HttpieDocumentation() { + + } + + /** + * Returns a new {@code Snippet} that will document the httpie request for the API + * operation. + * + * @return the snippet that will document the httpie request + */ + public static Snippet httpieRequest() { + return new HttpieRequestSnippet(); + } + + /** + * 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. + * + * @param attributes the attributes + * @return the snippet that will document the httpie request + */ + public static Snippet httpieRequest(Map attributes) { + return new HttpieRequestSnippet(attributes); + } + +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java new file mode 100644 index 00000000..7e251a90 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java @@ -0,0 +1,183 @@ +/* + * Copyright 2014-2016 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.httpie; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.http.HttpHeaders; +import org.springframework.restdocs.cli.AbstractCliSnippet; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.util.StringUtils; + +/** + * A {@link Snippet} that documents the httpie command for a request. + * + * @author Raman Gupta + * @see HttpieDocumentation#httpieRequest() + * @see HttpieDocumentation#httpieRequest(Map) + */ +public class HttpieRequestSnippet extends AbstractCliSnippet { + + /** + * Creates a new {@code CurlRequestSnippet} with no additional attributes. + */ + protected HttpieRequestSnippet() { + this(null); + } + + /** + * Creates a new {@code CurlRequestSnippet} with the given additional + * {@code attributes} that will be included in the model during template rendering. + * + * @param attributes The additional attributes + */ + protected HttpieRequestSnippet(Map attributes) { + super("httpie-request", attributes); + } + + @Override + protected Map createModel(final Operation operation) { + Map model = new HashMap<>(); + model.put("echo_content", getContentStdIn(operation)); + model.put("options", getOptions(operation)); + model.put("url", getUrl(operation)); + model.put("request_items", getRequestItems(operation)); + return model; + } + private Object getContentStdIn(final Operation operation) { + OperationRequest request = operation.getRequest(); + String content = request.getContentAsString(); + if (StringUtils.hasText(content)) { + return String.format("echo '%s' | ", content); + } + else { + return ""; + } + } + + private String getOptions(Operation operation) { + StringWriter options = new StringWriter(); + PrintWriter printer = new PrintWriter(options); + writeOptions(operation.getRequest(), printer); + writeUserOptionIfNecessary(operation.getRequest(), printer); + writeMethodIfNecessary(operation.getRequest(), printer); + return options.toString(); + } + + private String getUrl(Operation operation) { + return String.format("'%s'", operation.getRequest().getUri()); + } + + private String getRequestItems(final Operation operation) { + StringWriter requestItems = new StringWriter(); + PrintWriter printer = new PrintWriter(requestItems); + writeFormDataIfNecessary(operation.getRequest(), printer); + writeHeaders(operation.getRequest(), printer); + writeContent(operation.getRequest(), printer); + return requestItems.toString(); + } + private void writeOptions(OperationRequest request, PrintWriter writer) { + Parameters uniqueParameters = getUniqueParameters(request); + if (request.getParts().size() > 0 || uniqueParameters.size() > 0) { + writer.print("--form "); + } + } + + private void writeUserOptionIfNecessary(OperationRequest request, + PrintWriter writer) { + List headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION); + if (isBasicAuthHeader(headerValue)) { + String credentials = decodeBasicAuthHeader(headerValue); + writer.print(String.format("--auth '%s' ", credentials)); + } + } + + private void writeMethodIfNecessary(OperationRequest request, PrintWriter writer) { + writer.print(String.format("%s", request.getMethod().name())); + } + + private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) { + for (OperationRequestPart part : request.getParts()) { + writer.printf(" \\\n '%s'", part.getName()); + if (!StringUtils.hasText(part.getSubmittedFileName())) { + // httpie https://github.com/jkbrzt/httpie/issues/342 + writer.printf("@<(echo '%s')", part.getContentAsString()); + } + else { + writer.printf("@'%s'", part.getSubmittedFileName()); + } + // httpie does not currently support manually set content type by part + } + } + + private void writeHeaders(OperationRequest request, PrintWriter writer) { + HttpHeaders headers = request.getHeaders(); + for (Entry> entry : headers.entrySet()) { + if (allowedHeader(entry)) { + for (String header : entry.getValue()) { + // form Content-Type not required, added automatically by httpie with --form + if (request.getParts().size() > 0 + && entry.getKey().equals(HttpHeaders.CONTENT_TYPE) + && header.startsWith("multipart/form-data")) { + continue; + } + writer.print(String.format(" '%s:%s'", entry.getKey(), header)); + } + } + } + } + + private void writeContent(OperationRequest request, PrintWriter writer) { + String content = request.getContentAsString(); + if (!StringUtils.hasText(content)) { + if (!request.getParts().isEmpty()) { + for (Entry> entry : request.getParameters().entrySet()) { + for (String value : entry.getValue()) { + writer.print(String.format(" '%s=%s'", entry.getKey(), value)); + } + } + } + else if (isPutOrPost(request)) { + writeContentUsingParameters(request, writer); + } + } + } + + private void writeContentUsingParameters(OperationRequest request, + PrintWriter writer) { + Parameters uniqueParameters = getUniqueParameters(request); + for (Map.Entry> entry : uniqueParameters.entrySet()) { + if (entry.getValue().isEmpty()) { + writer.append(String.format(" '%s='", entry.getKey())); + } + else { + for (String value : entry.getValue()) { + writer.append(String.format(" '%s=%s'", entry.getKey(), value)); + } + } + } + } +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java new file mode 100644 index 00000000..2bdf5df6 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2015 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. + */ + +/** + * Documenting the httpie command required to make a request to a RESTful API. + */ +package org.springframework.restdocs.cli.httpie; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java index e8faa6ec..7c2b459c 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java @@ -21,7 +21,8 @@ import java.util.List; import java.util.Map; import org.springframework.restdocs.RestDocumentationContext; -import org.springframework.restdocs.curl.CurlDocumentation; +import org.springframework.restdocs.cli.curl.CurlDocumentation; +import org.springframework.restdocs.cli.httpie.HttpieDocumentation; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.http.HttpDocumentation; import org.springframework.restdocs.snippet.Snippet; @@ -38,8 +39,12 @@ import org.springframework.restdocs.templates.TemplateFormats; public abstract class SnippetConfigurer extends AbstractNestedConfigurer { - private List defaultSnippets = Arrays.asList(CurlDocumentation.curlRequest(), - HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse()); + private List defaultSnippets = Arrays.asList( + CurlDocumentation.curlRequest(), + HttpieDocumentation.httpieRequest(), + HttpDocumentation.httpRequest(), + HttpDocumentation.httpResponse() + ); /** * The default encoding for documentation snippets. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java index a3549443..a9b094f6 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 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. @@ -24,11 +24,14 @@ import org.springframework.restdocs.snippet.Snippet; * Static factory methods for documenting a RESTful API as if it were being driven using * the cURL command-line utility. * + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. + * * @author Andy Wilkinson * @author Yann Le Guern * @author Dmitriy Mayboroda * @author Jonathan Pearlin */ +@Deprecated public abstract class CurlDocumentation { private CurlDocumentation() { @@ -40,6 +43,8 @@ public abstract class CurlDocumentation { * operation. * * @return the snippet that will document the curl request + * + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. */ public static Snippet curlRequest() { return new CurlRequestSnippet(); @@ -52,6 +57,8 @@ public abstract class CurlDocumentation { * * @param attributes the attributes * @return the snippet that will document the curl request + * + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. */ public static Snippet curlRequest(Map attributes) { return new CurlRequestSnippet(attributes); diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java index 4f4ea07b..29396bac 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java @@ -16,243 +16,35 @@ package org.springframework.restdocs.curl; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.operation.OperationRequest; -import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.snippet.Snippet; -import org.springframework.restdocs.snippet.TemplatedSnippet; -import org.springframework.util.Base64Utils; -import org.springframework.util.StringUtils; /** * A {@link Snippet} that documents the curl command for a request. * - * @author Andy Wilkinson - * @author Paul-Christian Volkmer - * @see CurlDocumentation#curlRequest() - * @see CurlDocumentation#curlRequest(Map) + * @author Raman Gupta + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. */ -public class CurlRequestSnippet extends TemplatedSnippet { - - private static final Set HEADER_FILTERS; - - static { - Set headerFilters = new HashSet<>(); - headerFilters.add(new NamedHeaderFilter(HttpHeaders.HOST)); - headerFilters.add(new NamedHeaderFilter(HttpHeaders.CONTENT_LENGTH)); - headerFilters.add(new BasicAuthHeaderFilter()); - HEADER_FILTERS = Collections.unmodifiableSet(headerFilters); - } +public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.CurlRequestSnippet { /** * Creates a new {@code CurlRequestSnippet} with no additional attributes. + * + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. */ protected CurlRequestSnippet() { - this(null); + super(); } /** - * Creates a new {@code CurlRequestSnippet} with the given additional - * {@code attributes} that will be included in the model during template rendering. + * Creates a new {@code CurlRequestSnippet} with additional attributes. + * @param attributes The additional attributes. * - * @param attributes The additional attributes + * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. */ - protected CurlRequestSnippet(Map attributes) { - super("curl-request", attributes); - } - - @Override - protected Map createModel(Operation operation) { - Map model = new HashMap<>(); - model.put("url", getUrl(operation)); - model.put("options", getOptions(operation)); - return model; - } - - private String getUrl(Operation operation) { - return String.format("'%s'", operation.getRequest().getUri()); - } - - private String getOptions(Operation operation) { - StringWriter command = new StringWriter(); - PrintWriter printer = new PrintWriter(command); - writeIncludeHeadersInOutputOption(printer); - writeUserOptionIfNecessary(operation.getRequest(), printer); - writeHttpMethodIfNecessary(operation.getRequest(), printer); - writeHeaders(operation.getRequest().getHeaders(), printer); - writePartsIfNecessary(operation.getRequest(), printer); - writeContent(operation.getRequest(), printer); - - return command.toString(); - } - - private void writeIncludeHeadersInOutputOption(PrintWriter writer) { - writer.print("-i"); - } - - private void writeUserOptionIfNecessary(OperationRequest request, - PrintWriter writer) { - List headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION); - if (BasicAuthHeaderFilter.isBasicAuthHeader(headerValue)) { - String credentials = BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue); - writer.print(String.format(" -u '%s'", credentials)); - } - } - - private void writeHttpMethodIfNecessary(OperationRequest request, - PrintWriter writer) { - if (!HttpMethod.GET.equals(request.getMethod())) { - writer.print(String.format(" -X %s", request.getMethod())); - } - } - - private void writeHeaders(HttpHeaders headers, PrintWriter writer) { - for (Entry> entry : headers.entrySet()) { - if (allowedHeader(entry)) { - for (String header : entry.getValue()) { - writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); - } - } - } - } - - private boolean allowedHeader(Entry> header) { - for (HeaderFilter headerFilter : HEADER_FILTERS) { - if (!headerFilter.allow(header.getKey(), header.getValue())) { - return false; - } - } - return true; - } - - private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) { - for (OperationRequestPart part : request.getParts()) { - writer.printf(" -F '%s=", part.getName()); - if (!StringUtils.hasText(part.getSubmittedFileName())) { - writer.append(part.getContentAsString()); - } - else { - writer.printf("@%s", part.getSubmittedFileName()); - } - if (part.getHeaders().getContentType() != null) { - writer.append(";type=") - .append(part.getHeaders().getContentType().toString()); - } - - writer.append("'"); - } - } - - private void writeContent(OperationRequest request, PrintWriter writer) { - String content = request.getContentAsString(); - if (StringUtils.hasText(content)) { - writer.print(String.format(" -d '%s'", content)); - } - else if (!request.getParts().isEmpty()) { - for (Entry> entry : request.getParameters().entrySet()) { - for (String value : entry.getValue()) { - writer.print(String.format(" -F '%s=%s'", entry.getKey(), value)); - } - } - } - else if (isPutOrPost(request)) { - writeContentUsingParameters(request, writer); - } - } - - private void writeContentUsingParameters(OperationRequest request, - PrintWriter writer) { - Parameters uniqueParameters = getUniqueParameters(request); - String queryString = uniqueParameters.toQueryString(); - if (StringUtils.hasText(queryString)) { - writer.print(String.format(" -d '%s'", queryString)); - } - } - - private Parameters getUniqueParameters(OperationRequest request) { - Parameters queryStringParameters = new QueryStringParser() - .parse(request.getUri()); - Parameters uniqueParameters = new Parameters(); - - for (Entry> parameter : request.getParameters().entrySet()) { - addIfUnique(parameter, queryStringParameters, uniqueParameters); - } - return uniqueParameters; - } - - private void addIfUnique(Entry> parameter, - Parameters queryStringParameters, Parameters uniqueParameters) { - if (!queryStringParameters.containsKey(parameter.getKey())) { - uniqueParameters.put(parameter.getKey(), parameter.getValue()); - } - else { - List candidates = parameter.getValue(); - List existing = queryStringParameters.get(parameter.getKey()); - for (String candidate : candidates) { - if (!existing.contains(candidate)) { - uniqueParameters.add(parameter.getKey(), candidate); - } - } - } - } - - private boolean isPutOrPost(OperationRequest request) { - return HttpMethod.PUT.equals(request.getMethod()) - || HttpMethod.POST.equals(request.getMethod()); - } - - private interface HeaderFilter { - - boolean allow(String name, List value); - } - - private static final class BasicAuthHeaderFilter implements HeaderFilter { - - @Override - public boolean allow(String name, List value) { - if (HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value)) { - return false; - } - return true; - } - - static boolean isBasicAuthHeader(List value) { - return value != null && (!value.isEmpty()) - && value.get(0).startsWith("Basic "); - } - - static String decodeBasicAuthHeader(List value) { - return new String(Base64Utils.decodeFromString(value.get(0).substring(6))); - } - - } - - private static final class NamedHeaderFilter implements HeaderFilter { - - private final String name; - - private NamedHeaderFilter(String name) { - this.name = name; - } - - @Override - public boolean allow(String name, List value) { - return !this.name.equalsIgnoreCase(name); - } - + protected CurlRequestSnippet(final Map attributes) { + super(attributes); } } diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet new file mode 100644 index 00000000..05771238 --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet @@ -0,0 +1,4 @@ +[source,bash] +---- +$ {{echo_content}}http {{options}} {{url}}{{request_items}} +---- \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet new file mode 100644 index 00000000..acddb53f --- /dev/null +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet @@ -0,0 +1,3 @@ +```bash +$ {{echo_content}}http {{options}} {{url}}{{request_items}} +``` \ No newline at end of file diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/QueryStringParserTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/QueryStringParserTests.java similarity index 98% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/QueryStringParserTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/QueryStringParserTests.java index 634fede1..6a953e16 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/QueryStringParserTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/QueryStringParserTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.curl; +package org.springframework.restdocs.cli; import java.net.URI; import java.util.Arrays; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java similarity index 99% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java index 2b137192..0d8bb632 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.curl; +package org.springframework.restdocs.cli.curl; import java.io.IOException; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java new file mode 100644 index 00000000..1fb2abd7 --- /dev/null +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java @@ -0,0 +1,330 @@ +/* + * Copyright 2014-2016 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.httpie; + +import java.io.IOException; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.AbstractSnippetTests; +import org.springframework.restdocs.templates.TemplateEngine; +import org.springframework.restdocs.templates.TemplateFormat; +import org.springframework.restdocs.templates.TemplateResourceResolver; +import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine; +import org.springframework.util.Base64Utils; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.snippet.Attributes.attributes; +import static org.springframework.restdocs.snippet.Attributes.key; + +/** + * Tests for {@link HttpieRequestSnippet}. + * + * @author Andy Wilkinson + * @author Yann Le Guern + * @author Dmitriy Mayboroda + * @author Jonathan Pearlin + * @author Paul-Christian Volkmer + * @author Raman Gupta + */ +@RunWith(Parameterized.class) +public class HttpieRequestSnippetTests extends AbstractSnippetTests { + + public HttpieRequestSnippetTests(String name, TemplateFormat templateFormat) { + super(name, templateFormat); + } + + @Test + public void getRequest() throws IOException { + this.snippet.expectHttpieRequest("get-request").withContents( + codeBlock("bash").content("$ http GET 'http://localhost/foo'")); + new HttpieRequestSnippet().document( + operationBuilder("get-request").request("http://localhost/foo").build()); + } + + @Test + public void nonGetRequest() throws IOException { + this.snippet.expectHttpieRequest("non-get-request").withContents( + codeBlock("bash").content("$ http POST 'http://localhost/foo'")); + new HttpieRequestSnippet().document(operationBuilder("non-get-request") + .request("http://localhost/foo").method("POST").build()); + } + + @Test + public void requestWithContent() throws IOException { + this.snippet.expectHttpieRequest("request-with-content") + .withContents(codeBlock("bash") + .content("$ echo 'content' | http GET 'http://localhost/foo'")); + new HttpieRequestSnippet().document(operationBuilder("request-with-content") + .request("http://localhost/foo").content("content").build()); + } + + @Test + public void getRequestWithQueryString() throws IOException { + this.snippet.expectHttpieRequest("request-with-query-string") + .withContents(codeBlock("bash") + .content("$ http GET 'http://localhost/foo?param=value'")); + new HttpieRequestSnippet().document(operationBuilder("request-with-query-string") + .request("http://localhost/foo?param=value").build()); + } + + @Test + public void getRequestWithQueryStringWithNoValue() throws IOException { + this.snippet.expectHttpieRequest("request-with-query-string-with-no-value") + .withContents(codeBlock("bash") + .content("$ http GET 'http://localhost/foo?param'")); + new HttpieRequestSnippet() + .document(operationBuilder("request-with-query-string-with-no-value") + .request("http://localhost/foo?param").build()); + } + + @Test + public void postRequestWithQueryString() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-query-string") + .withContents(codeBlock("bash") + .content("$ http POST 'http://localhost/foo?param=value'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-query-string") + .request("http://localhost/foo?param=value").method("POST") + .build()); + } + + @Test + public void postRequestWithQueryStringWithNoValue() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-query-string-with-no-value") + .withContents(codeBlock("bash") + .content("$ http POST 'http://localhost/foo?param'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-query-string-with-no-value") + .request("http://localhost/foo?param").method("POST").build()); + } + + @Test + public void postRequestWithOneParameter() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-one-parameter") + .withContents(codeBlock("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1=v1'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-one-parameter") + .request("http://localhost/foo").method("POST").param("k1", "v1") + .build()); + } + + @Test + public void postRequestWithOneParameterWithNoValue() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-one-parameter-with-no-value") + .withContents(codeBlock("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1='")); + new HttpieRequestSnippet().document( + operationBuilder("post-request-with-one-parameter-with-no-value") + .request("http://localhost/foo").method("POST").param("k1") + .build()); + } + + @Test + public void postRequestWithMultipleParameters() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-multiple-parameters") + .withContents(codeBlock("bash") + .content("$ http --form POST 'http://localhost/foo'" + + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-multiple-parameters") + .request("http://localhost/foo").method("POST") + .param("k1", "v1", "v1-bis").param("k2", "v2").build()); + } + + @Test + public void postRequestWithUrlEncodedParameter() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-url-encoded-parameter") + .withContents(codeBlock("bash").content( + "$ http --form POST 'http://localhost/foo' 'k1=a&b'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-url-encoded-parameter") + .request("http://localhost/foo").method("POST").param("k1", "a&b") + .build()); + } + + @Test + public void postRequestWithQueryStringAndParameter() throws IOException { + this.snippet.expectHttpieRequest("post-request-with-query-string-and-parameter") + .withContents(codeBlock("bash").content( + "$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'")); + new HttpieRequestSnippet() + .document(operationBuilder("post-request-with-query-string-and-parameter") + .request("http://localhost/foo?a=alpha").method("POST") + .param("b", "bravo").build()); + } + + @Test + public void postRequestWithOverlappingQueryStringAndParameters() throws IOException { + this.snippet + .expectHttpieRequest( + "post-request-with-overlapping-query-string-and-parameters") + .withContents(codeBlock("bash").content( + "$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'")); + new HttpieRequestSnippet().document(operationBuilder( + "post-request-with-overlapping-query-string-and-parameters") + .request("http://localhost/foo?a=alpha").method("POST") + .param("a", "alpha").param("b", "bravo").build()); + } + + @Test + public void putRequestWithOneParameter() throws IOException { + this.snippet.expectHttpieRequest("put-request-with-one-parameter") + .withContents(codeBlock("bash") + .content("$ http --form PUT 'http://localhost/foo' 'k1=v1'")); + new HttpieRequestSnippet() + .document(operationBuilder("put-request-with-one-parameter") + .request("http://localhost/foo").method("PUT").param("k1", "v1") + .build()); + } + + @Test + public void putRequestWithMultipleParameters() throws IOException { + this.snippet.expectHttpieRequest("put-request-with-multiple-parameters") + .withContents(codeBlock("bash") + .content("$ http --form PUT 'http://localhost/foo'" + + " 'k1=v1' 'k1=v1-bis' 'k2=v2'")); + new HttpieRequestSnippet() + .document(operationBuilder("put-request-with-multiple-parameters") + .request("http://localhost/foo").method("PUT").param("k1", "v1") + .param("k1", "v1-bis").param("k2", "v2").build()); + } + + @Test + public void putRequestWithUrlEncodedParameter() throws IOException { + this.snippet.expectHttpieRequest("put-request-with-url-encoded-parameter") + .withContents(codeBlock("bash").content( + "$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); + new HttpieRequestSnippet() + .document(operationBuilder("put-request-with-url-encoded-parameter") + .request("http://localhost/foo").method("PUT").param("k1", "a&b") + .build()); + } + + @Test + public void requestWithHeaders() throws IOException { + this.snippet.expectHttpieRequest("request-with-headers") + .withContents(codeBlock("bash").content("$ http GET 'http://localhost/foo'" + + " 'Content-Type:application/json' 'a:alpha'")); + new HttpieRequestSnippet().document( + operationBuilder("request-with-headers").request("http://localhost/foo") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_JSON_VALUE) + .header("a", "alpha").build()); + } + + @Test + public void multipartPostWithNoSubmittedFileName() throws IOException { + String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n" + + " 'metadata'@<(echo '{\"description\": \"foo\"}')"; + this.snippet.expectHttpieRequest("multipart-post-no-original-filename") + .withContents(codeBlock("bash").content(expectedContent)); + new HttpieRequestSnippet() + .document(operationBuilder("multipart-post-no-original-filename") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.MULTIPART_FORM_DATA_VALUE) + .part("metadata", "{\"description\": \"foo\"}".getBytes()) + .build()); + } + + @Test + public void multipartPostWithContentType() throws IOException { + // httpie does not yet support manually set content type by part + String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n" + + " 'image'@'documents/images/example.png'"; + this.snippet.expectHttpieRequest("multipart-post-with-content-type") + .withContents(codeBlock("bash").content(expectedContent)); + new HttpieRequestSnippet() + .document(operationBuilder("multipart-post-with-content-type") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE) + .submittedFileName("documents/images/example.png").build()); + } + + @Test + public void multipartPost() throws IOException { + String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n" + + " 'image'@'documents/images/example.png'"; + this.snippet.expectHttpieRequest("multipart-post") + .withContents(codeBlock("bash").content(expectedContent)); + new HttpieRequestSnippet() + .document(operationBuilder("multipart-post") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .submittedFileName("documents/images/example.png").build()); + } + + @Test + public void multipartPostWithParameters() throws IOException { + String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n" + + " 'image'@'documents/images/example.png' 'a=apple' 'a=avocado' 'b=banana'"; + this.snippet.expectHttpieRequest("multipart-post-with-parameters") + .withContents(codeBlock("bash").content(expectedContent)); + new HttpieRequestSnippet() + .document(operationBuilder("multipart-post-with-parameters") + .request("http://localhost/upload").method("POST") + .header(HttpHeaders.CONTENT_TYPE, + MediaType.MULTIPART_FORM_DATA_VALUE) + .part("image", new byte[0]) + .submittedFileName("documents/images/example.png").and() + .param("a", "apple", "avocado").param("b", "banana").build()); + } + + @Test + public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { + this.snippet.expectHttpieRequest("basic-auth").withContents(codeBlock("bash") + .content("$ http --auth 'user:secret' GET 'http://localhost/foo'")); + new HttpieRequestSnippet() + .document(operationBuilder("basic-auth").request("http://localhost/foo") + .header(HttpHeaders.AUTHORIZATION, + "Basic " + Base64Utils + .encodeToString("user:secret".getBytes())) + .build()); + } + + @Test + public void customAttributes() throws IOException { + this.snippet.expectHttpieRequest("custom-attributes") + .withContents(containsString("httpie request title")); + TemplateResourceResolver resolver = mock(TemplateResourceResolver.class); + given(resolver.resolveTemplateResource("httpie-request")) + .willReturn(snippetResource("httpie-request-with-title")); + new HttpieRequestSnippet( + attributes( + key("title").value("httpie request title"))) + .document( + operationBuilder("custom-attributes") + .attribute(TemplateEngine.class.getName(), + new MustacheTemplateEngine( + resolver)) + .request("http://localhost/foo").build()); + } + +} diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index 0462fb30..7a36e320 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -24,8 +24,9 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.restdocs.RestDocumentationContext; -import org.springframework.restdocs.curl.CurlDocumentation; -import org.springframework.restdocs.curl.CurlRequestSnippet; +import org.springframework.restdocs.cli.curl.CurlDocumentation; +import org.springframework.restdocs.cli.curl.CurlRequestSnippet; +import org.springframework.restdocs.cli.httpie.HttpieRequestSnippet; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.http.HttpRequestSnippet; import org.springframework.restdocs.http.HttpResponseSnippet; @@ -71,6 +72,7 @@ public class RestDocumentationConfigurerTests { .get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS); assertThat(defaultSnippets, contains(instanceOf(CurlRequestSnippet.class), + instanceOf(HttpieRequestSnippet.class), instanceOf(HttpRequestSnippet.class), instanceOf(HttpResponseSnippet.class))); assertThat(configuration, hasEntry(equalTo(SnippetConfiguration.class.getName()), diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java index d706ddf6..2799f1cc 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/test/ExpectedSnippet.java @@ -76,6 +76,11 @@ public class ExpectedSnippet implements TestRule { return this; } + public ExpectedSnippet expectHttpieRequest(String name) { + expect(name, "httpie-request"); + return this; + } + public ExpectedSnippet expectRequestFields(String name) { expect(name, "request-fields"); return this; diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet new file mode 100644 index 00000000..0bff58b1 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet @@ -0,0 +1,5 @@ +[source,bash] +.{{title}} +---- +$ {{echo_content}}http {{options}} {{url}}{{request_items}} +---- \ No newline at end of file diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet new file mode 100644 index 00000000..29beade1 --- /dev/null +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet @@ -0,0 +1,4 @@ +{{title}} +```bash +$ {{echo_content}}http {{options}} {{url}}{{request_items}} +``` \ No newline at end of file diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index 162db0d1..ad06b187 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -56,7 +56,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest; +import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; From 37e16bc308b08d465b0a3aee3adbeeaa70877b86 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 9 Mar 2016 17:23:04 +0000 Subject: [PATCH 2/2] Polish contribution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit polishes the HTTPie request snippet contribution made in b26d8c0. It makes the following significant changes: - Applies project’s coding conventions for formatting and the like - Moves to a composition-based approach for sharing functionality between the curl and HTTPie snippets by replacing AbstractCliSnippet with CliOperationRequest. - Introduces a single package for CLI command snippets, thereby allowing more code to be package-private. See gh-207 --- config/checkstyle/checkstyle.xml | 2 +- docs/src/docs/asciidoc/configuration.adoc | 3 +- docs/src/docs/asciidoc/getting-started.adoc | 2 +- .../mockmvc/CustomDefaultSnippets.java | 10 +- .../restassured/CustomDefaultSnippets.java | 17 +- ...cumentation.java => CliDocumentation.java} | 39 +++- ...iSnippet.java => CliOperationRequest.java} | 124 ++++++------ .../cli/{curl => }/CurlRequestSnippet.java | 48 +++-- .../restdocs/cli/HttpieRequestSnippet.java | 175 +++++++++++++++++ .../cli/httpie/HttpieDocumentation.java | 59 ------ .../cli/httpie/HttpieRequestSnippet.java | 183 ------------------ .../cli/{httpie => }/package-info.java | 6 +- .../restdocs/config/SnippetConfigurer.java | 12 +- .../restdocs/curl/CurlDocumentation.java | 12 +- .../restdocs/curl/CurlRequestSnippet.java | 17 +- .../restdocs/{cli => }/curl/package-info.java | 5 +- .../default-httpie-request.snippet | 2 +- .../markdown/default-httpie-request.snippet | 2 +- .../{curl => }/CurlRequestSnippetTests.java | 2 +- .../HttpieRequestSnippetTests.java | 16 +- .../RestDocumentationConfigurerTests.java | 8 +- .../httpie-request-with-title.snippet | 2 +- .../httpie-request-with-title.snippet | 2 +- ...kMvcRestDocumentationIntegrationTests.java | 34 +++- 24 files changed, 395 insertions(+), 387 deletions(-) rename spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/{curl/CurlDocumentation.java => CliDocumentation.java} (59%) rename spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/{AbstractCliSnippet.java => CliOperationRequest.java} (59%) rename spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/{curl => }/CurlRequestSnippet.java (74%) create mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java delete mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java delete mode 100644 spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java rename spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/{httpie => }/package-info.java (75%) rename spring-restdocs-core/src/main/java/org/springframework/restdocs/{cli => }/curl/package-info.java (83%) rename spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/{curl => }/CurlRequestSnippetTests.java (99%) rename spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/{httpie => }/HttpieRequestSnippetTests.java (96%) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 86947f99..061bb501 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -73,7 +73,7 @@ + value="com.jayway.restassured.RestAssured.*, org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.cli.CliDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.templates.TemplateFormats.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" /> diff --git a/docs/src/docs/asciidoc/configuration.adoc b/docs/src/docs/asciidoc/configuration.adoc index a290c72c..00c31ac6 100644 --- a/docs/src/docs/asciidoc/configuration.adoc +++ b/docs/src/docs/asciidoc/configuration.adoc @@ -87,11 +87,12 @@ include::{examples-dir}/com/example/restassured/CustomFormat.java[tags=custom-fo [[configuration-default-snippets]] === Default snippets -Three snippets are produced by default: +Four snippets are produced by default: - `curl-request` - `http-request` - `http-response` +- `httpie-request` You can change the default snippet configuration during setup using the `RestDocumentationConfigurer` API. For example, to only produce the `curl-request` diff --git a/docs/src/docs/asciidoc/getting-started.adoc b/docs/src/docs/asciidoc/getting-started.adoc index 2e2cc2e3..daed3a4f 100644 --- a/docs/src/docs/asciidoc/getting-started.adoc +++ b/docs/src/docs/asciidoc/getting-started.adoc @@ -374,9 +374,9 @@ static `document` method on By default, four snippets are written: * `/index/curl-request.adoc` - * `/index/httpie-request.adoc` * `/index/http-request.adoc` * `/index/http-response.adoc` + * `/index/httpie-request.adoc` Refer to <> for more information about these and other snippets that can be produced by Spring REST Docs. diff --git a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java index 7164e058..4fb2990f 100644 --- a/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/mockmvc/CustomDefaultSnippets.java @@ -18,19 +18,21 @@ package com.example.mockmvc; import org.junit.Before; import org.junit.Rule; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.restdocs.JUnitRestDocumentation; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; -import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; +import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; public class CustomDefaultSnippets { @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build"); + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( + "build"); @Autowired private WebApplicationContext context; @@ -41,8 +43,8 @@ public class CustomDefaultSnippets { public void setUp() { // tag::custom-default-snippets[] this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .apply(documentationConfiguration(this.restDocumentation) - .snippets().withDefaults(curlRequest())) + .apply(documentationConfiguration(this.restDocumentation).snippets() + .withDefaults(curlRequest())) .build(); // end::custom-default-snippets[] } diff --git a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java index 632507c6..57c65d36 100644 --- a/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java +++ b/docs/src/test/java/com/example/restassured/CustomDefaultSnippets.java @@ -16,20 +16,21 @@ package com.example.restassured; -import org.junit.Before; -import org.junit.Rule; -import org.springframework.restdocs.JUnitRestDocumentation; - import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.specification.RequestSpecification; +import org.junit.Before; +import org.junit.Rule; -import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; +import org.springframework.restdocs.JUnitRestDocumentation; + +import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration; public class CustomDefaultSnippets { @Rule - public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build"); + public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation( + "build"); private RequestSpecification spec; @@ -37,8 +38,8 @@ public class CustomDefaultSnippets { public void setUp() { // tag::custom-default-snippets[] this.spec = new RequestSpecBuilder() - .addFilter(documentationConfiguration(this.restDocumentation) - .snippets().withDefaults(curlRequest())) + .addFilter(documentationConfiguration(this.restDocumentation).snippets() + .withDefaults(curlRequest())) .build(); // end::custom-default-snippets[] } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java similarity index 59% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java index 73ef5fe4..9b6b7675 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliDocumentation.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -14,24 +14,23 @@ * limitations under the License. */ -package org.springframework.restdocs.cli.curl; +package org.springframework.restdocs.cli; import java.util.Map; import org.springframework.restdocs.snippet.Snippet; /** - * Static factory methods for documenting a RESTful API as if it were being driven using - * the cURL command-line utility. + * Static factory methods for documenting a RESTful API as if it were being driven using a + * command-line utility such as curl or HTTPie. * * @author Andy Wilkinson - * @author Yann Le Guern - * @author Dmitriy Mayboroda - * @author Jonathan Pearlin + * @author Paul-Christian Volkmer + * @author Raman Gupta */ -public abstract class CurlDocumentation { +public abstract class CliDocumentation { - private CurlDocumentation() { + private CliDocumentation() { } @@ -57,4 +56,26 @@ public abstract class CurlDocumentation { return new CurlRequestSnippet(attributes); } + /** + * Returns a new {@code Snippet} that will document the HTTPie request for the API + * operation. + * + * @return the snippet that will document the HTTPie request + */ + public static Snippet httpieRequest() { + return new HttpieRequestSnippet(); + } + + /** + * 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. + * + * @param attributes the attributes + * @return the snippet that will document the HTTPie request + */ + public static Snippet httpieRequest(Map attributes) { + return new HttpieRequestSnippet(attributes); + } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java similarity index 59% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java index ef00f0ba..99ce6aeb 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/AbstractCliSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CliOperationRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2012-2016 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. @@ -16,29 +16,30 @@ package org.springframework.restdocs.cli; +import java.net.URI; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.Parameters; -import org.springframework.restdocs.snippet.Snippet; -import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.Base64Utils; /** - * An abstract {@link Snippet} that for CLI requests. + * An {@link OperationRequest} wrapper with methods that are useful when producing a + * snippet containing a CLI command for a request. * * @author Andy Wilkinson - * @author Paul-Christian Volkmer * @author Raman Gupta */ -public abstract class AbstractCliSnippet extends TemplatedSnippet { +final class CliOperationRequest implements OperationRequest { private static final Set HEADER_FILTERS; @@ -50,33 +51,19 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet { HEADER_FILTERS = Collections.unmodifiableSet(headerFilters); } - /** - * Create a new abstract cli snippet with the given name and attributes. - * @param snippetName The snippet name. - * @param attributes The snippet attributes. - */ - protected AbstractCliSnippet(String snippetName, Map attributes) { - super(snippetName, attributes); + private final OperationRequest delegate; + + CliOperationRequest(OperationRequest delegate) { + this.delegate = delegate; } - /** - * Create the model which will be passed to the template for rendering. - * @param operation The operation - * @return The model. - */ - protected abstract Map createModel(Operation operation); - - /** - * Gets the unique parameters given a request. - * @param request The operation request. - * @return The unique parameters. - */ - protected Parameters getUniqueParameters(OperationRequest request) { + Parameters getUniqueParameters() { Parameters queryStringParameters = new QueryStringParser() - .parse(request.getUri()); + .parse(this.delegate.getUri()); Parameters uniqueParameters = new Parameters(); - for (Map.Entry> parameter : request.getParameters().entrySet()) { + for (Map.Entry> parameter : this.delegate.getParameters() + .entrySet()) { addIfUnique(parameter, queryStringParameters, uniqueParameters); } return uniqueParameters; @@ -98,23 +85,42 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet { } } - /** - * Whether the request operation is a PUT or a POST. - * @param request The request. - * @return boolean - */ - protected boolean isPutOrPost(OperationRequest request) { - return HttpMethod.PUT.equals(request.getMethod()) - || HttpMethod.POST.equals(request.getMethod()); + boolean isPutOrPost() { + return HttpMethod.PUT.equals(this.delegate.getMethod()) + || HttpMethod.POST.equals(this.delegate.getMethod()); } - /** - * Whether the passed header is allowed according to the configured - * header filters. - * @param header The header to test. - * @return boolean - */ - protected boolean allowedHeader(Map.Entry> header) { + String getBasicAuthCredentials() { + List headerValue = this.delegate.getHeaders() + .get(HttpHeaders.AUTHORIZATION); + if (BasicAuthHeaderFilter.isBasicAuthHeader(headerValue)) { + return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue); + } + return null; + } + + @Override + public byte[] getContent() { + return this.delegate.getContent(); + } + + @Override + public String getContentAsString() { + return this.delegate.getContentAsString(); + } + + @Override + public HttpHeaders getHeaders() { + HttpHeaders filteredHeaders = new HttpHeaders(); + for (Entry> header : this.delegate.getHeaders().entrySet()) { + if (allowedHeader(header)) { + filteredHeaders.put(header.getKey(), header.getValue()); + } + } + return HttpHeaders.readOnlyHttpHeaders(filteredHeaders); + } + + private boolean allowedHeader(Map.Entry> header) { for (HeaderFilter headerFilter : HEADER_FILTERS) { if (!headerFilter.allow(header.getKey(), header.getValue())) { return false; @@ -123,22 +129,24 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet { return true; } - /** - * Determine if the header passed is a basic auth header. - * @param headerValue The header to test. - * @return boolean - */ - protected boolean isBasicAuthHeader(List headerValue) { - return BasicAuthHeaderFilter.isBasicAuthHeader(headerValue); + @Override + public HttpMethod getMethod() { + return this.delegate.getMethod(); } - /** - * Decodes a basic auth header into name:password credentials. - * @param headerValue The encoded header value. - * @return name:password credentials. - */ - protected String decodeBasicAuthHeader(List headerValue) { - return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue); + @Override + public Parameters getParameters() { + return this.delegate.getParameters(); + } + + @Override + public Collection getParts() { + return this.delegate.getParts(); + } + + @Override + public URI getUri() { + return this.delegate.getUri(); } private interface HeaderFilter { diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java similarity index 74% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java index af4c253c..5112b6dc 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/CurlRequestSnippet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.cli.curl; +package org.springframework.restdocs.cli; import java.io.PrintWriter; import java.io.StringWriter; @@ -23,14 +23,13 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.restdocs.cli.AbstractCliSnippet; import org.springframework.restdocs.operation.Operation; import org.springframework.restdocs.operation.OperationRequest; import org.springframework.restdocs.operation.OperationRequestPart; import org.springframework.restdocs.operation.Parameters; import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; import org.springframework.util.StringUtils; /** @@ -38,10 +37,10 @@ import org.springframework.util.StringUtils; * * @author Andy Wilkinson * @author Paul-Christian Volkmer - * @see CurlDocumentation#curlRequest() - * @see CurlDocumentation#curlRequest(Map) + * @see CliDocumentation#curlRequest() + * @see CliDocumentation#curlRequest(Map) */ -public class CurlRequestSnippet extends AbstractCliSnippet { +public class CurlRequestSnippet extends TemplatedSnippet { /** * Creates a new {@code CurlRequestSnippet} with no additional attributes. @@ -76,11 +75,12 @@ public class CurlRequestSnippet extends AbstractCliSnippet { StringWriter command = new StringWriter(); PrintWriter printer = new PrintWriter(command); writeIncludeHeadersInOutputOption(printer); - writeUserOptionIfNecessary(operation.getRequest(), printer); - writeHttpMethodIfNecessary(operation.getRequest(), printer); - writeHeaders(operation.getRequest().getHeaders(), printer); - writePartsIfNecessary(operation.getRequest(), printer); - writeContent(operation.getRequest(), printer); + CliOperationRequest request = new CliOperationRequest(operation.getRequest()); + writeUserOptionIfNecessary(request, printer); + writeHttpMethodIfNecessary(request, printer); + writeHeaders(request, printer); + writePartsIfNecessary(request, printer); + writeContent(request, printer); return command.toString(); } @@ -89,11 +89,10 @@ public class CurlRequestSnippet extends AbstractCliSnippet { writer.print("-i"); } - private void writeUserOptionIfNecessary(OperationRequest request, + private void writeUserOptionIfNecessary(CliOperationRequest request, PrintWriter writer) { - List headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION); - if (isBasicAuthHeader(headerValue)) { - String credentials = decodeBasicAuthHeader(headerValue); + String credentials = request.getBasicAuthCredentials(); + if (credentials != null) { writer.print(String.format(" -u '%s'", credentials)); } } @@ -105,12 +104,10 @@ public class CurlRequestSnippet extends AbstractCliSnippet { } } - private void writeHeaders(HttpHeaders headers, PrintWriter writer) { - for (Entry> entry : headers.entrySet()) { - if (allowedHeader(entry)) { - for (String header : entry.getValue()) { - writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); - } + private void writeHeaders(CliOperationRequest request, PrintWriter writer) { + for (Entry> entry : request.getHeaders().entrySet()) { + for (String header : entry.getValue()) { + writer.print(String.format(" -H '%s: %s'", entry.getKey(), header)); } } } @@ -133,7 +130,7 @@ public class CurlRequestSnippet extends AbstractCliSnippet { } } - private void writeContent(OperationRequest request, PrintWriter writer) { + private void writeContent(CliOperationRequest request, PrintWriter writer) { String content = request.getContentAsString(); if (StringUtils.hasText(content)) { writer.print(String.format(" -d '%s'", content)); @@ -145,17 +142,18 @@ public class CurlRequestSnippet extends AbstractCliSnippet { } } } - else if (isPutOrPost(request)) { + else if (request.isPutOrPost()) { writeContentUsingParameters(request, writer); } } - private void writeContentUsingParameters(OperationRequest request, + private void writeContentUsingParameters(CliOperationRequest request, PrintWriter writer) { - Parameters uniqueParameters = getUniqueParameters(request); + Parameters uniqueParameters = request.getUniqueParameters(); String queryString = uniqueParameters.toQueryString(); if (StringUtils.hasText(queryString)) { writer.print(String.format(" -d '%s'", queryString)); } } + } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java new file mode 100644 index 00000000..0d132e41 --- /dev/null +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/HttpieRequestSnippet.java @@ -0,0 +1,175 @@ +/* + * Copyright 2014-2016 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.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.restdocs.operation.Operation; +import org.springframework.restdocs.operation.OperationRequest; +import org.springframework.restdocs.operation.OperationRequestPart; +import org.springframework.restdocs.operation.Parameters; +import org.springframework.restdocs.snippet.Snippet; +import org.springframework.restdocs.snippet.TemplatedSnippet; +import org.springframework.util.StringUtils; + +/** + * A {@link Snippet} that documents the HTTPie command for a request. + * + * @author Raman Gupta + * @author Andy Wilkinson + * @see CliDocumentation#httpieRequest() + * @see CliDocumentation#httpieRequest(Map) + */ +public class HttpieRequestSnippet extends TemplatedSnippet { + + /** + * Creates a new {@code HttpieRequestSnippet} with no additional attributes. + */ + protected HttpieRequestSnippet() { + this(null); + } + + /** + * Creates a new {@code HttpieRequestSnippet} with the given additional + * {@code attributes} that will be included in the model during template rendering. + * + * @param attributes The additional attributes + */ + protected HttpieRequestSnippet(Map attributes) { + super("httpie-request", attributes); + } + + @Override + protected Map createModel(Operation operation) { + Map model = new HashMap<>(); + CliOperationRequest request = new CliOperationRequest(operation.getRequest()); + model.put("echoContent", getContentStandardIn(request)); + model.put("options", getOptions(request)); + model.put("url", getUrl(request)); + model.put("requestItems", getRequestItems(request)); + return model; + } + + private Object getContentStandardIn(CliOperationRequest request) { + String content = request.getContentAsString(); + if (StringUtils.hasText(content)) { + return String.format("echo '%s' | ", content); + } + return ""; + } + + private String getOptions(CliOperationRequest request) { + StringWriter options = new StringWriter(); + PrintWriter printer = new PrintWriter(options); + writeOptions(request, printer); + writeUserOptionIfNecessary(request, printer); + writeMethodIfNecessary(request, printer); + return options.toString(); + } + + private String getUrl(OperationRequest request) { + return String.format("'%s'", request.getUri()); + } + + private String getRequestItems(CliOperationRequest request) { + StringWriter requestItems = new StringWriter(); + PrintWriter printer = new PrintWriter(requestItems); + writeFormDataIfNecessary(request, printer); + writeHeaders(request, printer); + writeParametersIfNecessary(request, printer); + return requestItems.toString(); + } + + private void writeOptions(CliOperationRequest request, PrintWriter writer) { + if (!request.getParts().isEmpty() || !request.getUniqueParameters().isEmpty()) { + writer.print("--form "); + } + } + + private void writeUserOptionIfNecessary(CliOperationRequest request, + PrintWriter writer) { + String credentials = request.getBasicAuthCredentials(); + if (credentials != null) { + writer.print(String.format("--auth '%s' ", credentials)); + } + } + + private void writeMethodIfNecessary(OperationRequest request, PrintWriter writer) { + writer.print(String.format("%s", request.getMethod().name())); + } + + private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) { + for (OperationRequestPart part : request.getParts()) { + writer.printf(" \\%n '%s'", part.getName()); + if (!StringUtils.hasText(part.getSubmittedFileName())) { + // https://github.com/jkbrzt/httpie/issues/342 + writer.printf("@<(echo '%s')", part.getContentAsString()); + } + else { + writer.printf("@'%s'", part.getSubmittedFileName()); + } + } + } + + private void writeHeaders(OperationRequest request, PrintWriter writer) { + HttpHeaders headers = request.getHeaders(); + for (Entry> entry : headers.entrySet()) { + for (String header : entry.getValue()) { + // HTTPie adds Content-Type automatically with --form + if (!request.getParts().isEmpty() + && entry.getKey().equals(HttpHeaders.CONTENT_TYPE) + && header.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) { + continue; + } + writer.print(String.format(" '%s:%s'", entry.getKey(), header)); + } + } + } + + private void writeParametersIfNecessary(CliOperationRequest request, + PrintWriter writer) { + if (StringUtils.hasText(request.getContentAsString())) { + return; + } + if (!request.getParts().isEmpty()) { + writeContentUsingParameters(request.getParameters(), writer); + } + else if (request.isPutOrPost()) { + writeContentUsingParameters(request.getUniqueParameters(), writer); + } + } + + private void writeContentUsingParameters(Parameters parameters, PrintWriter writer) { + for (Map.Entry> entry : parameters.entrySet()) { + if (entry.getValue().isEmpty()) { + writer.append(String.format(" '%s='", entry.getKey())); + } + else { + for (String value : entry.getValue()) { + writer.append(String.format(" '%s=%s'", entry.getKey(), value)); + } + } + } + } +} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java deleted file mode 100644 index 0313ae02..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieDocumentation.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2014-2015 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.httpie; - -import java.util.Map; - -import org.springframework.restdocs.snippet.Snippet; - -/** - * Static factory methods for documenting a RESTful API as if it were being driven using - * the httpie command-line utility. - * - * @author Andy Wilkinson - * @author Paul-Christian Volkmer - * @author Raman Gupta - */ -public abstract class HttpieDocumentation { - - private HttpieDocumentation() { - - } - - /** - * Returns a new {@code Snippet} that will document the httpie request for the API - * operation. - * - * @return the snippet that will document the httpie request - */ - public static Snippet httpieRequest() { - return new HttpieRequestSnippet(); - } - - /** - * 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. - * - * @param attributes the attributes - * @return the snippet that will document the httpie request - */ - public static Snippet httpieRequest(Map attributes) { - return new HttpieRequestSnippet(attributes); - } - -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java deleted file mode 100644 index 7e251a90..00000000 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippet.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2014-2016 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.httpie; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.springframework.http.HttpHeaders; -import org.springframework.restdocs.cli.AbstractCliSnippet; -import org.springframework.restdocs.operation.Operation; -import org.springframework.restdocs.operation.OperationRequest; -import org.springframework.restdocs.operation.OperationRequestPart; -import org.springframework.restdocs.operation.Parameters; -import org.springframework.restdocs.snippet.Snippet; -import org.springframework.util.StringUtils; - -/** - * A {@link Snippet} that documents the httpie command for a request. - * - * @author Raman Gupta - * @see HttpieDocumentation#httpieRequest() - * @see HttpieDocumentation#httpieRequest(Map) - */ -public class HttpieRequestSnippet extends AbstractCliSnippet { - - /** - * Creates a new {@code CurlRequestSnippet} with no additional attributes. - */ - protected HttpieRequestSnippet() { - this(null); - } - - /** - * Creates a new {@code CurlRequestSnippet} with the given additional - * {@code attributes} that will be included in the model during template rendering. - * - * @param attributes The additional attributes - */ - protected HttpieRequestSnippet(Map attributes) { - super("httpie-request", attributes); - } - - @Override - protected Map createModel(final Operation operation) { - Map model = new HashMap<>(); - model.put("echo_content", getContentStdIn(operation)); - model.put("options", getOptions(operation)); - model.put("url", getUrl(operation)); - model.put("request_items", getRequestItems(operation)); - return model; - } - private Object getContentStdIn(final Operation operation) { - OperationRequest request = operation.getRequest(); - String content = request.getContentAsString(); - if (StringUtils.hasText(content)) { - return String.format("echo '%s' | ", content); - } - else { - return ""; - } - } - - private String getOptions(Operation operation) { - StringWriter options = new StringWriter(); - PrintWriter printer = new PrintWriter(options); - writeOptions(operation.getRequest(), printer); - writeUserOptionIfNecessary(operation.getRequest(), printer); - writeMethodIfNecessary(operation.getRequest(), printer); - return options.toString(); - } - - private String getUrl(Operation operation) { - return String.format("'%s'", operation.getRequest().getUri()); - } - - private String getRequestItems(final Operation operation) { - StringWriter requestItems = new StringWriter(); - PrintWriter printer = new PrintWriter(requestItems); - writeFormDataIfNecessary(operation.getRequest(), printer); - writeHeaders(operation.getRequest(), printer); - writeContent(operation.getRequest(), printer); - return requestItems.toString(); - } - private void writeOptions(OperationRequest request, PrintWriter writer) { - Parameters uniqueParameters = getUniqueParameters(request); - if (request.getParts().size() > 0 || uniqueParameters.size() > 0) { - writer.print("--form "); - } - } - - private void writeUserOptionIfNecessary(OperationRequest request, - PrintWriter writer) { - List headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION); - if (isBasicAuthHeader(headerValue)) { - String credentials = decodeBasicAuthHeader(headerValue); - writer.print(String.format("--auth '%s' ", credentials)); - } - } - - private void writeMethodIfNecessary(OperationRequest request, PrintWriter writer) { - writer.print(String.format("%s", request.getMethod().name())); - } - - private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) { - for (OperationRequestPart part : request.getParts()) { - writer.printf(" \\\n '%s'", part.getName()); - if (!StringUtils.hasText(part.getSubmittedFileName())) { - // httpie https://github.com/jkbrzt/httpie/issues/342 - writer.printf("@<(echo '%s')", part.getContentAsString()); - } - else { - writer.printf("@'%s'", part.getSubmittedFileName()); - } - // httpie does not currently support manually set content type by part - } - } - - private void writeHeaders(OperationRequest request, PrintWriter writer) { - HttpHeaders headers = request.getHeaders(); - for (Entry> entry : headers.entrySet()) { - if (allowedHeader(entry)) { - for (String header : entry.getValue()) { - // form Content-Type not required, added automatically by httpie with --form - if (request.getParts().size() > 0 - && entry.getKey().equals(HttpHeaders.CONTENT_TYPE) - && header.startsWith("multipart/form-data")) { - continue; - } - writer.print(String.format(" '%s:%s'", entry.getKey(), header)); - } - } - } - } - - private void writeContent(OperationRequest request, PrintWriter writer) { - String content = request.getContentAsString(); - if (!StringUtils.hasText(content)) { - if (!request.getParts().isEmpty()) { - for (Entry> entry : request.getParameters().entrySet()) { - for (String value : entry.getValue()) { - writer.print(String.format(" '%s=%s'", entry.getKey(), value)); - } - } - } - else if (isPutOrPost(request)) { - writeContentUsingParameters(request, writer); - } - } - } - - private void writeContentUsingParameters(OperationRequest request, - PrintWriter writer) { - Parameters uniqueParameters = getUniqueParameters(request); - for (Map.Entry> entry : uniqueParameters.entrySet()) { - if (entry.getValue().isEmpty()) { - writer.append(String.format(" '%s='", entry.getKey())); - } - else { - for (String value : entry.getValue()) { - writer.append(String.format(" '%s=%s'", entry.getKey(), value)); - } - } - } - } -} diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java similarity index 75% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java index 2bdf5df6..f06c9670 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/httpie/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 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. @@ -15,6 +15,6 @@ */ /** - * Documenting the httpie command required to make a request to a RESTful API. + * Documenting CLI commands required to make a request to a RESTful API. */ -package org.springframework.restdocs.cli.httpie; +package org.springframework.restdocs.cli; diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java index 7c2b459c..cb72f152 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/config/SnippetConfigurer.java @@ -21,8 +21,7 @@ import java.util.List; import java.util.Map; import org.springframework.restdocs.RestDocumentationContext; -import org.springframework.restdocs.cli.curl.CurlDocumentation; -import org.springframework.restdocs.cli.httpie.HttpieDocumentation; +import org.springframework.restdocs.cli.CliDocumentation; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.http.HttpDocumentation; import org.springframework.restdocs.snippet.Snippet; @@ -39,12 +38,9 @@ import org.springframework.restdocs.templates.TemplateFormats; public abstract class SnippetConfigurer extends AbstractNestedConfigurer { - private List defaultSnippets = Arrays.asList( - CurlDocumentation.curlRequest(), - HttpieDocumentation.httpieRequest(), - HttpDocumentation.httpRequest(), - HttpDocumentation.httpResponse() - ); + private List defaultSnippets = Arrays.asList(CliDocumentation.curlRequest(), + CliDocumentation.httpieRequest(), HttpDocumentation.httpRequest(), + HttpDocumentation.httpResponse()); /** * The default encoding for documentation snippets. diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java index a9b094f6..123c7a66 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlDocumentation.java @@ -24,8 +24,8 @@ import org.springframework.restdocs.snippet.Snippet; * Static factory methods for documenting a RESTful API as if it were being driven using * the cURL command-line utility. * - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. - * + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CliDocumentation}. * @author Andy Wilkinson * @author Yann Le Guern * @author Dmitriy Mayboroda @@ -44,8 +44,10 @@ public abstract class CurlDocumentation { * * @return the snippet that will document the curl request * - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CliDocumentation#curlRequest()}. */ + @Deprecated public static Snippet curlRequest() { return new CurlRequestSnippet(); } @@ -58,8 +60,10 @@ public abstract class CurlDocumentation { * @param attributes the attributes * @return the snippet that will document the curl request * - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}. + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CliDocumentation#curlRequest(Map)}. */ + @Deprecated public static Snippet curlRequest(Map attributes) { return new CurlRequestSnippet(attributes); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java index 29396bac..18148392 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/CurlRequestSnippet.java @@ -23,16 +23,23 @@ import org.springframework.restdocs.snippet.Snippet; /** * A {@link Snippet} that documents the curl command for a request. * + * @author Andy Wilkinson + * @author Paul-Christian Volkmer * @author Raman Gupta - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CurlRequestSnippet}. */ -public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.CurlRequestSnippet { +@Deprecated +public class CurlRequestSnippet + extends org.springframework.restdocs.cli.CurlRequestSnippet { /** * Creates a new {@code CurlRequestSnippet} with no additional attributes. * - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CurlRequestSnippet}. */ + @Deprecated protected CurlRequestSnippet() { super(); } @@ -41,8 +48,10 @@ public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.Cu * Creates a new {@code CurlRequestSnippet} with additional attributes. * @param attributes The additional attributes. * - * @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}. + * @deprecated Since 1.1 in favor of + * {@link org.springframework.restdocs.cli.CurlRequestSnippet}. */ + @Deprecated protected CurlRequestSnippet(final Map attributes) { super(attributes); } diff --git a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java similarity index 83% rename from spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java rename to spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java index 02ebe62c..e614e2b8 100644 --- a/spring-restdocs-core/src/main/java/org/springframework/restdocs/cli/curl/package-info.java +++ b/spring-restdocs-core/src/main/java/org/springframework/restdocs/curl/package-info.java @@ -16,5 +16,8 @@ /** * Documenting the curl command required to make a request to a RESTful API. + * + * @deprecated Since 1.1 in favor of functionality in + * {@code org.springframework.restdocs.cli} */ -package org.springframework.restdocs.cli.curl; +package org.springframework.restdocs.curl; diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet index 05771238..1b690a5f 100644 --- a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/asciidoctor/default-httpie-request.snippet @@ -1,4 +1,4 @@ [source,bash] ---- -$ {{echo_content}}http {{options}} {{url}}{{request_items}} +$ {{echoContent}}http {{options}} {{url}}{{requestItems}} ---- \ No newline at end of file diff --git a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet index acddb53f..65d78f74 100644 --- a/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet +++ b/spring-restdocs-core/src/main/resources/org/springframework/restdocs/templates/markdown/default-httpie-request.snippet @@ -1,3 +1,3 @@ ```bash -$ {{echo_content}}http {{options}} {{url}}{{request_items}} +$ {{echoContent}}http {{options}} {{url}}{{requestItems}} ``` \ No newline at end of file diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java similarity index 99% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java index 0d8bb632..bb7cff80 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/curl/CurlRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/CurlRequestSnippetTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.cli.curl; +package org.springframework.restdocs.cli; import java.io.IOException; diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java similarity index 96% rename from spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java rename to spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java index 1fb2abd7..6194fef9 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/httpie/HttpieRequestSnippetTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/cli/HttpieRequestSnippetTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.restdocs.cli.httpie; +package org.springframework.restdocs.cli; import java.io.IOException; @@ -156,8 +156,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void postRequestWithUrlEncodedParameter() throws IOException { this.snippet.expectHttpieRequest("post-request-with-url-encoded-parameter") - .withContents(codeBlock("bash").content( - "$ http --form POST 'http://localhost/foo' 'k1=a&b'")); + .withContents(codeBlock("bash") + .content("$ http --form POST 'http://localhost/foo' 'k1=a&b'")); new HttpieRequestSnippet() .document(operationBuilder("post-request-with-url-encoded-parameter") .request("http://localhost/foo").method("POST").param("k1", "a&b") @@ -214,8 +214,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void putRequestWithUrlEncodedParameter() throws IOException { this.snippet.expectHttpieRequest("put-request-with-url-encoded-parameter") - .withContents(codeBlock("bash").content( - "$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); + .withContents(codeBlock("bash") + .content("$ http --form PUT 'http://localhost/foo' 'k1=a&b'")); new HttpieRequestSnippet() .document(operationBuilder("put-request-with-url-encoded-parameter") .request("http://localhost/foo").method("PUT").param("k1", "a&b") @@ -224,8 +224,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { @Test public void requestWithHeaders() throws IOException { - this.snippet.expectHttpieRequest("request-with-headers") - .withContents(codeBlock("bash").content("$ http GET 'http://localhost/foo'" + this.snippet.expectHttpieRequest("request-with-headers").withContents( + codeBlock("bash").content("$ http GET 'http://localhost/foo'" + " 'Content-Type:application/json' 'a:alpha'")); new HttpieRequestSnippet().document( operationBuilder("request-with-headers").request("http://localhost/foo") @@ -298,7 +298,7 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests { } @Test - public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException { + public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException { this.snippet.expectHttpieRequest("basic-auth").withContents(codeBlock("bash") .content("$ http --auth 'user:secret' GET 'http://localhost/foo'")); new HttpieRequestSnippet() diff --git a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java index 7a36e320..7198ffc8 100644 --- a/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java +++ b/spring-restdocs-core/src/test/java/org/springframework/restdocs/config/RestDocumentationConfigurerTests.java @@ -24,9 +24,9 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.restdocs.RestDocumentationContext; -import org.springframework.restdocs.cli.curl.CurlDocumentation; -import org.springframework.restdocs.cli.curl.CurlRequestSnippet; -import org.springframework.restdocs.cli.httpie.HttpieRequestSnippet; +import org.springframework.restdocs.cli.CliDocumentation; +import org.springframework.restdocs.cli.CurlRequestSnippet; +import org.springframework.restdocs.cli.HttpieRequestSnippet; import org.springframework.restdocs.generate.RestDocumentationGenerator; import org.springframework.restdocs.http.HttpRequestSnippet; import org.springframework.restdocs.http.HttpResponseSnippet; @@ -108,7 +108,7 @@ public class RestDocumentationConfigurerTests { public void customDefaultSnippets() { RestDocumentationContext context = new RestDocumentationContext(null, null, null); Map configuration = new HashMap<>(); - this.configurer.snippets().withDefaults(CurlDocumentation.curlRequest()) + this.configurer.snippets().withDefaults(CliDocumentation.curlRequest()) .apply(configuration, context); assertThat(configuration, hasEntry( diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet index 0bff58b1..7c4eb048 100644 --- a/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/asciidoctor/httpie-request-with-title.snippet @@ -1,5 +1,5 @@ [source,bash] .{{title}} ---- -$ {{echo_content}}http {{options}} {{url}}{{request_items}} +$ {{echoContent}}http {{options}} {{url}}{{requestItems}} ---- \ No newline at end of file diff --git a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet index 29beade1..67dff455 100644 --- a/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet +++ b/spring-restdocs-core/src/test/resources/custom-snippet-templates/markdown/httpie-request-with-title.snippet @@ -1,4 +1,4 @@ {{title}} ```bash -$ {{echo_content}}http {{options}} {{url}}{{request_items}} +$ {{echoContent}}http {{options}} {{url}}{{requestItems}} ``` \ No newline at end of file diff --git a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java index ad06b187..51c35ee4 100644 --- a/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java +++ b/spring-restdocs-mockmvc/src/test/java/org/springframework/restdocs/mockmvc/MockMvcRestDocumentationIntegrationTests.java @@ -56,7 +56,7 @@ import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; -import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest; +import static org.springframework.restdocs.cli.CliDocumentation.curlRequest; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel; import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -171,6 +171,38 @@ public class MockMvcRestDocumentationIntegrationTests { + "-H 'Accept: application/json' -d 'a=alpha'")))); } + @Test + public void httpieSnippetWithContent() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration(this.restDocumentation)).build(); + + mockMvc.perform(post("/").accept(MediaType.APPLICATION_JSON).content("content")) + .andExpect(status().isOk()) + .andDo(document("httpie-snippet-with-content")); + assertThat( + new File( + "build/generated-snippets/httpie-snippet-with-content/httpie-request.adoc"), + is(snippet(asciidoctor()).withContents(codeBlock(asciidoctor(), "bash") + .content("$ echo 'content' | http POST 'http://localhost:8080/'" + + " 'Accept:application/json'")))); + } + + @Test + public void httpieSnippetWithQueryStringOnPost() throws Exception { + MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) + .apply(documentationConfiguration(this.restDocumentation)).build(); + mockMvc.perform(post("/?foo=bar").param("foo", "bar").param("a", "alpha") + .accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andDo(document("httpie-snippet-with-query-string")); + assertThat( + new File( + "build/generated-snippets/httpie-snippet-with-query-string/httpie-request.adoc"), + is(snippet(asciidoctor()) + .withContents(codeBlock(asciidoctor(), "bash").content("$ http " + + "--form POST 'http://localhost:8080/?foo=bar' " + + "'Accept:application/json' 'a=alpha'")))); + } + @Test public void linksSnippet() throws Exception { MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)