Skip to content

Commit 758de21

Browse files
committed
fix(core): dedupe paths containing special characters correctly
1 parent a79d26a commit 758de21

File tree

6 files changed

+161
-6
lines changed

6 files changed

+161
-6
lines changed

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
},
3636
"dependencies": {
3737
"@stoplight/better-ajv-errors": "1.0.3",
38-
"@stoplight/json": "~3.20.1",
38+
"@stoplight/json": "~3.21.0",
3939
"@stoplight/path": "1.3.2",
4040
"@stoplight/spectral-parsers": "^1.0.0",
4141
"@stoplight/spectral-ref-resolver": "^1.0.0",
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{
2+
"openapi": "3.0.2",
3+
"info": {
4+
"title": "Service for GoalDetail",
5+
"description": "Test You can find your company's API server",
6+
"version": "1.0.0"
7+
},
8+
"servers": [
9+
{
10+
"url": "https://localhost/service-root"
11+
}
12+
],
13+
"paths": {
14+
"/JobRelationship": {
15+
"get": {
16+
"summary": "Get entities from JobRelationship",
17+
"responses": {
18+
"200": {
19+
"description": "Retrieved entities",
20+
"content": {}
21+
},
22+
"4XX": {
23+
"$ref": "#/components/responses/error"
24+
}
25+
}
26+
}
27+
},
28+
"/JobRelationship({id})": {
29+
"get": {
30+
"summary": "Get entity from JobRelationship by key",
31+
"responses": {
32+
"200": {
33+
"description": "Retrieved entities",
34+
"content": {}
35+
},
36+
"4XX": {
37+
"$ref": "#/components/responses/error"
38+
}
39+
}
40+
}
41+
}
42+
},
43+
"components": {
44+
"schemas": {
45+
"error": {
46+
"type": "object",
47+
"required": ["error"],
48+
"properties": {
49+
"error": {
50+
"type": "object",
51+
"required": ["message"],
52+
"properties": {
53+
"message": {
54+
"type": "string"
55+
},
56+
"target": {
57+
"type": "string"
58+
},
59+
"details": {
60+
"type": "array",
61+
"items": {
62+
"type": "object",
63+
"required": ["message"],
64+
"properties": {
65+
"message": {
66+
"type": "string"
67+
},
68+
"target": {
69+
"type": "string"
70+
}
71+
}
72+
}
73+
},
74+
"innererror": {
75+
"type": "object",
76+
"description": "The structure of this object is service-specific"
77+
}
78+
}
79+
}
80+
}
81+
}
82+
},
83+
"responses": {
84+
"error": {
85+
"description": "Error",
86+
"content": {
87+
"application/json": {
88+
"schema": {
89+
"$ref": "#/components/schemas/error"
90+
}
91+
}
92+
}
93+
}
94+
}
95+
}
96+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Ruleset } from '../../../ruleset';
2+
import { DiagnosticSeverity } from '@stoplight/types';
3+
import { defined } from '@stoplight/spectral-functions';
4+
5+
export default new Ruleset({
6+
rules: {
7+
'error-code-defined': {
8+
message: '`code` property is missing in the Error object definition',
9+
severity: DiagnosticSeverity.Error,
10+
given:
11+
"$.paths.*.*.responses[?(@property.match(/^(4|5)/))].content.'application/json'.schema.properties.error.properties",
12+
then: {
13+
field: 'code',
14+
function: defined,
15+
},
16+
},
17+
},
18+
});

packages/core/src/__tests__/spectral.jest.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,4 +269,31 @@ describe('Spectral', () => {
269269
}),
270270
]);
271271
});
272+
273+
test('should dedupe paths containing special characters', async () => {
274+
const s = new Spectral({ resolver: httpAndFileResolver });
275+
const documentUri = path.join(__dirname, './__fixtures__/gh-2500/input.json');
276+
277+
s.setRuleset((await import('./__fixtures__/gh-2500/ruleset')).default);
278+
279+
const results = await s.run(new Document(fs.readFileSync(documentUri, 'utf8'), Parsers.Yaml, documentUri));
280+
281+
expect(results).toEqual([
282+
expect.objectContaining({
283+
code: 'error-code-defined',
284+
path: ['components', 'schemas', 'error', 'properties', 'error', 'properties'],
285+
source: documentUri,
286+
range: {
287+
end: {
288+
character: 81,
289+
line: 75,
290+
},
291+
start: {
292+
character: 25,
293+
line: 51,
294+
},
295+
},
296+
}),
297+
]);
298+
});
272299
});

