|
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; |
|
56 | 59 | import java.net.URI; |
57 | 60 | import java.net.URISyntaxException; |
58 | 61 | import java.net.URL; |
| 62 | +import java.text.ParseException; |
59 | 63 | import java.util.*; |
| 64 | +import java.util.regex.Matcher; |
60 | 65 | import java.util.regex.Pattern; |
61 | 66 | import java.util.stream.Collectors; |
62 | 67 | import java.util.stream.Stream; |
| 68 | +import static java.util.Calendar.*; |
63 | 69 |
|
64 | 70 |
|
65 | 71 | public class OpenAPIDeserializer { |
@@ -94,6 +100,9 @@ public class OpenAPIDeserializer { |
94 | 100 | private static final String COOKIE_PARAMETER = "cookie"; |
95 | 101 | private static final String PATH_PARAMETER = "path"; |
96 | 102 | private static final String HEADER_PARAMETER = "header"; |
| 103 | + 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}))$"); |
| 104 | + private static final Pattern RFC3339_DATE_PATTERN = Pattern.compile( "^(\\d{4})-(\\d{2})-(\\d{2})$"); |
| 105 | + |
97 | 106 | private Components components; |
98 | 107 | private final Set<String> operationIDs = new HashSet<>(); |
99 | 108 |
|
@@ -2201,8 +2210,13 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2201 | 2210 | for (JsonNode n : enumArray) { |
2202 | 2211 | if (n.isNumber()) { |
2203 | 2212 | schema.addEnumItemObject(n.numberValue()); |
2204 | | - }else if (n.isValueNode()) { |
2205 | | - schema.addEnumItemObject(n.asText()); |
| 2213 | + } else if (n.isValueNode()) { |
| 2214 | + try { |
| 2215 | + schema.addEnumItemObject( getDecodedObject( schema, n.asText(null))); |
| 2216 | + } |
| 2217 | + catch( ParseException e) { |
| 2218 | + result.invalidType( location, String.format( "enum=`%s`", e.getMessage()), schema.getFormat(), n); |
| 2219 | + } |
2206 | 2220 | } else { |
2207 | 2221 | result.invalidType(location, "enum", "value", n); |
2208 | 2222 | } |
@@ -2291,7 +2305,12 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2291 | 2305 | }else if(schema.getType().equals("string")) { |
2292 | 2306 | value = getString("default", node, false, location, result); |
2293 | 2307 | if (value != null) { |
2294 | | - schema.setDefault(value); |
| 2308 | + try { |
| 2309 | + schema.setDefault( getDecodedObject( schema, value)); |
| 2310 | + } |
| 2311 | + catch( ParseException e) { |
| 2312 | + result.invalidType( location, String.format( "default=`%s`", e.getMessage()), schema.getFormat(), node); |
| 2313 | + } |
2295 | 2314 | } |
2296 | 2315 | }else if(schema.getType().equals("boolean")) { |
2297 | 2316 | bool = getBoolean("default", node, false, location, result); |
@@ -2375,6 +2394,121 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2375 | 2394 | } |
2376 | 2395 |
|
2377 | 2396 |
|
| 2397 | + /** |
| 2398 | + * Decodes the given string and returns an object applicable to the given schema. |
| 2399 | + * Throws a ParseException if no applicable object can be recognized. |
| 2400 | + */ |
| 2401 | + private Object getDecodedObject( Schema schema, String objectString) throws ParseException { |
| 2402 | + Object object = |
| 2403 | + objectString == null? |
| 2404 | + null : |
| 2405 | + |
| 2406 | + schema.getClass().equals( DateSchema.class)? |
| 2407 | + toDate( objectString) : |
| 2408 | + |
| 2409 | + schema.getClass().equals( DateTimeSchema.class)? |
| 2410 | + toDateTime( objectString) : |
| 2411 | + |
| 2412 | + schema.getClass().equals( ByteArraySchema.class)? |
| 2413 | + toBytes( objectString) : |
| 2414 | + |
| 2415 | + objectString; |
| 2416 | + |
| 2417 | + if( object == null && objectString != null) { |
| 2418 | + throw new ParseException( objectString, 0); |
| 2419 | + } |
| 2420 | + |
| 2421 | + return object; |
| 2422 | + } |
| 2423 | + |
| 2424 | + |
| 2425 | + /** |
| 2426 | + * Returns the Date represented by the given RFC3339 date-time string. |
| 2427 | + * Returns null if this string can't be parsed as Date. |
| 2428 | + */ |
| 2429 | + private Date toDateTime( String dateString) { |
| 2430 | + // Note: For this conversion, regex matching is better than SimpleDateFormat, etc. |
| 2431 | + // Optional elements (e.g. milliseconds) are not directly handled by SimpleDateFormat. |
| 2432 | + // Also, SimpleDateFormat is not thread-safe. |
| 2433 | + Matcher matcher = RFC3339_DATE_TIME_PATTERN.matcher( dateString); |
| 2434 | + |
| 2435 | + Date dateTime = null; |
| 2436 | + if( matcher.matches()) { |
| 2437 | + try { |
| 2438 | + String year = matcher.group(1); |
| 2439 | + String month = matcher.group(2); |
| 2440 | + String day = matcher.group(3); |
| 2441 | + String hour = matcher.group(4); |
| 2442 | + String min = matcher.group(5); |
| 2443 | + String sec = matcher.group(6); |
| 2444 | + String ms = matcher.group(7); |
| 2445 | + String zone = matcher.group(10); |
| 2446 | + |
| 2447 | + Calendar calendar = Calendar.getInstance( TimeZone.getTimeZone( zone == null? "GMT" : "GMT" + zone)); |
| 2448 | + calendar.set( YEAR, Integer.parseInt( year)); |
| 2449 | + calendar.set( MONTH, Integer.parseInt( month) - 1); |
| 2450 | + calendar.set( DAY_OF_MONTH, Integer.parseInt( day)); |
| 2451 | + calendar.set( HOUR_OF_DAY, Integer.parseInt( hour)); |
| 2452 | + calendar.set( MINUTE, Integer.parseInt( min)); |
| 2453 | + calendar.set( SECOND, Integer.parseInt( sec)); |
| 2454 | + calendar.set( MILLISECOND, ms == null? 0 : (int) (Double.parseDouble( ms) * 1000)); |
| 2455 | + |
| 2456 | + dateTime = calendar.getTime(); |
| 2457 | + } |
| 2458 | + catch( Exception ignore) { |
| 2459 | + } |
| 2460 | + } |
| 2461 | + |
| 2462 | + return dateTime; |
| 2463 | + } |
| 2464 | + |
| 2465 | + |
| 2466 | + /** |
| 2467 | + * Returns the Date represented by the given RFC3339 full-date string. |
| 2468 | + * Returns null if this string can't be parsed as Date. |
| 2469 | + */ |
| 2470 | + private Date toDate( String dateString) { |
| 2471 | + Matcher matcher = RFC3339_DATE_PATTERN.matcher( dateString); |
| 2472 | + |
| 2473 | + Date date = null; |
| 2474 | + if( matcher.matches()) { |
| 2475 | + String year = matcher.group(1); |
| 2476 | + String month = matcher.group(2); |
| 2477 | + String day = matcher.group(3); |
| 2478 | + |
| 2479 | + try { |
| 2480 | + date= |
| 2481 | + new Calendar.Builder() |
| 2482 | + .setDate( Integer.parseInt( year), Integer.parseInt( month) - 1, Integer.parseInt( day)) |
| 2483 | + .build() |
| 2484 | + .getTime(); |
| 2485 | + } |
| 2486 | + catch( Exception ignore) { |
| 2487 | + } |
| 2488 | + } |
| 2489 | + |
| 2490 | + return date; |
| 2491 | + } |
| 2492 | + |
| 2493 | + |
| 2494 | + /** |
| 2495 | + * Returns the byte array represented by the given base64-encoded string. |
| 2496 | + * Returns null if this string is not a valid base64 encoding. |
| 2497 | + */ |
| 2498 | + private byte[] toBytes( String byteString) { |
| 2499 | + byte[] bytes; |
| 2500 | + |
| 2501 | + try { |
| 2502 | + bytes = Base64.getDecoder().decode( byteString); |
| 2503 | + } |
| 2504 | + catch( Exception e) { |
| 2505 | + bytes = null; |
| 2506 | + } |
| 2507 | + |
| 2508 | + return bytes; |
| 2509 | + } |
| 2510 | + |
| 2511 | + |
2378 | 2512 |
|
2379 | 2513 |
|
2380 | 2514 | public Map<String, Example> getExamples(ObjectNode obj, String location, ParseResult result) { |
|
0 commit comments