8de2fa9bd4
The AMvalue union, AMlistItem struct, AMmapItem struct, and AMobjItem struct are gone, replaced by the AMitem struct. The AMchangeHashes, AMchanges, AMlistItems, AMmapItems, AMobjItems, AMstrs, and AMsyncHaves iterators are gone, replaced by the AMitems iterator. The AMitem struct is opaque, getting and setting values is now achieved exclusively through function calls. The AMitemsNext(), AMitemsPrev(), and AMresultItem() functions return a pointer to an AMitem struct so you ultimately get the same thing whether you're iterating over a sequence or calling AMmapGet() or AMlistGet(). Calling AMitemResult() on an AMitem struct will produce a new AMresult struct referencing its storage so now the AMresult struct for an iterator can be subsequently freed without affecting the AMitem structs that were filtered out of it. The storage for a set of AMitem structs can be recombined into a single AMresult struct by passing pointers to their corresponding AMresult structs to AMresultCat(). For C/C++ programmers, I've added AMstrCmp(), AMstrdup(), AM{idxType,objType,status,valType}ToString() and AM{idxType,objType,status,valType}FromString(). It's also now possible to pass arbitrary parameters through AMstack{Item,Items,Result}() to a callback function.
207 lines
8.1 KiB
Markdown
207 lines
8.1 KiB
Markdown
# Overview
|
|
|
|
automerge-c exposes a C API that can either be used directly or as the basis
|
|
for other language bindings that have good support for calling C functions.
|
|
|
|
# Installing
|
|
|
|
See the main README for instructions on getting your environment set up and then
|
|
you can build the automerge-c library and install its constituent files within
|
|
a root directory of your choosing (e.g. "/usr/local") like so:
|
|
```shell
|
|
cmake -E make_directory automerge-c/build
|
|
cmake -S automerge-c -B automerge-c/build
|
|
cmake --build automerge-c/build
|
|
cmake --install automerge-c/build --prefix "/usr/local"
|
|
```
|
|
Installation is important because the name, location and structure of CMake's
|
|
out-of-source build subdirectory is subject to change based on the platform and
|
|
the release version; generated headers like `automerge-c/config.h` and
|
|
`automerge-c/utils/enum_string.h` are only sure to be found within their
|
|
installed locations.
|
|
|
|
It's not obvious because they are versioned but the `Cargo.toml` and
|
|
`cbindgen.toml` configuration files are also generated in order to ensure that
|
|
the project name, project version and library name that they contain match those
|
|
specified within the top-level `CMakeLists.txt` file.
|
|
|
|
If you'd like to cross compile the library for different platforms you can do so
|
|
using [cross](https://github.com/cross-rs/cross). For example:
|
|
|
|
- `cross build --manifest-path rust/automerge-c/Cargo.toml -r --target aarch64-unknown-linux-gnu`
|
|
|
|
This will output a shared library in the directory `rust/target/aarch64-unknown-linux-gnu/release/`.
|
|
|
|
You can replace `aarch64-unknown-linux-gnu` with any
|
|
[cross supported targets](https://github.com/cross-rs/cross#supported-targets).
|
|
The targets below are known to work, though other targets are expected to work
|
|
too:
|
|
|
|
- `x86_64-apple-darwin`
|
|
- `aarch64-apple-darwin`
|
|
- `x86_64-unknown-linux-gnu`
|
|
- `aarch64-unknown-linux-gnu`
|
|
|
|
As a caveat, CMake generates the `automerge.h` header file in terms of the
|
|
processor architecture of the computer on which it was built so, for example,
|
|
don't use a header generated for a 64-bit processor if your target is a 32-bit
|
|
processor.
|
|
|
|
# Usage
|
|
|
|
You can build and view the C API's HTML reference documentation like so:
|
|
```shell
|
|
cmake -E make_directory automerge-c/build
|
|
cmake -S automerge-c -B automerge-c/build
|
|
cmake --build automerge-c/build --target automerge_docs
|
|
firefox automerge-c/build/src/html/index.html
|
|
```
|
|
|
|
To get started quickly, look at the
|
|
[examples](https://github.com/automerge/automerge-rs/tree/main/rust/automerge-c/examples).
|
|
|
|
Almost all operations in automerge-c act on an Automerge document
|
|
(`AMdoc` struct) which is structurally similar to a JSON document.
|
|
|
|
You can get a document by calling either `AMcreate()` or `AMload()`. Operations
|
|
on a given document are not thread-safe so you must use a mutex or similar to
|
|
avoid calling more than one function on the same one concurrently.
|
|
|
|
A C API function that could succeed or fail returns a result (`AMresult` struct)
|
|
containing a status code (`AMstatus` enum) and either a sequence of at least one
|
|
item (`AMitem` struct) or a read-only view onto a UTF-8 error message string
|
|
(`AMbyteSpan` struct).
|
|
An item contains up to three components: an index within its parent object
|
|
(`AMbyteSpan` struct or `size_t`), a unique identifier (`AMobjId` struct) and a
|
|
value.
|
|
The result of a successful function call that doesn't produce any values will
|
|
contain a single item that is void (`AM_VAL_TYPE_VOID`).
|
|
A returned result **must** be passed to `AMresultFree()` once the item(s) or
|
|
error message it contains is no longer needed in order to avoid a memory leak.
|
|
```
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <automerge-c/automerge.h>
|
|
#include <automerge-c/utils/string.h>
|
|
|
|
int main(int argc, char** argv) {
|
|
AMresult *docResult = AMcreate(NULL);
|
|
|
|
if (AMresultStatus(docResult) != AM_STATUS_OK) {
|
|
char* const err_msg = AMstrdup(AMresultError(docResult), NULL);
|
|
printf("failed to create doc: %s", err_msg);
|
|
free(err_msg);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMdoc *doc;
|
|
AMitemToDoc(AMresultItem(docResult), &doc);
|
|
|
|
// useful code goes here!
|
|
|
|
cleanup:
|
|
AMresultFree(docResult);
|
|
}
|
|
```
|
|
|
|
If you are writing an application in C, the `AMstackItem()`, `AMstackItems()`
|
|
and `AMstackResult()` functions enable the lifetimes of anonymous results to be
|
|
centrally managed and allow the same validation logic to be reused without
|
|
relying upon the `goto` statement (see examples/quickstart.c).
|
|
|
|
If you are wrapping automerge-c in another language, particularly one that has a
|
|
garbage collector, you can call the `AMresultFree()` function within a finalizer
|
|
to ensure that memory is reclaimed when it is no longer needed.
|
|
|
|
Automerge documents consist of a mutable root which is always a map from string
|
|
keys to values. A value can be one of the following types:
|
|
|
|
- A number of type double / int64_t / uint64_t
|
|
- An explicit true / false / null
|
|
- An immutable UTF-8 string (`AMbyteSpan`).
|
|
- An immutable array of arbitrary bytes (`AMbyteSpan`).
|
|
- A mutable map from string keys to values.
|
|
- A mutable list of values.
|
|
- A mutable UTF-8 string.
|
|
|
|
If you read from a location in the document with no value, an item with type
|
|
`AM_VAL_TYPE_VOID` will be returned, but you cannot write such a value
|
|
explicitly.
|
|
|
|
Under the hood, automerge references a mutable object by its object identifier
|
|
where `AM_ROOT` signifies a document's root map object.
|
|
|
|
There are functions to put each type of value into either a map or a list, and
|
|
functions to read the current or a historical value from a map or a list. As (in general) collaborators
|
|
may edit the document at any time, you cannot guarantee that the type of the
|
|
value at a given part of the document will stay the same. As a result, reading
|
|
from the document will return an `AMitem` struct that you can inspect to
|
|
determine the type of value that it contains.
|
|
|
|
Strings in automerge-c are represented using an `AMbyteSpan` which contains a
|
|
pointer and a length. Strings must be valid UTF-8 and may contain NUL (`0`)
|
|
characters.
|
|
For your convenience, you can call `AMstr()` to get the `AMbyteSpan` struct
|
|
equivalent of a null-terminated byte string or `AMstrdup()` to get the
|
|
representation of an `AMbyteSpan` struct as a null-terminated byte string
|
|
wherein its NUL characters have been removed/replaced as you choose.
|
|
|
|
Putting all of that together, to read and write from the root of the document
|
|
you can do this:
|
|
|
|
```
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <automerge-c/automerge.h>
|
|
#include <automerge-c/utils/string.h>
|
|
|
|
int main(int argc, char** argv) {
|
|
// ...previous example...
|
|
AMdoc *doc;
|
|
AMitemToDoc(AMresultItem(docResult), &doc);
|
|
|
|
AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value"));
|
|
if (AMresultStatus(putResult) != AM_STATUS_OK) {
|
|
char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
|
|
printf("failed to put: %s", err_msg);
|
|
free(err_msg);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMresult *getResult = AMmapGet(doc, AM_ROOT, AMstr("key"), NULL);
|
|
if (AMresultStatus(getResult) != AM_STATUS_OK) {
|
|
char* const err_msg = AMstrdup(AMresultError(putResult), NULL);
|
|
printf("failed to get: %s", err_msg);
|
|
free(err_msg);
|
|
goto cleanup;
|
|
}
|
|
|
|
AMbyteSpan got;
|
|
if (AMitemToStr(AMresultItem(getResult), &got)) {
|
|
char* const c_str = AMstrdup(got, NULL);
|
|
printf("Got %zu-character string \"%s\"", got.count, c_str);
|
|
free(c_str);
|
|
} else {
|
|
printf("expected to read a string!");
|
|
goto cleanup;
|
|
}
|
|
|
|
|
|
cleanup:
|
|
AMresultFree(getResult);
|
|
AMresultFree(putResult);
|
|
AMresultFree(docResult);
|
|
}
|
|
```
|
|
|
|
Functions that do not return an `AMresult` (for example `AMitemKey()`) do
|
|
not allocate memory but rather reference memory that was previously
|
|
allocated. It's therefore important to keep the original `AMresult` alive (in
|
|
this case the one returned by `AMmapRange()`) until after you are finished with
|
|
the items that it contains. However, the memory for an individual `AMitem` can
|
|
be shared with a new `AMresult` by calling `AMitemResult()` on it. In other
|
|
words, a select group of items can be filtered out of a collection and only each
|
|
one's corresponding `AMresult` must be kept alive from that point forward; the
|
|
originating collection's `AMresult` can be safely freed.
|
|
|
|
Beyond that, good luck!
|