Skip to content

Commit

Permalink
Normalize relative ref paths to avoid duplicating schemas
Browse files Browse the repository at this point in the history
Currently, if an OpenAPI spec contains multiple relative refs to
the same file, but those refs are located in different files and
use different relative paths to reach the one file, swagger-parser
will create a separate, duplicate schema for each relative path
rather than reusing the same schema across all equivalent paths.

For example, given a spec with the following refs:

- In spec root directory, `$ref: ./components/schemas/Thing.yaml`
- In components/paths subdirectory, `$ref: ../../components/schemas/Thing.yaml`

The parser will produce a `Thing` and a `Thing_1` schema object
instead of reusing `Thing` for the second, equivalent reference.

This updates the ref processor to resolve relative paths before
processing relative refs in order to produce a single `Thing` schema
that is reused for all equivalent references.
  • Loading branch information
ctreatma committed Jun 17, 2024
1 parent 0ab2e8a commit 18d5450
Show file tree
Hide file tree
Showing 364 changed files with 15,103 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import io.swagger.v3.parser.ResolverCache;
import io.swagger.v3.parser.models.RefFormat;
import io.swagger.v3.parser.models.RefType;
import io.swagger.v3.parser.util.RefUtils;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -86,6 +88,18 @@ public String processRefToExternalSchema(String $ref, RefFormat refFormat) {
return renamedRef;
}

RefFormat format = computeRefFormat($ref);
if (format.equals(RefFormat.RELATIVE)) {
String normalizedRef = Paths.get($ref).normalize().toString();
System.out.println("Normalized " + $ref + " to " + normalizedRef);
renamedRef = cache.getRenamedRef($ref);
if (renamedRef != null) {
return renamedRef;
} else {
$ref = normalizedRef;
}
}

final Schema schema = cache.loadRef($ref, refFormat, Schema.class);

