Skip to content

Commit 0b3cf26

Browse files
authored
Allow paths resolving to same template but with different methods (#224)
Fixes #170
1 parent 631699e commit 0b3cf26

File tree

3 files changed

+102
-12
lines changed

3 files changed

+102
-12
lines changed

core/src/main/java/org/openapitools/openapidiff/core/compare/PathsDiff.java

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,41 @@ public Optional<ChangedPaths> diff(
3636
final Map<String, PathItem> left, final Map<String, PathItem> right) {
3737
ChangedPaths changedPaths = new ChangedPaths(left, right);
3838
changedPaths.getIncreased().putAll(right);
39+
3940
left.keySet()
4041
.forEach(
4142
(String url) -> {
4243
PathItem leftPath = left.get(url);
4344
String template = normalizePath(url);
44-
Optional<String> result =
45-
right.keySet().stream()
46-
.filter(s -> normalizePath(s).equals(template))
47-
.findFirst();
45+
Optional<Map.Entry<String, PathItem>> result =
46+
changedPaths.getIncreased().entrySet().stream()
47+
.filter(item -> normalizePath(item.getKey()).equals(template))
48+
.min((a, b) -> {
49+
if (methodsIntersect(a.getValue(), b.getValue())) {
50+
throw new IllegalArgumentException(
51+
"Two path items have the same signature: " + template);
52+
}
53+
if (a.getKey().equals(url)) {
54+
return -1;
55+
} else if (b.getKey().equals((url))) {
56+
return 1;
57+
} else {
58+
HashSet<PathItem.HttpMethod> methodsA = new HashSet<>(
59+
a.getValue().readOperationsMap().keySet());
60+
methodsA.retainAll(leftPath.readOperationsMap().keySet());
61+
HashSet<PathItem.HttpMethod> methodsB = new HashSet<>(
62+
b.getValue().readOperationsMap().keySet());
63+
methodsB.retainAll(leftPath.readOperationsMap().keySet());
64+
return Integer.compare(methodsB.size(), methodsA.size());
65+
}
66+
});
4867
if (result.isPresent()) {
49-
if (!changedPaths.getIncreased().containsKey(result.get())) {
50-
throw new IllegalArgumentException(
51-
"Two path items have the same signature: " + template);
52-
}
53-
PathItem rightPath = changedPaths.getIncreased().remove(result.get());
68+
String rightUrl = result.get().getKey();
69+
PathItem rightPath = changedPaths.getIncreased().remove(rightUrl);
5470
Map<String, String> params = new LinkedHashMap<>();
55-
if (!url.equals(result.get())) {
71+
if (!url.equals(rightUrl)) {
5672
List<String> oldParams = extractParameters(url);
57-
List<String> newParams = extractParameters(result.get());
73+
List<String> newParams = extractParameters(rightUrl);
5874
for (int i = 0; i < oldParams.size(); i++) {
5975
params.put(oldParams.get(i), newParams.get(i));
6076
}
@@ -65,7 +81,7 @@ public Optional<ChangedPaths> diff(
6581
openApiDiff
6682
.getPathDiff()
6783
.diff(leftPath, rightPath, context)
68-
.ifPresent(path -> changedPaths.getChanged().put(result.get(), path));
84+
.ifPresent(path -> changedPaths.getChanged().put(rightUrl, path));
6985
} else {
7086
changedPaths.getMissing().put(url, leftPath);
7187
}
@@ -79,4 +95,14 @@ public static Paths valOrEmpty(Paths path) {
7995
}
8096
return path;
8197
}
98+
99+
private static boolean methodsIntersect(PathItem a, PathItem b) {
100+
Set<PathItem.HttpMethod> methodsA = a.readOperationsMap().keySet();
101+
for (PathItem.HttpMethod method : b.readOperationsMap().keySet()) {
102+
if (methodsA.contains(method)) {
103+
return true;
104+
}
105+
}
106+
return false;
107+
}
82108
}

core/src/test/java/org/openapitools/openapidiff/core/PathDiffTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
package org.openapitools.openapidiff.core;
22

3+
import static org.assertj.core.api.Assertions.assertThat;
34
import static org.junit.jupiter.api.Assertions.assertThrows;
45
import static org.openapitools.openapidiff.core.TestUtils.assertOpenApiAreEquals;
56

67
import org.junit.jupiter.api.Test;
8+
import org.openapitools.openapidiff.core.model.ChangedOpenApi;
79

810
public class PathDiffTest {
911

1012
private final String OPENAPI_PATH1 = "path_1.yaml";
1113
private final String OPENAPI_PATH2 = "path_2.yaml";
1214
private final String OPENAPI_PATH3 = "path_3.yaml";
15+
private final String OPENAPI_PATH4 = "path_4.yaml";
1316

1417
@Test
1518
public void testEqual() {
@@ -21,4 +24,13 @@ public void testMultiplePathWithSameSignature() {
2124
assertThrows(
2225
IllegalArgumentException.class, () -> assertOpenApiAreEquals(OPENAPI_PATH3, OPENAPI_PATH3));
2326
}
27+
28+
@Test
29+
public void testSameTemplateDifferentMethods() {
30+
ChangedOpenApi changedOpenApi = OpenApiCompare.fromLocations(OPENAPI_PATH1, OPENAPI_PATH4);
31+
assertThat(changedOpenApi.getNewEndpoints())
32+
.hasSize(1)
33+
.satisfiesExactly(endpoint -> assertThat(endpoint.getOperation().getOperationId()).isEqualTo("deletePet"));
34+
assertThat(changedOpenApi.isCompatible()).isTrue();
35+
}
2436
}

core/src/test/resources/path_4.yaml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
openapi: 3.0.0
2+
servers:
3+
- url: 'http://petstore.swagger.io/v2'
4+
info:
5+
description: >-
6+
This is a sample server Petstore server. You can find out more about
7+
Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net,
8+
#swagger](http://swagger.io/irc/). For this sample, you can use the api key
9+
`special-key` to test the authorization filters.
10+
version: 1.0.0
11+
title: Swagger Petstore
12+
termsOfService: 'http://swagger.io/terms/'
13+
contact:
14+
email: apiteam@swagger.io
15+
license:
16+
name: Apache 2.0
17+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
18+
paths:
19+
/pet/{petId}:
20+
get:
21+
tags:
22+
- pet
23+
summary: gets a pet by id
24+
description: ''
25+
operationId: updatePetWithForm
26+
parameters:
27+
- name: petId
28+
in: path
29+
description: ID of pet that needs to be updated
30+
required: true
31+
schema:
32+
type: integer
33+
responses:
34+
'405':
35+
description: Invalid input
36+
/pet/{petId2}:
37+
post:
38+
tags:
39+
- pet
40+
summary: deletes a pet
41+
description: ''
42+
operationId: deletePet
43+
parameters:
44+
- name: petId2
45+
in: path
46+
description: Pet ID to delete
47+
required: true
48+
schema:
49+
type: integer
50+
responses:
51+
'405':
52+
description: Invalid input

0 commit comments

Comments
 (0)