001/* 002 * Copyright 2002-2018 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(final 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(final Matcher<T> matcher, final 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(final 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 request content and 138 * assert that an empty value exists at the given path. 139 * <p>For the semantics of <em>empty</em>, consult the Javadoc for 140 * {@link org.springframework.util.ObjectUtils#isEmpty(Object)}. 141 * @since 4.2.1 142 * @see #isNotEmpty() 143 * @see #exists() 144 * @see #doesNotExist() 145 */ 146 public RequestMatcher isEmpty() { 147 return new AbstractJsonPathRequestMatcher() { 148 @Override 149 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 150 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsEmpty(request.getBodyAsString()); 151 } 152 }; 153 } 154 155 /** 156 * Evaluate the JSON path expression against the request content and 157 * assert that a non-empty value exists at the given path. 158 * <p>For the semantics of <em>empty</em>, consult the Javadoc for 159 * {@link org.springframework.util.ObjectUtils#isEmpty(Object)}. 160 * @since 4.2.1 161 * @see #isEmpty() 162 * @see #exists() 163 * @see #doesNotExist() 164 */ 165 public RequestMatcher isNotEmpty() { 166 return new AbstractJsonPathRequestMatcher() { 167 @Override 168 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 169 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsNotEmpty(request.getBodyAsString()); 170 } 171 }; 172 } 173 174 /** 175 * Evaluate the JSON path expression against the request content and 176 * assert that the result is a {@link String}. 177 * @since 4.2.1 178 */ 179 public RequestMatcher isString() { 180 return new AbstractJsonPathRequestMatcher() { 181 @Override 182 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 183 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsString(request.getBodyAsString()); 184 } 185 }; 186 } 187 188 /** 189 * Evaluate the JSON path expression against the request content and 190 * assert that the result is a {@link Boolean}. 191 * @since 4.2.1 192 */ 193 public RequestMatcher isBoolean() { 194 return new AbstractJsonPathRequestMatcher() { 195 @Override 196 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 197 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsBoolean(request.getBodyAsString()); 198 } 199 }; 200 } 201 202 /** 203 * Evaluate the JSON path expression against the request content and 204 * assert that the result is a {@link Number}. 205 * @since 4.2.1 206 */ 207 public RequestMatcher isNumber() { 208 return new AbstractJsonPathRequestMatcher() { 209 @Override 210 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 211 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsNumber(request.getBodyAsString()); 212 } 213 }; 214 } 215 216 /** 217 * Evaluate the JSON path expression against the request content and 218 * assert that the result is an array. 219 */ 220 public RequestMatcher isArray() { 221 return new AbstractJsonPathRequestMatcher() { 222 @Override 223 protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 224 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsArray(request.getBodyAsString()); 225 } 226 }; 227 } 228 229 /** 230 * Evaluate the JSON path expression against the request content and 231 * assert that the result is a {@link java.util.Map}. 232 * @since 4.2.1 233 */ 234 public RequestMatcher isMap() { 235 return new AbstractJsonPathRequestMatcher() { 236 @Override 237 public void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { 238 JsonPathRequestMatchers.this.jsonPathHelper.assertValueIsMap(request.getBodyAsString()); 239 } 240 }; 241 } 242 243 244 /** 245 * Abstract base class for {@code JsonPath}-based {@link RequestMatcher}s. 246 * @see #matchInternal 247 */ 248 private abstract static class AbstractJsonPathRequestMatcher implements RequestMatcher { 249 250 @Override 251 public final void match(ClientHttpRequest request) throws IOException, AssertionError { 252 try { 253 MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; 254 matchInternal(mockRequest); 255 } 256 catch (ParseException ex) { 257 throw new AssertionError("Failed to parse JSON request content: " + ex.getMessage()); 258 } 259 } 260 261 abstract void matchInternal(MockClientHttpRequest request) throws IOException, ParseException; 262 } 263 264}