swift/ref: Implement decodeId in CGO layer

Instead of replicating the Go logic in Swift (and Java), wrap the
existing util.DecodeId in a CGO-accessible method.

Also renames Id.swift to Identifier.swift.

Closes vanadium/issues#1400

MultiPart: 2/2
Change-Id: I497f4161fcdd53a2421f3c5c8d27ff2ff8416ade
diff --git a/Syncbase/Source/Id.swift b/Syncbase/Source/Identifier.swift
similarity index 66%
rename from Syncbase/Source/Id.swift
rename to Syncbase/Source/Identifier.swift
index 9a19864..a052f5b 100644
--- a/Syncbase/Source/Id.swift
+++ b/Syncbase/Source/Identifier.swift
@@ -21,23 +21,13 @@
   }
 
   public func encode() throws -> String {
-    var cStr = v23_syncbase_String()
-    let id = try v23_syncbase_Id(toCore())
-    v23_syncbase_EncodeId(id, &cStr)
-    // If there was a UTF-8 problem, it would have been thrown when UTF-8 encoding the id above.
-    // Therefore, we can be confident in unwrapping the conditional here.
-    return cStr.toString()!
+    // If there was a UTF-8 problem, it would have been thrown when UTF-8 encoding core's call
+    // to CGO. Therefore, we can be confident in unwrapping the conditional here.
+    return try toCore().encode().toString()!
   }
 
-  // TODO(zinman): Replace decode method implementations with call to Cgo.
-  static let separator = ","
   public static func decode(encodedId: String) throws -> Identifier {
-    let parts = encodedId.componentsSeparatedByString(separator)
-    if parts.count != 2 {
-      throw SyncbaseError.IllegalArgument(detail: "Invalid encoded id: \(encodedId)")
-    }
-    let (blessing, name) = (parts[0], parts[1])
-    return Identifier(name: name, blessing: blessing)
+    return Identifier(coreId: try SyncbaseCore.Identifier.decode(encodedId))
   }
 
   public var hashValue: Int {
diff --git a/Syncbase/Syncbase.xcodeproj/project.pbxproj b/Syncbase/Syncbase.xcodeproj/project.pbxproj
index 24528bb..28b494a 100644
--- a/Syncbase/Syncbase.xcodeproj/project.pbxproj
+++ b/Syncbase/Syncbase.xcodeproj/project.pbxproj
@@ -14,7 +14,7 @@
 		9374F6AF1D01081D004ECE59 /* Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A41D01081D004ECE59 /* Collection.swift */; };
 		9374F6B01D01081D004ECE59 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A51D01081D004ECE59 /* Database.swift */; };
 		9374F6B11D01081D004ECE59 /* DatabaseHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A61D01081D004ECE59 /* DatabaseHandle.swift */; };
-		9374F6B21D01081D004ECE59 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A71D01081D004ECE59 /* Id.swift */; };
+		9374F6B21D01081D004ECE59 /* Identifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A71D01081D004ECE59 /* Identifier.swift */; };
 		9374F6B31D01081D004ECE59 /* Syncbase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A81D01081D004ECE59 /* Syncbase.swift */; };
 		9374F6B41D01081D004ECE59 /* Syncgroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6A91D01081D004ECE59 /* Syncgroup.swift */; };
 		9374F6B51D01081D004ECE59 /* SyncgroupInvite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6AA1D01081D004ECE59 /* SyncgroupInvite.swift */; };
@@ -64,7 +64,7 @@
 		9374F6A41D01081D004ECE59 /* Collection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = "<group>"; };
 		9374F6A51D01081D004ECE59 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
 		9374F6A61D01081D004ECE59 /* DatabaseHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseHandle.swift; sourceTree = "<group>"; };
-		9374F6A71D01081D004ECE59 /* Id.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
+		9374F6A71D01081D004ECE59 /* Identifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Identifier.swift; sourceTree = "<group>"; };
 		9374F6A81D01081D004ECE59 /* Syncbase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncbase.swift; sourceTree = "<group>"; };
 		9374F6A91D01081D004ECE59 /* Syncgroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Syncgroup.swift; sourceTree = "<group>"; };
 		9374F6AA1D01081D004ECE59 /* SyncgroupInvite.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncgroupInvite.swift; sourceTree = "<group>"; };
@@ -128,7 +128,7 @@
 				9374F6A51D01081D004ECE59 /* Database.swift */,
 				9374F6A61D01081D004ECE59 /* DatabaseHandle.swift */,
 				93D90C4C1D080E18004A8E72 /* Error.swift */,
-				9374F6A71D01081D004ECE59 /* Id.swift */,
+				9374F6A71D01081D004ECE59 /* Identifier.swift */,
 				9374F68F1D01074B004ECE59 /* Info.plist */,
 				938DEA9E1D12257E003C9734 /* OAuth.swift */,
 				9374F6901D01074B004ECE59 /* Syncbase.h */,
