Add helper methods for constructing mojom types.

The generated mojom constructors take zero arguments and all properties
must be set by assignment.  This is annoying in practice.  The new
methods wrap the constructor and provide validation and default values.

Change-Id: Ic0028221df94c9e567a945faeb90486f7bbe5318
diff --git a/dart/lib/src/naming/util.dart b/dart/lib/src/naming/util.dart
index 9f1d7b6..319519e 100644
--- a/dart/lib/src/naming/util.dart
+++ b/dart/lib/src/naming/util.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+library naming;
+
 import 'dart:convert' show UTF8;
 
 // TODO(aghassemi): Move these naming utilities out of Syncbase once we
diff --git a/dart/lib/src/nosql/database.dart b/dart/lib/src/nosql/database.dart
index 67907d8..1069614 100644
--- a/dart/lib/src/nosql/database.dart
+++ b/dart/lib/src/nosql/database.dart
@@ -90,7 +90,6 @@
     return v.resumeMarker;
   }
 
-  // TODO(nlacasse): Make a BatchDatabase class similar to what we did in JS.
   Future<String> beginBatch(mojom.BatchOptions opts) async {
     var v = await _proxy.ptr.dbBeginBatch(fullName, opts);
     if (isError(v.err)) throw v.err;
diff --git a/dart/lib/src/nosql/watch_change_types.dart b/dart/lib/src/nosql/watch_change_types.dart
index 8e5b2a5..50e5105 100644
--- a/dart/lib/src/nosql/watch_change_types.dart
+++ b/dart/lib/src/nosql/watch_change_types.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+part of syncbase_client;
+
 class WatchChangeTypes {
   static const int put = 0;
   static const int delete = 1;
diff --git a/dart/lib/syncbase_client.dart b/dart/lib/syncbase_client.dart
index 105ae38..39ec4f5 100644
--- a/dart/lib/syncbase_client.dart
+++ b/dart/lib/syncbase_client.dart
@@ -13,14 +13,9 @@
 import 'gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart' as mojom;
 
 // Export struct types from syncbase.mojom.
-// TODO(nlacasse): Create wrapper around Perms, and possibly other struct
-// constructors, since the default constructors are not user-friendly.  They
-// take zero arguments, so all fields must be populated with assignments.
 export 'gen/dart-gen/mojom/lib/mojo/syncbase.mojom.dart'
     show BatchOptions, Perms, SyncGroupMemberInfo, SyncGroupSpec, WatchChange;
 
-export 'src/nosql/watch_change_types.dart' show WatchChangeTypes;
-
 part 'src/app.dart';
 part 'src/named_resource.dart';
 part 'src/util.dart';
@@ -29,6 +24,7 @@
 part 'src/nosql/rowrange.dart';
 part 'src/nosql/syncgroup.dart';
 part 'src/nosql/table.dart';
+part 'src/nosql/watch_change_types.dart';
 
 typedef void ConnectToServiceFn(String url, bindings.ProxyBase proxy);
 
@@ -68,4 +64,69 @@
     var v = await _proxy.ptr.serviceSetPermissions(perms, version);
     if (isError(v.err)) throw v.err;
   }
+
+  // Helper methods that wrap the generated constructors for mojom types. The
+  // generated constructors take zero arguments, so they are difficult to use.
+
+  static mojom.BatchOptions batchOptions(
+      {String hint: null, bool readOnly: false}) {
+    return new mojom.BatchOptions()
+      ..hint = hint
+      ..readOnly = readOnly;
+  }
+
+  static mojom.Perms perms([String json = '{}']) {
+    return new mojom.Perms()..json = json;
+  }
+
+  static mojom.SyncGroupMemberInfo syncGroupMemberInfo({int syncPriority: 0}) {
+    return new mojom.SyncGroupMemberInfo()..syncPriority = syncPriority;
+  }
+
+  static mojom.SyncGroupSpec syncGroupSpec(
+      {String description: '',
+      bool isPrivate: false,
+      mojom.Perms perms,
+      List<String> prefixes,
+      List<String> mountTables}) {
+    if (prefixes == null) {
+      throw new ArgumentError('prefixes must be specified');
+    }
+    return new mojom.SyncGroupSpec()
+      ..description = description
+      ..isPrivate = isPrivate
+      ..perms = perms ?? SyncbaseClient.perms()
+      ..prefixes = prefixes
+      ..mountTables = mountTables ?? [];
+  }
+
+  static mojom.WatchChange watchChange(
+      {String tableName,
+      String rowName,
+      List<int> valueBytes,
+      List<int> resumeMarker,
+      int changeType,
+      bool fromSync: false,
+      bool continued: false}) {
+    if (tableName == null) {
+      throw new ArgumentError('tableName must be specified');
+    }
+    if (rowName == null) {
+      throw new ArgumentError('rowName must be specified');
+    }
+    if (resumeMarker == null) {
+      throw new ArgumentError('resumeMarker must be specified');
+    }
+    if (changeType == null) {
+      throw new ArgumentError('changeType must be specified');
+    }
+    return new mojom.WatchChange()
+      ..tableName = tableName
+      ..rowName = rowName
+      ..valueBytes = valueBytes ?? []
+      ..resumeMarker = resumeMarker
+      ..changeType = changeType
+      ..fromSync = fromSync
+      ..continued = continued;
+  }
 }
