Polish contribution

This commit polishes the HTTPie request snippet contribution made in
b26d8c0. It makes the following significant changes:

- Applies project’s coding conventions for formatting and the like
- Moves to a composition-based approach for sharing functionality
  between the curl and HTTPie snippets by replacing AbstractCliSnippet
  with CliOperationRequest.
- Introduces a single package for CLI command snippets, thereby allowing
  more code to be package-private.

See gh-207
This commit is contained in:
Andy Wilkinson
2016-03-09 17:23:04 +00:00
parent b26d8c085d
commit 37e16bc308
24 changed files with 395 additions and 387 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.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.*" />
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

@@ -374,9 +374,9 @@ static `document` method on
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`
* `<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,19 +18,21 @@ package com.example.mockmvc;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest;
import static org.springframework.restdocs.cli.CliDocumentation.curlRequest;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build");
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(
"build");
@Autowired
private WebApplicationContext context;
@@ -41,8 +43,8 @@ public class CustomDefaultSnippets {
public void setUp() {
// tag::custom-default-snippets[]
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply(documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.apply(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}

View File

@@ -16,20 +16,21 @@
package com.example.restassured;
import org.junit.Before;
import org.junit.Rule;
import org.springframework.restdocs.JUnitRestDocumentation;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.specification.RequestSpecification;
import org.junit.Before;
import org.junit.Rule;
import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest;
import org.springframework.restdocs.JUnitRestDocumentation;
import static org.springframework.restdocs.cli.CliDocumentation.curlRequest;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
public class CustomDefaultSnippets {
@Rule
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation("build");
public final JUnitRestDocumentation restDocumentation = new JUnitRestDocumentation(
"build");
private RequestSpecification spec;
@@ -37,8 +38,8 @@ public class CustomDefaultSnippets {
public void setUp() {
// tag::custom-default-snippets[]
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(this.restDocumentation)
.snippets().withDefaults(curlRequest()))
.addFilter(documentationConfiguration(this.restDocumentation).snippets()
.withDefaults(curlRequest()))
.build();
// end::custom-default-snippets[]
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,24 +14,23 @@
* limitations under the License.
*/
package org.springframework.restdocs.cli.curl;
package org.springframework.restdocs.cli;
import java.util.Map;
import org.springframework.restdocs.snippet.Snippet;
/**
* Static factory methods for documenting a RESTful API as if it were being driven using
* the cURL command-line utility.
* Static factory methods for documenting a RESTful API as if it were being driven using a
* command-line utility such as curl or HTTPie.
*
* @author Andy Wilkinson
* @author Yann Le Guern
* @author Dmitriy Mayboroda
* @author Jonathan Pearlin
* @author Paul-Christian Volkmer
* @author Raman Gupta
*/
public abstract class CurlDocumentation {
public abstract class CliDocumentation {
private CurlDocumentation() {
private CliDocumentation() {
}
@@ -57,4 +56,26 @@ public abstract class CurlDocumentation {
return new CurlRequestSnippet(attributes);
}
/**
* Returns a new {@code Snippet} that will document the HTTPie request for the API
* operation.
*
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest() {
return new HttpieRequestSnippet();
}
/**
* Returns a new {@code Snippet} that will document the HTTPie request for the API
* operation. The given {@code attributes} will be available during snippet
* generation.
*
* @param attributes the attributes
* @return the snippet that will document the HTTPie request
*/
public static Snippet httpieRequest(Map<String, Object> attributes) {
return new HttpieRequestSnippet(attributes);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2016 the original author or authors.
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,29 +16,30 @@
package org.springframework.restdocs.cli;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.Base64Utils;
/**
* An abstract {@link Snippet} that for CLI requests.
* An {@link OperationRequest} wrapper with methods that are useful when producing a
* snippet containing a CLI command for a request.
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @author Raman Gupta
*/
public abstract class AbstractCliSnippet extends TemplatedSnippet {
final class CliOperationRequest implements OperationRequest {
private static final Set<HeaderFilter> HEADER_FILTERS;
@@ -50,33 +51,19 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet {
HEADER_FILTERS = Collections.unmodifiableSet(headerFilters);
}
/**
* Create a new abstract cli snippet with the given name and attributes.
* @param snippetName The snippet name.
* @param attributes The snippet attributes.
*/
protected AbstractCliSnippet(String snippetName, Map<String, Object> attributes) {
super(snippetName, attributes);
private final OperationRequest delegate;
CliOperationRequest(OperationRequest delegate) {
this.delegate = delegate;
}
/**
* Create the model which will be passed to the template for rendering.
* @param operation The operation
* @return The model.
*/
protected abstract Map<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 getUniqueParameters() {
Parameters queryStringParameters = new QueryStringParser()
.parse(request.getUri());
.parse(this.delegate.getUri());
Parameters uniqueParameters = new Parameters();
for (Map.Entry<String, List<String>> parameter : request.getParameters().entrySet()) {
for (Map.Entry<String, List<String>> parameter : this.delegate.getParameters()
.entrySet()) {
addIfUnique(parameter, queryStringParameters, uniqueParameters);
}
return uniqueParameters;
@@ -98,23 +85,42 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet {
}
}
/**
* Whether the request operation is a PUT or a POST.
* @param request The request.
* @return boolean
*/
protected boolean isPutOrPost(OperationRequest request) {
return HttpMethod.PUT.equals(request.getMethod())
|| HttpMethod.POST.equals(request.getMethod());
boolean isPutOrPost() {
return HttpMethod.PUT.equals(this.delegate.getMethod())
|| HttpMethod.POST.equals(this.delegate.getMethod());
}
/**
* Whether the passed header is allowed according to the configured
* header filters.
* @param header The header to test.
* @return boolean
*/
protected boolean allowedHeader(Map.Entry<String, List<String>> header) {
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;
@@ -123,22 +129,24 @@ public abstract class AbstractCliSnippet extends TemplatedSnippet {
return true;
}
/**
* Determine if the header passed is a basic auth header.
* @param headerValue The header to test.
* @return boolean
*/
protected boolean isBasicAuthHeader(List<String> headerValue) {
return BasicAuthHeaderFilter.isBasicAuthHeader(headerValue);
@Override
public HttpMethod getMethod() {
return this.delegate.getMethod();
}
/**
* Decodes a basic auth header into name:password credentials.
* @param headerValue The encoded header value.
* @return name:password credentials.
*/
protected String decodeBasicAuthHeader(List<String> headerValue) {
return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue);
@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 {

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.cli.curl;
package org.springframework.restdocs.cli;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -23,14 +23,13 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.restdocs.cli.AbstractCliSnippet;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.restdocs.snippet.TemplatedSnippet;
import org.springframework.util.StringUtils;
/**
@@ -38,10 +37,10 @@ import org.springframework.util.StringUtils;
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @see CurlDocumentation#curlRequest()
* @see CurlDocumentation#curlRequest(Map)
* @see CliDocumentation#curlRequest()
* @see CliDocumentation#curlRequest(Map)
*/
public class CurlRequestSnippet extends AbstractCliSnippet {
public class CurlRequestSnippet extends TemplatedSnippet {
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
@@ -76,11 +75,12 @@ public class CurlRequestSnippet extends AbstractCliSnippet {
StringWriter command = new StringWriter();
PrintWriter printer = new PrintWriter(command);
writeIncludeHeadersInOutputOption(printer);
writeUserOptionIfNecessary(operation.getRequest(), printer);
writeHttpMethodIfNecessary(operation.getRequest(), printer);
writeHeaders(operation.getRequest().getHeaders(), printer);
writePartsIfNecessary(operation.getRequest(), printer);
writeContent(operation.getRequest(), printer);
CliOperationRequest request = new CliOperationRequest(operation.getRequest());
writeUserOptionIfNecessary(request, printer);
writeHttpMethodIfNecessary(request, printer);
writeHeaders(request, printer);
writePartsIfNecessary(request, printer);
writeContent(request, printer);
return command.toString();
}
@@ -89,11 +89,10 @@ public class CurlRequestSnippet extends AbstractCliSnippet {
writer.print("-i");
}
private void writeUserOptionIfNecessary(OperationRequest request,
private void writeUserOptionIfNecessary(CliOperationRequest request,
PrintWriter writer) {
List<String> headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
if (isBasicAuthHeader(headerValue)) {
String credentials = decodeBasicAuthHeader(headerValue);
String credentials = request.getBasicAuthCredentials();
if (credentials != null) {
writer.print(String.format(" -u '%s'", credentials));
}
}
@@ -105,12 +104,10 @@ public class CurlRequestSnippet extends AbstractCliSnippet {
}
}
private void writeHeaders(HttpHeaders headers, PrintWriter writer) {
for (Entry<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 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));
}
}
}
@@ -133,7 +130,7 @@ public class CurlRequestSnippet extends AbstractCliSnippet {
}
}
private void writeContent(OperationRequest request, PrintWriter writer) {
private void writeContent(CliOperationRequest request, PrintWriter writer) {
String content = request.getContentAsString();
if (StringUtils.hasText(content)) {
writer.print(String.format(" -d '%s'", content));
@@ -145,17 +142,18 @@ public class CurlRequestSnippet extends AbstractCliSnippet {
}
}
}
else if (isPutOrPost(request)) {
else if (request.isPutOrPost()) {
writeContentUsingParameters(request, writer);
}
}
private void writeContentUsingParameters(OperationRequest request,
private void writeContentUsingParameters(CliOperationRequest request,
PrintWriter writer) {
Parameters uniqueParameters = getUniqueParameters(request);
Parameters uniqueParameters = request.getUniqueParameters();
String queryString = uniqueParameters.toQueryString();
if (StringUtils.hasText(queryString)) {
writer.print(String.format(" -d '%s'", queryString));
}
}
}

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

@@ -1,59 +0,0 @@
/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.cli.httpie;
import java.util.Map;
import org.springframework.restdocs.snippet.Snippet;
/**
* Static factory methods for documenting a RESTful API as if it were being driven using
* the httpie command-line utility.
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @author Raman Gupta
*/
public abstract class HttpieDocumentation {
private HttpieDocumentation() {
}
/**
* Returns a new {@code Snippet} that will document the httpie request for the API
* operation.
*
* @return the snippet that will document the httpie request
*/
public static Snippet httpieRequest() {
return new HttpieRequestSnippet();
}
/**
* Returns a new {@code Snippet} that will document the httpie request for the API
* operation. The given {@code attributes} will be available during snippet
* generation.
*
* @param attributes the attributes
* @return the snippet that will document the httpie request
*/
public static Snippet httpieRequest(Map<String, Object> attributes) {
return new HttpieRequestSnippet(attributes);
}
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.restdocs.cli.httpie;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.http.HttpHeaders;
import org.springframework.restdocs.cli.AbstractCliSnippet;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.snippet.Snippet;
import org.springframework.util.StringUtils;
/**
* A {@link Snippet} that documents the httpie command for a request.
*
* @author Raman Gupta
* @see HttpieDocumentation#httpieRequest()
* @see HttpieDocumentation#httpieRequest(Map)
*/
public class HttpieRequestSnippet extends AbstractCliSnippet {
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
*/
protected HttpieRequestSnippet() {
this(null);
}
/**
* Creates a new {@code CurlRequestSnippet} with the given additional
* {@code attributes} that will be included in the model during template rendering.
*
* @param attributes The additional attributes
*/
protected HttpieRequestSnippet(Map<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

@@ -1,5 +1,5 @@
/*
* Copyright 2014-2015 the original author or authors.
* Copyright 2014-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,6 +15,6 @@
*/
/**
* Documenting the httpie command required to make a request to a RESTful API.
* Documenting CLI commands required to make a request to a RESTful API.
*/
package org.springframework.restdocs.cli.httpie;
package org.springframework.restdocs.cli;

View File

@@ -21,8 +21,7 @@ import java.util.List;
import java.util.Map;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.cli.curl.CurlDocumentation;
import org.springframework.restdocs.cli.httpie.HttpieDocumentation;
import org.springframework.restdocs.cli.CliDocumentation;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpDocumentation;
import org.springframework.restdocs.snippet.Snippet;
@@ -39,12 +38,9 @@ import org.springframework.restdocs.templates.TemplateFormats;
public abstract class SnippetConfigurer<PARENT, TYPE>
extends AbstractNestedConfigurer<PARENT> {
private List<Snippet> defaultSnippets = Arrays.asList(
CurlDocumentation.curlRequest(),
HttpieDocumentation.httpieRequest(),
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

@@ -24,8 +24,8 @@ import org.springframework.restdocs.snippet.Snippet;
* Static factory methods for documenting a RESTful API as if it were being driven using
* the cURL command-line utility.
*
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
*
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CliDocumentation}.
* @author Andy Wilkinson
* @author Yann Le Guern
* @author Dmitriy Mayboroda
@@ -44,8 +44,10 @@ public abstract class CurlDocumentation {
*
* @return the snippet that will document the curl request
*
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CliDocumentation#curlRequest()}.
*/
@Deprecated
public static Snippet curlRequest() {
return new CurlRequestSnippet();
}
@@ -58,8 +60,10 @@ public abstract class CurlDocumentation {
* @param attributes the attributes
* @return the snippet that will document the curl request
*
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CliDocumentation#curlRequest(Map)}.
*/
@Deprecated
public static Snippet curlRequest(Map<String, Object> attributes) {
return new CurlRequestSnippet(attributes);
}

View File

@@ -23,16 +23,23 @@ import org.springframework.restdocs.snippet.Snippet;
/**
* A {@link Snippet} that documents the curl command for a request.
*
* @author Andy Wilkinson
* @author Paul-Christian Volkmer
* @author Raman Gupta
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CurlRequestSnippet}.
*/
public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.CurlRequestSnippet {
@Deprecated
public class CurlRequestSnippet
extends org.springframework.restdocs.cli.CurlRequestSnippet {
/**
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
*
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CurlRequestSnippet}.
*/
@Deprecated
protected CurlRequestSnippet() {
super();
}
@@ -41,8 +48,10 @@ public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.Cu
* Creates a new {@code CurlRequestSnippet} with additional attributes.
* @param attributes The additional attributes.
*
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
* @deprecated Since 1.1 in favor of
* {@link org.springframework.restdocs.cli.CurlRequestSnippet}.
*/
@Deprecated
protected CurlRequestSnippet(final Map<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.cli.curl;
package org.springframework.restdocs.curl;

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.restdocs.cli.httpie;
package org.springframework.restdocs.cli;
import java.io.IOException;
@@ -156,8 +156,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests {
@Test
public void postRequestWithUrlEncodedParameter() throws IOException {
this.snippet.expectHttpieRequest("post-request-with-url-encoded-parameter")
.withContents(codeBlock("bash").content(
"$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
.withContents(codeBlock("bash")
.content("$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
new HttpieRequestSnippet()
.document(operationBuilder("post-request-with-url-encoded-parameter")
.request("http://localhost/foo").method("POST").param("k1", "a&b")
@@ -214,8 +214,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests {
@Test
public void putRequestWithUrlEncodedParameter() throws IOException {
this.snippet.expectHttpieRequest("put-request-with-url-encoded-parameter")
.withContents(codeBlock("bash").content(
"$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
.withContents(codeBlock("bash")
.content("$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
new HttpieRequestSnippet()
.document(operationBuilder("put-request-with-url-encoded-parameter")
.request("http://localhost/foo").method("PUT").param("k1", "a&b")
@@ -224,8 +224,8 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests {
@Test
public void requestWithHeaders() throws IOException {
this.snippet.expectHttpieRequest("request-with-headers")
.withContents(codeBlock("bash").content("$ http GET 'http://localhost/foo'"
this.snippet.expectHttpieRequest("request-with-headers").withContents(
codeBlock("bash").content("$ http GET 'http://localhost/foo'"
+ " 'Content-Type:application/json' 'a:alpha'"));
new HttpieRequestSnippet().document(
operationBuilder("request-with-headers").request("http://localhost/foo")
@@ -298,7 +298,7 @@ public class HttpieRequestSnippetTests extends AbstractSnippetTests {
}
@Test
public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException {
public void basicAuthCredentialsAreSuppliedUsingAuthOption() throws IOException {
this.snippet.expectHttpieRequest("basic-auth").withContents(codeBlock("bash")
.content("$ http --auth 'user:secret' GET 'http://localhost/foo'"));
new HttpieRequestSnippet()

View File

@@ -24,9 +24,9 @@ import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.cli.curl.CurlDocumentation;
import org.springframework.restdocs.cli.curl.CurlRequestSnippet;
import org.springframework.restdocs.cli.httpie.HttpieRequestSnippet;
import org.springframework.restdocs.cli.CliDocumentation;
import org.springframework.restdocs.cli.CurlRequestSnippet;
import org.springframework.restdocs.cli.HttpieRequestSnippet;
import org.springframework.restdocs.generate.RestDocumentationGenerator;
import org.springframework.restdocs.http.HttpRequestSnippet;
import org.springframework.restdocs.http.HttpResponseSnippet;
@@ -108,7 +108,7 @@ public class RestDocumentationConfigurerTests {
public void customDefaultSnippets() {
RestDocumentationContext context = new RestDocumentationContext(null, null, null);
Map<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

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

View File

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