@@ -296,7 +296,7 @@
 				938DEA9F1D12257E003C9734 /* OAuth.swift in Sources */,
 				9374F6AF1D01081D004ECE59 /* Collection.swift in Sources */,
 				9374F6B11D01081D004ECE59 /* DatabaseHandle.swift in Sources */,
-				9374F6B21D01081D004ECE59 /* Id.swift in Sources */,
+				9374F6B21D01081D004ECE59 /* Identifier.swift in Sources */,
 				93D90C4D1D080E18004A8E72 /* Error.swift in Sources */,
 				938DEA861D0BAE57003C9734 /* Blessing.swift in Sources */,
 				9374F6B51D01081D004ECE59 /* SyncgroupInvite.swift in Sources */,
diff --git a/Syncbase/Tests/BasicDatabaseTests.swift b/Syncbase/Tests/BasicDatabaseTests.swift
index ed35a32..3ce4e8d 100644
--- a/Syncbase/Tests/BasicDatabaseTests.swift
+++ b/Syncbase/Tests/BasicDatabaseTests.swift
@@ -107,16 +107,6 @@
   }
 }
 
-class SyncgroupTest: XCTestCase {
-  override class func setUp() {
-    configureDb(disableUserdataSyncgroup: false, disableSyncgroupPublishing: true)
-  }
-
-  override class func tearDown() {
-    Syncbase.shutdown()
-  }
-}
-
 class UserdataTest: SyncgroupTest {
   func testUserdata() {
     withDb { db in
diff --git a/Syncbase/Tests/TestHelpers.swift b/Syncbase/Tests/TestHelpers.swift
index 6bb1226..edc524e 100644
--- a/Syncbase/Tests/TestHelpers.swift
+++ b/Syncbase/Tests/TestHelpers.swift
@@ -77,3 +77,14 @@
     }
   }
 }
+
+// This class serves as a base class to inherit -- it doesn't have any tests itself.
+class SyncgroupTest: XCTestCase {
+  override class func setUp() {
+    configureDb(disableUserdataSyncgroup: false, disableSyncgroupPublishing: true)
+  }
+
+  override class func tearDown() {
+    Syncbase.shutdown()
+  }
+}
diff --git a/SyncbaseCore/Source/Collection.swift b/SyncbaseCore/Source/Collection.swift
index 1ce591b..b942746 100644
--- a/SyncbaseCore/Source/Collection.swift
+++ b/SyncbaseCore/Source/Collection.swift
@@ -352,7 +352,7 @@
   private static func encodedName(databaseId: Identifier, collectionId: Identifier) throws -> String {
     var cStr = v23_syncbase_String()
     v23_syncbase_NamingJoin(
-      v23_syncbase_Strings([try databaseId.encodeId(), try collectionId.encodeId()]),
+      v23_syncbase_Strings([try databaseId.encode(), try collectionId.encode()]),
       &cStr)
     return cStr.toString()!
   }
diff --git a/SyncbaseCore/Source/Database.swift b/SyncbaseCore/Source/Database.swift
index 0d50592..d6ef9aa 100644
--- a/SyncbaseCore/Source/Database.swift
+++ b/SyncbaseCore/Source/Database.swift
@@ -45,7 +45,7 @@
   init(databaseId: Identifier, batchHandle: String?) throws {
     self.databaseId = databaseId
     self.batchHandle = batchHandle
-    self.encodedDatabaseName = try databaseId.encodeId().toString()!
+    self.encodedDatabaseName = try databaseId.encode().toString()!
   }
 
   /// Create creates this Database.
diff --git a/SyncbaseCore/Source/Identifier.swift b/SyncbaseCore/Source/Identifier.swift
index d34ea16..a1fba1a 100644
--- a/SyncbaseCore/Source/Identifier.swift
+++ b/SyncbaseCore/Source/Identifier.swift
@@ -4,7 +4,7 @@
 
 import Foundation
 
