|
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; |
|
46 | 49 | import io.swagger.v3.oas.models.servers.ServerVariables; |
47 | 50 | import io.swagger.v3.parser.core.models.SwaggerParseResult; |
48 | 51 | import io.swagger.v3.core.util.Json; |
| 52 | +import io.swagger.v3.core.util.RefUtils; |
| 53 | + |
49 | 54 | import org.apache.commons.lang3.StringUtils; |
50 | 55 |
|
| 56 | +import static io.swagger.v3.core.util.RefUtils.extractSimpleName; |
| 57 | + |
51 | 58 | import java.math.BigDecimal; |
52 | 59 | import java.net.URI; |
53 | 60 | import java.net.URISyntaxException; |
54 | 61 | import java.net.URL; |
| 62 | +import java.text.ParseException; |
55 | 63 | import java.util.*; |
| 64 | +import java.util.regex.Matcher; |
56 | 65 | import java.util.regex.Pattern; |
57 | 66 | import java.util.stream.Collectors; |
58 | 67 | import java.util.stream.Stream; |
| 68 | +import static java.util.Calendar.*; |
59 | 69 |
|
60 | 70 |
|
61 | 71 | public class OpenAPIDeserializer { |
@@ -90,6 +100,9 @@ public class OpenAPIDeserializer { |
90 | 100 | private static final String COOKIE_PARAMETER = "cookie"; |
91 | 101 | private static final String PATH_PARAMETER = "path"; |
92 | 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 | + |
93 | 106 | private Components components; |
94 | 107 | private final Set<String> operationIDs = new HashSet<>(); |
95 | 108 |
|
@@ -1408,13 +1421,25 @@ public List<Parameter> getParameterList(ArrayNode obj, String location, ParseRes |
1408 | 1421 | } |
1409 | 1422 | Set<String> filter = new HashSet<>(); |
1410 | 1423 |
|
1411 | | - for(Parameter param:parameters) { |
| 1424 | + parameters.stream().map(this::getParameterDefinition).forEach(param -> { |
1412 | 1425 | if(!filter.add(param.getName()+"#"+param.getIn())) { |
1413 | 1426 | result.warning(location,"There are duplicate parameter values"); |
1414 | 1427 | } |
1415 | | - } |
| 1428 | + }); |
1416 | 1429 | return parameters; |
1417 | 1430 | } |
| 1431 | + |
| 1432 | + private Parameter getParameterDefinition(Parameter parameter) { |
| 1433 | + if (parameter.get$ref() == null) { |
| 1434 | + return parameter; |
| 1435 | + } |
| 1436 | + Object parameterSchemaName = extractSimpleName(parameter.get$ref()).getLeft(); |
| 1437 | + return Optional.ofNullable(components) |
| 1438 | + .map(Components::getParameters) |
| 1439 | + .map(parameters -> parameters.get(parameterSchemaName)) |
| 1440 | + .orElse(parameter); |
| 1441 | + |
| 1442 | + } |
1418 | 1443 |
|
1419 | 1444 | public Parameter getParameter(ObjectNode obj, String location, ParseResult result) { |
1420 | 1445 | if (obj == null) { |
@@ -2185,16 +2210,21 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2185 | 2210 | for (JsonNode n : enumArray) { |
2186 | 2211 | if (n.isNumber()) { |
2187 | 2212 | schema.addEnumItemObject(n.numberValue()); |
2188 | | - }else if (n.isValueNode()) { |
2189 | | - 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 | + } |
2190 | 2220 | } else { |
2191 | 2221 | result.invalidType(location, "enum", "value", n); |
2192 | 2222 | } |
2193 | 2223 | } |
2194 | 2224 | } |
2195 | 2225 |
|
2196 | 2226 | value = getString("type",node,false,location,result); |
2197 | | - if (StringUtils.isNotBlank(value)) { |
| 2227 | + if (StringUtils.isNotBlank(value) && StringUtils.isBlank(schema.getType())) { |
2198 | 2228 | schema.setType(value); |
2199 | 2229 | }else{ |
2200 | 2230 | // may have an enum where type can be inferred |
@@ -2267,35 +2297,41 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2267 | 2297 |
|
2268 | 2298 | //sets default value according to the schema type |
2269 | 2299 | if(node.get("default")!= null) { |
2270 | | - if(schema.getType().equals("array")) { |
2271 | | - ArrayNode array = getArray("default", node, false, location, result); |
2272 | | - if (array != null) { |
2273 | | - schema.setDefault(array); |
2274 | | - } |
2275 | | - }else if(schema.getType().equals("string")) { |
2276 | | - value = getString("default", node, false, location, result); |
2277 | | - if (value != null) { |
2278 | | - schema.setDefault(value); |
2279 | | - } |
2280 | | - }else if(schema.getType().equals("boolean")) { |
2281 | | - bool = getBoolean("default", node, false, location, result); |
2282 | | - if (bool != null) { |
2283 | | - schema.setDefault(bool); |
2284 | | - } |
2285 | | - }else if(schema.getType().equals("object")) { |
2286 | | - Object object = getObject("default", node, false, location, result); |
2287 | | - if (object != null) { |
2288 | | - schema.setDefault(object); |
2289 | | - } |
2290 | | - } else if(schema.getType().equals("integer")) { |
2291 | | - Integer number = getInteger("default", node, false, location, result); |
2292 | | - if (number != null) { |
2293 | | - schema.setDefault(number); |
2294 | | - } |
2295 | | - } else if(schema.getType().equals("number")) { |
2296 | | - BigDecimal number = getBigDecimal("default", node, false, location, result); |
2297 | | - if (number != null) { |
2298 | | - schema.setDefault(number); |
| 2300 | + if(!StringUtils.isBlank(schema.getType())) { |
| 2301 | + if (schema.getType().equals("array")) { |
| 2302 | + ArrayNode array = getArray("default", node, false, location, result); |
| 2303 | + if (array != null) { |
| 2304 | + schema.setDefault(array); |
| 2305 | + } |
| 2306 | + } else if (schema.getType().equals("string")) { |
| 2307 | + value = getString("default", node, false, location, result); |
| 2308 | + if (value != null) { |
| 2309 | + try { |
| 2310 | + schema.setDefault(getDecodedObject(schema, value)); |
| 2311 | + } catch (ParseException e) { |
| 2312 | + result.invalidType(location, String.format("default=`%s`", e.getMessage()), schema.getFormat(), node); |
| 2313 | + } |
| 2314 | + } |
| 2315 | + } else if (schema.getType().equals("boolean")) { |
| 2316 | + bool = getBoolean("default", node, false, location, result); |
| 2317 | + if (bool != null) { |
| 2318 | + schema.setDefault(bool); |
| 2319 | + } |
| 2320 | + } else if (schema.getType().equals("object")) { |
| 2321 | + Object object = getObject("default", node, false, location, result); |
| 2322 | + if (object != null) { |
| 2323 | + schema.setDefault(object); |
| 2324 | + } |
| 2325 | + } else if (schema.getType().equals("integer")) { |
| 2326 | + Integer number = getInteger("default", node, false, location, result); |
| 2327 | + if (number != null) { |
| 2328 | + schema.setDefault(number); |
| 2329 | + } |
| 2330 | + } else if (schema.getType().equals("number")) { |
| 2331 | + BigDecimal number = getBigDecimal("default", node, false, location, result); |
| 2332 | + if (number != null) { |
| 2333 | + schema.setDefault(number); |
| 2334 | + } |
2299 | 2335 | } |
2300 | 2336 | } |
2301 | 2337 | } |
@@ -2359,6 +2395,121 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){ |
2359 | 2395 | } |
2360 | 2396 |
|
2361 | 2397 |
|
| 2398 | + /** |
| 2399 | + * Decodes the given string and returns an object applicable to the given schema. |
| 2400 | + * Throws a ParseException if no applicable object can be recognized. |
| 2401 | + */ |
| 2402 | + private Object getDecodedObject( Schema schema, String objectString) throws ParseException { |
| 2403 | + Object object = |
| 2404 | + objectString == null? |
| 2405 | + null : |
| 2406 | + |
| 2407 | + schema.getClass().equals( DateSchema.class)? |
| 2408 | + toDate( objectString) : |
| 2409 | + |
| 2410 | + schema.getClass().equals( DateTimeSchema.class)? |
| 2411 | + toDateTime( objectString) : |
| 2412 | + |
| 2413 | + schema.getClass().equals( ByteArraySchema.class)? |
| 2414 | + toBytes( objectString) : |
| 2415 | + |
| 2416 | + objectString; |
| 2417 | + |
| 2418 | + if( object == null && objectString != null) { |
| 2419 | + throw new ParseException( objectString, 0); |
| 2420 | + } |
| 2421 | + |
| 2422 | + return object; |
| 2423 | + } |
| 2424 | + |
| 2425 | + |
| 2426 | + /** |
| 2427 | + * Returns the Date represented by the given RFC3339 date-time string. |
| 2428 | + * Returns null if this string can't be parsed as Date. |
| 2429 | + */ |
| 2430 | + private Date toDateTime( String dateString) { |
| 2431 | + // Note: For this conversion, regex matching is better than SimpleDateFormat, etc. |
| 2432 | + // Optional elements (e.g. milliseconds) are not directly handled by SimpleDateFormat. |
| 2433 | + // Also, SimpleDateFormat is not thread-safe. |
| 2434 | + Matcher matcher = RFC3339_DATE_TIME_PATTERN.matcher( dateString); |
| 2435 | + |
| 2436 | + Date dateTime = null; |
| 2437 | + if( matcher.matches()) { |
| 2438 | + try { |
| 2439 | + String year = matcher.group(1); |
| 2440 | + String month = matcher.group(2); |
| 2441 | + String day = matcher.group(3); |
| 2442 | + String hour = matcher.group(4); |
| 2443 | + String min = matcher.group(5); |
| 2444 | + String sec = matcher.group(6); |
| 2445 | + String ms = matcher.group(7); |
| 2446 | + String zone = matcher.group(10); |
| 2447 | + |
| 2448 | + Calendar calendar = Calendar.getInstance( TimeZone.getTimeZone( zone == null? "GMT" : "GMT" + zone)); |
| 2449 | + calendar.set( YEAR, Integer.parseInt( year)); |
| 2450 | + calendar.set( MONTH, Integer.parseInt( month) - 1); |
| 2451 | + calendar.set( DAY_OF_MONTH, Integer.parseInt( day)); |
| 2452 | + calendar.set( HOUR_OF_DAY, Integer.parseInt( hour)); |
| 2453 | + calendar.set( MINUTE, Integer.parseInt( min)); |
| 2454 | + calendar.set( SECOND, Integer.parseInt( sec)); |
| 2455 | + calendar.set( MILLISECOND, ms == null? 0 : (int) (Double.parseDouble( ms) * 1000)); |
| 2456 | + |
| 2457 | + dateTime = calendar.getTime(); |
| 2458 | + } |
| 2459 | + catch( Exception ignore) { |
| 2460 | + } |
| 2461 | + } |
| 2462 | + |
| 2463 | + return dateTime; |
| 2464 | + } |
| 2465 | + |
| 2466 | + |
| 2467 | + /** |
| 2468 | + * Returns the Date represented by the given RFC3339 full-date string. |
| 2469 | + * Returns null if this string can't be parsed as Date. |
| 2470 | + */ |
| 2471 | + private Date toDate( String dateString) { |
| 2472 | + Matcher matcher = RFC3339_DATE_PATTERN.matcher( dateString); |
| 2473 | + |
| 2474 | + Date date = null; |
| 2475 | + if( matcher.matches()) { |
| 2476 | + String year = matcher.group(1); |
| 2477 | + String month = matcher.group(2); |
| 2478 | + String day = matcher.group(3); |
| 2479 | + |
| 2480 | + try { |
| 2481 | + date= |
| 2482 | + new Calendar.Builder() |
| 2483 | + .setDate( Integer.parseInt( year), Integer.parseInt( month) - 1, Integer.parseInt( day)) |
| 2484 | + .build() |
| 2485 | + .getTime(); |
| 2486 | + } |
| 2487 | + catch( Exception ignore) { |
| 2488 | + } |
| 2489 | + } |
| 2490 | + |
| 2491 | + return date; |
| 2492 | + } |
| 2493 | + |
| 2494 | + |
| 2495 | + /** |
| 2496 | + * Returns the byte array represented by the given base64-encoded string. |
| 2497 | + * Returns null if this string is not a valid base64 encoding. |
| 2498 | + */ |
| 2499 | + private byte[] toBytes( String byteString) { |
| 2500 | + byte[] bytes; |
| 2501 | + |
| 2502 | + try { |
| 2503 | + bytes = Base64.getDecoder().decode( byteString); |
| 2504 | + } |
| 2505 | + catch( Exception e) { |
| 2506 | + bytes = null; |
| 2507 | + } |
| 2508 | + |
| 2509 | + return bytes; |
| 2510 | + } |
| 2511 | + |
| 2512 | + |
2362 | 2513 |
|
2363 | 2514 |
|
2364 | 2515 | public Map<String, Example> getExamples(ObjectNode obj, String location, ParseResult result) { |
|
0 commit comments