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
}
}