-public struct Identifier {
+public struct Identifier: Equatable {
   public let name: String
   public let blessing: String
 
@@ -13,12 +13,22 @@
     self.blessing = blessing
   }
 
-  func encodeId() throws -> v23_syncbase_String {
+  /// ***Advanced users only*** encodes this identifier as needed for low-level Syncbase APIs.
+  public func encode() throws -> v23_syncbase_String {
     var cStr = v23_syncbase_String()
     let id = try v23_syncbase_Id(self)
     v23_syncbase_EncodeId(id, &cStr)
     return cStr
   }
+
+  /// ***Advanced users only*** decodes an identifier as returned from low-level Syncbase APIs.
+  public static func decode(encodedId: String) throws -> Identifier {
+    var cId = v23_syncbase_Id()
+    try VError.maybeThrow { errPtr in
+      v23_syncbase_DecodeId(try v23_syncbase_String(encodedId), &cId, errPtr)
+    }
+    return cId.toIdentifier()!
+  }
 }
 
 public func == (d1: Identifier, d2: Identifier) -> Bool {
diff --git a/SyncbaseCore/SyncbaseCore.xcodeproj/project.pbxproj b/SyncbaseCore/SyncbaseCore.xcodeproj/project.pbxproj
index 3475776..1a89b5e 100644
--- a/SyncbaseCore/SyncbaseCore.xcodeproj/project.pbxproj
+++ b/SyncbaseCore/SyncbaseCore.xcodeproj/project.pbxproj
@@ -37,6 +37,7 @@
 		930DFCE21CEE46DE00738DB8 /* OAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 930DFCE11CEE46DE00738DB8 /* OAuth.swift */; };
 		9351A4941CE46DB9009CC4F4 /* sbcore_amd64.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9351A4931CE46DB9009CC4F4 /* sbcore_amd64.a */; };
 		9374F6681D00FFE5004ECE59 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9374F6661D00FF68004ECE59 /* TestHelpers.swift */; };
+		93B3C8391D41D7B5005B20C8 /* BridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93B3C8381D41D7B5005B20C8 /* BridgeTests.swift */; };
 		93C462791D29D7BD00394BAB /* Neighborhood.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93C462781D29D7BD00394BAB /* Neighborhood.swift */; };
 		93D3AD5C1CE4392A00A80CDA /* libleveldb_amd64.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D3AD581CE4392A00A80CDA /* libleveldb_amd64.a */; };
 		93D3AD5D1CE4392A00A80CDA /* libleveldb_arm64.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D3AD591CE4392A00A80CDA /* libleveldb_arm64.a */; };
@@ -88,6 +89,7 @@
 		930DFCE11CEE46DE00738DB8 /* OAuth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OAuth.swift; sourceTree = "<group>"; };
 		9351A4931CE46DB9009CC4F4 /* sbcore_amd64.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = sbcore_amd64.a; sourceTree = "<group>"; };
 		9374F6661D00FF68004ECE59 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = "<group>"; };
+		93B3C8381D41D7B5005B20C8 /* BridgeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BridgeTests.swift; sourceTree = "<group>"; };
 		93C462781D29D7BD00394BAB /* Neighborhood.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Neighborhood.swift; sourceTree = "<group>"; };
 		93D3AD581CE4392A00A80CDA /* libleveldb_amd64.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libleveldb_amd64.a; sourceTree = "<group>"; };
 		93D3AD591CE4392A00A80CDA /* libleveldb_arm64.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libleveldb_arm64.a; sourceTree = "<group>"; };
@@ -171,8 +173,9 @@
 		30AD2E461CDD508D00A28A0C /* Tests */ = {
 			isa = PBXGroup;
 			children = (
-				30AD2E481CDD508D00A28A0C /* Info.plist */,
 				30A1F52D1CE68465008FC205 /* BasicDatabaseTests.swift */,
+				93B3C8381D41D7B5005B20C8 /* BridgeTests.swift */,
+				30AD2E481CDD508D00A28A0C /* Info.plist */,
 				9374F6661D00FF68004ECE59 /* TestHelpers.swift */,
 			);
 			path = Tests;
@@ -353,6 +356,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				30A1F52E1CE68465008FC205 /* BasicDatabaseTests.swift in Sources */,
+				93B3C8391D41D7B5005B20C8 /* BridgeTests.swift in Sources */,
 				9374F6681D00FFE5004ECE59 /* TestHelpers.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/SyncbaseCore/Tests/BridgeTests.swift b/SyncbaseCore/Tests/BridgeTests.swift
new file mode 100644
index 0000000..493c751
--- /dev/null
+++ b/SyncbaseCore/Tests/BridgeTests.swift
@@ -0,0 +1,24 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+import Foundation
+@testable import SyncbaseCore
+import XCTest
+
+class IdentifierTest: XCTestCase {
+  func testEncodeDecodeEquality() {
+    do {
+      for id in [
+        Identifier(name: "cx_0EA0EA98AA9B4636AA058EA88C25DB2A", blessing: "root:o:app:user"),
+        Identifier(name: "_%/-,,hi", blessing: "root:o:app:user"),
+        Identifier(name: "", blessing: "root:o:app:user"),
+        Identifier(name: "something", blessing: "")] {
+          let mirror = try Identifier.decode(try id.encode().toString()!)
+          XCTAssertEqual(id, mirror)
+      }
+    } catch {
+      XCTFail("Unexpected error: \(error)")
+    }
+  }
+}
\ No newline at end of file