Bug 1251766 - Add new Date type to webextensions schemas r?kmag draft
authorAndrew Swan <aswan@mozilla.com>
Fri, 04 Mar 2016 13:08:19 -0800
changeset 337048 7872e4945425ffe841951098862c264cf6861086
parent 337011 b479a3b0fde9a3dfc67dfb6dbf35d3c958fd1a9c
child 337049 880dc01bb6cbe55d54fdb8dd5105dbbd3e65365f
push id12260
push useraswan@mozilla.com
push dateFri, 04 Mar 2016 21:14:32 +0000
reviewerskmag
bugs1251766
milestone47.0a1
Bug 1251766 - Add new Date type to webextensions schemas r?kmag MozReview-Commit-ID: EEX5FziiINo
toolkit/components/extensions/Schemas.jsm
toolkit/components/extensions/schemas/extension_types.json
toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -244,16 +244,31 @@ const FORMATS = {
         new URL(string);
       } catch (e) {
         return FORMATS.relativeUrl(string, context);
       }
     }
 
     throw new SyntaxError(`String ${JSON.stringify(string)} must be a relative URL`);
   },
+
+  date(string, context) {
+    // A valid ISO 8601 timestamp.
+    const PATTERN = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|([-+]\d{2}:?\d{2})))?$/;
+    if (!PATTERN.test(string)) {
+      throw new Error(`Invalid date string ${string}`);
+    }
+    // Our pattern just checks the format, we could still have invalid
+    // values (e.g., month=99 or month=02 and day=31).  Let the Date
+    // constructor do the dirty work of validating.
+    if (isNaN(new Date(string))) {
+      throw new Error(`Invalid date string ${string}`);
+    }
+    return string;
+  },
 };
 
 // Schema files contain namespaces, and each namespace contains types,
 // properties, functions, and events. An Entry is a base class for
 // types, properties, functions, and events.
 class Entry {
   constructor(schema = {}) {
     /**
--- a/toolkit/components/extensions/schemas/extension_types.json
+++ b/toolkit/components/extensions/schemas/extension_types.json
@@ -54,12 +54,30 @@
             "description": "The ID of the frame to inject the script into. This may not be used in combination with <code>allFrames</code>."
           },
           "runAt": {
             "$ref": "RunAt",
             "optional": true,
             "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
           }
         }
+      },
+      {
+        "id": "Date",
+        "choices": [
+          {
+            "type": "string",
+            "format": "date"
+          },
+          {
+            "type": "integer",
+            "minimum": 0
+          },
+          {
+            "type": "object",
+            "isInstanceOf": "Date",
+            "additionalProperties": { "type": "any" }
+          }
+        ]
       }
     ]
   }
 ]
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas.js
@@ -211,16 +211,30 @@ let json = [
              relativeUrl: {type: "string", "format": "relativeUrl", "optional": true},
              strictRelativeUrl: {type: "string", "format": "strictRelativeUrl", "optional": true},
            },
          },
        ],
      },
 
      {
+       name: "formatDate",
+       type: "function",
+       parameters: [
+         {
+           name: "arg",
+           type: "object",
+           properties: {
+             date: {type: "string", format: "date", optional: true},
+           },
+         },
+       ],
+     },
+
+     {
        name: "deep",
        type: "function",
        parameters: [
          {
            name: "arg",
            type: "object",
            properties: {
              foo: {
@@ -564,16 +578,53 @@ add_task(function* () {
   }
 
   for (let url of ["//foo.html", "http://foo/bar.html"]) {
     Assert.throws(() => root.testing.format({strictRelativeUrl: url}),
                   /must be a relative URL/,
                   "should throw for non-relative URL");
   }
 
+  const dates = [
+    "2016-03-04",
+    "2016-03-04T08:00:00Z",
+    "2016-03-04T08:00:00.000Z",
+    "2016-03-04T08:00:00-08:00",
+    "2016-03-04T08:00:00.000-08:00",
+    "2016-03-04T08:00:00+08:00",
+    "2016-03-04T08:00:00.000+08:00",
+    "2016-03-04T08:00:00+0800",
+    "2016-03-04T08:00:00-0800",
+  ];
+  dates.forEach(str => {
+    root.testing.formatDate({date: str});
+    verify("call", "testing", "formatDate", [{date: str}]);
+  });
+
+  // Make sure that a trivial change to a valid date invalidates it.
+  dates.forEach(str => {
+    Assert.throws(() => root.testing.formatDate({date: "0" + str}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+    Assert.throws(() => root.testing.formatDate({date: str + "0"}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+  });
+
+  const badDates = [
+    "I do not look anything like a date string",
+    "2016-99-99",
+    "2016-03-04T25:00:00Z",
+  ];
+  badDates.forEach(str => {
+    Assert.throws(() => root.testing.formatDate({date: str}),
+                  /Invalid date string/,
+                  "should throw for invalid iso date string");
+  });
+
   root.testing.deep({foo: {bar: [{baz: {required: 12, optional: "42"}}]}});
   verify("call", "testing", "deep", [{foo: {bar: [{baz: {required: 12, optional: "42"}}]}}]);
   tallied = null;
 
   Assert.throws(() => root.testing.deep({foo: {bar: [{baz: {optional: "42"}}]}}),
                 /Type error for parameter arg \(Error processing foo\.bar\.0\.baz: Property "required" is required\) for testing\.deep/,
                 "should throw with the correct object path");