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:
@@ -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" />
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[source,bash]
|
||||
----
|
||||
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
|
||||
----
|
||||
@@ -0,0 +1,3 @@
|
||||
```bash
|
||||
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
|
||||
```
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.curl;
|
||||
package org.springframework.restdocs.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[source,bash]
|
||||
.{{title}}
|
||||
----
|
||||
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
|
||||
----
|
||||
@@ -0,0 +1,4 @@
|
||||
{{title}}
|
||||
```bash
|
||||
$ {{echoContent}}http {{options}} {{url}}{{requestItems}}
|
||||
```
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user