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