diff --git a/dart/test/integration/syncbase_database_test.dart b/dart/test/integration/syncbase_database_test.dart
index ce3a3dd..e3dd55e 100644
--- a/dart/test/integration/syncbase_database_test.dart
+++ b/dart/test/integration/syncbase_database_test.dart
@@ -57,22 +57,21 @@
 
     await table.put('row2', UTF8.encode('value2'));
     resumeMarker = await db.getResumeMarker();
-    var expectedChange = new WatchChange();
-    expectedChange.tableName = table.name;
-    expectedChange.rowName = 'row2';
-    expectedChange.changeType = WatchChangeTypes.put;
-    expectedChange.valueBytes = UTF8.encode('value2');
-    expectedChange.resumeMarker = resumeMarker;
+    var expectedChange = SyncbaseClient.watchChange(
+        tableName: table.name,
+        rowName: 'row2',
+        changeType: WatchChangeTypes.put,
+        valueBytes: UTF8.encode('value2'),
+        resumeMarker: resumeMarker);
     expectedChanges.add(expectedChange);
 
     await table.delete('row2');
     resumeMarker = await db.getResumeMarker();
-    expectedChange = new WatchChange();
-    expectedChange.tableName = table.name;
-    expectedChange.rowName = 'row2';
-    expectedChange.changeType = WatchChangeTypes.delete;
-    expectedChange.valueBytes = new List<int>();
-    expectedChange.resumeMarker = resumeMarker;
+    expectedChange = SyncbaseClient.watchChange(
+        tableName: table.name,
+        rowName: 'row2',
+        changeType: WatchChangeTypes.delete,
+        resumeMarker: resumeMarker);
     expectedChanges.add(expectedChange);
 
     // Ensure we see all the expected changes in order in the watch stream.
diff --git a/dart/test/integration/syncbase_sync_group_test.dart b/dart/test/integration/syncbase_sync_group_test.dart
index 91532dd..1f2f467 100644
--- a/dart/test/integration/syncbase_sync_group_test.dart
+++ b/dart/test/integration/syncbase_sync_group_test.dart
@@ -14,8 +14,7 @@
 runSyncGroupTests(SyncbaseClient c) {
   // TODO(nlacasse): Where does this magic number 8 come from? It's in
   // syncgroup_test.go.
-  var myInfo = new SyncGroupMemberInfo();
-  myInfo.syncPriority = 8;
+  var myInfo = SyncbaseClient.syncGroupMemberInfo(syncPriority: 8);
 
   test('db.syncGroup returns a SyncGroup with name', () {
     var app = c.app(utils.uniqueName('app'));
@@ -32,7 +31,7 @@
     await db.create(utils.emptyPerms());
     var sg = db.syncGroup(utils.uniqueName('sg'));
 
-    var emptySpec = new SyncGroupSpec();
+    var emptySpec = SyncbaseClient.syncGroupSpec(prefixes: []);
     expect(sg.create(emptySpec, myInfo), throws);
   });
 
@@ -43,11 +42,8 @@
     await db.create(utils.emptyPerms());
     var sg = db.syncGroup(utils.uniqueName('sg'));
 
-    var spec = new SyncGroupSpec();
-    spec.description = 'test syncgroup ${sg.name}';
-    spec.perms = utils.emptyPerms();
-    spec.prefixes = ['t1/foo'];
-    spec.mountTables = [];
+    var spec = SyncbaseClient.syncGroupSpec(
+        description: 'test syncgroup ${sg.name}', prefixes: ['t1/foo']);
 
     await sg.create(spec, myInfo);
   });
@@ -61,11 +57,8 @@
     var sgName = utils.uniqueName('sg-/!@#%^&*():\$\x01\xfe');
     var sg = db.syncGroup(sgName);
 
