Skip to content

Commit d5522f5

Browse files
committed
Polish "Add support for documenting request and response cookies"
See gh-592
1 parent f72a9f1 commit d5522f5

File tree

26 files changed

+482
-311
lines changed

26 files changed

+482
-311
lines changed

docs/src/docs/asciidoc/documenting-your-api.adoc

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -988,61 +988,58 @@ Each contains a table describing the headers.
988988
When documenting HTTP Headers, the test fails if a documented header is not found in the request or response.
989989

990990

991+
991992
[[documenting-your-api-http-cookies]]
992993
=== HTTP Cookies
993994

994-
You can document the cookies in a request or response by using `requestCookies` and
995-
`responseCookies`, respectively. The following examples show how to do so:
995+
You can document the cookies in a request or response by using `requestCookies` and `responseCookies`, respectively.
996+
The following examples show how to do so:
996997

997-
====
998998
[source,java,indent=0,role="primary"]
999999
.MockMvc
10001000
----
10011001
include::{examples-dir}/com/example/mockmvc/HttpCookies.java[tags=cookies]
10021002
----
1003-
<1> Configure Spring REST Docs to produce a snippet describing the request's cookies.
1004-
Uses the static `requestCookies` method on
1005-
`org.springframework.restdocs.cookies.CookieDocumentation`.
1006-
<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on
1007-
`org.springframework.restdocs.cookies.CookieDocumentation.
1008-
<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies`
1009-
method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1010-
<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`.
1003+
<1> Make a GET request with a `JSESSIONID` cookie.
1004+
<2> Configure Spring REST Docs to produce a snippet describing the request's cookies.
1005+
Uses the static `requestCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1006+
<3> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1007+
<4> Produce a snippet describing the response's cookies.
1008+
Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
10111009

