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