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.assertions;
018
019import java.io.*;
020import java.lang.reflect.*;
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.assertions.*;
025import org.apache.juneau.http.response.*;
026import org.apache.juneau.internal.*;
027import org.apache.juneau.rest.httppart.*;
028import org.apache.juneau.serializer.*;
029
030/**
031 * Used for fluent assertion calls against {@link RequestContent} objects.
032 *
033 * <h5 class='topic'>Test Methods</h5>
034 * <p>
035 * <ul class='javatree'>
036 *    <li class='jc'>{@link FluentRequestContentAssertion}
037 *    <ul class='javatreec'>
038 *       <li class='jm'>{@link FluentRequestContentAssertion#is(String) is(String)}
039 *       <li class='jm'>{@link FluentRequestContentAssertion#isContains(String...) isContains(String...)}
040 *       <li class='jm'>{@link FluentRequestContentAssertion#isNotContains(String...) isNotContains(String...)}
041 *       <li class='jm'>{@link FluentRequestContentAssertion#isEmpty() isEmpty()}
042 *       <li class='jm'>{@link FluentRequestContentAssertion#isNotEmpty() isNotEmpty()}
043 *    </ul>
044 *    <li class='jc'>{@link FluentObjectAssertion}
045 *    <ul class='javatreec'>
046 *       <li class='jm'>{@link FluentObjectAssertion#isExists() isExists()}
047 *       <li class='jm'>{@link FluentObjectAssertion#is(Object) is(Object)}
048 *       <li class='jm'>{@link FluentObjectAssertion#is(Predicate) is(Predicate)}
049 *       <li class='jm'>{@link FluentObjectAssertion#isNot(Object) isNot(Object)}
050 *       <li class='jm'>{@link FluentObjectAssertion#isAny(Object...) isAny(Object...)}
051 *       <li class='jm'>{@link FluentObjectAssertion#isNotAny(Object...) isNotAny(Object...)}
052 *       <li class='jm'>{@link FluentObjectAssertion#isNull() isNull()}
053 *       <li class='jm'>{@link FluentObjectAssertion#isNotNull() isNotNull()}
054 *       <li class='jm'>{@link FluentObjectAssertion#isString(String) isString(String)}
055 *       <li class='jm'>{@link FluentObjectAssertion#isJson(String) isJson(String)}
056 *       <li class='jm'>{@link FluentObjectAssertion#isSame(Object) isSame(Object)}
057 *       <li class='jm'>{@link FluentObjectAssertion#isSameJsonAs(Object) isSameJsonAs(Object)}
058 *       <li class='jm'>{@link FluentObjectAssertion#isSameSortedJsonAs(Object) isSameSortedJsonAs(Object)}
059 *       <li class='jm'>{@link FluentObjectAssertion#isSameSerializedAs(Object, WriterSerializer) isSameSerializedAs(Object, WriterSerializer)}
060 *       <li class='jm'>{@link FluentObjectAssertion#isType(Class) isType(Class)}
061 *       <li class='jm'>{@link FluentObjectAssertion#isExactType(Class) isExactType(Class)}
062 *    </ul>
063 * </ul>
064 *
065 * <h5 class='topic'>Transform Methods</h5>
066 * <p>
067 * <ul class='javatree'>
068 *    <li class='jc'>{@link FluentRequestContentAssertion}
069 *    <ul class='javatreec'>
070 *       <li class='jm'>{@link FluentRequestContentAssertion#asBytes() asBytes()}
071 *       <li class='jm'>{@link FluentRequestContentAssertion#as(Class) as(Class)}
072 *       <li class='jm'>{@link FluentRequestContentAssertion#as(Type,Type...) as(Type,Type...)}
073 *    </ul>
074 *    <li class='jc'>{@link FluentObjectAssertion}
075 *    <ul class='javatreec'>
076 *       <li class='jm'>{@link FluentObjectAssertion#asString() asString()}
077 *       <li class='jm'>{@link FluentObjectAssertion#asString(WriterSerializer) asString(WriterSerializer)}
078 *       <li class='jm'>{@link FluentObjectAssertion#asString(Function) asString(Function)}
079 *       <li class='jm'>{@link FluentObjectAssertion#asJson() asJson()}
080 *       <li class='jm'>{@link FluentObjectAssertion#asJsonSorted() asJsonSorted()}
081 *       <li class='jm'>{@link FluentObjectAssertion#asTransformed(Function) asApplied(Function)}
082 *       <li class='jm'>{@link FluentObjectAssertion#asAny() asAny()}
083 * </ul>
084 * </ul>
085 *
086 * <h5 class='topic'>Configuration Methods</h5>
087 * <p>
088 * <ul class='javatree'>
089 *    <li class='jc'>{@link Assertion}
090 *    <ul class='javatreec'>
091 *       <li class='jm'>{@link Assertion#setMsg(String, Object...) setMsg(String, Object...)}
092 *       <li class='jm'>{@link Assertion#setOut(PrintStream) setOut(PrintStream)}
093 *       <li class='jm'>{@link Assertion#setSilent() setSilent()}
094 *       <li class='jm'>{@link Assertion#setStdOut() setStdOut()}
095 *       <li class='jm'>{@link Assertion#setThrowable(Class) setThrowable(Class)}
096 *    </ul>
097 * </ul>
098 *
099 * <h5 class='section'>See Also:</h5><ul>
100 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauEcosystemOverview">Juneau Ecosystem Overview</a>
101 * </ul>
102 *
103 * @param <R> The return type.
104 */
105public class FluentRequestContentAssertion<R> extends FluentObjectAssertion<RequestContent,R> {
106
107   //-----------------------------------------------------------------------------------------------------------------
108   // Constructors
109   //-----------------------------------------------------------------------------------------------------------------
110
111   /**
112    * Constructor.
113    *
114    * @param value
115    *    The object being tested.
116    *    <br>Can be <jk>null</jk>.
117    * @param returns
118    *    The object to return after a test method is called.
119    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
120    * used on the same assertion.
121    */
122   public FluentRequestContentAssertion(RequestContent value, R returns) {
123      this(null, value, returns);
124   }
125
126   /**
127    * Chained constructor.
128    *
129    * <p>
130    * Used when transforming one assertion into another so that the assertion config can be used by the new assertion.
131    *
132    * @param creator
133    *    The assertion that created this assertion.
134    *    <br>Should be <jk>null</jk> if this is the top-level assertion.
135    * @param value
136    *    The object being tested.
137    *    <br>Can be <jk>null</jk>.
138    * @param returns
139    *    The object to return after a test method is called.
140    *    <br>If <jk>null</jk>, the test method returns this object allowing multiple test method calls to be
141    * used on the same assertion.
142    */
143   public FluentRequestContentAssertion(Assertion creator, RequestContent value, R returns) {
144      super(creator, value, returns);
145      setThrowable(BadRequest.class);
146   }
147
148   //-----------------------------------------------------------------------------------------------------------------
149   // Transform methods
150   //-----------------------------------------------------------------------------------------------------------------
151
152   /**
153    * Provides the ability to perform fluent-style assertions on the bytes of the request content.
154    *
155    * <h5 class='section'>Examples:</h5>
156    * <p class='bjava'>
157    *    <jc>// Validates the request content equals the text "foo".</jc>
158    *    <jv>request</jv>
159    *       .assertContent().asBytes().asHex().is(<js>"666F6F"</js>);
160    * </p>
161    *
162    * <h5 class='section'>Notes:</h5><ul>
163    *    <li class='note'>
164    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
165    *  <li class='note'>
166    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
167    *    <li class='note'>
168    *       The input stream is automatically closed after this call.
169    * </ul>
170    *
171    * @return A new fluent assertion object.
172    */
173   public FluentByteArrayAssertion<R> asBytes() {
174      return new FluentByteArrayAssertion<>(valueAsBytes(), returns());
175   }
176
177   /**
178    * Converts the content to a type using {@link RequestContent#as(Class)} and then returns the value as an object assertion.
179    *
180    * <h5 class='section'>Examples:</h5>
181    * <p class='bjava'>
182    *    <jc>// Validates the request content bean is the expected value.</jc>
183    *    <jv>request</jv>
184    *       .assertContent()
185    *       .as(MyBean.<jk>class</jk>)
186    *          .asJson().is(<js>"{foo:'bar'}"</js>);
187    * </p>
188    *
189    * <h5 class='section'>Notes:</h5><ul>
190    *    <li class='note'>
191    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
192    *  <li class='note'>
193    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
194    *    <li class='note'>
195    *       The input stream is automatically closed after this call.
196    * </ul>
197    *
198    * <p>
199    * 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}.
200    *
201    * @param <T> The object type to create.
202    * @param type The object type to create.
203    * @return A new fluent assertion object.
204    */
205   public <T> FluentObjectAssertion<T,R> as(Class<T> type) {
206      return new FluentObjectAssertion<>(valueAsType(type), returns());
207   }
208
209   /**
210    * Converts the content to a type using {@link RequestContent#as(Type,Type...)} and then returns the value as an object assertion.
211    *
212    * <h5 class='section'>Examples:</h5>
213    * <p class='bjava'>
214    *    <jc>// Validates the request content bean is the expected value.</jc>
215    *    <jv>request</jv>
216    *       .assertContent()
217    *       .as(Map.<jk>class</jk>,String.<jk>class</jk>,Integer.<jk>class</jk>)
218    *          .asJson().is(<js>"{foo:123}"</js>);
219    * </p>
220    *
221    * <h5 class='section'>Notes:</h5><ul>
222    *    <li class='note'>
223    *       If no charset was found on the <code>Content-Type</code> request header, <js>"UTF-8"</js> is assumed.
224    *  <li class='note'>
225    *    When using this method, the content is automatically cached by calling the {@link RequestContent#cache()}.
226    *    <li class='note'>
227    *       The input stream is automatically closed after this call.
228    * </ul>
229    *
230    * <p>
231    * 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}.
232    *
233    * @param <T> The type to create.
234    * @param type The object type to create.
235    * @param args Optional type arguments.
236    * @return A new fluent assertion object.
237    */
238   public <T> FluentObjectAssertion<T,R> as(Type type, Type...args) {
239      return new FluentObjectAssertion<>(valueAsType(type, args), returns());
240   }
241
242   //-----------------------------------------------------------------------------------------------------------------
243   // Test methods
244   //-----------------------------------------------------------------------------------------------------------------
245
246   /**
247    * Asserts that the content contains the specified value.
248    *
249    * @param values The value to check against.
250    * @return This object.
251    * @throws AssertionError If assertion failed.
252    */
253   public R is(String values) throws AssertionError {
254      return asString().is(values);
255   }
256
257   /**
258    * Asserts that the text contains all of the specified substrings.
259    *
260    * @param values The values to check against.
261    * @return This object.
262    * @throws AssertionError If assertion failed.
263    */
264   public R isContains(String...values) throws AssertionError {
265      return asString().isContains(values);
266   }
267
268   /**
269    * Asserts that the content doesn't contain any of the specified substrings.
270    *
271    * @param values The values to check against.
272    * @return This object.
273    * @throws AssertionError If assertion failed.
274    */
275   public R isNotContains(String...values) throws AssertionError {
276      return asString().isNotContains(values);
277   }
278
279   /**
280    * Asserts that the content is empty.
281    *
282    * @return This object.
283    * @throws AssertionError If assertion failed.
284    */
285   public R isEmpty() {
286      return asString().isEmpty();
287   }
288
289   /**
290    * Asserts that the content is not empty.
291    *
292    * @return This object.
293    * @throws AssertionError If assertion failed.
294    */
295   public R isNotEmpty() {
296      return asString().isNotEmpty();
297   }
298
299   //-----------------------------------------------------------------------------------------------------------------
300   // Helper methods.
301   //-----------------------------------------------------------------------------------------------------------------
302
303   @Override
304   protected String valueAsString() throws AssertionError {
305      try {
306         return value().cache().asString();
307      } catch (IOException e) {
308         throw error(e, "Exception occurred during call.");
309      }
310   }
311
312   private byte[] valueAsBytes() throws AssertionError {
313      try {
314         return value().cache().asBytes();
315      } catch (IOException e) {
316         throw error(e, "Exception occurred during call.");
317      }
318   }
319
320   private <T> T valueAsType(Class<T> c) throws AssertionError {
321      try {
322         return value().cache().as(c);
323      } catch (IOException e) {
324         throw error(e, "Exception occurred during call.");
325      }
326   }
327
328   private <T> T valueAsType(Type c, Type...args) throws AssertionError {
329      try {
330         return value().cache().as(c, args);
331      } catch (IOException e) {
332         throw error(e, "Exception occurred during call.");
333      }
334   }
335
336   //-----------------------------------------------------------------------------------------------------------------
337   // Fluent setters
338   //-----------------------------------------------------------------------------------------------------------------
339   @Override /* Overridden from Assertion */
340   public FluentRequestContentAssertion<R> setMsg(String msg, Object...args) {
341      super.setMsg(msg, args);
342      return this;
343   }
344
345   @Override /* Overridden from Assertion */
346   public FluentRequestContentAssertion<R> setOut(PrintStream value) {
347      super.setOut(value);
348      return this;
349   }
350
351   @Override /* Overridden from Assertion */
352   public FluentRequestContentAssertion<R> setSilent() {
353      super.setSilent();
354      return this;
355   }
356
357   @Override /* Overridden from Assertion */
358   public FluentRequestContentAssertion<R> setStdOut() {
359      super.setStdOut();
360      return this;
361   }
362
363   @Override /* Overridden from Assertion */
364   public FluentRequestContentAssertion<R> setThrowable(Class<? extends java.lang.RuntimeException> value) {
365      super.setThrowable(value);
366      return this;
367   }
368}