10121010
[source,java,indent=0,role="secondary"]
10131011
.WebTestClient
10141012
----
10151013
include::{examples-dir}/com/example/webtestclient/HttpCookies.java[tags=cookies]
10161014
----
1017-
<1> Configure Spring REST Docs to produce a snippet describing the request's cookies.
1015+
<1> Make a GET request with a `JSESSIONID` cookie.
1016+
<2> Configure Spring REST Docs to produce a snippet describing the request's cookies.
10181017
Uses the static `requestCookies` method on
10191018
`org.springframework.restdocs.cookies.CookieDocumentation`.
1020-
<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on
1021-
`org.springframework.restdocs.cookies.CookieDocumentation.
1022-
<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies`
1023-
method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1024-
<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`.
1019+
<3> Document the `JSESSIONID` cookie.
1020+
Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1021+
<4> Produce a snippet describing the response's cookies.
1022+
Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
10251023

10261024
[source,java,indent=0,role="secondary"]
10271025
.REST Assured
10281026
----
10291027
include::{examples-dir}/com/example/restassured/HttpCookies.java[tags=cookies]
10301028
----
10311029
<1> Configure Spring REST Docs to produce a snippet describing the request's cookies.
1032-
Uses the static `requestCookies` method on
1033-
`org.springframework.restdocs.cookies.CookieDocumentation`.
1034-
<2> Document the `JSESSIONID` cookie. Uses the static `cookieWithName` method on
1035-
`org.springframework.restdocs.cookies.CookieDocumentation.
1036-
<3> Produce a snippet describing the response's cookies. Uses the static `responseCookies`
1037-
method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1038-
<4> Configure the request with an `JSESSIONID` and an additional cookie `logged_in`.
1039-
====
1040-
1041-
The result is a snippet named `request-cookies.adoc` and a snippet named
1042-
`response-cookies.adoc`. Each contains a table describing the cookies.
1043-
1044-
When documenting HTTP Cookies, the test fails if a documented cookie is not found in
1045-
the request or response.
1030+
Uses the static `requestCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1031+
<2> Document the `JSESSIONID` cookie.
1032+
Uses the static `cookieWithName` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1033+
<3> Produce a snippet describing the response's cookies.
1034+
Uses the static `responseCookies` method on `org.springframework.restdocs.cookies.CookieDocumentation`.
1035+
<4> Send a `JSESSIONID` cookie with the request.
1036+
1037+
The result is a snippet named `request-cookies.adoc` and a snippet named `response-cookies.adoc`.
1038+
Each contains a table describing the cookies.
1039+
1040+
When documenting HTTP Cookies, the test fails if a documented cookie is not found in the request or response.
1041+
1042+
10461043

10471044
[[documenting-your-api-reusing-snippets]]
10481045
=== Reusing Snippets

docs/src/test/java/com/example/mockmvc/HttpCookies.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2016 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

docs/src/test/java/com/example/restassured/HttpCookies.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2017 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,7 @@ public class HttpCookies {
2929

3030
private RequestSpecification spec;
3131

32-
public void cookies() throws Exception {
32+
public void cookies() {
3333
// tag::cookies[]
3434
RestAssured.given(this.spec).filter(document("cookies", requestCookies(// <1>
3535
cookieWithName("JSESSIONID").description("Saved session token")), // <2>

docs/src/test/java/com/example/webtestclient/HttpCookies.java

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2017 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,23 +25,17 @@
2525

2626
public class HttpCookies {
2727

28-
// @formatter:off
29-
3028
private WebTestClient webTestClient;
3129

32-
public void cookies() throws Exception {
30+
public void cookies() {
3331
// tag::cookies[]
34-
this.webTestClient
35-
.get().uri("/people").cookie("JSESSIONID", "ACBCDFD0FF93D5BB=") // <1>
36-
.exchange().expectStatus().isOk().expectBody()
37-
.consumeWith(document("cookies",
38-
requestCookies(// <2>
39-
cookieWithName("JSESSIONID").description("Session token")), // <3>
40-
responseCookies(// <4>
41-
cookieWithName("JSESSIONID")
42-
.description("Updated session token"),
43-
cookieWithName("logged_in")
44-
.description("User is logged in"))));
32+
this.webTestClient.get().uri("/people").cookie("JSESSIONID", "ACBCDFD0FF93D5BB=") // <1>
33+
.exchange().expectStatus().isOk().expectBody().consumeWith(document("cookies", requestCookies(// <2>
34+
cookieWithName("JSESSIONID").description("Session token")), // <3>
35+
responseCookies(// <4>
36+
cookieWithName("JSESSIONID").description("Updated session token"),
37+
cookieWithName("logged_in").description("User is logged in"))));
4538
// end::cookies[]
4639
}
40+
4741
}

spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/AbstractCookiesSnippet.java

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2017 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,31 +17,32 @@
1717
package org.springframework.restdocs.cookies;
1818

1919
import java.util.ArrayList;
20+
import java.util.Collections;
2021
import java.util.HashMap;
22+
import java.util.HashSet;
23+
import java.util.LinkedHashMap;
2124
import java.util.List;
2225
import java.util.Map;
26+
import java.util.Map.Entry;
2327
import java.util.Set;
2428

2529
import org.springframework.restdocs.operation.Operation;
26-
import org.springframework.restdocs.snippet.SnippetException;
2730
import org.springframework.restdocs.snippet.TemplatedSnippet;
2831
import org.springframework.util.Assert;
2932

3033
/**
3134
* Abstract {@link TemplatedSnippet} subclass that provides a base for snippets that
3235
* document a RESTful resource's request or response cookies.
3336
*
34-
* @author Andreas Evers
3537
* @author Clyde Stubbs
36-
* @since 2.1
38+
* @author Andy Wilkinson
39+
* @since 3.0
3740
*/
3841
public abstract class AbstractCookiesSnippet extends TemplatedSnippet {
3942

40-
private List<CookieDescriptor> cookieDescriptors;
43+
private final Map<String, CookieDescriptor> descriptorsByName = new LinkedHashMap<>();
4144

42-
protected final boolean ignoreUndocumentedCookies;
43-
44-
private String type;
45+
private final boolean ignoreUndocumentedCookies;
4546

4647
/**
4748
* Creates a new {@code AbstractCookiesSnippet} that will produce a snippet named
@@ -57,58 +58,53 @@ protected AbstractCookiesSnippet(String type, List<CookieDescriptor> descriptors
5758
boolean ignoreUndocumentedCookies) {
5859
super(type + "-cookies", attributes);
5960
for (CookieDescriptor descriptor : descriptors) {
60-
Assert.notNull(descriptor.getName(), "The name of the cookie must not be null");
61+
Assert.notNull(descriptor.getName(), "Cookie descriptors must have a name");
6162
if (!descriptor.isIgnored()) {
62-
Assert.notNull(descriptor.getDescription(), "The description of the cookie must not be null");
63+
Assert.notNull(descriptor.getDescription(), "The descriptor for cookie '" + descriptor.getName()
64+
+ "' must either have a description or be marked as ignored");
6365
}
66+
this.descriptorsByName.put(descriptor.getName(), descriptor);
6467
}
65-
this.cookieDescriptors = descriptors;
66-
this.type = type;
6768
this.ignoreUndocumentedCookies = ignoreUndocumentedCookies;
6869
}
6970

7071
@Override
7172
protected Map<String, Object> createModel(Operation operation) {
72-
validateCookieDocumentation(operation);
73+
verifyCookieDescriptors(operation);
7374

7475
Map<String, Object> model = new HashMap<>();
7576
List<Map<String, Object>> cookies = new ArrayList<>();
76-
model.put("cookies", cookies);
77-
for (CookieDescriptor descriptor : this.cookieDescriptors) {
78-
cookies.add(createModelForDescriptor(descriptor));
79-
}
80-
return model;
81-
}
82-
83-
private void validateCookieDocumentation(Operation operation) {
84-
List<CookieDescriptor> missingCookies = findMissingCookies(operation);
85-
if (!missingCookies.isEmpty()) {
86-
List<String> names = new ArrayList<>();
87-
for (CookieDescriptor cookieDescriptor : missingCookies) {
88-
names.add(cookieDescriptor.getName());
77+
for (CookieDescriptor descriptor : this.descriptorsByName.values()) {
78+
if (!descriptor.isIgnored()) {
79+
cookies.add(createModelForDescriptor(descriptor));
8980
}
90-
throw new SnippetException(
91-
"Cookies with the following names were not found" + " in the " + this.type + ": " + names);
9281
}
82+
model.put("cookies", cookies);
83+
return model;
9384
}
9485

95-
/**
96-
* Finds the cookies that are missing from the operation. A cookie is missing if it is
97-
* described by one of the {@code cookieDescriptors} but is not present in the
98-
* operation.
99-
* @param operation the operation
100-
* @return descriptors for the cookies that are missing from the operation
101-
*/
102-
protected List<CookieDescriptor> findMissingCookies(Operation operation) {
103-
List<CookieDescriptor> missingCookies = new ArrayList<>();
86+
private void verifyCookieDescriptors(Operation operation) {
10487
Set<String> actualCookies = extractActualCookies(operation);
105-
for (CookieDescriptor cookieDescriptor : this.cookieDescriptors) {
106-
if (!cookieDescriptor.isOptional() && !actualCookies.contains(cookieDescriptor.getName())) {
107-
missingCookies.add(cookieDescriptor);
88+
Set<String> expectedCookies = new HashSet<>();
89+
for (Entry<String, CookieDescriptor> entry : this.descriptorsByName.entrySet()) {
90+
if (!entry.getValue().isOptional()) {
91+
expectedCookies.add(entry.getKey());
10892
}
10993
}
94+
Set<String> undocumentedCookies;
95+
if (this.ignoreUndocumentedCookies) {
96+
undocumentedCookies = Collections.emptySet();
97+
}
98+
else {
99+
undocumentedCookies = new HashSet<>(actualCookies);
100+
undocumentedCookies.removeAll(this.descriptorsByName.keySet());
101+
}
102+
Set<String> missingCookies = new HashSet<>(expectedCookies);
103+
missingCookies.removeAll(actualCookies);
110104

111-
return missingCookies;
105+
if (!undocumentedCookies.isEmpty() || !missingCookies.isEmpty()) {
106+
verificationFailed(undocumentedCookies, missingCookies);
107+
}
112108
}
113109

114110
/**
@@ -119,13 +115,30 @@ protected List<CookieDescriptor> findMissingCookies(Operation operation) {
119115
*/
120116
protected abstract Set<String> extractActualCookies(Operation operation);
121117

118+
/**
119+
* Called when the documented cookies do not match the actual cookies.
120+
* @param undocumentedCookies the cookies that were found in the operation but were
121+
* not documented
122+
* @param missingCookies the cookies that were documented but were not found in the
123+
* operation
124+
*/
125+
protected abstract void verificationFailed(Set<String> undocumentedCookies, Set<String> missingCookies);
126+
122127
/**
123128
* Returns the list of {@link CookieDescriptor CookieDescriptors} that will be used to
124129
* generate the documentation.
125130
* @return the cookie descriptors
126131
*/
127-
protected final List<CookieDescriptor> getCookieDescriptors() {
128-
return this.cookieDescriptors;
132+
protected final Map<String, CookieDescriptor> getCookieDescriptors() {
133+
return this.descriptorsByName;
134+
}
135+
136+
/**
137+
* Returns whether or not this snippet ignores undocumented cookies.
138+
* @return {@code true} if undocumented cookies are ignored, otherwise {@code false}
139+
*/
140+
protected final boolean isIgnoreUndocumentedCookies() {
141+
return this.ignoreUndocumentedCookies;
129142
}
130143

131144
/**

spring-restdocs-core/src/main/java/org/springframework/restdocs/cookies/CookieDescriptor.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2014-2015 the original author or authors.
2+
* Copyright 2014-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,9 +21,9 @@
2121
/**
2222
* A description of a cookie found in a request or response.
2323
*
24-
* @author Andreas Evers
2524
* @author Clyde Stubbs
26-
* @since 2.1
25+
* @author Andy Wilkinson
26+
* @since 3.0
2727
* @see CookieDocumentation#cookieWithName(String)
2828
*/
2929
public class CookieDescriptor extends IgnorableDescriptor<CookieDescriptor> {

0 commit comments

Comments
 (0)