|
17 | 17 | import io.swagger.v3.oas.models.info.Contact; |
18 | 18 | import io.swagger.v3.oas.models.info.Info; |
19 | 19 | import io.swagger.v3.oas.models.media.ArraySchema; |
| 20 | +import io.swagger.v3.oas.models.media.ByteArraySchema; |
20 | 21 | import io.swagger.v3.oas.models.media.ComposedSchema; |
21 | 22 | import io.swagger.v3.oas.models.media.Content; |
| 23 | +import io.swagger.v3.oas.models.media.DateSchema; |
| 24 | +import io.swagger.v3.oas.models.media.DateTimeSchema; |
22 | 25 | import io.swagger.v3.oas.models.media.Discriminator; |
23 | 26 | import io.swagger.v3.oas.models.media.Encoding; |
24 | 27 | import io.swagger.v3.oas.models.media.MediaType; |
|
52 | 55 | import java.net.URI; |
53 | 56 | import java.net.URISyntaxException; |
54 | 57 | import java.net.URL; |
| 58 | +import java.text.ParseException; |
55 | 59 | import java.util.*; |
| 60 | +import java.util.regex.Matcher; |
56 | 61 | import java.util.regex.Pattern; |
57 | 62 | import java.util.stream.Collectors; |
58 | 63 | import java.util.stream.Stream; |
| 64 | +import static java.util.Calendar.*; |
59 | 65 |
|
60 | 66 |
|
61 | 67 | public class OpenAPIDeserializer { |
@@ -90,6 +96,9 @@ public class OpenAPIDeserializer { |
90 | 96 | private static final String COOKIE_PARAMETER = "cookie"; |
91 | 97 | private static final String PATH_PARAMETER = "path"; |
92 | 98 | private static final String HEADER_PARAMETER = "header"; |
| 99 | + private static final Pattern RFC3339_DATE_TIME_PATTERN = Pattern.compile( "^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?((Z)|([+-]\\d{2}:\\d{2}))$"); |
| 100 | + private static final Pattern RFC3339_DATE_PATTERN = Pattern.compile( "^(\\d{4})-(\\d{2})-(\\d{2})$"); |
| 101 | + |
93 | 102 | private Components components; |
94 | 103 | private final Set<String> operationIDs = new HashSet<>(); |
95 | 104 |
|
@@ -2185,8 +2194,13 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2185 | 2194 | for (JsonNode n : enumArray) { |
2186 | 2195 | if (n.isNumber()) { |
2187 | 2196 | schema.addEnumItemObject(n.numberValue()); |
2188 | | - }else if (n.isValueNode()) { |
2189 | | - schema.addEnumItemObject(n.asText()); |
| 2197 | + } else if (n.isValueNode()) { |
| 2198 | + try { |
| 2199 | + schema.addEnumItemObject( getDecodedObject( schema, n.asText(null))); |
| 2200 | + } |
| 2201 | + catch( ParseException e) { |
| 2202 | + result.invalidType( location, String.format( "enum=`%s`", e.getMessage()), schema.getFormat(), n); |
| 2203 | + } |
2190 | 2204 | } else { |
2191 | 2205 | result.invalidType(location, "enum", "value", n); |
2192 | 2206 | } |
@@ -2275,7 +2289,12 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2275 | 2289 | }else if(schema.getType().equals("string")) { |
2276 | 2290 | value = getString("default", node, false, location, result); |
2277 | 2291 | if (value != null) { |
2278 | | - schema.setDefault(value); |
| 2292 | + try { |
| 2293 | + schema.setDefault( getDecodedObject( schema, value)); |
| 2294 | + } |
| 2295 | + catch( ParseException e) { |
| 2296 | + result.invalidType( location, String.format( "default=`%s`", e.getMessage()), schema.getFormat(), node); |
| 2297 | + } |
2279 | 2298 | } |
2280 | 2299 | }else if(schema.getType().equals("boolean")) { |
2281 | 2300 | bool = getBoolean("default", node, false, location, result); |
@@ -2359,6 +2378,121 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2359 | 2378 | } |
2360 | 2379 |
|
2361 | 2380 |
|
| 2381 | + /** |
| 2382 | + * Decodes the given string and returns an object applicable to the given schema. |
| 2383 | + * Throws a ParseException if no applicable object can be recognized. |
| 2384 | + */ |
| 2385 | + private Object getDecodedObject( Schema schema, String objectString) throws ParseException { |
| 2386 | + Object object = |
| 2387 | + objectString == null? |
| 2388 | + null : |
| 2389 | + |
| 2390 | + schema.getClass().equals( DateSchema.class)? |
| 2391 | + toDate( objectString) : |
| 2392 | + |
| 2393 | + schema.getClass().equals( DateTimeSchema.class)? |
| 2394 | + toDateTime( objectString) : |
| 2395 | + |
| 2396 | + schema.getClass().equals( ByteArraySchema.class)? |
| 2397 | + toBytes( objectString) : |
| 2398 | + |
| 2399 | + objectString; |
| 2400 | + |
| 2401 | + if( object == null && objectString != null) { |
| 2402 | + throw new ParseException( objectString, 0); |
| 2403 | + } |
| 2404 | + |
| 2405 | + return object; |
| 2406 | + } |
| 2407 | + |
| 2408 | + |
| 2409 | + /** |
| 2410 | + * Returns the Date represented by the given RFC3339 date-time string. |
| 2411 | + * Returns null if this string can't be parsed as Date. |
| 2412 | + */ |
| 2413 | + private Date toDateTime( String dateString) { |
| 2414 | + // Note: For this conversion, regex matching is better than SimpleDateFormat, etc. |
| 2415 | + // Optional elements (e.g. milliseconds) are not directly handled by SimpleDateFormat. |
| 2416 | + // Also, SimpleDateFormat is not thread-safe. |
| 2417 | + Matcher matcher = RFC3339_DATE_TIME_PATTERN.matcher( dateString); |
| 2418 | + |
| 2419 | + Date dateTime = null; |
| 2420 | + if( matcher.matches()) { |
| 2421 | + try { |
| 2422 | + String year = matcher.group(1); |
| 2423 | + String month = matcher.group(2); |
| 2424 | + String day = matcher.group(3); |
| 2425 | + String hour = matcher.group(4); |
| 2426 | + String min = matcher.group(5); |
| 2427 | + String sec = matcher.group(6); |
| 2428 | + String ms = matcher.group(7); |
| 2429 | + String zone = matcher.group(10); |
| 2430 | + |
| 2431 | + Calendar calendar = Calendar.getInstance( TimeZone.getTimeZone( zone == null? "GMT" : "GMT" + zone)); |
| 2432 | + calendar.set( YEAR, Integer.parseInt( year)); |
| 2433 | + calendar.set( MONTH, Integer.parseInt( month) - 1); |
| 2434 | + calendar.set( DAY_OF_MONTH, Integer.parseInt( day)); |
| 2435 | + calendar.set( HOUR_OF_DAY, Integer.parseInt( hour)); |
| 2436 | + calendar.set( MINUTE, Integer.parseInt( min)); |
| 2437 | + calendar.set( SECOND, Integer.parseInt( sec)); |
| 2438 | + calendar.set( MILLISECOND, ms == null? 0 : (int) (Double.parseDouble( ms) * 1000)); |
| 2439 | + |
| 2440 | + dateTime = calendar.getTime(); |
| 2441 | + } |
| 2442 | + catch( Exception ignore) { |
| 2443 | + } |
| 2444 | + } |
| 2445 | + |
| 2446 | + return dateTime; |
| 2447 | + } |
| 2448 | + |
| 2449 | + |
| 2450 | + /** |
| 2451 | + * Returns the Date represented by the given RFC3339 full-date string. |
| 2452 | + * Returns null if this string can't be parsed as Date. |
| 2453 | + */ |
| 2454 | + private Date toDate( String dateString) { |
| 2455 | + Matcher matcher = RFC3339_DATE_PATTERN.matcher( dateString); |
| 2456 | + |
| 2457 | + Date date = null; |
| 2458 | + if( matcher.matches()) { |
| 2459 | + String year = matcher.group(1); |
| 2460 | + String month = matcher.group(2); |
| 2461 | + String day = matcher.group(3); |
| 2462 | + |
| 2463 | + try { |
| 2464 | + date= |
| 2465 | + new Calendar.Builder() |
| 2466 | + .setDate( Integer.parseInt( year), Integer.parseInt( month) - 1, Integer.parseInt( day)) |
| 2467 | + .build() |
| 2468 | + .getTime(); |
| 2469 | + } |
| 2470 | + catch( Exception ignore) { |
| 2471 | + } |
| 2472 | + } |
| 2473 | + |
| 2474 | + return date; |
| 2475 | + } |
| 2476 | + |
| 2477 | + |
| 2478 | + /** |
| 2479 | + * Returns the byte array represented by the given base64-encoded string. |
| 2480 | + * Returns null if this string is not a valid base64 encoding. |
| 2481 | + */ |
| 2482 | + private byte[] toBytes( String byteString) { |
| 2483 | + byte[] bytes; |
| 2484 | + |
| 2485 | + try { |
| 2486 | + bytes = Base64.getDecoder().decode( byteString); |
| 2487 | + } |
| 2488 | + catch( Exception e) { |
| 2489 | + bytes = null; |
| 2490 | + } |
| 2491 | + |
| 2492 | + return bytes; |
| 2493 | + } |
| 2494 | + |
| 2495 | + |
2362 | 2496 |
|
2363 | 2497 |
|
2364 | 2498 | public Map<String, Example> getExamples(ObjectNode obj, String location, ParseResult result) { |
|
0 commit comments