packages/core/src/documentInventory.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { decodePointerFragment, encodePointerFragment, extractSourceFromRef, isLocalRef } from '@stoplight/json';
1+
import { decodePointerFragment, encodePointerUriFragment, extractSourceFromRef, isLocalRef } from '@stoplight/json';
22
import { extname, resolve } from '@stoplight/path';
33
import { Dictionary, IParserResult, JsonPath } from '@stoplight/types';
44
import { isObjectLike } from 'lodash';
@@ -113,7 +113,7 @@ export class DocumentInventory implements IDocumentInventory {
113113
let resolvedDoc = this.document;
114114

115115
// Add '#' on the beginning of "path" to simplify the logic below.
116-
const adjustedPath: string[] = ['#', ...path.map(String)];
116+
const adjustedPath: string[] = ['#', ...path.map(encodePointerUriFragment).map(String)];
117117

118118
// Walk through the segments of 'path' one at a time, looking for
119119
// json path locations containing a $ref.
@@ -123,7 +123,7 @@ export class DocumentInventory implements IDocumentInventory {
123123
refMapKey += '/';
124124
}
125125

126-
refMapKey += encodePointerFragment(segment);
126+
refMapKey += segment;
127127

128128
// If our current refMapKey value is in fact a key in refMap,
129129
// then we'll "reverse-resolve" it by replacing refMapKey with

yarn.lock

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,7 +2606,21 @@ __metadata:
26062606
languageName: node
26072607
linkType: hard
26082608

2609-
"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1, @stoplight/json@npm:^3.20.1, @stoplight/json@npm:~3.20.1":
2609+
"@stoplight/json@npm:^3.17.0, @stoplight/json@npm:^3.17.1, @stoplight/json@npm:^3.20.1, @stoplight/json@npm:~3.21.0":
2610+
version: 3.21.0
2611+
resolution: "@stoplight/json@npm:3.21.0"
2612+
dependencies:
2613+
"@stoplight/ordered-object-literal": ^1.0.3
2614+
"@stoplight/path": ^1.3.2
2615+
"@stoplight/types": ^13.6.0
2616+
jsonc-parser: ~2.2.1
2617+
lodash: ^4.17.21
2618+
safe-stable-stringify: ^1.1
2619+
checksum: 16fe56a6804cd47837bd82d85a8500c4226669558f3feda55d8fb0cd615ca2261622963700f04f049cf30a3a9764eb3c861516003d948743b6ae85dbbabf8a59
2620+
languageName: node
2621+
linkType: hard
2622+
2623+
"@stoplight/json@npm:~3.20.1":
26102624
version: 3.20.1
26112625
resolution: "@stoplight/json@npm:3.20.1"
26122626
dependencies:
@@ -2674,7 +2688,7 @@ __metadata:
26742688
resolution: "@stoplight/spectral-core@workspace:packages/core"
26752689
dependencies:
26762690
"@stoplight/better-ajv-errors": 1.0.3
2677-
"@stoplight/json": ~3.20.1
2691+
"@stoplight/json": ~3.21.0
26782692
"@stoplight/path": 1.3.2
26792693
"@stoplight/spectral-formats": "*"
26802694
"@stoplight/spectral-functions": "*"

0 commit comments

Comments
 (0)