From f788e6b8a6d2c25fb89b5daf95389ddfa059884f Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 7 Jun 2021 18:20:32 +0100 Subject: [PATCH] refactor(docs-infra): prepare DocumentService to handle new disambiguated URLs (#42509) A subsequent commit is going to change disambiguated URLs. This commit prepares the AIO application to attempt the new URLs if the old URLs fail. This will help to mitigate problems that may occur during the period between deployment of the new version and the service-worker not being updated. PR Close #42509 --- .../app/documents/document.service.spec.ts | 26 ++++++++++- aio/src/app/documents/document.service.ts | 43 ++++++++++++++++++- goldens/size-tracking/aio-payloads.json | 4 +- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/aio/src/app/documents/document.service.spec.ts b/aio/src/app/documents/document.service.spec.ts index 756651c1ce..4acddefe7c 100644 --- a/aio/src/app/documents/document.service.spec.ts +++ b/aio/src/app/documents/document.service.spec.ts @@ -67,6 +67,30 @@ describe('DocumentService', () => { expect(latestDocument).toEqual(doc1); }); + // HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS + it('should attempt disambiguated document paths if the document is not found on the server', () => { + let currentDocument: DocumentContents|undefined; + const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '

Page Not Found

' }; + const { docService, logger } = getServices('missing/Doc-1'); + docService.currentDocument.subscribe(doc => currentDocument = doc); + + // Initial request return 404. + httpMock.expectOne({url: 'generated/docs/missing/Doc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); + httpMock.expectOne({url: 'generated/docs/missing/d_oc-1.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); + httpMock.expectOne({url: 'generated/docs/missing/d_oc.json'}).flush(null, {status: 404, statusText: 'NOT FOUND'}); + expect(logger.output.error).toEqual([ + [jasmine.any(Error)] + ]); + expect(logger.output.error[0][0].message).toEqual(`Document file not found at 'missing/Doc-1'`); + + // Subsequent request for not-found document. + logger.output.error = []; + httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc); + expect(logger.output.error).toEqual([]); // does not report repeated errors + expect(currentDocument).toEqual(notFoundDoc); + }); + // END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS + it('should emit the not-found document if the document is not found on the server', () => { let currentDocument: DocumentContents|undefined; const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '

Page Not Found

' }; @@ -83,7 +107,7 @@ describe('DocumentService', () => { // Subsequent request for not-found document. logger.output.error = []; httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc); - expect(logger.output.error).toEqual([]); // does not report repeate errors + expect(logger.output.error).toEqual([]); // does not report repeated errors expect(currentDocument).toEqual(notFoundDoc); }); diff --git a/aio/src/app/documents/document.service.ts b/aio/src/app/documents/document.service.ts index 168e6e71c8..9d521f3232 100644 --- a/aio/src/app/documents/document.service.ts +++ b/aio/src/app/documents/document.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; -import { AsyncSubject, Observable, of } from 'rxjs'; +import { AsyncSubject, Observable, of, throwError } from 'rxjs'; import { catchError, switchMap, tap } from 'rxjs/operators'; import { DocumentContents } from './document-contents'; @@ -66,6 +66,20 @@ export class DocumentService { throw Error('Invalid data'); } }), + // HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS + catchError((error: HttpErrorResponse) => { + const encodedPath = encodeToLowercase(requestPath); + return error.status === 404 && encodedPath !== requestPath ? + this.http.get(encodedPath) : + throwError(error); + }), + catchError((error: HttpErrorResponse) => { + const disambiguatedPath = convertDisambiguatedPath(requestPath); + return error.status === 404 && disambiguatedPath !== requestPath ? + this.http.get(disambiguatedPath) : + throwError(error); + }), + // END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS catchError((error: HttpErrorResponse) => { return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error); }), @@ -97,3 +111,30 @@ export class DocumentService { }); } } + +/** + * Encode the path to the content in a deterministic, reversible, case-insensitive form. + * + * This avoids collisions on case-insensitive file-systems. + * + * - Escape underscores (_) to double underscores (__). + * - Convert all uppercase letters to lowercase followed by an underscore. + */ +function encodeToLowercase(str: string): string { + return str.replace(/[A-Z_]/g, char => char.toLowerCase() + '_'); +} + +/** + * A temporary function to deal with a future change to URL disambiguation. + * + * Currently there are disambiguated URLs such as `INJECTOR-0` and `Injector-1`, which + * will attempt to load their document contents from `injector-0.json` and `injector-1.json` + * respectively. In a future version of the AIO app, the disambiguation will be changed to + * escape the upper-case characters instead. + * + * This function will be called if the current AIO is trying to request documents from a + * server that has been updated to use the new disambiguated URLs. + */ +function convertDisambiguatedPath(str: string): string { + return encodeToLowercase(str.replace(/-\d+\.json$/, '.json')); +} diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index cbb7ae312c..94291ad955 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime-es2015": 4619, - "main-es2015": 453172, + "main-es2015": 453855, "polyfills-es2015": 55210 } } @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 4619, - "main-es2015": 453394, + "main-es2015": 453981, "polyfills-es2015": 55291 } }