if(schema == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1953,6 +1953,18 @@ public void testRelativePath2() {
Assert.assertEquals(readResult.getOpenAPI().getPaths().get("/pet/findByTags").getGet().getResponses().get("default").getContent().get("application/json").getSchema().get$ref(), "#/components/schemas/ErrorModel");
}

@Test
public void testExternalRefsNormalization() throws Exception {
ParseOptions options = new ParseOptions();
options.setResolve(true);
SwaggerParseResult result = new OpenAPIV3Parser()
.readLocation("src/test/resources/oas3.fetched/openapi3.yaml", null, options);

OpenAPI openAPI = result.getOpenAPI();
Schema localModel = openAPI.getComponents().getSchemas().get("Event_2");
assertNull(localModel);
}

private OpenAPI doRelativeFileTest(String location) {
OpenAPIV3Parser parser = new OpenAPIV3Parser();
ParseOptions options = new ParseOptions();
Expand Down Expand Up @@ -3298,4 +3310,4 @@ public void testIssue2081() {
assertEquals(openAPI.getComponents().getSchemas().get("PetCreate").getRequired().size(), 1);
assertEquals(openAPI.getComponents().getSchemas().get("PetCreate").getProperties().size(), 2);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: Search by hostname, description, short_id, reservation short_id, tags, plan name, plan slug, facility code, facility name, operating system name, operating system slug, IP addresses.
in: query
name: search
schema:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
description: |-
Nested attributes to exclude. Excluded objects will return only the href
attribute. Attribute names can be dotted (up to 3 levels) to exclude deeply
nested objects.
in: query
name: exclude
schema:
items:
type: string
type: array
style: form
explode: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
description: |-
Nested attributes to include. Included objects will return their full
attributes. Attribute names can be dotted (up to 3 levels) to included deeply
nested objects.
in: query
name: include
schema:
items:
type: string
type: array
style: form
explode: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Page to return
in: query
name: page
schema:
default: 1
format: int32
maximum: 100000
minimum: 1
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
description: Items returned per page
in: query
name: per_page
schema:
default: 10
format: int32
maximum: 1000
minimum: 1
type: integer
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: Filter results by name.
in: query
name: name
schema:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
content:
application/json:
schema:
oneOf:
- $ref: '../schemas/DedicatedPortCreateInput.yaml'
- $ref: '../schemas/VlanFabricVcCreateInput.yaml'
- $ref: '../schemas/VrfFabricVcCreateInput.yaml'
description: Dedicated port or shared interconnection (also known as Fabric VC) creation request
required: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
content:
application/json:
schema:
$ref: '../schemas/InvitationInput.yaml'
description: Invitation to create
required: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
content:
application/json:
schema:
$ref: '../schemas/PortAssignInput.yaml'
description: 'Virtual Network ID. May be the UUID of the Virtual Network record, or the VLAN value itself (ex: ''1001'').'
required: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
content:
application/json:
schema:
$ref: '../schemas/SSHKeyCreateInput.yaml'
description: ssh key to create
required: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
properties:
address:
type: string
address2:
type: string
city:
type: string
coordinates:
$ref: './Coordinates.yaml'
country:
type: string
state:
type: string
zip_code:
type: string
required:
- address
- zip_code
- country
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
properties:
namespace:
readOnly: true
type: string
description: Attribute namespace
created_at:
readOnly: true
type: string
format: date-time
description: Datetime when the block was created.
updated_at:
readOnly: true
type: string
format: date-time
description: Datetime when the block was updated.
data:
$ref: "./AttributeData.yaml"
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
properties:
latest:
readOnly: true
type: boolean
description: Boolean flag to know if the firmware set is the latest for the model and vendor
model:
readOnly: true
type: string
description: Model on which this firmware set can be applied
vendor:
readOnly: true
type: string
description: Vendor on which this firmware set can be applied
plan:
readOnly: true
type: string
description: Plan where the firmware set can be applied
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
properties:
created_at:
format: date-time
type: string
description:
description: Available only for API keys
type: string
id:
format: uuid
type: string
project:
allOf:
- $ref: './Project.yaml'
- description: Available only for project tokens
read_only:
type: boolean
token:
type: string
updated_at:
format: date-time
type: string
user:
allOf:
- $ref: './User.yaml'
- description: Available only for user tokens
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
properties:
description:
type: string
read_only:
type: boolean
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
properties:
api_keys:
items:
$ref: './AuthToken.yaml'
type: array
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
properties:
address_family:
description: Address family for BGP session.
enum:
- ipv4
- ipv6
example: ipv4
type: string
default_route:
default: false
description: Set the default route policy.
type: boolean
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
properties:
created_at:
format: date-time
type: string
devices:
items:
$ref: './Href.yaml'
type: array
error_messages:
items:
type: string
type: array
id:
format: uuid
type: string
project:
$ref: './Href.yaml'
quantity:
type: integer
state:
type: string
updated_at:
format: date-time
type: string
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
properties:
batches:
items:
$ref: './Batch.yaml'
type: array
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
properties:
asn:
description: Autonomous System Number. ASN is required with Global BGP. With Local
BGP the private ASN, 65000, is assigned.
example: 65000
format: int32
type: integer
created_at:
format: date-time
type: string
deployment_type:
description: |
In a Local BGP deployment, a customer uses an internal ASN to control routes within a single Equinix Metal datacenter. This means that the routes are never advertised to the global Internet. Global BGP, on the other hand, requires a customer to have a registered ASN and IP space.
enum:
- global
- local
example: local
type: string
href:
type: string
id:
format: uuid
type: string
max_prefix:
default: 10
description: The maximum number of route filters allowed per server
type: integer
md5:
description: (Optional) Password for BGP session in plaintext (not a checksum)
nullable: true
type: string
project:
$ref: './Href.yaml'
ranges:
description: The IP block ranges associated to the ASN (Populated in Global BGP
only)
items:
$ref: './GlobalBgpRange.yaml'
type: array
requested_at:
format: date-time
type: string
route_object:
description: Specifies AS-MACRO (aka AS-SET) to use when building client route
filters
type: string
sessions:
description: The direct connections between neighboring routers that want to exchange
routing information.
items:
$ref: './BgpSession.yaml'
type: array
status:
description: Status of the BGP Config. Status "requested" is valid only with the
"global" deployment_type.
enum:
- requested
- enabled
- disabled
type: string
type: object
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
properties:
asn:
type: integer
minimum: 0
maximum: 4294967295
description: Autonomous System Number for local BGP deployment.
example: 65000
deployment_type:
description: Wether the BGP deployment is local or global. Local deployments are configured immediately. Global deployments will need to be reviewed by Equinix Metal engineers.
type: string
example: local
enum:
- local
- global
md5:
type: string
description: |
The plaintext password to share between BGP neighbors as an MD5 checksum:
* must be 10-20 characters long
* may not include punctuation
* must be a combination of numbers and letters
* must contain at least one lowercase, uppercase, and digit character
pattern: '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{10,20}$'
use_case:
description: A use case explanation (necessary for global BGP request review).
type: string
required:
- deployment_type
- asn
type: object
Loading

0 comments on commit 18d5450

Please sign in to comment.