Skip to content

Commit 4a2f9e8

Browse files
authored
feat: implement encodePointerUriFragment and consume it in pathToPointer (#122)
1 parent 79cfa74 commit 4a2f9e8

8 files changed

+93
-6
lines changed

src/__tests__/bundle.spec.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,60 @@ describe('bundleTargetPath()', () => {
10211021
});
10221022
});
10231023

1024+
it('should encode pointers', () => {
1025+
const document = {
1026+
definitions: {
1027+
'User Admin': {
1028+
id: 'foo',
1029+
address: {
1030+
$ref: '#/definitions/%25Address',
1031+
},
1032+
},
1033+
'%Address': {
1034+
street: 'foo',
1035+
user: {
1036+
$ref: '#/definitions/user',
1037+
},
1038+
},
1039+
card: {
1040+
zip: '20815',
1041+
},
1042+
},
1043+
__target__: {
1044+
entity: {
1045+
$ref: '#/definitions/User%20Admin',
1046+
},
1047+
},
1048+
};
1049+
1050+
const clone = cloneDeep(document);
1051+
1052+
const result = bundleTarget({
1053+
document: clone,
1054+
path: '#/__target__',
1055+
});
1056+
1057+
expect(result).toEqual({
1058+
__bundled__: {
1059+
'%Address': {
1060+
street: 'foo',
1061+
user: {
1062+
$ref: '#/definitions/user',
1063+
},
1064+
},
1065+
'User Admin': {
1066+
address: {
1067+
$ref: '#/__bundled__/%25Address',
1068+
},
1069+
id: 'foo',
1070+
},
1071+
},
1072+
entity: {
1073+
$ref: '#/__bundled__/User%20Admin',
1074+
},
1075+
});
1076+
});
1077+
10241078
describe('when custom keyProvider is provided', () => {
10251079
it('should work', () => {
10261080
const document = {

src/__tests__/pathToPointer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ test('pathToPointer', () => {
88
expect(pathToPointer(['paths', 'foo~users'])).toEqual('#/paths/foo~0users');
99
expect(pathToPointer([])).toEqual('#');
1010
expect(pathToPointer([''])).toEqual('#/');
11-
expect(pathToPointer(['paths', '/user/{userId}'])).toEqual('#/paths/~1user~1{userId}');
12-
expect(pathToPointer(['paths', '/user/{userId}/~foo'])).toEqual('#/paths/~1user~1{userId}~1~0foo');
11+
expect(pathToPointer(['paths', '/user/{userId}'])).toEqual('#/paths/~1user~1%7BuserId%7D');
12+
expect(pathToPointer(['paths', '/user/{userId}/~foo'])).toEqual('#/paths/~1user~1%7BuserId%7D~1~0foo');
13+
expect(pathToPointer(['$defs', 'User Model'])).toEqual('#/%24defs/User%20Model');
14+
expect(pathToPointer(['\uD803\uDE6D', 'valid-pair'])).toEqual('#/%F0%90%B9%AD/valid-pair');
15+
expect(pathToPointer(['\uD83D', 'unmatched-surrogate'])).toEqual('#/\uD83D/unmatched-surrogate');
16+
expect(pathToPointer(['\uD83D', '\uDC00unmatched-surrogate'])).toEqual('#/\uD83D/\uDC00unmatched-surrogate');
1317
});

src/decodePointerUriFragment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { decodePointer as decodePointerUriFragment } from './decodePointer';

src/encodePointerUriFragment.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Segment } from '@stoplight/types';
2+
3+
import { encodePointerFragment } from './encodePointerFragment';
4+
import { encodeUriPointer } from './encodeUriPointer';
5+
6+
/**
7+
* Escapes special json pointer characters in a value.
8+
* Percent-encode characters.
9+
*
10+
* @example encodePointer('/paths/~users) => '~1paths~1~0users'
11+
*/
12+
export const encodePointerUriFragment = (value: Segment): Segment => {
13+
const encoded = encodePointerFragment(value);
14+
return typeof encoded === 'number' ? encoded : encodeUriPointer(encoded);
15+
};

src/encodeUriPointer.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const ENCODABLE_CHAR = /[^a-zAZ09_.!~*'()\/\-\u{D800}-\u{DFFF}]/gu;
2+
3+
/**
4+
* Percent-encode a JSON Pointer.
5+
*
6+
* encodePointer('paths/users) => '#/paths/~1users'
7+
*/
8+
export function encodeUriPointer(pointer: string): string {
9+
return pointer.replace(ENCODABLE_CHAR, encodeURIComponent);
10+
}

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
export * from './bundle';
22
export * from './decodePointer';
33
export * from './decodePointerFragment';
4+
export * from './decodePointerUriFragment';
45
export * from './decycle';
56
export * from './encodePointer';
67
export * from './encodePointerFragment';
8+
export * from './encodePointerUriFragment';
9+
export * from './encodeUriPointer';
710
export * from './extractPointerFromRef';
811
export * from './extractSourceFromRef';
912
export * from './getFirstPrimitiveProperty';

src/pathToPointer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { JsonPath } from '@stoplight/types';
22

3-
import { encodePointerFragment } from './encodePointerFragment';
3+
import { encodePointerUriFragment } from './encodePointerUriFragment';
44

55
export const pathToPointer = (path: JsonPath): string => {
66
return encodeUriFragmentIdentifier(path);
@@ -15,5 +15,5 @@ const encodeUriFragmentIdentifier = (path: JsonPath): string => {
1515
return '#';
1616
}
1717

18-
return `#/${path.map(encodePointerFragment).join('/')}`;
18+
return `#/${path.map(encodePointerUriFragment).join('/')}`;
1919
};

src/pointerToPath.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { JsonPath } from '@stoplight/types';
22

3-
import { decodePointer } from './decodePointer';
3+
import { decodePointerUriFragment } from './decodePointerUriFragment';
44

55
export const pointerToPath = (pointer: string): JsonPath => {
66
return decodeUriFragmentIdentifier(pointer);
@@ -12,7 +12,7 @@ const decodeFragmentSegments = (segments: string[]): string[] => {
1212
let i = -1;
1313

1414
while (++i < len) {
15-
res.push(decodePointer(segments[i]));
15+
res.push(decodePointerUriFragment(segments[i]));
1616
}
1717

1818
return res;

0 commit comments

Comments
 (0)