Skip to content

Commit

Permalink
fix: issue with navigation when operationId contains backslash or quo…
Browse files Browse the repository at this point in the history
…tes (#1513)

Co-authored-by: Andrey Lomakin <lom16133@gmail.com>
Co-authored-by: Anastasiia Derymarko <anastasiia@redocly.com>
Co-authored-by: Andriy Zaleskyy <andriy.zaleskyy@redocly.com>
  • Loading branch information
3 people committed Apr 15, 2022
1 parent fd8917e commit 8f7e56c
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 19 deletions.
32 changes: 21 additions & 11 deletions demo/openapi-3-1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ paths:
parameters:
- name: Accept-Language
in: header
description: "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US"
description: 'The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US'
example: en-US
required: false
schema:
Expand Down Expand Up @@ -182,6 +182,16 @@ paths:
}
requestBody:
$ref: '#/components/requestBodies/Pet'
delete:
tags:
- pet
summary: OperationId with quotes
operationId: deletePetBy"Id
get:
tags:
- pet
summary: OperationId with backslash
operationId: delete\PetById
'/pet/{petId}':
get:
tags:
Expand Down Expand Up @@ -259,7 +269,7 @@ paths:
required: false
schema:
type: string
example: "Bearer <TOKEN>"
example: 'Bearer <TOKEN>'
- name: petId
in: path
description: Pet id to delete
Expand Down Expand Up @@ -432,7 +442,7 @@ paths:
application/json:
example:
status: 400
message: "Invalid Order"
message: 'Invalid Order'
requestBody:
content:
application/json:
Expand Down Expand Up @@ -894,11 +904,11 @@ paths:
type: string
examples:
response:
value: <Message> OK </Message>
value: <Message> OK </Message>
text/plain:
examples:
response:
value: OK
value: OK
'400':
description: Invalid username/password supplied
/user/logout:
Expand All @@ -925,7 +935,7 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: update Cat details
Expand All @@ -940,7 +950,7 @@ components:
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/Cat"
$ref: '#/components/schemas/Cat'
responses:
'200':
description: create Cat details
Expand Down Expand Up @@ -1073,8 +1083,8 @@ components:
properties:
id:
externalDocs:
description: "Find more info here"
url: "https://example.com"
description: 'Find more info here'
url: 'https://example.com'
description: Pet ID
$ref: '#/components/schemas/Id'
category:
Expand Down Expand Up @@ -1251,9 +1261,9 @@ webhooks:
content:
application/json:
schema:
$ref: "#/components/schemas/Pet"
$ref: '#/components/schemas/Pet'
responses:
"200":
'200':
description: Return a 200 status to indicate that the data was received successfully
myWebhook:
$ref: '#/components/pathItems/webhooks'
Expand Down
16 changes: 16 additions & 0 deletions e2e/integration/menu.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,20 @@ describe('Menu', () => {
.then($h5 => $h5[0].firstChild!.nodeValue!.trim())
.should('eq', 'Response Schema:');
});

it('should be able to open the operation details when the operation IDs have quotes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with quotes').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with quotes').should('be.visible');
cy.url().should('include', 'deletePetBy%22Id');
});

it.only('should encode URL when the operation IDs have backslashes', () => {
cy.visit('e2e/standalone-3-1.html');
cy.get('label span[title="pet"]').click({ multiple: true, force: true });
cy.get('li').contains('OperationId with backslash').click({ multiple: true, force: true });
cy.get('h2').contains('OperationId with backslash').should('be.visible');
cy.url().should('include', 'delete%5CPetById');
});
});
2 changes: 1 addition & 1 deletion src/common-elements/linkify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function navigate(history: HistoryService, event: React.MouseEvent<HTMLAnchorEle
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
history.replace(to);
history.replace(encodeURI(to));
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/services/MenuStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { SpecStore } from './models';
import { history as historyInst, HistoryService } from './HistoryService';
import { ScrollService } from './ScrollService';

import { flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
import { escapeHTMLAttrChars, flattenByProp, SECURITY_SCHEMES_SECTION_PREFIX } from '../utils';
import { GROUP_DEPTH } from './MenuBuilder';

export type MenuItemGroupType = 'group' | 'tag' | 'section';
Expand Down Expand Up @@ -47,7 +47,7 @@ export class MenuStore {
if (!id) {
return;
}
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
}

/**
Expand Down Expand Up @@ -153,7 +153,7 @@ export class MenuStore {
item = this.flatItems.find(i => SECURITY_SCHEMES_SECTION_PREFIX.startsWith(i.id));
this.activateAndScroll(item, false);
}
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${id}"]`);
this.scroll.scrollIntoViewBySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(id)}"]`);
}
};

Expand All @@ -163,7 +163,7 @@ export class MenuStore {
*/
getElementAt(idx: number): Element | null {
const item = this.flatItems[idx];
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
}

/**
Expand All @@ -175,7 +175,7 @@ export class MenuStore {
if (item && item.type === 'group') {
item = item.items[0];
}
return (item && querySelector(`[${SECTION_ATTR}="${item.id}"]`)) || null;
return (item && querySelector(`[${SECTION_ATTR}="${escapeHTMLAttrChars(item.id)}"]`)) || null;
}

/**
Expand Down Expand Up @@ -224,7 +224,7 @@ export class MenuStore {

this.activeItemIdx = item.absoluteIdx!;
if (updateLocation) {
this.history.replace(item.id, rewriteHistory);
this.history.replace(encodeURI(item.id), rewriteHistory);
}

item.activate();
Expand Down
14 changes: 14 additions & 0 deletions src/utils/__tests__/__snapshots__/loadAndBundleSpec.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2330,6 +2330,20 @@ and standard method from web, mobile and desktop applications.
"openapi": "3.1.0",
"paths": Object {
"/pet": Object {
"delete": Object {
"operationId": "deletePetBy\\"Id",
"summary": "OperationId with quotes",
"tags": Array [
"pet",
],
},
"get": Object {
"operationId": "delete\\\\PetById",
"summary": "OperationId with backslash",
"tags": Array [
"pet",
],
},
"parameters": Array [
Object {
"description": "The language you prefer for messages. Supported values are en-AU, en-CA, en-GB, en-US",
Expand Down
7 changes: 6 additions & 1 deletion src/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,13 @@ function parseURL(url: string) {
}
}

export function escapeHTMLAttrChars(str: string): string {
return str.replace(/["\\]/g, '\\$&');
}

export function unescapeHTMLChars(str: string): string {
return str
.replace(/&#(\d+);/g, (_m, code) => String.fromCharCode(parseInt(code, 10)))
.replace(/&amp;/g, '&');
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"');
}

0 comments on commit 8f7e56c

Please sign in to comment.