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;