Add support for generating an HTTPie snippet
This commit adds support for generating a snippet that contains
the HTTPie command for the request. As the snippet does not require
any additional configuration, it has added to the existing default
snippets.
Httpie does not currently support setting the content type for each
part in a multipart form -- these multipart types are currently
ignored. See:
https://github.com/jkbrzt/httpie/issues/199
https://github.com/jkbrzt/httpie/issues/271
https://github.com/jkbrzt/httpie/pull/285
https://github.com/jkbrzt/httpie/pull/398
There is an issue with specifying piped input for multipart form
data. There is no way currently to specify the data without specifying
a filename parameter in the Content-Disposition. For now, this is
ignored. See:
https://github.com/jkbrzt/httpie/issues/342
See gh-207
This commit is contained in:
committed by
Andy Wilkinson
parent
536e49287d
commit
b26d8c085d
@@ -73,7 +73,7 @@
|
||||
<module name="AvoidStarImport" />
|
||||
<module name="AvoidStaticImport">
|
||||
<property name="excludes"
|
||||
value="com.jayway.restassured.RestAssured.*, org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.curl.CurlDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.templates.TemplateFormats.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
|
||||
value="com.jayway.restassured.RestAssured.*, org.junit.Assert.*, org.junit.Assume.*, org.hamcrest.CoreMatchers.*, org.hamcrest.Matchers.*, org.mockito.Mockito.*, org.mockito.BDDMockito.*, org.mockito.Matchers.*, org.springframework.restdocs.cli.curl.CurlDocumentation.*, org.springframework.restdocs.headers.HeaderDocumentation.*, org.springframework.restdocs.hypermedia.HypermediaDocumentation.*, org.springframework.restdocs.mockmvc.IterableEnumeration.*, org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.*, org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*, org.springframework.restdocs.payload.PayloadDocumentation.*, org.springframework.restdocs.operation.preprocess.Preprocessors.*, org.springframework.restdocs.request.RequestDocumentation.*, org.springframework.restdocs.restassured.RestAssuredRestDocumentation.*, org.springframework.restdocs.restassured.operation.preprocess.RestAssuredPreprocessors.*, org.springframework.restdocs.snippet.Attributes.*, org.springframework.restdocs.templates.TemplateFormats.*, org.springframework.restdocs.test.SnippetMatchers.*, org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*, org.springframework.test.web.servlet.result.MockMvcResultMatchers.*" />
|
||||
</module>
|
||||
<module name="IllegalImport" />
|
||||
<module name="RedundantImport" />
|
||||
|
||||
@@ -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,9 +371,10 @@ static `document` method on
|
||||
<4> Invoke the root (`/`) of the service.
|
||||
<5> Assert that the service produce the expected response.
|
||||
|
||||
By default, three snippets are written:
|
||||
By default, four snippets are written:
|
||||
|
||||
* `<output-directory>/index/curl-request.adoc`
|
||||
* `<output-directory>/index/httpie-request.adoc`
|
||||
* `<output-directory>/index/http-request.adoc`
|
||||
* `<output-directory>/index/http-response.adoc`
|
||||
|
||||
|
||||
@@ -20,12 +20,11 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.restdocs.JUnitRestDocumentation;
|
||||
import org.springframework.restdocs.RestDocumentation;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
|
||||
|
||||
public class CustomDefaultSnippets {
|
||||
|
||||
@@ -19,12 +19,11 @@ package com.example.restassured;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.springframework.restdocs.JUnitRestDocumentation;
|
||||
import org.springframework.restdocs.RestDocumentation;
|
||||
|
||||
import com.jayway.restassured.builder.RequestSpecBuilder;
|
||||
import com.jayway.restassured.specification.RequestSpecification;
|
||||
|
||||
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.documentationConfiguration;
|
||||
|
||||
public class CustomDefaultSnippets {
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.cli;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.operation.OperationRequest;
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
/**
|
||||
* An abstract {@link Snippet} that for CLI requests.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Paul-Christian Volkmer
|
||||
* @author Raman Gupta
|
||||
*/
|
||||
public abstract class AbstractCliSnippet extends TemplatedSnippet {
|
||||
|
||||
private static final Set<HeaderFilter> HEADER_FILTERS;
|
||||
|
||||
static {
|
||||
Set<HeaderFilter> headerFilters = new HashSet<>();
|
||||
headerFilters.add(new NamedHeaderFilter(HttpHeaders.HOST));
|
||||
headerFilters.add(new NamedHeaderFilter(HttpHeaders.CONTENT_LENGTH));
|
||||
headerFilters.add(new BasicAuthHeaderFilter());
|
||||
HEADER_FILTERS = Collections.unmodifiableSet(headerFilters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new abstract cli snippet with the given name and attributes.
|
||||
* @param snippetName The snippet name.
|
||||
* @param attributes The snippet attributes.
|
||||
*/
|
||||
protected AbstractCliSnippet(String snippetName, Map<String, Object> attributes) {
|
||||
super(snippetName, attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the model which will be passed to the template for rendering.
|
||||
* @param operation The operation
|
||||
* @return The model.
|
||||
*/
|
||||
protected abstract Map<String, Object> createModel(Operation operation);
|
||||
|
||||
/**
|
||||
* Gets the unique parameters given a request.
|
||||
* @param request The operation request.
|
||||
* @return The unique parameters.
|
||||
*/
|
||||
protected Parameters getUniqueParameters(OperationRequest request) {
|
||||
Parameters queryStringParameters = new QueryStringParser()
|
||||
.parse(request.getUri());
|
||||
Parameters uniqueParameters = new Parameters();
|
||||
|
||||
for (Map.Entry<String, List<String>> parameter : request.getParameters().entrySet()) {
|
||||
addIfUnique(parameter, queryStringParameters, uniqueParameters);
|
||||
}
|
||||
return uniqueParameters;
|
||||
}
|
||||
|
||||
private void addIfUnique(Map.Entry<String, List<String>> parameter,
|
||||
Parameters queryStringParameters, Parameters uniqueParameters) {
|
||||
if (!queryStringParameters.containsKey(parameter.getKey())) {
|
||||
uniqueParameters.put(parameter.getKey(), parameter.getValue());
|
||||
}
|
||||
else {
|
||||
List<String> candidates = parameter.getValue();
|
||||
List<String> existing = queryStringParameters.get(parameter.getKey());
|
||||
for (String candidate : candidates) {
|
||||
if (!existing.contains(candidate)) {
|
||||
uniqueParameters.add(parameter.getKey(), candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the request operation is a PUT or a POST.
|
||||
* @param request The request.
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean isPutOrPost(OperationRequest request) {
|
||||
return HttpMethod.PUT.equals(request.getMethod())
|
||||
|| HttpMethod.POST.equals(request.getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the passed header is allowed according to the configured
|
||||
* header filters.
|
||||
* @param header The header to test.
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean allowedHeader(Map.Entry<String, List<String>> header) {
|
||||
for (HeaderFilter headerFilter : HEADER_FILTERS) {
|
||||
if (!headerFilter.allow(header.getKey(), header.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the header passed is a basic auth header.
|
||||
* @param headerValue The header to test.
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean isBasicAuthHeader(List<String> headerValue) {
|
||||
return BasicAuthHeaderFilter.isBasicAuthHeader(headerValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a basic auth header into name:password credentials.
|
||||
* @param headerValue The encoded header value.
|
||||
* @return name:password credentials.
|
||||
*/
|
||||
protected String decodeBasicAuthHeader(List<String> headerValue) {
|
||||
return BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue);
|
||||
}
|
||||
|
||||
private interface HeaderFilter {
|
||||
|
||||
boolean allow(String name, List<String> value);
|
||||
}
|
||||
|
||||
private static final class BasicAuthHeaderFilter implements HeaderFilter {
|
||||
|
||||
@Override
|
||||
public boolean allow(String name, List<String> value) {
|
||||
return !(HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value));
|
||||
}
|
||||
|
||||
static boolean isBasicAuthHeader(List<String> value) {
|
||||
return value != null && (!value.isEmpty())
|
||||
&& value.get(0).startsWith("Basic ");
|
||||
}
|
||||
|
||||
static String decodeBasicAuthHeader(List<String> value) {
|
||||
return new String(Base64Utils.decodeFromString(value.get(0).substring(6)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class NamedHeaderFilter implements HeaderFilter {
|
||||
|
||||
private final String name;
|
||||
|
||||
NamedHeaderFilter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allow(String name, List<String> value) {
|
||||
return !this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,60 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.cli.curl;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* Static factory methods for documenting a RESTful API as if it were being driven using
|
||||
* the cURL command-line utility.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Yann Le Guern
|
||||
* @author Dmitriy Mayboroda
|
||||
* @author Jonathan Pearlin
|
||||
*/
|
||||
public abstract class CurlDocumentation {
|
||||
|
||||
private CurlDocumentation() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the curl request for the API
|
||||
* operation.
|
||||
*
|
||||
* @return the snippet that will document the curl request
|
||||
*/
|
||||
public static Snippet curlRequest() {
|
||||
return new CurlRequestSnippet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the curl request for the API
|
||||
* operation. The given {@code attributes} will be available during snippet
|
||||
* generation.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @return the snippet that will document the curl request
|
||||
*/
|
||||
public static Snippet curlRequest(Map<String, Object> attributes) {
|
||||
return new CurlRequestSnippet(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.cli.curl;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.restdocs.cli.AbstractCliSnippet;
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.operation.OperationRequest;
|
||||
import org.springframework.restdocs.operation.OperationRequestPart;
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the curl command for a request.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Paul-Christian Volkmer
|
||||
* @see CurlDocumentation#curlRequest()
|
||||
* @see CurlDocumentation#curlRequest(Map)
|
||||
*/
|
||||
public class CurlRequestSnippet extends AbstractCliSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
|
||||
*/
|
||||
protected CurlRequestSnippet() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with the given additional
|
||||
* {@code attributes} that will be included in the model during template rendering.
|
||||
*
|
||||
* @param attributes The additional attributes
|
||||
*/
|
||||
protected CurlRequestSnippet(Map<String, Object> attributes) {
|
||||
super("curl-request", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(Operation operation) {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("url", getUrl(operation));
|
||||
model.put("options", getOptions(operation));
|
||||
return model;
|
||||
}
|
||||
|
||||
private String getUrl(Operation operation) {
|
||||
return String.format("'%s'", operation.getRequest().getUri());
|
||||
}
|
||||
|
||||
private String getOptions(Operation operation) {
|
||||
StringWriter command = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(command);
|
||||
writeIncludeHeadersInOutputOption(printer);
|
||||
writeUserOptionIfNecessary(operation.getRequest(), printer);
|
||||
writeHttpMethodIfNecessary(operation.getRequest(), printer);
|
||||
writeHeaders(operation.getRequest().getHeaders(), printer);
|
||||
writePartsIfNecessary(operation.getRequest(), printer);
|
||||
writeContent(operation.getRequest(), printer);
|
||||
|
||||
return command.toString();
|
||||
}
|
||||
|
||||
private void writeIncludeHeadersInOutputOption(PrintWriter writer) {
|
||||
writer.print("-i");
|
||||
}
|
||||
|
||||
private void writeUserOptionIfNecessary(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
List<String> headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
|
||||
if (isBasicAuthHeader(headerValue)) {
|
||||
String credentials = decodeBasicAuthHeader(headerValue);
|
||||
writer.print(String.format(" -u '%s'", credentials));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHttpMethodIfNecessary(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
if (!HttpMethod.GET.equals(request.getMethod())) {
|
||||
writer.print(String.format(" -X %s", request.getMethod()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeaders(HttpHeaders headers, PrintWriter writer) {
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
if (allowedHeader(entry)) {
|
||||
for (String header : entry.getValue()) {
|
||||
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) {
|
||||
for (OperationRequestPart part : request.getParts()) {
|
||||
writer.printf(" -F '%s=", part.getName());
|
||||
if (!StringUtils.hasText(part.getSubmittedFileName())) {
|
||||
writer.append(part.getContentAsString());
|
||||
}
|
||||
else {
|
||||
writer.printf("@%s", part.getSubmittedFileName());
|
||||
}
|
||||
if (part.getHeaders().getContentType() != null) {
|
||||
writer.append(";type=")
|
||||
.append(part.getHeaders().getContentType().toString());
|
||||
}
|
||||
|
||||
writer.append("'");
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContent(OperationRequest request, PrintWriter writer) {
|
||||
String content = request.getContentAsString();
|
||||
if (StringUtils.hasText(content)) {
|
||||
writer.print(String.format(" -d '%s'", content));
|
||||
}
|
||||
else if (!request.getParts().isEmpty()) {
|
||||
for (Entry<String, List<String>> entry : request.getParameters().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isPutOrPost(request)) {
|
||||
writeContentUsingParameters(request, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContentUsingParameters(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
Parameters uniqueParameters = getUniqueParameters(request);
|
||||
String queryString = uniqueParameters.toQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
writer.print(String.format(" -d '%s'", queryString));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,4 +17,4 @@
|
||||
/**
|
||||
* Documenting the curl command required to make a request to a RESTful API.
|
||||
*/
|
||||
package org.springframework.restdocs.curl;
|
||||
package org.springframework.restdocs.cli.curl;
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.cli.httpie;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
|
||||
/**
|
||||
* Static factory methods for documenting a RESTful API as if it were being driven using
|
||||
* the httpie command-line utility.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Paul-Christian Volkmer
|
||||
* @author Raman Gupta
|
||||
*/
|
||||
public abstract class HttpieDocumentation {
|
||||
|
||||
private HttpieDocumentation() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the httpie request for the API
|
||||
* operation.
|
||||
*
|
||||
* @return the snippet that will document the httpie request
|
||||
*/
|
||||
public static Snippet httpieRequest() {
|
||||
return new HttpieRequestSnippet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@code Snippet} that will document the httpie request for the API
|
||||
* operation. The given {@code attributes} will be available during snippet
|
||||
* generation.
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @return the snippet that will document the httpie request
|
||||
*/
|
||||
public static Snippet httpieRequest(Map<String, Object> attributes) {
|
||||
return new HttpieRequestSnippet(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.cli.httpie;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.restdocs.cli.AbstractCliSnippet;
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.operation.OperationRequest;
|
||||
import org.springframework.restdocs.operation.OperationRequestPart;
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the httpie command for a request.
|
||||
*
|
||||
* @author Raman Gupta
|
||||
* @see HttpieDocumentation#httpieRequest()
|
||||
* @see HttpieDocumentation#httpieRequest(Map)
|
||||
*/
|
||||
public class HttpieRequestSnippet extends AbstractCliSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
|
||||
*/
|
||||
protected HttpieRequestSnippet() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with the given additional
|
||||
* {@code attributes} that will be included in the model during template rendering.
|
||||
*
|
||||
* @param attributes The additional attributes
|
||||
*/
|
||||
protected HttpieRequestSnippet(Map<String, Object> attributes) {
|
||||
super("httpie-request", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(final Operation operation) {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("echo_content", getContentStdIn(operation));
|
||||
model.put("options", getOptions(operation));
|
||||
model.put("url", getUrl(operation));
|
||||
model.put("request_items", getRequestItems(operation));
|
||||
return model;
|
||||
}
|
||||
private Object getContentStdIn(final Operation operation) {
|
||||
OperationRequest request = operation.getRequest();
|
||||
String content = request.getContentAsString();
|
||||
if (StringUtils.hasText(content)) {
|
||||
return String.format("echo '%s' | ", content);
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private String getOptions(Operation operation) {
|
||||
StringWriter options = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(options);
|
||||
writeOptions(operation.getRequest(), printer);
|
||||
writeUserOptionIfNecessary(operation.getRequest(), printer);
|
||||
writeMethodIfNecessary(operation.getRequest(), printer);
|
||||
return options.toString();
|
||||
}
|
||||
|
||||
private String getUrl(Operation operation) {
|
||||
return String.format("'%s'", operation.getRequest().getUri());
|
||||
}
|
||||
|
||||
private String getRequestItems(final Operation operation) {
|
||||
StringWriter requestItems = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(requestItems);
|
||||
writeFormDataIfNecessary(operation.getRequest(), printer);
|
||||
writeHeaders(operation.getRequest(), printer);
|
||||
writeContent(operation.getRequest(), printer);
|
||||
return requestItems.toString();
|
||||
}
|
||||
private void writeOptions(OperationRequest request, PrintWriter writer) {
|
||||
Parameters uniqueParameters = getUniqueParameters(request);
|
||||
if (request.getParts().size() > 0 || uniqueParameters.size() > 0) {
|
||||
writer.print("--form ");
|
||||
}
|
||||
}
|
||||
|
||||
private void writeUserOptionIfNecessary(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
List<String> headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
|
||||
if (isBasicAuthHeader(headerValue)) {
|
||||
String credentials = decodeBasicAuthHeader(headerValue);
|
||||
writer.print(String.format("--auth '%s' ", credentials));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMethodIfNecessary(OperationRequest request, PrintWriter writer) {
|
||||
writer.print(String.format("%s", request.getMethod().name()));
|
||||
}
|
||||
|
||||
private void writeFormDataIfNecessary(OperationRequest request, PrintWriter writer) {
|
||||
for (OperationRequestPart part : request.getParts()) {
|
||||
writer.printf(" \\\n '%s'", part.getName());
|
||||
if (!StringUtils.hasText(part.getSubmittedFileName())) {
|
||||
// httpie https://github.com/jkbrzt/httpie/issues/342
|
||||
writer.printf("@<(echo '%s')", part.getContentAsString());
|
||||
}
|
||||
else {
|
||||
writer.printf("@'%s'", part.getSubmittedFileName());
|
||||
}
|
||||
// httpie does not currently support manually set content type by part
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeaders(OperationRequest request, PrintWriter writer) {
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
if (allowedHeader(entry)) {
|
||||
for (String header : entry.getValue()) {
|
||||
// form Content-Type not required, added automatically by httpie with --form
|
||||
if (request.getParts().size() > 0
|
||||
&& entry.getKey().equals(HttpHeaders.CONTENT_TYPE)
|
||||
&& header.startsWith("multipart/form-data")) {
|
||||
continue;
|
||||
}
|
||||
writer.print(String.format(" '%s:%s'", entry.getKey(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContent(OperationRequest request, PrintWriter writer) {
|
||||
String content = request.getContentAsString();
|
||||
if (!StringUtils.hasText(content)) {
|
||||
if (!request.getParts().isEmpty()) {
|
||||
for (Entry<String, List<String>> entry : request.getParameters().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
writer.print(String.format(" '%s=%s'", entry.getKey(), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isPutOrPost(request)) {
|
||||
writeContentUsingParameters(request, writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContentUsingParameters(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
Parameters uniqueParameters = getUniqueParameters(request);
|
||||
for (Map.Entry<String, List<String>> entry : uniqueParameters.entrySet()) {
|
||||
if (entry.getValue().isEmpty()) {
|
||||
writer.append(String.format(" '%s='", entry.getKey()));
|
||||
}
|
||||
else {
|
||||
for (String value : entry.getValue()) {
|
||||
writer.append(String.format(" '%s=%s'", entry.getKey(), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Documenting the httpie command required to make a request to a RESTful API.
|
||||
*/
|
||||
package org.springframework.restdocs.cli.httpie;
|
||||
@@ -21,7 +21,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.restdocs.RestDocumentationContext;
|
||||
import org.springframework.restdocs.curl.CurlDocumentation;
|
||||
import org.springframework.restdocs.cli.curl.CurlDocumentation;
|
||||
import org.springframework.restdocs.cli.httpie.HttpieDocumentation;
|
||||
import org.springframework.restdocs.generate.RestDocumentationGenerator;
|
||||
import org.springframework.restdocs.http.HttpDocumentation;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
@@ -38,8 +39,12 @@ import org.springframework.restdocs.templates.TemplateFormats;
|
||||
public abstract class SnippetConfigurer<PARENT, TYPE>
|
||||
extends AbstractNestedConfigurer<PARENT> {
|
||||
|
||||
private List<Snippet> defaultSnippets = Arrays.asList(CurlDocumentation.curlRequest(),
|
||||
HttpDocumentation.httpRequest(), HttpDocumentation.httpResponse());
|
||||
private List<Snippet> defaultSnippets = Arrays.asList(
|
||||
CurlDocumentation.curlRequest(),
|
||||
HttpieDocumentation.httpieRequest(),
|
||||
HttpDocumentation.httpRequest(),
|
||||
HttpDocumentation.httpResponse()
|
||||
);
|
||||
|
||||
/**
|
||||
* The default encoding for documentation snippets.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2015 the original author or authors.
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,11 +24,14 @@ import org.springframework.restdocs.snippet.Snippet;
|
||||
* Static factory methods for documenting a RESTful API as if it were being driven using
|
||||
* the cURL command-line utility.
|
||||
*
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Yann Le Guern
|
||||
* @author Dmitriy Mayboroda
|
||||
* @author Jonathan Pearlin
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class CurlDocumentation {
|
||||
|
||||
private CurlDocumentation() {
|
||||
@@ -40,6 +43,8 @@ public abstract class CurlDocumentation {
|
||||
* operation.
|
||||
*
|
||||
* @return the snippet that will document the curl request
|
||||
*
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
|
||||
*/
|
||||
public static Snippet curlRequest() {
|
||||
return new CurlRequestSnippet();
|
||||
@@ -52,6 +57,8 @@ public abstract class CurlDocumentation {
|
||||
*
|
||||
* @param attributes the attributes
|
||||
* @return the snippet that will document the curl request
|
||||
*
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlDocumentation}.
|
||||
*/
|
||||
public static Snippet curlRequest(Map<String, Object> attributes) {
|
||||
return new CurlRequestSnippet(attributes);
|
||||
|
||||
@@ -16,243 +16,35 @@
|
||||
|
||||
package org.springframework.restdocs.curl;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.restdocs.operation.Operation;
|
||||
import org.springframework.restdocs.operation.OperationRequest;
|
||||
import org.springframework.restdocs.operation.OperationRequestPart;
|
||||
import org.springframework.restdocs.operation.Parameters;
|
||||
import org.springframework.restdocs.snippet.Snippet;
|
||||
import org.springframework.restdocs.snippet.TemplatedSnippet;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link Snippet} that documents the curl command for a request.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Paul-Christian Volkmer
|
||||
* @see CurlDocumentation#curlRequest()
|
||||
* @see CurlDocumentation#curlRequest(Map)
|
||||
* @author Raman Gupta
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
|
||||
*/
|
||||
public class CurlRequestSnippet extends TemplatedSnippet {
|
||||
|
||||
private static final Set<HeaderFilter> HEADER_FILTERS;
|
||||
|
||||
static {
|
||||
Set<HeaderFilter> headerFilters = new HashSet<>();
|
||||
headerFilters.add(new NamedHeaderFilter(HttpHeaders.HOST));
|
||||
headerFilters.add(new NamedHeaderFilter(HttpHeaders.CONTENT_LENGTH));
|
||||
headerFilters.add(new BasicAuthHeaderFilter());
|
||||
HEADER_FILTERS = Collections.unmodifiableSet(headerFilters);
|
||||
}
|
||||
public class CurlRequestSnippet extends org.springframework.restdocs.cli.curl.CurlRequestSnippet {
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with no additional attributes.
|
||||
*
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
|
||||
*/
|
||||
protected CurlRequestSnippet() {
|
||||
this(null);
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code CurlRequestSnippet} with the given additional
|
||||
* {@code attributes} that will be included in the model during template rendering.
|
||||
* Creates a new {@code CurlRequestSnippet} with additional attributes.
|
||||
* @param attributes The additional attributes.
|
||||
*
|
||||
* @param attributes The additional attributes
|
||||
* @deprecated Since 1.1 in favor of {@link org.springframework.restdocs.cli.curl.CurlRequestSnippet}.
|
||||
*/
|
||||
protected CurlRequestSnippet(Map<String, Object> attributes) {
|
||||
super("curl-request", attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, Object> createModel(Operation operation) {
|
||||
Map<String, Object> model = new HashMap<>();
|
||||
model.put("url", getUrl(operation));
|
||||
model.put("options", getOptions(operation));
|
||||
return model;
|
||||
}
|
||||
|
||||
private String getUrl(Operation operation) {
|
||||
return String.format("'%s'", operation.getRequest().getUri());
|
||||
}
|
||||
|
||||
private String getOptions(Operation operation) {
|
||||
StringWriter command = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(command);
|
||||
writeIncludeHeadersInOutputOption(printer);
|
||||
writeUserOptionIfNecessary(operation.getRequest(), printer);
|
||||
writeHttpMethodIfNecessary(operation.getRequest(), printer);
|
||||
writeHeaders(operation.getRequest().getHeaders(), printer);
|
||||
writePartsIfNecessary(operation.getRequest(), printer);
|
||||
writeContent(operation.getRequest(), printer);
|
||||
|
||||
return command.toString();
|
||||
}
|
||||
|
||||
private void writeIncludeHeadersInOutputOption(PrintWriter writer) {
|
||||
writer.print("-i");
|
||||
}
|
||||
|
||||
private void writeUserOptionIfNecessary(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
List<String> headerValue = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
|
||||
if (BasicAuthHeaderFilter.isBasicAuthHeader(headerValue)) {
|
||||
String credentials = BasicAuthHeaderFilter.decodeBasicAuthHeader(headerValue);
|
||||
writer.print(String.format(" -u '%s'", credentials));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHttpMethodIfNecessary(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
if (!HttpMethod.GET.equals(request.getMethod())) {
|
||||
writer.print(String.format(" -X %s", request.getMethod()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeaders(HttpHeaders headers, PrintWriter writer) {
|
||||
for (Entry<String, List<String>> entry : headers.entrySet()) {
|
||||
if (allowedHeader(entry)) {
|
||||
for (String header : entry.getValue()) {
|
||||
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowedHeader(Entry<String, List<String>> header) {
|
||||
for (HeaderFilter headerFilter : HEADER_FILTERS) {
|
||||
if (!headerFilter.allow(header.getKey(), header.getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void writePartsIfNecessary(OperationRequest request, PrintWriter writer) {
|
||||
for (OperationRequestPart part : request.getParts()) {
|
||||
writer.printf(" -F '%s=", part.getName());
|
||||
if (!StringUtils.hasText(part.getSubmittedFileName())) {
|
||||
writer.append(part.getContentAsString());
|
||||
}
|
||||
else {
|
||||
writer.printf("@%s", part.getSubmittedFileName());
|
||||
}
|
||||
if (part.getHeaders().getContentType() != null) {
|
||||
writer.append(";type=")
|
||||
.append(part.getHeaders().getContentType().toString());
|
||||
}
|
||||
|
||||
writer.append("'");
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContent(OperationRequest request, PrintWriter writer) {
|
||||
String content = request.getContentAsString();
|
||||
if (StringUtils.hasText(content)) {
|
||||
writer.print(String.format(" -d '%s'", content));
|
||||
}
|
||||
else if (!request.getParts().isEmpty()) {
|
||||
for (Entry<String, List<String>> entry : request.getParameters().entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
writer.print(String.format(" -F '%s=%s'", entry.getKey(), value));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isPutOrPost(request)) {
|
||||
writeContentUsingParameters(request, writer);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeContentUsingParameters(OperationRequest request,
|
||||
PrintWriter writer) {
|
||||
Parameters uniqueParameters = getUniqueParameters(request);
|
||||
String queryString = uniqueParameters.toQueryString();
|
||||
if (StringUtils.hasText(queryString)) {
|
||||
writer.print(String.format(" -d '%s'", queryString));
|
||||
}
|
||||
}
|
||||
|
||||
private Parameters getUniqueParameters(OperationRequest request) {
|
||||
Parameters queryStringParameters = new QueryStringParser()
|
||||
.parse(request.getUri());
|
||||
Parameters uniqueParameters = new Parameters();
|
||||
|
||||
for (Entry<String, List<String>> parameter : request.getParameters().entrySet()) {
|
||||
addIfUnique(parameter, queryStringParameters, uniqueParameters);
|
||||
}
|
||||
return uniqueParameters;
|
||||
}
|
||||
|
||||
private void addIfUnique(Entry<String, List<String>> parameter,
|
||||
Parameters queryStringParameters, Parameters uniqueParameters) {
|
||||
if (!queryStringParameters.containsKey(parameter.getKey())) {
|
||||
uniqueParameters.put(parameter.getKey(), parameter.getValue());
|
||||
}
|
||||
else {
|
||||
List<String> candidates = parameter.getValue();
|
||||
List<String> existing = queryStringParameters.get(parameter.getKey());
|
||||
for (String candidate : candidates) {
|
||||
if (!existing.contains(candidate)) {
|
||||
uniqueParameters.add(parameter.getKey(), candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPutOrPost(OperationRequest request) {
|
||||
return HttpMethod.PUT.equals(request.getMethod())
|
||||
|| HttpMethod.POST.equals(request.getMethod());
|
||||
}
|
||||
|
||||
private interface HeaderFilter {
|
||||
|
||||
boolean allow(String name, List<String> value);
|
||||
}
|
||||
|
||||
private static final class BasicAuthHeaderFilter implements HeaderFilter {
|
||||
|
||||
@Override
|
||||
public boolean allow(String name, List<String> value) {
|
||||
if (HttpHeaders.AUTHORIZATION.equals(name) && isBasicAuthHeader(value)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean isBasicAuthHeader(List<String> value) {
|
||||
return value != null && (!value.isEmpty())
|
||||
&& value.get(0).startsWith("Basic ");
|
||||
}
|
||||
|
||||
static String decodeBasicAuthHeader(List<String> value) {
|
||||
return new String(Base64Utils.decodeFromString(value.get(0).substring(6)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class NamedHeaderFilter implements HeaderFilter {
|
||||
|
||||
private final String name;
|
||||
|
||||
private NamedHeaderFilter(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allow(String name, List<String> value) {
|
||||
return !this.name.equalsIgnoreCase(name);
|
||||
}
|
||||
|
||||
protected CurlRequestSnippet(final Map<String, Object> attributes) {
|
||||
super(attributes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[source,bash]
|
||||
----
|
||||
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
|
||||
----
|
||||
@@ -0,0 +1,3 @@
|
||||
```bash
|
||||
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
|
||||
```
|
||||
@@ -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;
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.restdocs.curl;
|
||||
package org.springframework.restdocs.cli.curl;
|
||||
|
||||
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.httpie;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.restdocs.AbstractSnippetTests;
|
||||
import org.springframework.restdocs.templates.TemplateEngine;
|
||||
import org.springframework.restdocs.templates.TemplateFormat;
|
||||
import org.springframework.restdocs.templates.TemplateResourceResolver;
|
||||
import org.springframework.restdocs.templates.mustache.MustacheTemplateEngine;
|
||||
import org.springframework.util.Base64Utils;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.springframework.restdocs.snippet.Attributes.attributes;
|
||||
import static org.springframework.restdocs.snippet.Attributes.key;
|
||||
|
||||
/**
|
||||
* Tests for {@link HttpieRequestSnippet}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Yann Le Guern
|
||||
* @author Dmitriy Mayboroda
|
||||
* @author Jonathan Pearlin
|
||||
* @author Paul-Christian Volkmer
|
||||
* @author Raman Gupta
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class HttpieRequestSnippetTests extends AbstractSnippetTests {
|
||||
|
||||
public HttpieRequestSnippetTests(String name, TemplateFormat templateFormat) {
|
||||
super(name, templateFormat);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequest() throws IOException {
|
||||
this.snippet.expectHttpieRequest("get-request").withContents(
|
||||
codeBlock("bash").content("$ http GET 'http://localhost/foo'"));
|
||||
new HttpieRequestSnippet().document(
|
||||
operationBuilder("get-request").request("http://localhost/foo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonGetRequest() throws IOException {
|
||||
this.snippet.expectHttpieRequest("non-get-request").withContents(
|
||||
codeBlock("bash").content("$ http POST 'http://localhost/foo'"));
|
||||
new HttpieRequestSnippet().document(operationBuilder("non-get-request")
|
||||
.request("http://localhost/foo").method("POST").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWithContent() throws IOException {
|
||||
this.snippet.expectHttpieRequest("request-with-content")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ echo 'content' | http GET 'http://localhost/foo'"));
|
||||
new HttpieRequestSnippet().document(operationBuilder("request-with-content")
|
||||
.request("http://localhost/foo").content("content").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestWithQueryString() throws IOException {
|
||||
this.snippet.expectHttpieRequest("request-with-query-string")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http GET 'http://localhost/foo?param=value'"));
|
||||
new HttpieRequestSnippet().document(operationBuilder("request-with-query-string")
|
||||
.request("http://localhost/foo?param=value").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRequestWithQueryStringWithNoValue() throws IOException {
|
||||
this.snippet.expectHttpieRequest("request-with-query-string-with-no-value")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http GET 'http://localhost/foo?param'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("request-with-query-string-with-no-value")
|
||||
.request("http://localhost/foo?param").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithQueryString() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-query-string")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http POST 'http://localhost/foo?param=value'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-query-string")
|
||||
.request("http://localhost/foo?param=value").method("POST")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithQueryStringWithNoValue() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-query-string-with-no-value")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http POST 'http://localhost/foo?param'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-query-string-with-no-value")
|
||||
.request("http://localhost/foo?param").method("POST").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithOneParameter() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-one-parameter")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http --form POST 'http://localhost/foo' 'k1=v1'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-one-parameter")
|
||||
.request("http://localhost/foo").method("POST").param("k1", "v1")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithOneParameterWithNoValue() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-one-parameter-with-no-value")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http --form POST 'http://localhost/foo' 'k1='"));
|
||||
new HttpieRequestSnippet().document(
|
||||
operationBuilder("post-request-with-one-parameter-with-no-value")
|
||||
.request("http://localhost/foo").method("POST").param("k1")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithMultipleParameters() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-multiple-parameters")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http --form POST 'http://localhost/foo'"
|
||||
+ " 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-multiple-parameters")
|
||||
.request("http://localhost/foo").method("POST")
|
||||
.param("k1", "v1", "v1-bis").param("k2", "v2").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithUrlEncodedParameter() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-url-encoded-parameter")
|
||||
.withContents(codeBlock("bash").content(
|
||||
"$ http --form POST 'http://localhost/foo' 'k1=a&b'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-url-encoded-parameter")
|
||||
.request("http://localhost/foo").method("POST").param("k1", "a&b")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithQueryStringAndParameter() throws IOException {
|
||||
this.snippet.expectHttpieRequest("post-request-with-query-string-and-parameter")
|
||||
.withContents(codeBlock("bash").content(
|
||||
"$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("post-request-with-query-string-and-parameter")
|
||||
.request("http://localhost/foo?a=alpha").method("POST")
|
||||
.param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postRequestWithOverlappingQueryStringAndParameters() throws IOException {
|
||||
this.snippet
|
||||
.expectHttpieRequest(
|
||||
"post-request-with-overlapping-query-string-and-parameters")
|
||||
.withContents(codeBlock("bash").content(
|
||||
"$ http --form POST 'http://localhost/foo?a=alpha' 'b=bravo'"));
|
||||
new HttpieRequestSnippet().document(operationBuilder(
|
||||
"post-request-with-overlapping-query-string-and-parameters")
|
||||
.request("http://localhost/foo?a=alpha").method("POST")
|
||||
.param("a", "alpha").param("b", "bravo").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putRequestWithOneParameter() throws IOException {
|
||||
this.snippet.expectHttpieRequest("put-request-with-one-parameter")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http --form PUT 'http://localhost/foo' 'k1=v1'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("put-request-with-one-parameter")
|
||||
.request("http://localhost/foo").method("PUT").param("k1", "v1")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putRequestWithMultipleParameters() throws IOException {
|
||||
this.snippet.expectHttpieRequest("put-request-with-multiple-parameters")
|
||||
.withContents(codeBlock("bash")
|
||||
.content("$ http --form PUT 'http://localhost/foo'"
|
||||
+ " 'k1=v1' 'k1=v1-bis' 'k2=v2'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("put-request-with-multiple-parameters")
|
||||
.request("http://localhost/foo").method("PUT").param("k1", "v1")
|
||||
.param("k1", "v1-bis").param("k2", "v2").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putRequestWithUrlEncodedParameter() throws IOException {
|
||||
this.snippet.expectHttpieRequest("put-request-with-url-encoded-parameter")
|
||||
.withContents(codeBlock("bash").content(
|
||||
"$ http --form PUT 'http://localhost/foo' 'k1=a&b'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("put-request-with-url-encoded-parameter")
|
||||
.request("http://localhost/foo").method("PUT").param("k1", "a&b")
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWithHeaders() throws IOException {
|
||||
this.snippet.expectHttpieRequest("request-with-headers")
|
||||
.withContents(codeBlock("bash").content("$ http GET 'http://localhost/foo'"
|
||||
+ " 'Content-Type:application/json' 'a:alpha'"));
|
||||
new HttpieRequestSnippet().document(
|
||||
operationBuilder("request-with-headers").request("http://localhost/foo")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_JSON_VALUE)
|
||||
.header("a", "alpha").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartPostWithNoSubmittedFileName() throws IOException {
|
||||
String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n"
|
||||
+ " 'metadata'@<(echo '{\"description\": \"foo\"}')";
|
||||
this.snippet.expectHttpieRequest("multipart-post-no-original-filename")
|
||||
.withContents(codeBlock("bash").content(expectedContent));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("multipart-post-no-original-filename")
|
||||
.request("http://localhost/upload").method("POST")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
.part("metadata", "{\"description\": \"foo\"}".getBytes())
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartPostWithContentType() throws IOException {
|
||||
// httpie does not yet support manually set content type by part
|
||||
String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n"
|
||||
+ " 'image'@'documents/images/example.png'";
|
||||
this.snippet.expectHttpieRequest("multipart-post-with-content-type")
|
||||
.withContents(codeBlock("bash").content(expectedContent));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("multipart-post-with-content-type")
|
||||
.request("http://localhost/upload").method("POST")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
.part("image", new byte[0])
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.IMAGE_PNG_VALUE)
|
||||
.submittedFileName("documents/images/example.png").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartPost() throws IOException {
|
||||
String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n"
|
||||
+ " 'image'@'documents/images/example.png'";
|
||||
this.snippet.expectHttpieRequest("multipart-post")
|
||||
.withContents(codeBlock("bash").content(expectedContent));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("multipart-post")
|
||||
.request("http://localhost/upload").method("POST")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
.part("image", new byte[0])
|
||||
.submittedFileName("documents/images/example.png").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartPostWithParameters() throws IOException {
|
||||
String expectedContent = "$ http --form POST 'http://localhost/upload' \\\n"
|
||||
+ " 'image'@'documents/images/example.png' 'a=apple' 'a=avocado' 'b=banana'";
|
||||
this.snippet.expectHttpieRequest("multipart-post-with-parameters")
|
||||
.withContents(codeBlock("bash").content(expectedContent));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("multipart-post-with-parameters")
|
||||
.request("http://localhost/upload").method("POST")
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
.part("image", new byte[0])
|
||||
.submittedFileName("documents/images/example.png").and()
|
||||
.param("a", "apple", "avocado").param("b", "banana").build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void basicAuthCredentialsAreSuppliedUsingUserOption() throws IOException {
|
||||
this.snippet.expectHttpieRequest("basic-auth").withContents(codeBlock("bash")
|
||||
.content("$ http --auth 'user:secret' GET 'http://localhost/foo'"));
|
||||
new HttpieRequestSnippet()
|
||||
.document(operationBuilder("basic-auth").request("http://localhost/foo")
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
"Basic " + Base64Utils
|
||||
.encodeToString("user:secret".getBytes()))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customAttributes() throws IOException {
|
||||
this.snippet.expectHttpieRequest("custom-attributes")
|
||||
.withContents(containsString("httpie request title"));
|
||||
TemplateResourceResolver resolver = mock(TemplateResourceResolver.class);
|
||||
given(resolver.resolveTemplateResource("httpie-request"))
|
||||
.willReturn(snippetResource("httpie-request-with-title"));
|
||||
new HttpieRequestSnippet(
|
||||
attributes(
|
||||
key("title").value("httpie request title")))
|
||||
.document(
|
||||
operationBuilder("custom-attributes")
|
||||
.attribute(TemplateEngine.class.getName(),
|
||||
new MustacheTemplateEngine(
|
||||
resolver))
|
||||
.request("http://localhost/foo").build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,8 +24,9 @@ import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.restdocs.RestDocumentationContext;
|
||||
import org.springframework.restdocs.curl.CurlDocumentation;
|
||||
import org.springframework.restdocs.curl.CurlRequestSnippet;
|
||||
import org.springframework.restdocs.cli.curl.CurlDocumentation;
|
||||
import org.springframework.restdocs.cli.curl.CurlRequestSnippet;
|
||||
import org.springframework.restdocs.cli.httpie.HttpieRequestSnippet;
|
||||
import org.springframework.restdocs.generate.RestDocumentationGenerator;
|
||||
import org.springframework.restdocs.http.HttpRequestSnippet;
|
||||
import org.springframework.restdocs.http.HttpResponseSnippet;
|
||||
@@ -71,6 +72,7 @@ public class RestDocumentationConfigurerTests {
|
||||
.get(RestDocumentationGenerator.ATTRIBUTE_NAME_DEFAULT_SNIPPETS);
|
||||
assertThat(defaultSnippets,
|
||||
contains(instanceOf(CurlRequestSnippet.class),
|
||||
instanceOf(HttpieRequestSnippet.class),
|
||||
instanceOf(HttpRequestSnippet.class),
|
||||
instanceOf(HttpResponseSnippet.class)));
|
||||
assertThat(configuration, hasEntry(equalTo(SnippetConfiguration.class.getName()),
|
||||
|
||||
@@ -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}}
|
||||
----
|
||||
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
|
||||
----
|
||||
@@ -0,0 +1,4 @@
|
||||
{{title}}
|
||||
```bash
|
||||
$ {{echo_content}}http {{options}} {{url}}{{request_items}}
|
||||
```
|
||||
@@ -56,7 +56,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.springframework.restdocs.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.cli.curl.CurlDocumentation.curlRequest;
|
||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
|
||||
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
|
||||
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
|
||||
|
||||
Reference in New Issue
Block a user