Skip to content

Commit

Permalink
adds explanatory detail for AutomergeText (#146)
Browse files Browse the repository at this point in the history
* adds explanatory detail about AutomergeText, bind/no bound, and some examples of how it might be used
* re-ordered text to make it an easier flow to read
* better docs and code cleanup
  • Loading branch information
heckj authored Apr 27, 2024
1 parent bb38954 commit e8e01ab
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 31 deletions.
34 changes: 34 additions & 0 deletions Sources/Automerge/Codable/AutomergeText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,33 @@ import Foundation
// the flat `String` variations from the underlying data source in Automerge.

/// A reference to a Text object within an Automerge document.
///
/// You can create an instance of this class and provide initial values before it is attached to an Automerge document.
/// Changes that you make this instance will be local only to this class until it is explicitly attached (bound).
/// Use ``bind(doc:path:)`` to associate this instance with a specific schema location within an Automerge document,
/// or ``AutomergeEncoder/encode(_:)-7gbuh`` it as part of a larger document model into an Automerge document to store
/// the value.
///
/// When you use ``AutomergeDecoder/decode(_:)`` into a type that uses `AutomergeText`, the instances returned from the
/// decoder in your model are already bound to Automerge document.
///
/// For example, the initial non-bound state allows you to create next Text fields in SwiftUI,
/// and at a later point encode them the data model for your app.
///
/// In the [MeetingNotes demo app](https://github.com/automerge/MeetingNotes/), this is done when it creates a new
/// agenda item [within the
/// app](https://github.com/automerge/MeetingNotes/blob/main/MeetingNotes/Views/MeetingNotesDocumentView.swift):
///
/// ```swift
/// // Add a new AgendaItem (which uses AutomergeText) with a new value, not yet reflected in the Document.
/// document.model.agendas.append(AgendaItem(title: ""))
///
/// // Store the updated list of agenda items back into the document to save the value into the Document.
/// updateDoc()
/// ```
///
/// > Warning: Although `AutomergeText` conforms to `ObservableObject`, it does not send notifications of content
/// changes until it has been bound to an Automerge document.
public final class AutomergeText: Codable {
var doc: Document?
var objId: ObjId?
Expand Down Expand Up @@ -40,6 +67,13 @@ public final class AutomergeText: Codable {
}
}

/// Returns a Boolean value that indicates the AutomergeText instance is actively bound to an Automerge document.
///
/// Before an instance is bound, changes made to the content of AutomergeText are local only to this class, and
/// and updates won't be reflected in an Automerge document.
///
/// Use ``bind(doc:path:)`` to associate this instance with a specific schema location within an Automerge document,
/// or encode it as part of a larger document model into an Automerge document to store the value.
public var isBound: Bool {
doc != nil && objId != nil
}
Expand Down
22 changes: 3 additions & 19 deletions Sources/Automerge/ScalarValueRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ public enum BooleanScalarConversionError: LocalizedError {
}

extension Bool: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Bool, BooleanScalarConversionError> {
switch val {
case let .Boolean(b):
Expand All @@ -78,9 +77,9 @@ public enum URLScalarConversionError: LocalizedError {
/// A localized message describing what error occurred.
public var errorDescription: String? {
switch self {
case .notStringScalarValue(let scalarValue):
case let .notStringScalarValue(scalarValue):
return "Failed to read the scalar value \(scalarValue) as a String before converting to URL."
case .notMatchingURLScheme(let string):
case let .notMatchingURLScheme(string):
return "Failed to convert the string \(string) to URL."
}
}
Expand All @@ -90,7 +89,6 @@ public enum URLScalarConversionError: LocalizedError {
}

extension URL: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<URL, URLScalarConversionError> {
switch val {
case let .String(urlString):
Expand All @@ -105,11 +103,10 @@ extension URL: ScalarValueRepresentable {
}

public func toScalarValue() -> ScalarValue {
.String(self.absoluteString)
.String(absoluteString)
}
}


// MARK: String Conversions

/// A failure to convert an Automerge scalar value to or from a String representation.
Expand All @@ -129,7 +126,6 @@ public enum StringScalarConversionError: LocalizedError {
}

extension String: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<String, StringScalarConversionError> {
switch val {
case let .String(s):
Expand Down Expand Up @@ -163,7 +159,6 @@ public enum BytesScalarConversionError: LocalizedError {
}

extension Data: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Data, BytesScalarConversionError> {
switch val {
case let .Bytes(d):
Expand Down Expand Up @@ -197,7 +192,6 @@ public enum UIntScalarConversionError: LocalizedError {
}

extension UInt: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<UInt, UIntScalarConversionError> {
switch val {
case let .Uint(d):
Expand Down Expand Up @@ -231,7 +225,6 @@ public enum IntScalarConversionError: LocalizedError {
}

extension Int: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Int, IntScalarConversionError> {
switch val {
case let .Int(d):
Expand All @@ -247,7 +240,6 @@ extension Int: ScalarValueRepresentable {
}

extension Int8: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Int8, IntScalarConversionError> {
switch val {
case let .Int(d):
Expand All @@ -263,7 +255,6 @@ extension Int8: ScalarValueRepresentable {
}

extension Int16: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Int16, IntScalarConversionError> {
switch val {
case let .Int(d):
Expand All @@ -279,7 +270,6 @@ extension Int16: ScalarValueRepresentable {
}

extension Int32: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Int32, IntScalarConversionError> {
switch val {
case let .Int(d):
Expand All @@ -295,7 +285,6 @@ extension Int32: ScalarValueRepresentable {
}

extension Int64: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Int64, IntScalarConversionError> {
switch val {
case let .Int(d):
Expand All @@ -313,7 +302,6 @@ extension Int64: ScalarValueRepresentable {
// MARK: UInt types

extension UInt8: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<UInt8, IntScalarConversionError> {
switch val {
case let .Uint(d):
Expand All @@ -329,7 +317,6 @@ extension UInt8: ScalarValueRepresentable {
}

extension UInt16: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<UInt16, IntScalarConversionError> {
switch val {
case let .Uint(d):
Expand All @@ -345,7 +332,6 @@ extension UInt16: ScalarValueRepresentable {
}

extension UInt32: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<UInt32, IntScalarConversionError> {
switch val {
case let .Uint(d):
Expand All @@ -361,7 +347,6 @@ extension UInt32: ScalarValueRepresentable {
}

extension UInt64: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<UInt64, IntScalarConversionError> {
switch val {
case let .Uint(d):
Expand Down Expand Up @@ -447,7 +432,6 @@ public enum TimestampScalarConversionError: LocalizedError {
}

extension Date: ScalarValueRepresentable {

public static func fromScalarValue(_ val: ScalarValue) -> Result<Date, TimestampScalarConversionError> {
switch val {
case let .Timestamp(d):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public extension Document {
/// Returns a Boolean value that indicates whether the latest contents another document are equivalent.
func equivalentContents(_ anotherDoc: Document) -> Bool {
do {
let doc1Contents = try self.parseToSchema(self, from: .ROOT)
let doc1Contents = try parseToSchema(self, from: .ROOT)
let doc2Contents = try anotherDoc.parseToSchema(anotherDoc, from: .ROOT)
return doc1Contents == doc2Contents
} catch {
Expand Down
7 changes: 3 additions & 4 deletions Tests/AutomergeTests/CodableTests/AutomergeEncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ final class AutomergeEncoderTests: XCTestCase {
}
let automergeEncoder = AutomergeEncoder(doc: doc)


let sample = SimpleStruct(
name: "henry",
duration: 3.14159,
Expand Down Expand Up @@ -124,7 +123,8 @@ final class AutomergeEncoderTests: XCTestCase {
duration: 3.14159,
flag: true,
count: 5,
url: URL(string: "http://url.com")!)
url: URL(string: "http://url.com")!
)
)

try automergeEncoder.encode(sample)
Expand Down Expand Up @@ -398,8 +398,7 @@ final class AutomergeEncoderTests: XCTestCase {

let model: TestModel? = TestModel(urls: [URL(string: "url.com")!])
try automergeEncoder.encode(model)
if case let .Object(listNode, .List) = try doc.get(obj: ObjId.ROOT, key: "urls")
{
if case let .Object(listNode, .List) = try doc.get(obj: ObjId.ROOT, key: "urls") {
XCTAssertEqual(try doc.get(obj: listNode, index: 0), .Scalar(.String("url.com")))
} else {
try XCTFail("Didn't find an object at \(String(describing: doc.get(obj: ObjId.ROOT, key: "urls")))")
Expand Down
14 changes: 7 additions & 7 deletions Tests/AutomergeTests/InteropTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class InteropTests: XCTestCase {
func testFixtureFileLoad() throws {
XCTAssertNotNil(markdownData)
}

struct ExemplarStructure: Codable, Equatable {
var title: String
var notes: AutomergeText
Expand All @@ -63,24 +63,24 @@ class InteropTests: XCTestCase {
var bytes: Data
var bool: Bool
}

func testExemplarAutomergeDocRepresentations() throws {
guard let data = try dataFrom(resource: "exemplar") else {
XCTFail("Unable to load exemplar fixture")
return
}
let doc = try Document(data)
let decoder = AutomergeDecoder(doc: doc)

let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
let expectedDate = formatter.date(from: "1941-04-26T08:17:01.000Z")
let magicValue: String = "856f4a83"

let magicValue = "856f4a83"
// hex values for the magic value of an Automerge document

let exemplar = try decoder.decode(ExemplarStructure.self)

XCTAssertEqual(exemplar.timestamp, expectedDate)
XCTAssertEqual(exemplar.title, "Hello πŸ‡¬πŸ‡§πŸ‘¨β€πŸ‘¨β€πŸ‘§β€πŸ‘¦πŸ˜€")
XCTAssertEqual(exemplar.notes.value, "πŸ‡¬πŸ‡§πŸ‘¨β€πŸ‘¨β€πŸ‘§β€πŸ‘¦πŸ˜€")
Expand Down

0 comments on commit e8e01ab

Please sign in to comment.