Merge pull request #207 from Raman Gupta

* gh-207:
  Polish contribution
  Add support for generating an HTTPie snippet

Closes gh-207
This commit is contained in:
Andy Wilkinson
2016-03-09 17:38:27 +00:00
26 changed files with 1078 additions and 245 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.CliDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.templates.TemplateFormats.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
</module>
<module name="IllegalImport" />
<module name="RedundantImport" />

View File

@@ -87,11 +87,12 @@ include::{examples-dir}/com/example/restassured/CustomFormat.java[tags=custom-fo
[[configuration-default-snippets]]
=== Default snippets
Three snippets are produced by default:
Four snippets are produced by default:
- `curl-request`
- `http-request`
- `http-response`
- `httpie-request`
You can change the default snippet configuration during setup using the
`RestDocumentationConfigurer` API. For example, to only produce the `curl-request`

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,11 +371,12 @@ 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/http-request.adoc`
* `<output-directory>/index/http-response.adoc`
* `<output-directory>/index/httpie-request.adoc`
Refer to <<documenting-your-api>> for more information about these and other snippets
that can be produced by Spring REST Docs.

View File

@@ -18,20 +18,21 @@ package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.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.CliDocumentation.curlRequest;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build");
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(
"build");
@Autowired
private WebApplicationContext context;
@@ -42,8 +43,8 @@ public class CustomDefaultSnippets {
public void setUp() {
// tag::custom-default-snippets[]
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.apply(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}

View File

@@ -16,21 +16,21 @@
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 org.junit.Before;
import org.junit.Rule;
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.cli.CliDocumentation.curlRequest;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build");
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(
"build");
private RequestSpecification spec;
@@ -38,8 +38,8 @@ public class CustomDefaultSnippets {
public void setUp() {
// tag::custom-default-snippets[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.addFilter(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.Map;
import org.springframework.restdocs.snippet.Snippet;
/**
* Static factory methods for documenting a RESTful API as if it were being driven using a
* command-line utility such as curl or HTTPie.
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @author Raman Gupta
*/
public abstract class CliDocumentation {
private CliDocumentation() {
}
/**
* 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);
}
/**
* 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,190 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* 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.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.util.Base64Utils;
/**
* An {@link OperationRequest} wrapper with methods that are useful when producing a
* snippet containing a CLI command for a request.
*
* @author Andy Wilkinson
* @author Raman Gupta
*/
final class CliOperationRequest implements OperationRequest {
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);
}
private final OperationRequest delegate;
CliOperationRequest(OperationRequest delegate) {
this.delegate = delegate;
}
Parameters getUniqueParameters() {
Parameters queryStringParameters = new QueryStringParser()
.parse(this.delegate.getUri());
Parameters uniqueParameters = new Parameters();
for (Map.Entry<String, List<String>> parameter : this.delegate.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);
}
}
}
}
boolean isPutOrPost() {
return HttpMethod.PUT.equals(this.delegate.getMethod())
|| HttpMethod.POST.equals(this.delegate.getMethod());
}
String getBasicAuthCredentials() {
List<String> headerValue = this.delegate.getHeaders()
.get(HttpHeaders.AUTHORIZATION);
if (BasicAuthHeaderFilter.isBasicAuthHeader(headerValue)) {
return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue);
}
return null;
}
@Override
public byte[] getContent() {
return this.delegate.getContent();
}
@Override
public String getContentAsString() {
return this.delegate.getContentAsString();
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders filteredHeaders = new HttpHeaders();
for (Entry<String, List<String>> header : this.delegate.getHeaders().entrySet()) {
if (allowedHeader(header)) {
filteredHeaders.put(header.getKey(), header.getValue());
}
}
return HttpHeaders.readOnlyHttpHeaders(filteredHeaders);
}
private boolean allowedHeader(Map.Entry<String, List<String>> header) {
for (HeaderFilter headerFilter : HEADER_FILTERS) {
if (!headerFilter.allow(header.getKey(), header.getValue())) {
return false;
}
}
return true;
}
@Override
public HttpMethod getMethod() {
return this.delegate.getMethod();
}
@Override
public Parameters getParameters() {
return this.delegate.getParameters();
}
@Override
public Collection<OperationRequestPart> getParts() {
return this.delegate.getParts();
}
@Override
public URI getUri() {
return this.delegate.getUri();
}
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

@@ -0,0 +1,159 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.cli;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.http.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.StringUtils;
/**
* A {@link Snippet} that documents the curl command for a request.
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @see CliDocumentation#curlRequest()
* @see CliDocumentation#curlRequest(Map)
*/
public class CurlRequestSnippet extends TemplatedSnippet {
/**
* 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);
CliOperationRequest request = new CliOperationRequest(operation.getRequest());
writeUserOptionIfNecessary(request, printer);
writeHttpMethodIfNecessary(request, printer);
writeHeaders(request, printer);
writePartsIfNecessary(request, printer);
writeContent(request, printer);
return command.toString();
}
private void writeIncludeHeadersInOutputOption(PrintWriter writer) {
writer.print("-i");
}
private void writeUserOptionIfNecessary(CliOperationRequest request,
PrintWriter writer) {
String credentials = request.getBasicAuthCredentials();
if (credentials != null) {
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(CliOperationRequest request, PrintWriter writer) {
for (Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
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(CliOperationRequest 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 (request.isPutOrPost()) {
writeContentUsingParameters(request, writer);
}
}
private void writeContentUsingParameters(CliOperationRequest request,
PrintWriter writer) {
Parameters uniqueParameters = request.getUniqueParameters();
String queryString = uniqueParameters.toQueryString();
if (StringUtils.hasText(queryString)) {
writer.print(String.format(" -d '%s'", queryString));
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.cli;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.StringUtils;
/**
* A {@link Snippet} that documents the HTTPie command for a request.
*
* @author Raman Gupta
* @author Andy Wilkinson
* @see CliDocumentation#httpieRequest()
* @see CliDocumentation#httpieRequest(Map)
*/
public class HttpieRequestSnippet extends TemplatedSnippet {
/**
* Creates a new {@code HttpieRequestSnippet} with no additional attributes.
*/
protected HttpieRequestSnippet() {
this(null);
}
/**
* Creates a new {@code HttpieRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
protected HttpieRequestSnippet(Map<String, Object> attributes) {
super("httpie-request", attributes);
}
@Override
protected Map<String, Object> createModel(Operation operation) {
Map<String, Object> model = new HashMap<>();
CliOperationRequest request = new CliOperationRequest(operation.getRequest());
model.put("echoContent", getContentStandardIn(request));
model.put("options", getOptions(request));
model.put("url", getUrl(request));
model.put("requestItems", getRequestItems(request));
return model;
}
private Object getContentStandardIn(CliOperationRequest request) {
String content = request.getContentAsString();
if (StringUtils.hasText(content)) {
return String.format("echo '%s' | ", content);
}
return "";
}
private String getOptions(CliOperationRequest request) {
StringWriter options = new StringWriter();
PrintWriter printer = new PrintWriter(options);
writeOptions(request, printer);
writeUserOptionIfNecessary(request, printer);
writeMethodIfNecessary(request, printer);
return options.toString();
}
private String getUrl(OperationRequest request) {
return String.format("'%s'", request.getUri());
}
private String getRequestItems(CliOperationRequest request) {
StringWriter requestItems = new StringWriter();
PrintWriter printer = new PrintWriter(requestItems);
writeFormDataIfNecessary(request, printer);
writeHeaders(request, printer);
writeParametersIfNecessary(request, printer);
return requestItems.toString();
}
private void writeOptions(CliOperationRequest request, PrintWriter writer) {
if (!request.getParts().isEmpty() || !request.getUniqueParameters().isEmpty()) {
writer.print("--form ");
}
}
private void writeUserOptionIfNecessary(CliOperationRequest request,
PrintWriter writer) {
String credentials = request.getBasicAuthCredentials();
if (credentials != null) {
writer.print(String.format("--auth '%s' ", credentials));
}
}
private void writeMethodIfNecessary(OperationRequest request, PrintWriter writer) {
writer.print(String.format("%s", request.getMethod().name()));
}
private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) {
for (OperationRequestPart part : request.getParts()) {
writer.printf(" \\%n '%s'", part.getName());
if (!StringUtils.hasText(part.getSubmittedFileName())) {
// https://github.com/jkbrzt/httpie/issues/342
writer.printf("@<(echo '%s')", part.getContentAsString());
}
else {
writer.printf("@'%s'", part.getSubmittedFileName());
}
}
}
private void writeHeaders(OperationRequest request, PrintWriter writer) {
HttpHeaders headers = request.getHeaders();
for (Entry<String, List<String>> entry : headers.entrySet()) {
for (String header : entry.getValue()) {
// HTTPie adds Content-Type automatically with --form
if (!request.getParts().isEmpty()
&& entry.getKey().equals(HttpHeaders.CONTENT_TYPE)
&& header.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
continue;
}
writer.print(String.format(" '%s:%s'", entry.getKey(), header));
}
}
}
private void writeParametersIfNecessary(CliOperationRequest request,
PrintWriter writer) {
if (StringUtils.hasText(request.getContentAsString())) {
return;
}
if (!request.getParts().isEmpty()) {
writeContentUsingParameters(request.getParameters(), writer);
}
else if (request.isPutOrPost()) {
writeContentUsingParameters(request.getUniqueParameters(), writer);
}
}
private void writeContentUsingParameters(Parameters parameters, PrintWriter writer) {
for (Map.Entry<String, List<String>> entry : parameters.entrySet()) {
if (entry.getValue().isEmpty()) {
writer.append(String.format(" '%s='", entry.getKey()));
}
else {
for (String value : entry.getValue()) {
writer.append(String.format(" '%s=%s'", entry.getKey(), value));
}
}
}
}
}

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,20 @@
/*
* 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.
*/
/**
* Documenting CLI commands required to make a request to a RESTful API.
*/
package org.springframework.restdocs.cli;

View File

@@ -21,7 +21,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.curl.CurlDocumentation;
import org.springframework.restdocs.cli.CliDocumentation;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.snippet.Snippet;
@@ -38,8 +38,9 @@ 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(CliDocumentation.curlRequest(),
CliDocumentation.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.CliDocumentation}.
* @author Andy Wilkinson
* @author Yann Le Guern
* @author Dmitriy Mayboroda
* @author Jonathan Pearlin
*/
@Deprecated
public abstract class CurlDocumentation {
private CurlDocumentation() {
@@ -40,7 +43,11 @@ 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.CliDocumentation#curlRequest()}.
*/
@Deprecated
public static Snippet curlRequest() {
return new CurlRequestSnippet();
}
@@ -52,7 +59,11 @@ 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.CliDocumentation#curlRequest(Map)}.
*/
@Deprecated
public static Snippet curlRequest(Map<String, Object> attributes) {
return new CurlRequestSnippet(attributes);
}

View File

@@ -16,243 +16,44 @@
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.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);
}
@Deprecated
public class CurlRequestSnippet
extends org.springframework.restdocs.cli.CurlRequestSnippet {
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
*
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CurlRequestSnippet}.
*/
@Deprecated
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.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);
}
@Deprecated
protected CurlRequestSnippet(final Map<String, Object> attributes) {
super(attributes);
}
}

View File

@@ -16,5 +16,8 @@
/**
* Documenting the curl command required to make a request to a RESTful API.
*
* @deprecated Since 1.1 in favor of functionality in
* {@code org.springframework.restdocs.cli}
*/
package org.springframework.restdocs.curl;

View File

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

View File

@@ -0,0 +1,3 @@
```bash
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
```

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.curl;
package org.springframework.restdocs.cli;
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;
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 basicAuthCredentialsAreSuppliedUsingAuthOption() 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

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

@@ -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.CliDocumentation;
import org.springframework.restdocs.cli.CurlRequestSnippet;
import org.springframework.restdocs.cli.HttpieRequestSnippet;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpRequestSnippet;
import org.springframework.restdocs.http.HttpResponseSnippet;
@@ -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()),
@@ -106,7 +108,7 @@ public class RestDocumentationConfigurerTests {
public void customDefaultSnippets() {
RestDocumentationContext context = new RestDocumentationContext(null, null, null);
Map<String, Object> configuration = new HashMap<>();
this.configurer.snippets().withDefaults(CurlDocumentation.curlRequest())
this.configurer.snippets().withDefaults(CliDocumentation.curlRequest())
.apply(configuration, context);
assertThat(configuration,
hasEntry(

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}}
----
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
----

View File

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

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.CliDocumentation.curlRequest;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
@@ -171,6 +171,38 @@ public class MockMvcRestDocumentationIntegrationTests {
+ "-H 'Accept: application/json' -d 'a=alpha'"))));
}
@Test
public void httpieSnippetWithContent() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)).build();
mockMvc.perform(post("/").accept(MediaType.APPLICATION_JSON).content("content"))
.andExpect(status().isOk())
.andDo(document("httpie-snippet-with-content"));
assertThat(
new File(
"build/generated-snippets/httpie-snippet-with-content/httpie-request.adoc"),
is(snippet(asciidoctor()).withContents(codeBlock(asciidoctor(), "bash")
.content("$ echo 'content' | http POST 'http://localhost:8080/'"
+ " 'Accept:application/json'"))));
}
@Test
public void httpieSnippetWithQueryStringOnPost() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)).build();
mockMvc.perform(post("/?foo=bar").param("foo", "bar").param("a", "alpha")
.accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())
.andDo(document("httpie-snippet-with-query-string"));
assertThat(
new File(
"build/generated-snippets/httpie-snippet-with-query-string/httpie-request.adoc"),
is(snippet(asciidoctor())
.withContents(codeBlock(asciidoctor(), "bash").content("$ http "
+ "--form POST 'http://localhost:8080/?foo=bar' "
+ "'Accept:application/json' 'a=alpha'"))));
}
@Test
public void linksSnippet() throws Exception {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context)