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
This commit is contained in:
Raman Gupta
2016-03-01 16:35:32 -05:00
committed by Andy Wilkinson
parent 536e49287d
commit b26d8c085d
26 changed files with 1061 additions and 236 deletions

View File

@@ -73,7 +73,7 @@
<module name="AvoidStarImport" />
<module name="AvoidStaticImport">
<property name="excludes"
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.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.*" />
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.*" />
</module>
<module name="IllegalImport" />
<module name="RedundantImport" />

View File

@@ -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

View File

@@ -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:
* `<output-directory>/index/curl-request.adoc`
* `<output-directory>/index/httpie-request.adoc`
* `<output-directory>/index/http-request.adoc`
* `<output-directory>/index/http-response.adoc`

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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<HeaderFilter> HEADER_FILTERS;
static {
Set<HeaderFilter> 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<String, Object> 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<String, Object> 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<String, List<String>> parameter : request.getParameters().entrySet()) {
addIfUnique(parameter, queryStringParameters, uniqueParameters);
}
return uniqueParameters;
}
private void addIfUnique(Map.Entry<String, List<String>> parameter,
Parameters queryStringParameters, Parameters uniqueParameters) {
if (!queryStringParameters.containsKey(parameter.getKey())) {
uniqueParameters.put(parameter.getKey(), parameter.getValue());
}
else {
List<String> candidates = parameter.getValue();
List<String> 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<String, List<String>> 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<String> 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<String> headerValue) {
return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue);
}
private interface HeaderFilter {
boolean allow(String name, List<String> value);
}
private static final class BasicAuthHeaderFilter implements HeaderFilter {
@Override
public boolean allow(String name, List<String> value) {
return !(HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value));
}
static boolean isBasicAuthHeader(List<String> value) {
return value != null && (!value.isEmpty())
&& value.get(0).startsWith("Basic ");
}
static String decodeBasicAuthHeader(List<String> 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<String> value) {
return !this.name.equalsIgnoreCase(name);
}
}
}

View File

@@ -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;

View File

@@ -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<String, Object> attributes) {
return new CurlRequestSnippet(attributes);
}
}

View File

@@ -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<String, Object> attributes) {
super("curl-request", attributes);
}
@Override
protected Map<String, Object> createModel(Operation operation) {
Map<String, Object> 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<String> 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<String, List<String>> 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<String, List<String>> 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));
}
}
}

View File

@@ -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;

View File

@@ -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<String, Object> attributes) {
return new HttpieRequestSnippet(attributes);
}
}

View File

@@ -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<String, Object> attributes) {
super("httpie-request", attributes);
}
@Override
protected Map<String, Object> createModel(final Operation operation) {
Map<String, Object> 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<String> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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));
}
}
}
}
}

View File

@@ -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;

View File

@@ -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<PARENT, TYPE>
extends AbstractNestedConfigurer<PARENT> {
private List<Snippet> defaultSnippets = Arrays.asList(CurlDocumentation.curlRequest(),
HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse());
private List<Snippet> defaultSnippets = Arrays.asList(
CurlDocumentation.curlRequest(),
HttpieDocumentation.httpieRequest(),
HttpDocumentation.httpRequest(),
HttpDocumentation.httpResponse()
);
/**
* The default encoding for documentation snippets.

View File

@@ -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<String, Object> attributes) {
return new CurlRequestSnippet(attributes);

View File

@@ -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<HeaderFilter> HEADER_FILTERS;
static {
Set<HeaderFilter> 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<String, Object> attributes) {
super("curl-request", attributes);
}
@Override
protected Map<String, Object> createModel(Operation operation) {
Map<String, Object> 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<String> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> 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<String, List<String>> parameter : request.getParameters().entrySet()) {
addIfUnique(parameter, queryStringParameters, uniqueParameters);
}
return uniqueParameters;
}
private void addIfUnique(Entry<String, List<String>> parameter,
Parameters queryStringParameters, Parameters uniqueParameters) {
if (!queryStringParameters.containsKey(parameter.getKey())) {
uniqueParameters.put(parameter.getKey(), parameter.getValue());
}
else {
List<String> candidates = parameter.getValue();
List<String> 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<String> value);
}
private static final class BasicAuthHeaderFilter implements HeaderFilter {
@Override
public boolean allow(String name, List<String> value) {
if (HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value)) {
return false;
}
return true;
}
static boolean isBasicAuthHeader(List<String> value) {
return value != null && (!value.isEmpty())
&& value.get(0).startsWith("Basic ");
}
static String decodeBasicAuthHeader(List<String> 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<String> value) {
return !this.name.equalsIgnoreCase(name);
}
protected CurlRequestSnippet(final Map<String, Object> attributes) {
super(attributes);
}
}

View File

@@ -0,0 +1,4 @@
[source,bash]
----
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
----

View File

@@ -0,0 +1,3 @@
```bash
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
```

View File

@@ -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;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.curl;
package org.springframework.restdocs.cli.curl;
import java.io.IOException;

View File

@@ -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());
}
}

View File

@@ -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()),

View File

@@ -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;

View File

@@ -0,0 +1,5 @@
[source,bash]
.{{title}}
----
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
----

View File

@@ -0,0 +1,4 @@
{{title}}
```bash
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
```

View File

@@ -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;