001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.web.client.match; 018 019import java.io.IOException; 020import java.text.ParseException; 021 022import com.jayway.jsonpath.JsonPath; 023import org.hamcrest.Matcher; 024 025import org.springframework.http.client.ClientHttpRequest; 026import org.springframework.mock.http.client.MockClientHttpRequest; 027import org.springframework.test.util.JsonPathExpectationsHelper; 028import org.springframework.test.web.client.RequestMatcher; 029 030/** 031 * Factory for assertions on the request content using 032 * <a href="https://github.com/jayway/JsonPath">JsonPath</a> expressions. 033 * 034 * <p>An instance of this class is typically accessed via 035 * {@link MockRestRequestMatchers#jsonPath(String, Matcher)} or 036 * {@link MockRestRequestMatchers#jsonPath(String, Object...)}. 037 * 038 * @author Rossen Stoyanchev 039 * @author Sam Brannen 040 * @since 3.2 041 */ 042public class JsonPathRequestMatchers { 043 044 private final JsonPathExpectationsHelper jsonPathHelper; 045 046 047 /** 048 * Protected constructor. 049 * <p>Use {@link MockRestRequestMatchers#jsonPath(String, Matcher)} or 050 * {@link MockRestRequestMatchers#jsonPath(String, Object...)}. 051 * @param expression the {@link JsonPath} expression; never {@code null} or empty 052 * @param args arguments to parameterize the {@code JsonPath} expression with, 053 * using formatting specifiers defined in {@link String#format(String, Object...)} 054 */ 055 protected JsonPathRequestMatchers(String expression, Object ... args) { 056 this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); 057 } 058 059 060 /** 061 * Evaluate the JSON path expression against the request content and 062 * assert the resulting value with the given Hamcrest {@link Matcher}. 063 */ 064 public <T> RequestMatcher value(Matcher<T> matcher) { 065 return new AbstractJsonPathRequestMatcher() { 066 @Override 067 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 068 JsonPathRequestMatchers.this.jsonPathHelper.assertValue(request.getBodyAsString(), matcher); 069 } 070 }; 071 } 072 073 /** 074 * An overloaded variant of {@link #value(Matcher)} that also accepts a 075 * target type for the resulting value that the matcher can work reliably 076 * against. 077 * <p>This can be useful for matching numbers reliably — for example, 078 * to coerce an integer into a double. 079 * @since 4.3.3 080 */ 081 public <T> RequestMatcher value(Matcher<T> matcher, Class<T> targetType) { 082 return new AbstractJsonPathRequestMatcher() { 083 @Override 084 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 085 String body = request.getBodyAsString(); 086 JsonPathRequestMatchers.this.jsonPathHelper.assertValue(body, matcher, targetType); 087 } 088 }; 089 } 090 091 /** 092 * Evaluate the JSON path expression against the request content and 093 * assert that the result is equal to the supplied value. 094 */ 095 public RequestMatcher value(Object expectedValue) { 096 return new AbstractJsonPathRequestMatcher() { 097 @Override 098 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 099 JsonPathRequestMatchers.this.jsonPathHelper.assertValue(request.getBodyAsString(), expectedValue); 100 } 101 }; 102 } 103 104 /** 105 * Evaluate the JSON path expression against the request content and 106 * assert that a non-null value exists at the given path. 107 * <p>If the JSON path expression is not {@linkplain JsonPath#isDefinite 108 * definite}, this method asserts that the value at the given path is not 109 * <em>empty</em>. 110 */ 111 public RequestMatcher exists() { 112 return new AbstractJsonPathRequestMatcher() { 113 @Override 114 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 115 JsonPathRequestMatchers.this.jsonPathHelper.exists(request.getBodyAsString()); 116 } 117 }; 118 } 119 120 /** 121 * Evaluate the JSON path expression against the request content and 122 * assert that a value does not exist at the given path. 123 * <p>If the JSON path expression is not {@linkplain JsonPath#isDefinite 124 * definite}, this method asserts that the value at the given path is 125 * <em>empty</em>. 126 */ 127 public RequestMatcher doesNotExist() { 128 return new AbstractJsonPathRequestMatcher() { 129 @Override 130 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 131 JsonPathRequestMatchers.this.jsonPathHelper.doesNotExist(request.getBodyAsString()); 132 } 133 }; 134 } 135 136 /** 137 * Evaluate the JSON path expression against the response content 138 * and assert that a value, possibly {@code null}, exists. 139 * <p>If the JSON path expression is not 140 * {@linkplain JsonPath#isDefinite() definite}, this method asserts 141 * that the list of values at the given path is not <em>empty</em>. 142 * @since 5.0.3 143 * @see #exists() 144 * @see #isNotEmpty() 145 */ 146 public RequestMatcher hasJsonPath() { 147 return new AbstractJsonPathRequestMatcher() { 148 @Override 149 protected void matchInternal(MockClientHttpRequest request) { 150 JsonPathRequestMatchers.this.jsonPathHelper.hasJsonPath(request.getBodyAsString()); 151 } 152 }; 153 } 154 155 /** 156 * Evaluate the JSON path expression against the supplied {@code content} 157 * and assert that a value, including {@code null} values, does not exist 158 * at the given path. 159 * <p>If the JSON path expression is not 160 * {@linkplain JsonPath#isDefinite() definite}, this method asserts 161 * that the list of values at the given path is <em>empty</em>. 162 * @since 5.0.3 163 * @see #doesNotExist() 164 * @see #isEmpty() 165 */ 166 public RequestMatcher doesNotHaveJsonPath() { 167 return new AbstractJsonPathRequestMatcher() { 168 @Override 169 protected void matchInternal(MockClientHttpRequest request) { 170 JsonPathRequestMatchers.this.jsonPathHelper.doesNotHaveJsonPath(request.getBodyAsString()); 171 } 172 }; 173 } 174 175 /** 176 * Evaluate the JSON path expression against the request content and 177 * assert that an empty value exists at the given path. 178 * <p>For the semantics of <em>empty</em>, consult the Javadoc for 179 * {@link org.springframework.util.ObjectUtils#isEmpty(Object)}. 180 * @since 4.2.1 181 * @see #isNotEmpty() 182 * @see #exists() 183 * @see #doesNotExist() 184 */ 185 public RequestMatcher isEmpty() { 186 return new AbstractJsonPathRequestMatcher() { 187 @Override 188 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 189 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsEmpty(request.getBodyAsString()); 190 } 191 }; 192 } 193 194 /** 195 * Evaluate the JSON path expression against the request content and 196 * assert that a non-empty value exists at the given path. 197 * <p>For the semantics of <em>empty</em>, consult the Javadoc for 198 * {@link org.springframework.util.ObjectUtils#isEmpty(Object)}. 199 * @since 4.2.1 200 * @see #isEmpty() 201 * @see #exists() 202 * @see #doesNotExist() 203 */ 204 public RequestMatcher isNotEmpty() { 205 return new AbstractJsonPathRequestMatcher() { 206 @Override 207 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 208 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsNotEmpty(request.getBodyAsString()); 209 } 210 }; 211 } 212 213 /** 214 * Evaluate the JSON path expression against the request content and 215 * assert that the result is a {@link String}. 216 * @since 4.2.1 217 */ 218 public RequestMatcher isString() { 219 return new AbstractJsonPathRequestMatcher() { 220 @Override 221 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 222 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsString(request.getBodyAsString()); 223 } 224 }; 225 } 226 227 /** 228 * Evaluate the JSON path expression against the request content and 229 * assert that the result is a {@link Boolean}. 230 * @since 4.2.1 231 */ 232 public RequestMatcher isBoolean() { 233 return new AbstractJsonPathRequestMatcher() { 234 @Override 235 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 236 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsBoolean(request.getBodyAsString()); 237 } 238 }; 239 } 240 241 /** 242 * Evaluate the JSON path expression against the request content and 243 * assert that the result is a {@link Number}. 244 * @since 4.2.1 245 */ 246 public RequestMatcher isNumber() { 247 return new AbstractJsonPathRequestMatcher() { 248 @Override 249 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 250 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsNumber(request.getBodyAsString()); 251 } 252 }; 253 } 254 255 /** 256 * Evaluate the JSON path expression against the request content and 257 * assert that the result is an array. 258 */ 259 public RequestMatcher isArray() { 260 return new AbstractJsonPathRequestMatcher() { 261 @Override 262 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 263 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsArray(request.getBodyAsString()); 264 } 265 }; 266 } 267 268 /** 269 * Evaluate the JSON path expression against the request content and 270 * assert that the result is a {@link java.util.Map}. 271 * @since 4.2.1 272 */ 273 public RequestMatcher isMap() { 274 return new AbstractJsonPathRequestMatcher() { 275 @Override 276 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 277 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsMap(request.getBodyAsString()); 278 } 279 }; 280 } 281 282 283 /** 284 * Abstract base class for {@code JsonPath}-based {@link RequestMatcher RequestMatchers}. 285 * @see #matchInternal 286 */ 287 private abstract static class AbstractJsonPathRequestMatcher implements RequestMatcher { 288 289 @Override 290 public final void match(ClientHttpRequest request) throws IOException, AssertionError { 291 try { 292 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 293 matchInternal(mockRequest); 294 } 295 catch (ParseException ex) { 296 throw new AssertionError("Failed to parse JSON request content", ex); 297 } 298 } 299 300 abstract void matchInternal(MockClientHttpRequest request) throws IOException, ParseException; 301 } 302 303}