View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau.http.remote;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.apache.juneau.http.HttpParts.*;
21  import static org.junit.jupiter.api.Assertions.*;
22  
23  import java.io.*;
24  import java.math.*;
25  import java.util.*;
26  import java.util.concurrent.atomic.*;
27  
28  import org.apache.http.*;
29  import org.apache.juneau.*;
30  import org.apache.juneau.annotation.*;
31  import org.apache.juneau.collections.*;
32  import org.apache.juneau.http.annotation.*;
33  import org.apache.juneau.http.part.*;
34  import org.apache.juneau.internal.*;
35  import org.apache.juneau.rest.RestRequest;
36  import org.apache.juneau.rest.annotation.*;
37  import org.apache.juneau.rest.client.*;
38  import org.apache.juneau.rest.mock.*;
39  import org.apache.juneau.uon.*;
40  import org.apache.juneau.utest.utils.*;
41  import org.junit.jupiter.api.*;
42  
43  class Remote_QueryAnnotation_Test extends TestBase {
44  
45  	public static class Bean {
46  		public int f;
47  
48  		public static Bean create() {
49  			var b = new Bean();
50  			b.f = 1;
51  			return b;
52  		}
53  	}
54  
55  	//-----------------------------------------------------------------------------------------------------------------
56  	// Basic tests
57  	//-----------------------------------------------------------------------------------------------------------------
58  
59  	@Rest
60  	public static class A {
61  		@RestGet
62  		public String a(@Query("*") JsonMap m) {
63  			return m.toString();
64  		}
65  	}
66  
67  	@Remote
68  	public interface A1 {
69  		@RemoteOp(path="a") String getX1(@Query("x") int b);
70  		@RemoteOp(path="a") String getX2(@Query("x") float b);
71  		@RemoteOp(path="a") String getX3(@Query("x") Bean b);
72  		@RemoteOp(path="a") String getX4(@Query("*") Bean b);
73  		@RemoteOp(path="a") String getX5(@Query Bean b);
74  		@RemoteOp(path="a") String getX6(@Query("x") Bean[] b);
75  		@RemoteOp(path="a") String getX7(@Query("x") @Schema(cf="uon") Bean[] b);
76  		@RemoteOp(path="a") String getX8(@Query("x") List<Bean> b);
77  		@RemoteOp(path="a") String getX9(@Query("x") @Schema(cf="uon") List<Bean> b);
78  		@RemoteOp(path="a") String getX10(@Query("x") Map<String,Bean> b);
79  		@RemoteOp(path="a") String getX11(@Query("*") Map<String,Bean> b);
80  		@RemoteOp(path="a") String getX12(@Query Map<String,Bean> b);
81  		@RemoteOp(path="a") String getX13(@Query("x") @Schema(cf="uon") Map<String,Bean> b);
82  		@RemoteOp(path="a") String getX14(@Query() @Schema(f="uon") Map<String,Bean> b);
83  		@RemoteOp(path="a") String getX15(@Query("*") Reader b);
84  		@RemoteOp(path="a") String getX16(@Query Reader b);
85  		@RemoteOp(path="a") String getX17(@Query("*") InputStream b);
86  		@RemoteOp(path="a") String getX18(@Query InputStream b);
87  		@RemoteOp(path="a") String getX19(@Query("*") PartList b);
88  		@RemoteOp(path="a") String getX20(@Query PartList b);
89  		@RemoteOp(path="a") String getX21(@Query NameValuePair b);
90  		@RemoteOp(path="a") String getX22(@Query NameValuePair[] b);
91  		@RemoteOp(path="a") String getX24(@Query String b);
92  		@RemoteOp(path="a") String getX25(@Query List<NameValuePair> b);
93  	}
94  
95  	@Test void a01_objectTypes() {
96  		var x = remote(A.class,A1.class);
97  		assertEquals("{x:'1'}",x.getX1(1));
98  		assertEquals("{x:'1.0'}",x.getX2(1));
99  		assertEquals("{x:'f=1'}",x.getX3(Bean.create()));
100 		assertEquals("{f:'1'}",x.getX4(Bean.create()));
101 		assertEquals("{f:'1'}",x.getX5(Bean.create()));
102 		assertEquals("{x:'f=1,f=1'}",x.getX6(new Bean[]{Bean.create(),Bean.create()}));
103 		assertEquals("{x:'@((f=1),(f=1))'}",x.getX7(new Bean[]{Bean.create(),Bean.create()}));
104 		assertEquals("{x:'f=1,f=1'}",x.getX8(alist(Bean.create(),Bean.create())));
105 		assertEquals("{x:'@((f=1),(f=1))'}",x.getX9(alist(Bean.create(),Bean.create())));
106 		assertEquals("{x:'k1=f\\\\=1'}",x.getX10(map("k1",Bean.create())));
107 		assertEquals("{k1:'f=1'}",x.getX11(map("k1",Bean.create())));
108 		assertEquals("{k1:'f=1'}",x.getX12(map("k1",Bean.create())));
109 		assertEquals("{x:'(k1=(f=1))'}",x.getX13(map("k1",Bean.create())));
110 		assertEquals("{k1:'f=1'}",x.getX14(map("k1",Bean.create())));
111 		assertEquals("{x:'1'}",x.getX15(reader("x=1")));
112 		assertEquals("{x:'1'}",x.getX16(reader("x=1")));
113 		assertEquals("{x:'1'}",x.getX17(inputStream("x=1")));
114 		assertEquals("{x:'1'}",x.getX18(inputStream("x=1")));
115 		assertEquals("{foo:'bar'}",x.getX19(parts("foo","bar")));
116 		assertEquals("{foo:'bar'}",x.getX20(parts("foo","bar")));
117 		assertEquals("{foo:'bar'}",x.getX21(part("foo","bar")));
118 		assertEquals("{foo:'bar'}",x.getX22(parts("foo","bar").getAll()));
119 		assertEquals("{foo:'bar'}",x.getX24("foo=bar"));
120 		assertEquals("{}",x.getX24(null));
121 		assertEquals("{foo:'bar'}",x.getX25(alist(part("foo","bar"))));
122 	}
123 
124 	//-----------------------------------------------------------------------------------------------------------------
125 	// @Query(_default/allowEmptyValue)
126 	//-----------------------------------------------------------------------------------------------------------------
127 
128 	@Rest
129 	public static class B {
130 		@RestOp
131 		public String get(@Query("*") JsonMap m) {
132 			return m.toString();
133 		}
134 	}
135 
136 	@Remote
137 	public interface B1 {
138 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(df="foo") String b);
139 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(df="foo",aev=true) String b);
140 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(df="") String b);
141 		@RemoteOp(path="/") String getX4(@Query("x") @Schema(df="",aev=true) String b);
142 	}
143 
144 	@Test void b01_default_aev() {
145 		var x = remote(B.class,B1.class);
146 		assertEquals("{x:'foo'}",x.getX1(null));
147 		assertThrowsWithMessage(Exception.class, "Empty value not allowed.", ()->x.getX1(""));
148 		assertEquals("{x:'foo'}",x.getX2(null));
149 		assertEquals("{x:''}",x.getX2(""));
150 		assertEquals("{x:''}",x.getX3(null));
151 		assertThrowsWithMessage(Exception.class, "Empty value not allowed.", ()->x.getX3(""));
152 		assertEquals("{x:''}",x.getX4(null));
153 		assertEquals("{x:''}",x.getX4(""));
154 	}
155 
156 	//-----------------------------------------------------------------------------------------------------------------
157 	// @Query(collectionFormat)
158 	//-----------------------------------------------------------------------------------------------------------------
159 
160 	@Rest
161 	public static class C {
162 		@RestGet
163 		public String a(@Query("*") JsonMap m) {
164 			return m.toString();
165 		}
166 		@RestGet
167 		public Reader b(RestRequest req) {
168 			return reader(req.getQueryString());
169 		}
170 	}
171 
172 	@Remote
173 	public interface C1 {
174 		@RemoteOp(path="/a") String getX1(@Query("x") String...b);
175 		@RemoteOp(path="/b") String getX2(@Query("x") String...b);
176 		@RemoteOp(path="/a") String getX3(@Query("x") @Schema(cf="csv") String...b);
177 		@RemoteOp(path="/b") String getX4(@Query("x") @Schema(cf="csv") String...b);
178 		@RemoteOp(path="/a") String getX5(@Query("x") @Schema(cf="ssv") String...b);
179 		@RemoteOp(path="/b") String getX6(@Query("x") @Schema(cf="ssv") String...b);
180 		@RemoteOp(path="/a") String getX7(@Query("x") @Schema(cf="tsv") String...b);
181 		@RemoteOp(path="/b") String getX8(@Query("x") @Schema(cf="tsv") String...b);
182 		@RemoteOp(path="/a") String getX9(@Query("x") @Schema(cf="pipes") String...b);
183 		@RemoteOp(path="/b") String getX10(@Query("x") @Schema(cf="pipes") String...b);
184 		@RemoteOp(path="/a") String getX11(@Query("x") @Schema(cf="multi") String...b); // Not supported,but should be treated as csv.
185 		@RemoteOp(path="/b") String getX12(@Query("x") @Schema(cf="multi") String...b); // Not supported,but should be treated as csv.
186 		@RemoteOp(path="/a") String getX13(@Query("x") @Schema(cf="uon") String...b);
187 		@RemoteOp(path="/b") String getX14(@Query("x") @Schema(cf="uon") String...b);
188 	}
189 
190 	@Test void c01_collectionFormat() {
191 		var x = remote(C.class,C1.class);
192 		assertEquals("{x:'foo,bar'}",x.getX1("foo","bar"));
193 		assertEquals("x=foo%2Cbar",x.getX2("foo","bar"));
194 		assertEquals("{x:'foo,bar'}",x.getX3("foo","bar"));
195 		assertEquals("x=foo%2Cbar",x.getX4("foo","bar"));
196 		assertEquals("{x:'foo bar'}",x.getX5("foo","bar"));
197 		assertEquals("x=foo+bar",x.getX6("foo","bar"));
198 		assertEquals("{x:'foo\\tbar'}",x.getX7("foo","bar"));
199 		assertEquals("x=foo%09bar",x.getX8("foo","bar"));
200 		assertEquals("{x:'foo|bar'}",x.getX9("foo","bar"));
201 		assertEquals("x=foo%7Cbar",x.getX10("foo","bar"));
202 		assertEquals("{x:'foo,bar'}",x.getX11("foo","bar"));
203 		assertEquals("x=foo%2Cbar",x.getX12("foo","bar"));
204 		assertEquals("{x:'@(foo,bar)'}",x.getX13("foo","bar"));
205 		assertEquals("x=%40%28foo%2Cbar%29",x.getX14("foo","bar"));
206 	}
207 
208 	//-----------------------------------------------------------------------------------------------------------------
209 	// @Query(maximum,exclusiveMaximum,minimum,exclusiveMinimum)
210 	//-----------------------------------------------------------------------------------------------------------------
211 
212 	@Rest
213 	public static class D {
214 		@RestOp
215 		public String get(@Query("*") JsonMap m) {
216 			return m.toString();
217 		}
218 	}
219 
220 	@Remote
221 	public interface D1 {
222 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(min="1",max="10") int b);
223 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) int b);
224 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) int b);
225 		@RemoteOp(path="/") String getX4(@Query("x") @Schema(min="1",max="10") short b);
226 		@RemoteOp(path="/") String getX5(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) short b);
227 		@RemoteOp(path="/") String getX6(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) short b);
228 		@RemoteOp(path="/") String getX7(@Query("x") @Schema(min="1",max="10") long b);
229 		@RemoteOp(path="/") String getX8(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) long b);
230 		@RemoteOp(path="/") String getX9(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) long b);
231 		@RemoteOp(path="/") String getX10(@Query("x") @Schema(min="1",max="10") float b);
232 		@RemoteOp(path="/") String getX11(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) float b);
233 		@RemoteOp(path="/") String getX12(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) float b);
234 		@RemoteOp(path="/") String getX13(@Query("x") @Schema(min="1",max="10") double b);
235 		@RemoteOp(path="/") String getX14(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) double b);
236 		@RemoteOp(path="/") String getX15(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) double b);
237 		@RemoteOp(path="/") String getX16(@Query("x") @Schema(min="1",max="10") byte b);
238 		@RemoteOp(path="/") String getX17(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) byte b);
239 		@RemoteOp(path="/") String getX18(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) byte b);
240 		@RemoteOp(path="/") String getX19(@Query("x") @Schema(min="1",max="10") AtomicInteger b);
241 		@RemoteOp(path="/") String getX20(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) AtomicInteger b);
242 		@RemoteOp(path="/") String getX21(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) AtomicInteger b);
243 		@RemoteOp(path="/") String getX22(@Query("x") @Schema(min="1",max="10") BigDecimal b);
244 		@RemoteOp(path="/") String getX23(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) BigDecimal b);
245 		@RemoteOp(path="/") String getX24(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) BigDecimal b);
246 		@RemoteOp(path="/") String getX25(@Query("x") @Schema(min="1",max="10") Integer b);
247 		@RemoteOp(path="/") String getX26(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Integer b);
248 		@RemoteOp(path="/") String getX27(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Integer b);
249 		@RemoteOp(path="/") String getX28(@Query("x") @Schema(min="1",max="10") Short b);
250 		@RemoteOp(path="/") String getX29(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Short b);
251 		@RemoteOp(path="/") String getX30(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Short b);
252 		@RemoteOp(path="/") String getX31(@Query("x") @Schema(min="1",max="10") Long b);
253 		@RemoteOp(path="/") String getX32(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Long b);
254 		@RemoteOp(path="/") String getX33(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Long b);
255 		@RemoteOp(path="/") String getX34(@Query("x") @Schema(min="1",max="10") Float b);
256 		@RemoteOp(path="/") String getX35(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Float b);
257 		@RemoteOp(path="/") String getX36(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Float b);
258 		@RemoteOp(path="/") String getX37(@Query("x") @Schema(min="1",max="10") Double b);
259 		@RemoteOp(path="/") String getX38(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Double b);
260 		@RemoteOp(path="/") String getX39(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Double b);
261 		@RemoteOp(path="/") String getX40(@Query("x") @Schema(min="1",max="10") Byte b);
262 		@RemoteOp(path="/") String getX41(@Query("x") @Schema(min="1",max="10",emin=false,emax=false) Byte b);
263 		@RemoteOp(path="/") String getX42(@Query("x") @Schema(min="1",max="10",emin=true,emax=true) Byte b);
264 	}
265 
266 	@Test void d01_min_max_emin_emax() {
267 		var x = remote(D.class,D1.class);
268 		assertEquals("{x:'1'}",x.getX1(1));
269 		assertEquals("{x:'10'}",x.getX1(10));
270 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX1(0));
271 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX1(11));
272 		assertEquals("{x:'1'}",x.getX2(1));
273 		assertEquals("{x:'10'}",x.getX2(10));
274 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX2(0));
275 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX2(11));
276 		assertEquals("{x:'2'}",x.getX3(2));
277 		assertEquals("{x:'9'}",x.getX3(9));
278 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX3(1));
279 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX3(10));
280 		assertEquals("{x:'1'}",x.getX4((short)1));
281 		assertEquals("{x:'10'}",x.getX4((short)10));
282 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX4((short)0));
283 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX4((short)11));
284 		assertEquals("{x:'1'}",x.getX5((short)1));
285 		assertEquals("{x:'10'}",x.getX5((short)10));
286 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX5((short)0));
287 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX5((short)11));
288 		assertEquals("{x:'2'}",x.getX6((short)2));
289 		assertEquals("{x:'9'}",x.getX6((short)9));
290 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX6((short)1));
291 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX6((short)10));
292 		assertEquals("{x:'1'}",x.getX7(1L));
293 		assertEquals("{x:'10'}",x.getX7(10L));
294 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX7(0L));
295 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX7(11L));
296 		assertEquals("{x:'1'}",x.getX8(1L));
297 		assertEquals("{x:'10'}",x.getX8(10L));
298 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX8(0L));
299 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX8(11L));
300 		assertEquals("{x:'2'}",x.getX9(2L));
301 		assertEquals("{x:'9'}",x.getX9(9L));
302 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX9(1L));
303 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX9(10L));
304 		assertEquals("{x:'1.0'}",x.getX10(1f));
305 		assertEquals("{x:'10.0'}",x.getX10(10f));
306 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX10(0.9f));
307 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX10(10.1f));
308 		assertEquals("{x:'1.0'}",x.getX11(1f));
309 		assertEquals("{x:'10.0'}",x.getX11(10f));
310 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX11(0.9f));
311 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX11(10.1f));
312 		assertEquals("{x:'1.1'}",x.getX12(1.1f));
313 		assertEquals("{x:'9.9'}",x.getX12(9.9f));
314 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX12(1f));
315 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX12(10f));
316 		assertEquals("{x:'1.0'}",x.getX13(1d));
317 		assertEquals("{x:'10.0'}",x.getX13(10d));
318 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX13(0.9d));
319 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX13(10.1d));
320 		assertEquals("{x:'1.0'}",x.getX14(1d));
321 		assertEquals("{x:'10.0'}",x.getX14(10d));
322 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX14(0.9d));
323 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX14(10.1d));
324 		assertEquals("{x:'1.1'}",x.getX15(1.1d));
325 		assertEquals("{x:'9.9'}",x.getX15(9.9d));
326 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX15(1d));
327 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX15(10d));
328 		assertEquals("{x:'1'}",x.getX16((byte)1));
329 		assertEquals("{x:'10'}",x.getX16((byte)10));
330 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX16((byte)0));
331 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX16((byte)11));
332 		assertEquals("{x:'1'}",x.getX17((byte)1));
333 		assertEquals("{x:'10'}",x.getX17((byte)10));
334 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX17((byte)0));
335 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX17((byte)11));
336 		assertEquals("{x:'2'}",x.getX18((byte)2));
337 		assertEquals("{x:'9'}",x.getX18((byte)9));
338 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX18((byte)1));
339 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX18((byte)10));
340 		assertEquals("{x:'1'}",x.getX19(new AtomicInteger(1)));
341 		assertEquals("{x:'10'}",x.getX19(new AtomicInteger(10)));
342 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX19(new AtomicInteger(0)));
343 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX19(new AtomicInteger(11)));
344 		assertEquals("{x:'1'}",x.getX20(new AtomicInteger(1)));
345 		assertEquals("{x:'10'}",x.getX20(new AtomicInteger(10)));
346 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX20(new AtomicInteger(0)));
347 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX20(new AtomicInteger(11)));
348 		assertEquals("{x:'2'}",x.getX21(new AtomicInteger(2)));
349 		assertEquals("{x:'9'}",x.getX21(new AtomicInteger(9)));
350 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX21(new AtomicInteger(1)));
351 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX21(new AtomicInteger(10)));
352 		assertEquals("{x:'1'}",x.getX22(new BigDecimal(1)));
353 		assertEquals("{x:'10'}",x.getX22(new BigDecimal(10)));
354 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX22(new BigDecimal(0)));
355 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX22(new BigDecimal(11)));
356 		assertEquals("{x:'1'}",x.getX23(new BigDecimal(1)));
357 		assertEquals("{x:'10'}",x.getX23(new BigDecimal(10)));
358 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX23(new BigDecimal(0)));
359 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX23(new BigDecimal(11)));
360 		assertEquals("{x:'2'}",x.getX24(new BigDecimal(2)));
361 		assertEquals("{x:'9'}",x.getX24(new BigDecimal(9)));
362 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX24(new BigDecimal(1)));
363 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX24(new BigDecimal(10)));
364 		assertEquals("{x:'1'}",x.getX25(1));
365 		assertEquals("{x:'10'}",x.getX25(10));
366 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX25(0));
367 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX25(11));
368 		assertEquals("{}",x.getX25(null));
369 		assertEquals("{x:'1'}",x.getX26(1));
370 		assertEquals("{x:'10'}",x.getX26(10));
371 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX26(0));
372 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX26(11));
373 		assertEquals("{}",x.getX26(null));
374 		assertEquals("{x:'2'}",x.getX27(2));
375 		assertEquals("{x:'9'}",x.getX27(9));
376 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX27(1));
377 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX27(10));
378 		assertEquals("{}",x.getX27(null));
379 		assertEquals("{x:'1'}",x.getX28((short)1));
380 		assertEquals("{x:'10'}",x.getX28((short)10));
381 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX28((short)0));
382 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX28((short)11));
383 		assertEquals("{}",x.getX28(null));
384 		assertEquals("{x:'1'}",x.getX29((short)1));
385 		assertEquals("{x:'10'}",x.getX29((short)10));
386 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX29((short)0));
387 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX29((short)11));
388 		assertEquals("{}",x.getX29(null));
389 		assertEquals("{x:'2'}",x.getX30((short)2));
390 		assertEquals("{x:'9'}",x.getX30((short)9));
391 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX30((short)1));
392 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX30((short)10));
393 		assertEquals("{}",x.getX30(null));
394 		assertEquals("{x:'1'}",x.getX31(1L));
395 		assertEquals("{x:'10'}",x.getX31(10L));
396 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX31(0L));
397 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX31(11L));
398 		assertEquals("{}",x.getX31(null));
399 		assertEquals("{x:'1'}",x.getX32(1L));
400 		assertEquals("{x:'10'}",x.getX32(10L));
401 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX32(0L));
402 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX32(11L));
403 		assertEquals("{}",x.getX32(null));
404 		assertEquals("{x:'2'}",x.getX33(2L));
405 		assertEquals("{x:'9'}",x.getX33(9L));
406 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX33(1L));
407 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX33(10L));
408 		assertEquals("{}",x.getX33(null));
409 		assertEquals("{x:'1.0'}",x.getX34(1f));
410 		assertEquals("{x:'10.0'}",x.getX34(10f));
411 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX34(0.9f));
412 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX34(10.1f));
413 		assertEquals("{}",x.getX34(null));
414 		assertEquals("{x:'1.0'}",x.getX35(1f));
415 		assertEquals("{x:'10.0'}",x.getX35(10f));
416 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX35(0.9f));
417 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX35(10.1f));
418 		assertEquals("{}",x.getX35(null));
419 		assertEquals("{x:'1.1'}",x.getX36(1.1f));
420 		assertEquals("{x:'9.9'}",x.getX36(9.9f));
421 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX36(1f));
422 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX36(10f));
423 		assertEquals("{}",x.getX36(null));
424 		assertEquals("{x:'1.0'}",x.getX37(1d));
425 		assertEquals("{x:'10.0'}",x.getX37(10d));
426 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX37(0.9d));
427 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX37(10.1d));
428 		assertEquals("{}",x.getX37(null));
429 		assertEquals("{x:'1.0'}",x.getX38(1d));
430 		assertEquals("{x:'10.0'}",x.getX38(10d));
431 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX38(0.9d));
432 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX38(10.1d));
433 		assertEquals("{}",x.getX38(null));
434 		assertEquals("{x:'1.1'}",x.getX39(1.1d));
435 		assertEquals("{x:'9.9'}",x.getX39(9.9d));
436 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX39(1d));
437 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX39(10d));
438 		assertEquals("{}",x.getX39(null));
439 		assertEquals("{x:'1'}",x.getX40((byte)1));
440 		assertEquals("{x:'10'}",x.getX40((byte)10));
441 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX40((byte)0));
442 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX40((byte)11));
443 		assertEquals("{}",x.getX40(null));
444 		assertEquals("{x:'1'}",x.getX41((byte)1));
445 		assertEquals("{x:'10'}",x.getX41((byte)10));
446 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX41((byte)0));
447 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX41((byte)11));
448 		assertEquals("{}",x.getX41(null));
449 		assertEquals("{x:'2'}",x.getX42((byte)2));
450 		assertEquals("{x:'9'}",x.getX42((byte)9));
451 		assertThrowsWithMessage(Exception.class, "Minimum value not met.", ()->x.getX42((byte)1));
452 		assertThrowsWithMessage(Exception.class, "Maximum value exceeded.", ()->x.getX42((byte)10));
453 		assertEquals("{}",x.getX42(null));
454 	}
455 
456 	//-----------------------------------------------------------------------------------------------------------------
457 	// @Query(maxItems,minItems,uniqueItems)
458 	//-----------------------------------------------------------------------------------------------------------------
459 
460 	@Rest
461 	public static class E {
462 		@RestOp
463 		public String get(@Query("*") JsonMap m) {
464 			return m.toString();
465 		}
466 	}
467 
468 	@Remote
469 	public interface E1 {
470 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(cf="pipes",mini=1,maxi=2) String...b);
471 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(items=@Items(cf="pipes",mini=1,maxi=2)) String[]...b);
472 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(cf="pipes",ui=false) String...b);
473 		@RemoteOp(path="/") String getX4(@Query("x") @Schema(items=@Items(cf="pipes",ui=false)) String[]...b);
474 		@RemoteOp(path="/") String getX5(@Query("x") @Schema(cf="pipes",ui=true) String...b);
475 		@RemoteOp(path="/") String getX6(@Query("x") @Schema(items=@Items(cf="pipes",ui=true)) String[]...b);
476 	}
477 
478 	@Test void e01_mini_maxi_ui() {
479 		var x = remote(E.class,E1.class);
480 		assertEquals("{x:'1'}",x.getX1("1"));
481 		assertEquals("{x:'1|2'}",x.getX1("1","2"));
482 		assertThrowsWithMessage(Exception.class, "Minimum number of items not met.", x::getX1);
483 		assertThrowsWithMessage(Exception.class, "Maximum number of items exceeded.", ()->x.getX1("1","2","3"));
484 		assertEquals("{x:null}",x.getX1((String)null));
485 		assertEquals("{x:'1'}",x.getX2(new String[]{"1"}));
486 		assertEquals("{x:'1|2'}",x.getX2(new String[]{"1","2"}));
487 		assertThrowsWithMessage(Exception.class, "Minimum number of items not met.", ()->x.getX2(new String[]{}));
488 		assertThrowsWithMessage(Exception.class, "Maximum number of items exceeded.", ()->x.getX2(new String[]{"1","2","3"}));
489 		assertEquals("{x:null}",x.getX2(new String[]{null}));
490 		assertEquals("{x:'1|1'}",x.getX3("1","1"));
491 		assertEquals("{x:'1|1'}",x.getX4(new String[]{"1","1"}));
492 		assertEquals("{x:'1|2'}",x.getX5("1","2"));
493 		assertThrowsWithMessage(Exception.class, "Duplicate items not allowed.", ()->x.getX5("1","1"));
494 		assertEquals("{x:'1|2'}",x.getX6(new String[]{"1","2"}));
495 		assertThrowsWithMessage(Exception.class, "Duplicate items not allowed.", ()->x.getX6(new String[]{"1","1"}));
496 	}
497 
498 	//-----------------------------------------------------------------------------------------------------------------
499 	// @Query(maxLength,minLength,enum)
500 	//-----------------------------------------------------------------------------------------------------------------
501 
502 	@Rest
503 	public static class F {
504 		@RestOp
505 		public String get(@Query("*") JsonMap m) {
506 			return m.toString();
507 		}
508 	}
509 
510 	@Remote
511 	public interface F1 {
512 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(minl=2,maxl=3) String b);
513 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(cf="pipes",items=@Items(minl=2,maxl=3)) String...b);
514 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(e={"foo"}) String b);
515 		@RemoteOp(path="/") String getX4(@Query("x") @Schema(cf="pipes",items=@Items(e={"foo"})) String...b);
516 		@RemoteOp(path="/") String getX5(@Query("x") @Schema(p="foo\\d{1,3}") String b);
517 		@RemoteOp(path="/") String getX6(@Query("x") @Schema(cf="pipes",items=@Items(p="foo\\d{1,3}")) String...b);
518 	}
519 
520 	@Test void f01_minl_maxl_enum() {
521 		var x = remote(F.class,F1.class);
522 		assertEquals("{x:'12'}",x.getX1("12"));
523 		assertEquals("{x:'123'}",x.getX1("123"));
524 		assertThrowsWithMessage(Exception.class, "Minimum length of value not met.", ()->x.getX1("1"));
525 		assertThrowsWithMessage(Exception.class, "Maximum length of value exceeded.", ()->x.getX1("1234"));
526 		assertEquals("{}",x.getX1(null));
527 		assertEquals("{x:'12|34'}",x.getX2("12","34"));
528 		assertEquals("{x:'123|456'}",x.getX2("123","456"));
529 		assertThrowsWithMessage(Exception.class, "Minimum length of value not met.", ()->x.getX2("1","2"));
530 		assertThrowsWithMessage(Exception.class, "Maximum length of value exceeded.", ()->x.getX2("1234","5678"));
531 		assertEquals("{x:'12|null'}",x.getX2("12",null));
532 		assertEquals("{x:'foo'}",x.getX3("foo"));
533 		assertThrowsWithMessage(Exception.class, "Value does not match one of the expected values.  Must be one of the following:  foo", ()->x.getX3("bar"));
534 		assertEquals("{}",x.getX3(null));
535 		assertEquals("{x:'foo'}",x.getX4("foo"));
536 		assertThrowsWithMessage(Exception.class, "Value does not match one of the expected values.  Must be one of the following:  foo", ()->x.getX4("bar"));
537 		assertEquals("{x:null}",x.getX4((String)null));
538 		assertEquals("{x:'foo123'}",x.getX5("foo123"));
539 		assertThrowsWithMessage(Exception.class, "Value does not match expected pattern.  Must match pattern: foo\\d{1,3}", ()->x.getX5("bar"));
540 		assertEquals("{}",x.getX5(null));
541 		assertEquals("{x:'foo123'}",x.getX6("foo123"));
542 		assertThrowsWithMessage(Exception.class, "Value does not match expected pattern.  Must match pattern: foo\\d{1,3}", ()->x.getX6("foo"));
543 		assertEquals("{x:null}",x.getX6((String)null));
544 	}
545 
546 	//-----------------------------------------------------------------------------------------------------------------
547 	// @Query(multipleOf)
548 	//-----------------------------------------------------------------------------------------------------------------
549 
550 	@Rest
551 	public static class G {
552 		@RestOp
553 		public String get(@Query("*") JsonMap m) {
554 			return m.toString();
555 		}
556 	}
557 
558 	@Remote
559 	public interface G1 {
560 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(mo="2") int b);
561 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(mo="2") short b);
562 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(mo="2") long b);
563 		@RemoteOp(path="/") String getX4(@Query("x") @Schema(mo="2") float b);
564 		@RemoteOp(path="/") String getX5(@Query("x") @Schema(mo="2") double b);
565 		@RemoteOp(path="/") String getX6(@Query("x") @Schema(mo="2") byte b);
566 		@RemoteOp(path="/") String getX7(@Query("x") @Schema(mo="2") AtomicInteger b);
567 		@RemoteOp(path="/") String getX8(@Query("x") @Schema(mo="2") BigDecimal b);
568 		@RemoteOp(path="/") String getX9(@Query("x") @Schema(mo="2") Integer b);
569 		@RemoteOp(path="/") String getX10(@Query("x") @Schema(mo="2") Short b);
570 		@RemoteOp(path="/") String getX11(@Query("x") @Schema(mo="2") Long b);
571 		@RemoteOp(path="/") String getX12(@Query("x") @Schema(mo="2") Float b);
572 		@RemoteOp(path="/") String getX13(@Query("x") @Schema(mo="2") Double b);
573 		@RemoteOp(path="/") String getX14(@Query("x") @Schema(mo="2") Byte b);
574 	}
575 
576 	@Test void g01_multipleOf() {
577 		var x = remote(G.class,G1.class);
578 		assertEquals("{x:'4'}",x.getX1(4));
579 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX1(5));
580 		assertEquals("{x:'4'}",x.getX2((short)4));
581 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX2((short)5));
582 		assertEquals("{x:'4'}",x.getX3(4));
583 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX3(5));
584 		assertEquals("{x:'4.0'}",x.getX4(4));
585 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX4(5));
586 		assertEquals("{x:'4.0'}",x.getX5(4));
587 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX5(5));
588 		assertEquals("{x:'4'}",x.getX6((byte)4));
589 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX6((byte)5));
590 		assertEquals("{x:'4'}",x.getX7(new AtomicInteger(4)));
591 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX7(new AtomicInteger(5)));
592 		assertEquals("{x:'4'}",x.getX8(new BigDecimal(4)));
593 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX8(new BigDecimal(5)));
594 		assertEquals("{x:'4'}",x.getX9(4));
595 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX9(5));
596 		assertEquals("{x:'4'}",x.getX10((short)4));
597 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX10((short)5));
598 		assertEquals("{x:'4'}",x.getX11(4L));
599 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX11(5L));
600 		assertEquals("{x:'4.0'}",x.getX12(4f));
601 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX12(5f));
602 		assertEquals("{x:'4.0'}",x.getX13(4d));
603 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX13(5d));
604 		assertEquals("{x:'4'}",x.getX14((byte)4));
605 		assertThrowsWithMessage(Exception.class, "Multiple-of not met.", ()->x.getX14((byte)5));
606 	}
607 
608 	//-----------------------------------------------------------------------------------------------------------------
609 	// @Query(required)
610 	//-----------------------------------------------------------------------------------------------------------------
611 
612 	@Rest
613 	public static class H {
614 		@RestOp
615 		public String get(@Query("*") JsonMap m) {
616 			return m.toString();
617 		}
618 	}
619 
620 	@Remote
621 	public interface H1 {
622 		@RemoteOp(path="/") String getX1(@Query("x") String b);
623 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(r=false) String b);
624 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(r=true) String b);
625 	}
626 
627 	@Test void h01_required() {
628 		var x = remote(H.class,H1.class);
629 		assertEquals("{}",x.getX1(null));
630 		assertEquals("{}",x.getX2(null));
631 		assertEquals("{x:'1'}",x.getX3("1"));
632 		assertThrowsWithMessage(Exception.class, "Required value not provided.", ()->x.getX3(null));
633 	}
634 
635 	//-----------------------------------------------------------------------------------------------------------------
636 	// @Query(skipIfEmpty)
637 	//-----------------------------------------------------------------------------------------------------------------
638 
639 	@Rest
640 	public static class I {
641 		@RestOp
642 		public String get(@Query("*") JsonMap m) {
643 			return m.toString();
644 		}
645 	}
646 
647 	@Remote
648 	public interface I1 {
649 		@RemoteOp(path="/") String getX1(@Query("x") @Schema(aev=true) String b);
650 		@RemoteOp(path="/") String getX2(@Query("x") @Schema(aev=true,sie=false) String b);
651 		@RemoteOp(path="/") String getX3(@Query("x") @Schema(sie=true) String b);
652 	}
653 
654 	@Test void i01_skipIfEmpty() {
655 		var x = remote(I.class,I1.class);
656 		assertEquals("{x:''}",x.getX1(""));
657 		assertEquals("{x:''}",x.getX2(""));
658 		assertEquals("{}",x.getX3(""));
659 	}
660 
661 	//-----------------------------------------------------------------------------------------------------------------
662 	// @Query(serializer)
663 	//-----------------------------------------------------------------------------------------------------------------
664 
665 	@Rest
666 	public static class J {
667 		@RestOp
668 		public String get(@Query("*") JsonMap m) {
669 			return m.toString();
670 		}
671 	}
672 
673 	@Remote
674 	public interface J1 {
675 		@RemoteOp(path="/") String getX1(@Query(name="x",serializer=FakeWriterSerializer.X.class) String b);
676 	}
677 
678 	@Test void j01_serializer() {
679 		var x = remote(J.class,J1.class);
680 		assertEquals("{x:'xXx'}",x.getX1("X"));
681 	}
682 
683 	//-----------------------------------------------------------------------------------------------------------------
684 	// RequestBean @Query - Return types
685 	//-----------------------------------------------------------------------------------------------------------------
686 
687 	@Rest
688 	public static class K {
689 		@RestOp
690 		public String get(RestRequest req) {
691 			return req.getQueryParams().toString();
692 		}
693 	}
694 
695 	//-----------------------------------------------------------------------------------------------------------------
696 	// RequestBean @Query - Simple values
697 	//-----------------------------------------------------------------------------------------------------------------
698 
699 	@Remote(path="/")
700 	public interface K1 {
701 		@RemoteOp(path="/") String getX1(@Request K1b rb);
702 		@RemoteOp(path="/") String getX2(@Request(serializer=FakeWriterSerializer.X.class) K1b rb);
703 	}
704 
705 	public interface K1a {
706 		@Query String getA();
707 		@Query("b") String getX1();
708 		@Query("c") String getX2();
709 		@Query("e") @Schema(allowEmptyValue=true) String getX4();
710 		@Query("f") String getX5();
711 		@Query("g") String getX6();
712 		@Query("h") String getX7();
713 		@Query("i1") @Schema(sie=true) String getX8();
714 		@Query("i2") @Schema(sie=true) String getX9();
715 		@Query("i3") @Schema(sie=true) String getX10();
716 	}
717 
718 	public static class K1b implements K1a {
719 		@Override public String getA() { return "a1"; }
720 		@Override public String getX1() { return "b1"; }
721 		@Override public String getX2() { return "c1"; }
722 		@Override public String getX4() { return ""; }
723 		@Override public String getX5() { return null; }
724 		@Override public String getX6() { return "true"; }
725 		@Override public String getX7() { return "123"; }
726 		@Override public String getX8() { return "foo"; }
727 		@Override public String getX9() { return ""; }
728 		@Override public String getX10() { return null; }
729 	}
730 
731 	@Test void k01_requestBean_simpleVals() {
732 		var x1 = remote(K.class,K1.class);
733 		var x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K1.class);
734 		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'true',h:'123',i1:'foo'}",x1.getX1(new K1b()));
735 		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'\\'true\\'',h:'\\'123\\'',i1:'foo'}",x2.getX1(new K1b()));
736 		assertEquals("{a:'xa1x',b:'xb1x',c:'xc1x',e:'xx',g:'xtruex',h:'x123x',i1:'xfoox'}",x2.getX2(new K1b()));
737 	}
738 
739 	//-----------------------------------------------------------------------------------------------------------------
740 	// RequestBean @Query - Maps
741 	//-----------------------------------------------------------------------------------------------------------------
742 
743 	@Remote(path="/")
744 	public interface K2 {
745 		@RemoteOp(path="/") String getX1(@Request K2a rb);
746 		@RemoteOp(path="/") String getX2(@Request(serializer=FakeWriterSerializer.X.class) K2a rb);
747 	}
748 
749 	public static class K2a {
750 		@Query public Map<String,Object> getA() { return CollectionUtils.mapBuilder(String.class,Object.class).add("a1","v1").add("a2",123).add("a3",null).add("a4","").build(); }
751 		@Query("*") public Map<String,Object> getB() { return map("b1","true","b2","123","b3","null"); }
752 		@Query("*") @Schema(allowEmptyValue=true) public Map<String,Object> getC() { return CollectionUtils.mapBuilder(String.class,Object.class).add("c1","v1").add("c2",123).add("c3",null).add("c4","").build(); }
753 		@Query("*")
754 		public Map<String,Object> getD() { return null; }
755 	}
756 
757 	@Test void k02_requestBean_maps() {
758 		var x1 = remote(K.class,K2.class);
759 		var x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K2.class);
760 		assertEquals("{a:'a1=v1,a2=123,a3=null,a4=',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",x1.getX1(new K2a()));
761 		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",x2.getX1(new K2a()));
762 		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.getX2(new K2a()));
763 	}
764 
765 	//-----------------------------------------------------------------------------------------------------------------
766 	// RequestBean @Query - NameValuePairs
767 	//-----------------------------------------------------------------------------------------------------------------
768 
769 	@Remote(path="/")
770 	public interface K3 {
771 		@RemoteOp(path="/") String getX1(@Request K3a rb);
772 		@RemoteOp(path="/") String getX2(@Request(serializer=FakeWriterSerializer.X.class) K3a rb);
773 	}
774 
775 	public static class K3a {
776 		@Query() @Schema(aev=true) public PartList getA() { return parts("a1","v1","a2","123","a3",null,"a4",""); }
777 		@Query("*") public PartList getB() { return parts("b1","true","b2","123","b3","null"); }
778 		@Query("*") @Schema(aev=true) public PartList getC() { return parts("c1","v1","c2","123","c3",null,"c4",""); }
779 		@Query("*")
780 		public PartList getD() { return null; }
781 		@Query() @Schema(aev=true) public NameValuePair[] getE() { return parts("e1","v1","e2","123","e3",null,"e4","").getAll(); }
782 	}
783 
784 	@Test void k03_requestBean_nameValuePairs() {
785 		var x1 = remote(K.class,K3.class);
786 		var x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K3.class);
787 		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x1.getX1(new K3a()));
788 		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:'',e1:'v1',e2:'123',e4:''}",x2.getX1(new K3a()));
789 	}
790 
791 	//-----------------------------------------------------------------------------------------------------------------
792 	// RequestBean @Query - CharSequence
793 	//-----------------------------------------------------------------------------------------------------------------
794 
795 	@Remote(path="/")
796 	public interface K4 {
797 		String get(@Request K4a rb);
798 	}
799 
800 	public static class K4a {
801 		@Query("*") public StringBuilder getA() { return new StringBuilder("foo=bar&baz=qux"); }
802 	}
803 
804 	@Test void k04_requestBean_charSequence() {
805 		var x = remote(K.class,K4.class);
806 		assertEquals("{foo:'bar',baz:'qux'}",x.get(new K4a()));
807 	}
808 
809 	//-----------------------------------------------------------------------------------------------------------------
810 	// RequestBean @Query - Reader
811 	//-----------------------------------------------------------------------------------------------------------------
812 
813 	@Remote(path="/")
814 	public interface K5 {
815 		String get(@Request K5a rb);
816 	}
817 
818 	public static class K5a {
819 		@Query("*") public Reader getA() { return reader("foo=bar&baz=qux"); }
820 	}
821 
822 	@Test void k05_requestBean_reader() {
823 		var x = remote(K.class,K5.class);
824 		assertEquals("{foo:'bar',baz:'qux'}",x.get(new K5a()));
825 	}
826 
827 	//-----------------------------------------------------------------------------------------------------------------
828 	// RequestBean @Query - Collections
829 	//-----------------------------------------------------------------------------------------------------------------
830 
831 	@Remote(path="/")
832 	public interface K6 {
833 		@RemoteOp(path="/") String getX1(@Request K6a rb);
834 		@RemoteOp(path="/") String getX2(@Request(serializer=FakeWriterSerializer.X.class) K6a rb);
835 	}
836 
837 	public static class K6a {
838 		@Query public List<Object> getA() { return alist("foo","","true","123","null",true,123,null); }
839 		@Query("b") public List<Object> getX1() { return alist("foo","","true","123","null",true,123,null); }
840 		@Query(name="c",serializer=FakeWriterSerializer.X.class) public List<Object> getX2() { return alist("foo","","true","123","null",true,123,null); }
841 		@Query("d") @Schema(allowEmptyValue=true) public List<Object> getX3() { return alist(); }
842 		@Query("e") public List<Object> getX4() { return null; }
843 		@Query("f") public Object[] getX5() { return new Object[]{"foo","","true","123","null",true,123,null}; }
844 		@Query(name="g",serializer=FakeWriterSerializer.X.class) public Object[] getX6() { return new Object[]{"foo","","true","123","null",true,123,null}; }
845 		@Query("h") @Schema(allowEmptyValue=true) public Object[] getX7() { return new Object[]{}; }
846 		@Query("i") public Object[] getX8() { return null; }
847 	}
848 
849 	@Test void k06_requestBean_collections() {
850 		var x1 = remote(K.class,K6.class);
851 		var x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K6.class);
852 		assertEquals("{a:'foo,,true,123,null,true,123,null',b:'foo,,true,123,null,true,123,null',c:'xfoo||true|123|null|true|123|nullx',d:'',f:'foo,,true,123,null,true,123,null',g:'xfoo||true|123|null|true|123|nullx',h:''}", x1.getX1(new K6a()));
853 		assertEquals("{a:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',b:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',c:'xfoo||true|123|null|true|123|nullx',d:'@()',f:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',g:'xfoo||true|123|null|true|123|nullx',h:'@()'}", x2.getX1(new K6a()));
854 		assertEquals("{a:'xfoo||true|123|null|true|123|nullx',b:'xfoo||true|123|null|true|123|nullx',c:'xfoo||true|123|null|true|123|nullx',d:'xx',f:'xfoo||true|123|null|true|123|nullx',g:'xfoo||true|123|null|true|123|nullx',h:'xx'}", x2.getX2(new K6a()));
855 	}
856 
857 	//------------------------------------------------------------------------------------------------------------------
858 	// Helper methods.
859 	//------------------------------------------------------------------------------------------------------------------
860 
861 	private static PartList parts(String...pairs) {
862 		return partList(pairs);
863 	}
864 
865 	private static NameValuePair part(String key,Object val) {
866 		return basicPart(key,val);
867 	}
868 
869 	private static RestClient.Builder client(Class<?> c) {
870 		return MockRestClient.create(c);
871 	}
872 
873 	private static <T> T remote(Class<?> rest,Class<T> t) {
874 		return MockRestClient.build(rest).getRemote(t);
875 	}
876 }