Add helper getters for URIs and date/times to json_rpc_2.

R=rnystrom@google.com
BUG=17700

Review URL: https://codereview.chromium.org//205713007

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/json_rpc_2@34341 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/src/parameters.dart b/lib/src/parameters.dart
index afc4a40..1e25220 100644
--- a/lib/src/parameters.dart
+++ b/lib/src/parameters.dart
@@ -224,7 +224,7 @@
 
   /// Asserts that [value] exists and is a [Map] and returns it.
   ///
-  /// [asListOr] may be used to provide a default value instead of rejecting the
+  /// [asMapOr] may be used to provide a default value instead of rejecting the
   /// request if [value] doesn't exist.
   Map get asMap => _getTyped('an Object', (value) => value is Map);
 
@@ -233,6 +233,32 @@
   /// If [value] doesn't exist, this returns [defaultValue].
   Map asMapOr(Map defaultValue) => asMap;
 
+  /// Asserts that [value] exists, is a string, and can be parsed as a
+  /// [DateTime] and returns it.
+  ///
+  /// [asDateTimeOr] may be used to provide a default value instead of rejecting
+  /// the request if [value] doesn't exist.
+  DateTime get asDateTime => _getParsed('date/time', DateTime.parse);
+
+  /// Asserts that [value] exists, is a string, and can be parsed as a
+  /// [DateTime] and returns it.
+  ///
+  /// If [value] doesn't exist, this returns [defaultValue].
+  DateTime asDateTimeOr(DateTime defaultValue) => asDateTime;
+
+  /// Asserts that [value] exists, is a string, and can be parsed as a
+  /// [Uri] and returns it.
+  ///
+  /// [asUriOr] may be used to provide a default value instead of rejecting the
+  /// request if [value] doesn't exist.
+  Uri get asUri => _getParsed('URI', Uri.parse);
+
+  /// Asserts that [value] exists, is a string, and can be parsed as a
+  /// [Uri] and returns it.
+  ///
+  /// If [value] doesn't exist, this returns [defaultValue].
+  Uri asUriOr(Uri defaultValue) => asUri;
+
   /// Get a parameter named [named] that matches [test], or the value of calling
   /// [orElse].
   ///
@@ -244,6 +270,27 @@
         '"$method" must be $type, but was ${JSON.encode(value)}.');
   }
 
+  _getParsed(String description, parse(String value)) {
+    var string = asString;
+    try {
+      return parse(string);
+    } on FormatException catch (error) {
+      // DateTime.parse doesn't actually include any useful information in the
+      // FormatException, just the string that was being parsed. There's no use
+      // in including that in the RPC exception. See issue 17753.
+      var message = error.message;
+      if (message == string) {
+        message = '';
+      } else {
+        message = '\n$message';
+      }
+
+      throw new RpcException.invalidParams('Parameter $_path for method '
+          '"$method" must be a valid $description, but was '
+          '${JSON.encode(string)}.$message');
+    }
+  }
+
   void _assertPositional() {
     // Throw the standard exception for a mis-typed list.
     asList;
@@ -280,4 +327,8 @@
   List asListOr(List defaultValue) => defaultValue;
 
   Map asMapOr(Map defaultValue) => defaultValue;
+
+  DateTime asDateTimeOr(DateTime defaultValue) => defaultValue;
+
+  Uri asUriOr(Uri defaultValue) => defaultValue;
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 0919ac1..f12be64 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: json_rpc_2
-version: 0.0.1
+version: 0.0.2
 author: Dart Team <misc@dartlang.org>
 description: An implementation of the JSON-RPC 2.0 spec.
 homepage: http://www.dartlang.org
diff --git a/test/server/parameters_test.dart b/test/server/parameters_test.dart
index 9219475..8bd126a 100644
--- a/test/server/parameters_test.dart
+++ b/test/server/parameters_test.dart
@@ -20,6 +20,9 @@
         "bool": true,
         "string": "zap",
         "list": [1, 2, 3],
+        "date-time": "1990-01-01 00:00:00.000",
+        "uri": "http://dartlang.org",
+        "invalid-uri": "http://[::1",
         "map": {
           "num": 4.2,
           "bool": false
@@ -34,6 +37,9 @@
         "bool": true,
         "string": "zap",
         "list": [1, 2, 3],
+        "date-time": "1990-01-01 00:00:00.000",
+        "uri": "http://dartlang.org",
+        "invalid-uri": "http://[::1",
         "map": {
           "num": 4.2,
           "bool": false
@@ -195,6 +201,71 @@
       expect(parameters['fblthp'].asMapOr({}), equals({}));
     });
 
+    test("[].asDateTime returns date/time parameters", () {
+      expect(parameters['date-time'].asDateTime, equals(new DateTime(1990)));
+    });
+
+    test("[].asDateTimeOr returns date/time parameters", () {
+      expect(parameters['date-time'].asDateTimeOr(new DateTime(2014)),
+          equals(new DateTime(1990)));
+    });
+
+    test("[].asDateTime fails for non-date/time parameters", () {
+      expect(() => parameters['int'].asDateTime,
+          throwsInvalidParams('Parameter "int" for method "foo" must be a '
+              'string, but was 1.'));
+    });
+
+    test("[].asDateTimeOr succeeds for absent parameters", () {
+      expect(parameters['fblthp'].asDateTimeOr(new DateTime(2014)),
+          equals(new DateTime(2014)));
+    });
+
+    test("[].asDateTime fails for non-date/time parameters", () {
+      expect(() => parameters['int'].asDateTime,
+          throwsInvalidParams('Parameter "int" for method "foo" must be a '
+              'string, but was 1.'));
+    });
+
+    test("[].asDateTime fails for invalid date/times", () {
+      expect(() => parameters['string'].asDateTime,
+          throwsInvalidParams('Parameter "string" for method "foo" must be a '
+              'valid date/time, but was "zap".'));
+    });
+
+    test("[].asUri returns URI parameters", () {
+      expect(parameters['uri'].asUri, equals(Uri.parse('http://dartlang.org')));
+    });
+
+    test("[].asUriOr returns URI parameters", () {
+      expect(parameters['uri'].asUriOr(Uri.parse('http://google.com')),
+          equals(Uri.parse('http://dartlang.org')));
+    });
+
+    test("[].asUri fails for non-URI parameters", () {
+      expect(() => parameters['int'].asUri,
+          throwsInvalidParams('Parameter "int" for method "foo" must be a '
+              'string, but was 1.'));
+    });
+
+    test("[].asUriOr succeeds for absent parameters", () {
+      expect(parameters['fblthp'].asUriOr(Uri.parse('http://google.com')),
+          equals(Uri.parse('http://google.com')));
+    });
+
+    test("[].asUri fails for non-URI parameters", () {
+      expect(() => parameters['int'].asUri,
+          throwsInvalidParams('Parameter "int" for method "foo" must be a '
+              'string, but was 1.'));
+    });
+
+    test("[].asUri fails for invalid URIs", () {
+      expect(() => parameters['invalid-uri'].asUri,
+          throwsInvalidParams('Parameter "invalid-uri" for method "foo" must '
+              'be a valid URI, but was "http://[::1".\n'
+              'Bad end of IPv6 host'));
+    });
+
     group("with a nested parameter map", () {
       var nested;
       setUp(() => nested = parameters['map']);