001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.rest.server.assertions;
018
019import java.io.*;
020import java.lang.reflect.*;
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.function.*;
025import org.apache.juneau.http.response.*;
026import org.apache.juneau.rest.server.httppart.*;
027import org.apache.juneau.bct.assertions.*;
028
029/**
030 * Used for fluent assertion calls against {@link RequestContent} objects.
031 *
032 * <h5 class='topic'>Test Methods</h5>
033 * <p>
034 * <ul class='javatree'>
035 *    <li class='jc'>{@link FluentRequestContentAssertion}
036 *    <ul class='javatreec'>
037 *       <li class='jm'>{@link FluentRequestContentAssertion#is(String) is(String)}
038 *       <li class='jm'>{@link FluentRequestContentAssertion#isContains(String...) isContains(String...)}
039 *       <li class='jm'>{@link FluentRequestContentAssertion#isNotContains(String...) isNotContains(String...)}
040 *       <li class='jm'>{@link FluentRequestContentAssertion#isEmpty() isEmpty()}
041 *       <li class='jm'>{@link FluentRequestContentAssertion#isNotEmpty() isNotEmpty()}
042 *    </ul>
043 *    <li class='jc'>{@link FluentObjectAssertion}
044 *    <ul class='javatreec'>
045 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
046 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
047 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
048 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
049 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
052 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
053 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
054 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
057 *    </ul>
058 * </ul>
059 *
060 * <h5 class='topic'>Transform Methods</h5>
061 * <p>
062 * <ul class='javatree'>
063 *    <li class='jc'>{@link FluentRequestContentAssertion}
064 *    <ul class='javatreec'>
065 *       <li class='jm'>{@link FluentRequestContentAssertion#asBytes() asBytes()}
066 *       <li class='jm'>{@link FluentRequestContentAssertion#as(Class) as(Class)}
067 *       <li class='jm'>{@link FluentRequestContentAssertion#as(Type,Type...) as(Type,Type...)}
068 *    </ul>
069 *    <li class='jc'>{@link FluentObjectAssertion}
070 *    <ul class='javatreec'>
071 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
072 *       <li class='jm'>{@link FluentObjectAssertion#asString(ThrowingFunction) asString(ThrowingFunction)}
073 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(UnaryOperator) asTransformed(UnaryOperator)}
074 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
075 * </ul>
076 * </ul>
077 *
078 * <h5 class='topic'>Configuration Methods</h5>
079 * <p>
080 * <ul class='javatree'>
081 *    <li class='jc'>{@link Assertion}
082 *    <ul class='javatreec'>
083 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
084 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
085 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
086 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
087 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
088 *    </ul>
089 * </ul>
090 *
091 * <h5 class='section'>See Also:</h5><ul>
092 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem</a>
093 * </ul>
094 *
095 * @param <R> The return type.
096 */
097@SuppressWarnings({
098   "java:S115", // Constants use UPPER_snakeCase convention (e.g., MSG_exceptionDuringCall)
099   "java:S1192" // Duplicated literals read more clearly inline than as constants.
100})
101public class FluentRequestContentAssertion<R> extends FluentObjectAssertion<RequestContent,R> {
102
103   /**
104    * Chained constructor.
105    *
106    * <p>
107    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
108    *
109    * @param creator
110    *    The assertion that created this assertion.
111    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
112    * @param value
113    *    The object being tested.
114    *    <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value).
115    * @param returns
116    *    The object to return after a test method is called.
117    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
118    * used on the same assertion.
119    */
120   public FluentRequestContentAssertion(Assertion creator, RequestContent value, R returns) {
121      super(creator, value, returns);
122      setThrowable(BadRequest.class);
123   }
124
125   /**
126    * Constructor.
127    *
128    * @param value
129    *    The object being tested.
130    *    <br>Can be <jk>null</jk> (assertions such as {@code isNull()} then operate on the absence of a value).
131    * @param returns
132    *    The object to return after a test method is called.
133    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
134    * used on the same assertion.
135    */
136   public FluentRequestContentAssertion(RequestContent value, R returns) {
137      this(null, value, returns);
138   }
139
140   /**
141    * Converts the content to a type using {@link RequestContent#as(Class)} and then returns the value as an object assertion.
142    *
143    * <h5 class='section'>Examples:</h5>
144    * <p class='bjava'>
145    *    <jc>// Validates the request content bean is the expected value.</jc>
146    *    <jv>request</jv>
147    *       .assertContent()
148    *       .as(MyBean.<jk>class</jk>)
149    *          .asJson().is(<js>"{foo:'bar'}"</js>);
150    * </p>
151    *
152    * <h5 class='section'>Notes:</h5><ul>
153    *    <li class='note'>
154    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
155    *  <li class='note'>
156    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
157    *    <li class='note'>
158    *       The input stream is automatically closed after this call.
159    * </ul>
160    *
161    * <p>
162    * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}.
163    *
164    * @param <T> The object type to create.
165    * @param type The object type to create.
166    * @return A new fluent assertion object.
167    */
168   public <T> FluentObjectAssertion<T,R> as(Class<T> type) {
169      return new FluentObjectAssertion<>(valueAsType(type), returns());
170   }
171
172   /**
173    * Converts the content to a type using {@link RequestContent#as(Type,Type...)} and then returns the value as an object assertion.
174    *
175    * <h5 class='section'>Examples:</h5>
176    * <p class='bjava'>
177    *    <jc>// Validates the request content bean is the expected value.</jc>
178    *    <jv>request</jv>
179    *       .assertContent()
180    *       .as(Map.<jk>class</jk>,String.<jk>class</jk>,Integer.<jk>class</jk>)
181    *          .asJson().is(<js>"{foo:123}"</js>);
182    * </p>
183    *
184    * <h5 class='section'>Notes:</h5><ul>
185    *    <li class='note'>
186    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
187    *  <li class='note'>
188    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
189    *    <li class='note'>
190    *       The input stream is automatically closed after this call.
191    * </ul>
192    *
193    * <p>
194    * See <a class="doclink" href="https://juneau.apache.org/docs/topics/ComplexDataTypes">Complex Data Types</a> for information on defining complex generic types of {@link Map Maps} and {@link Collection Collections}.
195    *
196    * @param <T> The type to create.
197    * @param type The object type to create.
198    * @param args Optional type arguments.
199    * @return A new fluent assertion object.
200    */
201   public <T> FluentObjectAssertion<T,R> as(Type type, Type...args) {
202      return new FluentObjectAssertion<>(valueAsType(type, args), returns());
203   }
204
205   /**
206    * Provides the ability to perform fluent-style assertions on the bytes of the request content.
207    *
208    * <h5 class='section'>Examples:</h5>
209    * <p class='bjava'>
210    *    <jc>// Validates the request content equals the text "foo".</jc>
211    *    <jv>request</jv>
212    *       .assertContent().asBytes().asHex().is(<js>"666F6F"</js>);
213    * </p>
214    *
215    * <h5 class='section'>Notes:</h5><ul>
216    *    <li class='note'>
217    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
218    *  <li class='note'>
219    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
220    *    <li class='note'>
221    *       The input stream is automatically closed after this call.
222    * </ul>
223    *
224    * @return A new fluent assertion object.
225    */
226   public FluentByteArrayAssertion<R> asBytes() {
227      return new FluentByteArrayAssertion<>(valueAsBytes(), returns());
228   }
229
230   /**
231    * Asserts that the content contains the specified value.
232    *
233    * @param values The value to check against.
234    * @return This object.
235    * @throws AssertionError If assertion failed.
236    */
237   public R is(String values) throws AssertionError {
238      return asString().is(values);
239   }
240
241   /**
242    * Asserts that the text contains all of the specified substrings.
243    *
244    * @param values The values to check against.
245    * @return This object.
246    * @throws AssertionError If assertion failed.
247    */
248   public R isContains(String...values) throws AssertionError {
249      return asString().isContains(values);
250   }
251
252   /**
253    * Asserts that the content is empty.
254    *
255    * @return This object.
256    * @throws AssertionError If assertion failed.
257    */
258   public R isEmpty() { return asString().isEmpty(); }
259
260   /**
261    * Asserts that the content doesn't contain any of the specified substrings.
262    *
263    * @param values The values to check against.
264    * @return This object.
265    * @throws AssertionError If assertion failed.
266    */
267   public R isNotContains(String...values) throws AssertionError {
268      return asString().isNotContains(values);
269   }
270
271   /**
272    * Asserts that the content is not empty.
273    *
274    * @return This object.
275    * @throws AssertionError If assertion failed.
276    */
277   public R isNotEmpty() { return asString().isNotEmpty(); }
278
279   @Override /* Overridden from Assertion */
280   public FluentRequestContentAssertion<R> setMsg(String msg, Object...args) {
281      super.setMsg(msg, args);
282      return this;
283   }
284
285   @Override /* Overridden from Assertion */
286   public FluentRequestContentAssertion<R> setOut(PrintStream value) {
287      super.setOut(value);
288      return this;
289   }
290
291   @Override /* Overridden from Assertion */
292   public FluentRequestContentAssertion<R> setSilent() {
293      super.setSilent();
294      return this;
295   }
296
297   @Override /* Overridden from Assertion */
298   public FluentRequestContentAssertion<R> setStdOut() {
299      super.setStdOut();
300      return this;
301   }
302
303   @Override /* Overridden from Assertion */
304   public FluentRequestContentAssertion<R> setThrowable(Class<? extends RuntimeException> value) {
305      super.setThrowable(value);
306      return this;
307   }
308
309   private byte[] valueAsBytes() throws AssertionError {
310      try {
311         return value().cache().asBytes();
312      } catch (IOException e) {
313         throw error(e, "Exception occurred during call.");
314      }
315   }
316
317   private <T> T valueAsType(Class<T> c) throws AssertionError {
318      try {
319         return value().cache().as(c);
320      } catch (IOException e) {
321         throw error(e, "Exception occurred during call.");
322      }
323   }
324
325   private <T> T valueAsType(Type c, Type...args) throws AssertionError {
326      try {
327         return value().cache().as(c, args);
328      } catch (IOException e) {
329         throw error(e, "Exception occurred during call.");
330      }
331   }
332
333   @Override
334   protected String valueAsString() throws AssertionError {
335      try {
336         return value().cache().asString();
337      } catch (IOException e) {
338         throw error(e, "Exception occurred during call.");
339      }
340   }
341}