Improve handling of git dependencies and improve associated tests
diff --git a/pkgs/pubspec_parse/build.yaml b/pkgs/pubspec_parse/build.yaml
index ec706e4..930aad1 100644
--- a/pkgs/pubspec_parse/build.yaml
+++ b/pkgs/pubspec_parse/build.yaml
@@ -6,6 +6,7 @@
       json_serializable:
         generate_for:
           - lib/src/pubspec.dart
+          - lib/src/dependency.dart
         options:
           any_map: true
           checked: true
diff --git a/pkgs/pubspec_parse/lib/src/dependency.dart b/pkgs/pubspec_parse/lib/src/dependency.dart
index 9353250..5260239 100644
--- a/pkgs/pubspec_parse/lib/src/dependency.dart
+++ b/pkgs/pubspec_parse/lib/src/dependency.dart
@@ -4,6 +4,9 @@
 
 import 'package:json_annotation/json_annotation.dart';
 import 'package:pub_semver/pub_semver.dart';
+import 'package:pubspec_parse/src/errors.dart';
+
+part 'dependency.g.dart';
 
 Map<String, Dependency> parseDeps(Map source) =>
     source?.map((k, v) {
@@ -96,36 +99,43 @@
   String get _info => name;
 }
 
+@JsonSerializable(createToJson: false)
 class GitDependency extends Dependency {
+  @JsonKey(fromJson: _parseUri)
   final Uri url;
   final String ref;
   final String path;
 
-  GitDependency(this.url, this.ref, this.path) : super._();
+  GitDependency(this.url, this.ref, this.path) : super._() {
+    if (url == null) {
+      throw new ArgumentError.value(url, 'url', '"url" cannot be null.');
+    }
+  }
 
   factory GitDependency.fromData(Object data) {
-    String url;
-    String path;
-    String ref;
-
     if (data is String) {
-      url = data;
-    } else if (data is Map) {
-      url = data['url'] as String;
-      path = data['path'] as String;
-      ref = data['ref'] as String;
-    } else {
-      throw new ArgumentError.value(data, 'git', 'Must be a String or a Map.');
+      data = {'url': data};
     }
 
-    // TODO: validate `url` is a valid URI
-    return new GitDependency(Uri.parse(url), ref, path);
+    if (data is Map) {
+      // TODO: Need JsonKey.required
+      // https://github.com/dart-lang/json_serializable/issues/216
+      if (!data.containsKey('url')) {
+        throw new BadKeyException(data, 'url', '"url" is required.');
+      }
+
+      return _$GitDependencyFromJson(data);
+    }
+
+    throw new ArgumentError.value(data, 'git', 'Must be a String or a Map.');
   }
 
   @override
   String get _info => 'url@$url';
 }
 
+Uri _parseUri(String value) => Uri.parse(value);
+
 class PathDependency extends Dependency {
   final String path;
 
diff --git a/pkgs/pubspec_parse/lib/src/dependency.g.dart b/pkgs/pubspec_parse/lib/src/dependency.g.dart
new file mode 100644
index 0000000..cdb06bb
--- /dev/null
+++ b/pkgs/pubspec_parse/lib/src/dependency.g.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'dependency.dart';
+
+// **************************************************************************
+// Generator: JsonSerializableGenerator
+// **************************************************************************
+
+GitDependency _$GitDependencyFromJson(Map json) => $checkedNew(
+    'GitDependency',
+    json,
+    () => new GitDependency(
+        $checkedConvert(
+            json, 'url', (v) => v == null ? null : _parseUri(v as String)),
+        $checkedConvert(json, 'ref', (v) => v as String),
+        $checkedConvert(json, 'path', (v) => v as String)));
diff --git a/pkgs/pubspec_parse/lib/src/errors.dart b/pkgs/pubspec_parse/lib/src/errors.dart
index 9b1489c..288553c 100644
--- a/pkgs/pubspec_parse/lib/src/errors.dart
+++ b/pkgs/pubspec_parse/lib/src/errors.dart
@@ -14,14 +14,14 @@
   if (innerError is BadKeyException) {
     var map = innerError.map;
     if (map is YamlMap) {
-      var key = map.nodes.keys.singleWhere((key) {
-        return (key as YamlScalar).value == innerError.key;
-      }, orElse: () => null);
+      // if the associated key exists, use that as the error node,
+      // otherwise use the map itself
+      var node = map.nodes.keys.cast<YamlNode>().singleWhere((key) {
+        return key.value == innerError.key;
+      }, orElse: () => map);
 
-      if (key is YamlScalar) {
-        return new ParsedYamlException._(innerError.message, key,
-            innerError: error, innerStack: stack);
-      }
+      return new ParsedYamlException._(innerError.message, node,
+          innerError: error, innerStack: stack);
     }
   } else if (innerError is ParsedYamlException) {
     return innerError;
diff --git a/pkgs/pubspec_parse/test/dependency_test.dart b/pkgs/pubspec_parse/test/dependency_test.dart
index 21c621d..8ede482 100644
--- a/pkgs/pubspec_parse/test/dependency_test.dart
+++ b/pkgs/pubspec_parse/test/dependency_test.dart
@@ -29,10 +29,22 @@
     expect(dep.toString(), 'SdkDependency: flutter');
   });
 
-  test('GitDependency', () {
-    var dep = _dependency<GitDependency>({'git': 'bob'});
-    expect(dep.url.toString(), 'bob');
-    expect(dep.toString(), 'GitDependency: url@bob');
+  test('GitDependency - string', () {
+    var dep = _dependency<GitDependency>({'git': 'url'});
+    expect(dep.url.toString(), 'url');
+    expect(dep.path, isNull);
+    expect(dep.ref, isNull);
+    expect(dep.toString(), 'GitDependency: url@url');
+  });
+
+  test('GitDependency - map', () {
+    var dep = _dependency<GitDependency>({
+      'git': {'url': 'url', 'path': 'path', 'ref': 'ref'}
+    });
+    expect(dep.url.toString(), 'url');
+    expect(dep.path, 'path');
+    expect(dep.ref, 'ref');
+    expect(dep.toString(), 'GitDependency: url@url');
   });
 
   test('HostedDepedency', () {
@@ -90,6 +102,31 @@
           ^^^''');
     });
 
+    test('git - empty map', () {
+      _expectThrows({'git': {}}, r'''
+line 5, column 11: "url" is required.
+   "git": {}
+          ^^''');
+    });
+
+    test('git - null url', () {
+      _expectThrows({
+        'git': {'url': null}
+      }, r'''
+line 6, column 12: "url" cannot be null.
+    "url": null
+           ^^^^^''');
+    });
+
+    test('git - int url', () {
+      _expectThrows({
+        'git': {'url': 42}
+      }, r'''
+line 6, column 12: Unsupported value for `url`.
+    "url": 42
+           ^^^''');
+    });
+
     test('path - null content', () {
       _expectThrows({'path': null}, r'''
 line 5, column 12: Cannot be null.