-    var spec = new SyncGroupSpec();
-    spec.description = 'test syncgroup ${sgName}';
-    spec.perms = utils.emptyPerms();
-    spec.prefixes = ['t1/foo'];
-    spec.mountTables = [];
+    var spec = SyncbaseClient.syncGroupSpec(
+        description: 'test syncgroup ${sgName}', prefixes: ['t1/foo']);
 
     await sg.create(spec, myInfo);
   });
@@ -77,20 +70,14 @@
     await db.create(utils.emptyPerms());
 
     var sg1 = db.syncGroup(utils.uniqueName('sg'));
-    var spec1 = new SyncGroupSpec();
-    spec1.description = 'test nested syncgroup ${sg1.name}';
-    spec1.perms = utils.emptyPerms();
-    spec1.prefixes = ['t1/foo'];
-    spec1.mountTables = [];
+    var spec1 = SyncbaseClient.syncGroupSpec(
+        description: 'test nested syncgroup ${sg1.name}', prefixes: ['t1/foo']);
 
     await sg1.create(spec1, myInfo);
 
     var sg2 = db.syncGroup(utils.uniqueName('sg'));
-    var spec2 = new SyncGroupSpec();
-    spec2.description = 'test nested syncgroup ${sg2.name}';
-    spec2.perms = utils.emptyPerms();
-    spec2.prefixes = ['t1/foo'];
-    spec2.mountTables = [];
+    var spec2 = SyncbaseClient.syncGroupSpec(
+        description: 'test nested syncgroup ${sg2.name}', prefixes: ['t1/foo']);
 
     await sg2.create(spec2, myInfo);
   });
@@ -102,20 +89,14 @@
     await db.create(utils.emptyPerms());
 
     var sgName = utils.uniqueName('sg');
-    var spec1 = new SyncGroupSpec();
-    spec1.description = 'test syncgroup ${sgName}';
-    spec1.perms = utils.emptyPerms();
-    spec1.prefixes = ['t1/foo'];
-    spec1.mountTables = [];
+    var spec1 = SyncbaseClient.syncGroupSpec(
+        description: 'test syncgroup ${sgName}', prefixes: ['t1/foo']);
 
     var sg1 = db.syncGroup(sgName);
     await sg1.create(spec1, myInfo);
 
-    var spec2 = new SyncGroupSpec();
-    spec2.description = 'another syncgroup ${sgName}';
-    spec2.perms = utils.emptyPerms();
-    spec2.prefixes = ['t2/bar'];
-    spec2.mountTables = [];
+    var spec2 = SyncbaseClient.syncGroupSpec(
+        description: 'another syncgroup ${sgName}', prefixes: ['t2/bar']);
 
     var sg2 = db.syncGroup(sgName);
     expect(sg2.create(spec2, myInfo), throws);
@@ -129,11 +110,8 @@
     var sgName = utils.uniqueName('sg');
     var sg = db.syncGroup(sgName);
 
-    var spec = new SyncGroupSpec();
-    spec.description = 'test syncgroup ${sgName}';
-    spec.perms = utils.emptyPerms();
-    spec.prefixes = ['t1/foo'];
-    spec.mountTables = [];
+    var spec = SyncbaseClient.syncGroupSpec(
+        description: 'test syncgroup ${sgName}', prefixes: ['t1/foo']);
 
     await sg.create(spec, myInfo);
 
@@ -141,11 +119,8 @@
     expect(gotSpec.description, equals(spec.description));
     expect(gotSpec.prefixes, equals(spec.prefixes));
 
-    var newSpec = new SyncGroupSpec();
-    newSpec.description = 'a totally new spec ${sgName}';
-    newSpec.perms = utils.emptyPerms();
-    newSpec.prefixes = ['t1/foo'];
-    newSpec.mountTables = [];
+    var newSpec = SyncbaseClient.syncGroupSpec(
+        description: 'a totally new spec ${sgName}', prefixes: ['t1/foo']);
 
     await sg.setSpec(newSpec, '');
 
diff --git a/dart/test/integration/utils.dart b/dart/test/integration/utils.dart
index 38f2efa..578db8e 100644
--- a/dart/test/integration/utils.dart
+++ b/dart/test/integration/utils.dart
@@ -4,10 +4,10 @@
 
 library utils;
 
-import 'package:ether/syncbase_client.dart' show Perms;
+import 'package:ether/syncbase_client.dart' show Perms, SyncbaseClient;
 
 // Returns an empty Perms object.
-Perms emptyPerms() => new Perms()..json = '{}';
+Perms emptyPerms() => SyncbaseClient.perms();
 
 // Returns the current timestamp in ms since epoch.
 int timestamp() => new DateTime.now().millisecondsSinceEpoch;