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
This commit is contained in:
Pete Bacon Darwin 2021-06-07 18:20:32 +01:00 committed by Jessica Janiuk
parent c44ab4f6da
commit f788e6b8a6
3 changed files with 69 additions and 4 deletions

View File

@ -67,6 +67,30 @@ describe('DocumentService', () => {
expect(latestDocument).toEqual(doc1); 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: '<h1>Page Not Found</h1>' };
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', () => { it('should emit the not-found document if the document is not found on the server', () => {
let currentDocument: DocumentContents|undefined; let currentDocument: DocumentContents|undefined;
const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '<h1>Page Not Found</h1>' }; const notFoundDoc = { id: FILE_NOT_FOUND_ID, contents: '<h1>Page Not Found</h1>' };
@ -83,7 +107,7 @@ describe('DocumentService', () => {
// Subsequent request for not-found document. // Subsequent request for not-found document.
logger.output.error = []; logger.output.error = [];
httpMock.expectOne(CONTENT_URL_PREFIX + 'file-not-found.json').flush(notFoundDoc); 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); expect(currentDocument).toEqual(notFoundDoc);
}); });

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 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 { catchError, switchMap, tap } from 'rxjs/operators';
import { DocumentContents } from './document-contents'; import { DocumentContents } from './document-contents';
@ -66,6 +66,20 @@ export class DocumentService {
throw Error('Invalid data'); 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<DocumentContents>(encodedPath) :
throwError(error);
}),
catchError((error: HttpErrorResponse) => {
const disambiguatedPath = convertDisambiguatedPath(requestPath);
return error.status === 404 && disambiguatedPath !== requestPath ?
this.http.get<DocumentContents>(disambiguatedPath) :
throwError(error);
}),
// END HACK: PREPARE FOR CHANGING TO CASE-INSENSITIVE URLS
catchError((error: HttpErrorResponse) => { catchError((error: HttpErrorResponse) => {
return error.status === 404 ? this.getFileNotFoundDoc(id) : this.getErrorDoc(id, error); 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'));
}

View File

@ -3,7 +3,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 4619, "runtime-es2015": 4619,
"main-es2015": 453172, "main-es2015": 453855,
"polyfills-es2015": 55210 "polyfills-es2015": 55210
} }
} }
@ -12,7 +12,7 @@
"master": { "master": {
"uncompressed": { "uncompressed": {
"runtime-es2015": 4619, "runtime-es2015": 4619,
"main-es2015": 453394, "main-es2015": 453981,
"polyfills-es2015": 55291 "polyfills-es2015": 55291
} }
} }