Skip to content
This repository was archived by the owner on Nov 24, 2022. It is now read-only.

Commit 3dd768e

Browse files
authored
Merge branch 'master' into master
2 parents 420ac99 + 7160ffb commit 3dd768e

4 files changed

Lines changed: 438 additions & 5 deletions

File tree

modules/swagger-parser-v3/src/main/java/io/swagger/v3/parser/util/OpenAPIDeserializer.java

Lines changed: 155 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
import io.swagger.v3.oas.models.info.Contact;
1818
import io.swagger.v3.oas.models.info.Info;
1919
import io.swagger.v3.oas.models.media.ArraySchema;
20+
import io.swagger.v3.oas.models.media.ByteArraySchema;
2021
import io.swagger.v3.oas.models.media.ComposedSchema;
2122
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;
2225
import io.swagger.v3.oas.models.media.Discriminator;
2326
import io.swagger.v3.oas.models.media.Encoding;
2427
import io.swagger.v3.oas.models.media.MediaType;
@@ -46,16 +49,23 @@
4649
import io.swagger.v3.oas.models.servers.ServerVariables;
4750
import io.swagger.v3.parser.core.models.SwaggerParseResult;
4851
import io.swagger.v3.core.util.Json;
52+
import io.swagger.v3.core.util.RefUtils;
53+
4954
import org.apache.commons.lang3.StringUtils;
5055

56+
import static io.swagger.v3.core.util.RefUtils.extractSimpleName;
57+
5158
import java.math.BigDecimal;
5259
import java.net.URI;
5360
import java.net.URISyntaxException;
5461
import java.net.URL;
62+
import java.text.ParseException;
5563
import java.util.*;
64+
import java.util.regex.Matcher;
5665
import java.util.regex.Pattern;
5766
import java.util.stream.Collectors;
5867
import java.util.stream.Stream;
68+
import static java.util.Calendar.*;
5969

6070

6171
public class OpenAPIDeserializer {
@@ -90,6 +100,9 @@ public class OpenAPIDeserializer {
90100
private static final String COOKIE_PARAMETER = "cookie";
91101
private static final String PATH_PARAMETER = "path";
92102
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+
93106
private Components components;
94107
private final Set<String> operationIDs = new HashSet<>();
95108

@@ -1408,13 +1421,25 @@ public List<Parameter> getParameterList(ArrayNode obj, String location, ParseRes
14081421
}
14091422
Set<String> filter = new HashSet<>();
14101423

1411-
for(Parameter param:parameters) {
1424+
parameters.stream().map(this::getParameterDefinition).forEach(param -> {
14121425
if(!filter.add(param.getName()+"#"+param.getIn())) {
14131426
result.warning(location,"There are duplicate parameter values");
14141427
}
1415-
}
1428+
});
14161429
return parameters;
14171430
}
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+
}
14181443

14191444
public Parameter getParameter(ObjectNode obj, String location, ParseResult result) {
14201445
if (obj == null) {
@@ -2185,8 +2210,13 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){
21852210
for (JsonNode n : enumArray) {
21862211
if (n.isNumber()) {
21872212
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+
}
21902220
} else {
21912221
result.invalidType(location, "enum", "value", n);
21922222
}
@@ -2275,7 +2305,12 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){
22752305
}else if(schema.getType().equals("string")) {
22762306
value = getString("default", node, false, location, result);
22772307
if (value != null) {
2278-
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+
}
22792314
}
22802315
}else if(schema.getType().equals("boolean")) {
22812316
bool = getBoolean("default", node, false, location, result);
@@ -2359,6 +2394,121 @@ public Schema getSchema(ObjectNode node, String location, ParseResult result){
23592394
}
23602395

23612396

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+
23622512

23632513

23642514
public Map<String, Example> getExamples(ObjectNode obj, String location, ParseResult result) {

modules/swagger-parser-v3/src/test/java/io/swagger/v3/parser/test/OpenAPIV3ParserTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
import java.util.*;
5353

5454
import static com.github.tomakehurst.wiremock.client.WireMock.*;
55+
import static java.util.Arrays.asList;
56+
import static java.util.Collections.emptyList;
5557
import static org.hamcrest.CoreMatchers.equalTo;
5658
import static org.hamcrest.CoreMatchers.instanceOf;
5759
import static org.hamcrest.CoreMatchers.notNullValue;
@@ -1801,6 +1803,7 @@ public void shouldParseExternalSchemaModelHavingReferenceToItsLocalModel() {
18011803
assertThat(((Schema) modelSchema.getProperties().get("id")).get$ref(), equalTo("#/components/schemas/ValueId"));
18021804
}
18031805

1806+
18041807
@Test(description = "Test that extensions can be found on the class classloader in addition to tccl.")
18051808
public void testIssue1003_ExtensionsClassloader() {
18061809
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
@@ -1816,6 +1819,27 @@ public void testIssue1003_ExtensionsClassloader() {
18161819
}
18171820
assertNotNull(api);
18181821
}
1822+
1823+
@Test
1824+
public void shouldParseApiWithMultipleParameterReferences() {
1825+
// given
1826+
String location = "src/test/resources/issue-1063/api.yaml";
1827+
ParseOptions options = new ParseOptions();
1828+
OpenAPIV3Parser tested = new OpenAPIV3Parser();
1829+
1830+
// when
1831+
SwaggerParseResult result = tested.readLocation(location, emptyList(), options);
1832+
1833+
// then
1834+
OpenAPI api = result.getOpenAPI();
1835+
Map<String, Parameter> parameters = api.getComponents().getParameters();
1836+
assertThat(parameters.keySet(), equalTo(new HashSet<>(asList("IdParam", "NameParam"))));
1837+
assertThat(parameters.get("IdParam").getName(), equalTo("id"));
1838+
assertThat(parameters.get("NameParam").getName(), equalTo("name"));
1839+
1840+
assertThat(result.getMessages(), equalTo(emptyList()));
1841+
1842+
}
18191843

18201844
private static int getDynamicPort() {
18211845
return new Random().ints(10000, 20000).findFirst().getAsInt();

0 commit comments

Comments
 (0)