From 28849ed9dc9290450f77c90f38a09ca8727be00e Mon Sep 17 00:00:00 2001 From: actions Date: Thu, 12 Jan 2023 11:21:52 +0000 Subject: [PATCH 01/45] Add deno release files --- .github/workflows/advisory-cron.yaml | 17 - .github/workflows/ci.yaml | 177 --- .github/workflows/docs.yaml | 52 - .github/workflows/release.yaml | 100 -- deno_wasm_dist/automerge_wasm.js | 1663 +++++++++++++++++++++++++ deno_wasm_dist/automerge_wasm_bg.wasm | Bin 0 -> 2637428 bytes deno_wasm_dist/index.d.ts | 232 ++++ 7 files changed, 1895 insertions(+), 346 deletions(-) delete mode 100644 .github/workflows/advisory-cron.yaml delete mode 100644 .github/workflows/ci.yaml delete mode 100644 .github/workflows/docs.yaml delete mode 100644 .github/workflows/release.yaml create mode 100644 deno_wasm_dist/automerge_wasm.js create mode 100644 deno_wasm_dist/automerge_wasm_bg.wasm create mode 100644 deno_wasm_dist/index.d.ts diff --git a/.github/workflows/advisory-cron.yaml b/.github/workflows/advisory-cron.yaml deleted file mode 100644 index 31bac5a3..00000000 --- a/.github/workflows/advisory-cron.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Advisories -on: - schedule: - - cron: '0 18 * * *' -jobs: - cargo-deny: - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories - - bans licenses sources - steps: - - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check ${{ matrix.checks }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index a5d42010..00000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,177 +0,0 @@ -name: CI -on: - push: - branches: - - main - pull_request: - branches: - - main -jobs: - fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - components: rustfmt - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/fmt - shell: bash - - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - components: clippy - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/lint - shell: bash - - docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - name: Build rust docs - run: ./scripts/ci/rust-docs - shell: bash - - name: Install doxygen - run: sudo apt-get install -y doxygen - shell: bash - - cargo-deny: - runs-on: ubuntu-latest - strategy: - matrix: - checks: - - advisories - - bans licenses sources - continue-on-error: ${{ matrix.checks == 'advisories' }} - steps: - - uses: actions/checkout@v2 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - arguments: '--manifest-path ./rust/Cargo.toml' - command: check ${{ matrix.checks }} - - wasm_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/wasm_tests - deno_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/deno_tests - - js_fmt: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: install - run: yarn global add prettier - - name: format - run: prettier -c javascript/.prettierrc javascript - - js_tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run tests - run: ./scripts/ci/js_tests - - cmake_build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - name: Install CMocka - run: sudo apt-get install -y libcmocka-dev - - name: Install/update CMake - uses: jwlawson/actions-setup-cmake@v1.12 - with: - cmake-version: latest - - name: Build and test C bindings - run: ./scripts/ci/cmake-build Release Static - shell: bash - - linux: - runs-on: ubuntu-latest - strategy: - matrix: - toolchain: - - 1.60.0 - - nightly - continue-on-error: ${{ matrix.toolchain == 'nightly' }} - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.toolchain }} - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash - - macos: - runs-on: macos-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash - - windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: 1.66.0 - default: true - - uses: Swatinem/rust-cache@v1 - - run: ./scripts/ci/build-test - shell: bash diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml deleted file mode 100644 index b501d526..00000000 --- a/.github/workflows/docs.yaml +++ /dev/null @@ -1,52 +0,0 @@ -on: - push: - branches: - - main - -name: Documentation - -jobs: - deploy-docs: - concurrency: deploy-docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - - name: Cache - uses: Swatinem/rust-cache@v1 - - - name: Clean docs dir - run: rm -rf docs - shell: bash - - - name: Clean Rust docs dir - uses: actions-rs/cargo@v1 - with: - command: clean - args: --manifest-path ./rust/Cargo.toml --doc - - - name: Build Rust docs - uses: actions-rs/cargo@v1 - with: - command: doc - args: --manifest-path ./rust/Cargo.toml --workspace --all-features --no-deps - - - name: Move Rust docs - run: mkdir -p docs && mv rust/target/doc/* docs/. - shell: bash - - - name: Configure root page - run: echo '' > docs/index.html - - - name: Deploy docs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 530f07c7..00000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,100 +0,0 @@ -name: Release -on: - push: - branches: - - main - -jobs: - check_if_wasm_version_upgraded: - name: Check if WASM version has been upgraded - runs-on: ubuntu-latest - outputs: - wasm_version: ${{ steps.version-updated.outputs.current-package-version }} - wasm_has_updated: ${{ steps.version-updated.outputs.has-updated }} - steps: - - uses: JiPaix/package-json-updated-action@v1.0.5 - id: version-updated - with: - path: rust/automerge-wasm/package.json - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - publish-wasm: - name: Publish WASM package - runs-on: ubuntu-latest - needs: - - check_if_wasm_version_upgraded - # We create release only if the version in the package.json has been upgraded - #if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true' - steps: - - uses: actions/setup-node@v3 - with: - node-version: '16.x' - registry-url: 'https://registry.npmjs.org' - - uses: denoland/setup-deno@v1 - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.ref }} - - name: Get rid of local github workflows - run: rm -r .github/workflows - - name: Remove tmp_branch if it exists - run: git push origin :tmp_branch || true - - run: git checkout -b tmp_branch - - name: Install wasm-bindgen-cli - run: cargo install wasm-bindgen-cli wasm-opt - - name: Install wasm32 target - run: rustup target add wasm32-unknown-unknown - - name: run wasm js tests - id: wasm_js_tests - run: ./scripts/ci/wasm_tests - - name: run wasm deno tests - id: wasm_deno_tests - run: ./scripts/ci/deno_tests - - name: Collate deno release files - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - run: | - mkdir $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/deno/* $GITHUB_WORKSPACE/deno_wasm_dist - cp $GITHUB_WORKSPACE/rust/automerge-wasm/index.d.ts $GITHUB_WORKSPACE/deno_wasm_dist - sed -i '1i /// ' $GITHUB_WORKSPACE/deno_wasm_dist/automerge_wasm.js - - name: Create npm release - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - run: | - if [ "$(npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm show . version)" = "$VERSION" ]; then - echo "This version is already published" - exit 0 - fi - EXTRA_ARGS="--access public" - if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then - echo "Is pre-release version" - EXTRA_ARGS="$EXTRA_ARGS --tag next" - fi - if [ "$NODE_AUTH_TOKEN" = "" ]; then - echo "Can't publish on NPM, You need a NPM_TOKEN secret." - false - fi - npm publish $GITHUB_WORKSPACE/rust/automerge-wasm $EXTRA_ARGS - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - - name: Commit wasm deno release files - run: | - git config --global user.name "actions" - git config --global user.email actions@github.com - git add $GITHUB_WORKSPACE/deno_wasm_dist - git commit -am "Add deno release files" - git push origin tmp_branch - - name: Tag wasm release - if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' - uses: softprops/action-gh-release@v1 - with: - name: Automerge Wasm v${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - tag_name: js/automerge-wasm-${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} - target_commitish: tmp_branch - generate_release_notes: false - draft: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Remove tmp_branch - run: git push origin :tmp_branch - diff --git a/deno_wasm_dist/automerge_wasm.js b/deno_wasm_dist/automerge_wasm.js new file mode 100644 index 00000000..c1a71684 --- /dev/null +++ b/deno_wasm_dist/automerge_wasm.js @@ -0,0 +1,1663 @@ +/// + + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +function getObject(idx) { return heap[idx]; } + +function isLikeNone(x) { + return x === undefined || x === null; +} + +let cachedFloat64Memory0 = new Float64Array(); + +function getFloat64Memory0() { + if (cachedFloat64Memory0.byteLength === 0) { + cachedFloat64Memory0 = new Float64Array(wasm.memory.buffer); + } + return cachedFloat64Memory0; +} + +let cachedInt32Memory0 = new Int32Array(); + +function getInt32Memory0() { + if (cachedInt32Memory0.byteLength === 0) { + cachedInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachedInt32Memory0; +} + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachedUint8Memory0 = new Uint8Array(); + +function getUint8Memory0() { + if (cachedUint8Memory0.byteLength === 0) { + cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = new TextEncoder('utf-8'); + +const encodeString = function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +}; + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length); + getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len); + + const mem = getUint8Memory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3); + const view = getUint8Memory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} + +function _assertClass(instance, klass) { + if (!(instance instanceof klass)) { + throw new Error(`expected instance of ${klass.name}`); + } + return instance.ptr; +} +/** +* @param {boolean} text_v2 +* @param {string | undefined} actor +* @returns {Automerge} +*/ +export function create(text_v2, actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.create(retptr, text_v2, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} data +* @param {boolean} text_v2 +* @param {string | undefined} actor +* @returns {Automerge} +*/ +export function load(data, text_v2, actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.load(retptr, addHeapObject(data), text_v2, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {any} change +* @returns {Uint8Array} +*/ +export function encodeChange(change) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.encodeChange(retptr, addHeapObject(change)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} change +* @returns {any} +*/ +export function decodeChange(change) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeChange(retptr, addHeapObject(change)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @returns {SyncState} +*/ +export function initSyncState() { + const ret = wasm.initSyncState(); + return SyncState.__wrap(ret); +} + +/** +* @param {any} state +* @returns {SyncState} +*/ +export function importSyncState(state) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.importSyncState(retptr, addHeapObject(state)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return SyncState.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {SyncState} state +* @returns {any} +*/ +export function exportSyncState(state) { + _assertClass(state, SyncState); + const ret = wasm.exportSyncState(state.ptr); + return takeObject(ret); +} + +/** +* @param {any} message +* @returns {Uint8Array} +*/ +export function encodeSyncMessage(message) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.encodeSyncMessage(retptr, addHeapObject(message)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {Uint8Array} msg +* @returns {any} +*/ +export function decodeSyncMessage(msg) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeSyncMessage(retptr, addHeapObject(msg)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +/** +* @param {SyncState} state +* @returns {Uint8Array} +*/ +export function encodeSyncState(state) { + _assertClass(state, SyncState); + const ret = wasm.encodeSyncState(state.ptr); + return takeObject(ret); +} + +/** +* @param {Uint8Array} data +* @returns {SyncState} +*/ +export function decodeSyncState(data) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.decodeSyncState(retptr, addHeapObject(data)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return SyncState.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } +} + +function getArrayU8FromWasm0(ptr, len) { + return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len); +} +/** +* How text is represented in materialized objects on the JS side +*/ +export const TextRepresentation = Object.freeze({ +/** +* As an array of characters and objects +*/ +Array:0,"0":"Array", +/** +* As a single JS string +*/ +String:1,"1":"String", }); + +const AutomergeFinalization = new FinalizationRegistry(ptr => wasm.__wbg_automerge_free(ptr)); +/** +*/ +export class Automerge { + + static __wrap(ptr) { + const obj = Object.create(Automerge.prototype); + obj.ptr = ptr; + AutomergeFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + AutomergeFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_automerge_free(ptr); + } + /** + * @param {string | undefined} actor + * @param {number} text_rep + * @returns {Automerge} + */ + static new(actor, text_rep) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_new(retptr, ptr0, len0, text_rep); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} actor + * @returns {Automerge} + */ + clone(actor) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_clone(retptr, this.ptr, ptr0, len0); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} actor + * @param {any} heads + * @returns {Automerge} + */ + fork(actor, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = isLikeNone(actor) ? 0 : passStringToWasm0(actor, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + wasm.automerge_fork(retptr, this.ptr, ptr0, len0, addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return Automerge.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {any} + */ + pendingOps() { + const ret = wasm.automerge_pendingOps(this.ptr); + return takeObject(ret); + } + /** + * @param {string | undefined} message + * @param {number | undefined} time + * @returns {any} + */ + commit(message, time) { + var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + const ret = wasm.automerge_commit(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time); + return takeObject(ret); + } + /** + * @param {Automerge} other + * @returns {Array} + */ + merge(other) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(other, Automerge); + wasm.automerge_merge(retptr, this.ptr, other.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {number} + */ + rollback() { + const ret = wasm.automerge_rollback(this.ptr); + return ret; + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {Array} + */ + keys(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_keys(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {string} + */ + text(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_text(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + var ptr0 = r0; + var len0 = r1; + if (r3) { + ptr0 = 0; len0 = 0; + throw takeObject(r2); + } + return getStringFromWasm0(ptr0, len0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(ptr0, len0); + } + } + /** + * @param {any} obj + * @param {number} start + * @param {number} delete_count + * @param {any} text + */ + splice(obj, start, delete_count, text) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_splice(retptr, this.ptr, addHeapObject(obj), start, delete_count, addHeapObject(text)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} value + * @param {any} datatype + */ + push(obj, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_push(retptr, this.ptr, addHeapObject(obj), addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} value + * @returns {string | undefined} + */ + pushObject(obj, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_pushObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + let v0; + if (r0 !== 0) { + v0 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + } + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {number} index + * @param {any} value + * @param {any} datatype + */ + insert(obj, index, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_insert(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {number} index + * @param {any} value + * @returns {string | undefined} + */ + insertObject(obj, index, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_insertObject(retptr, this.ptr, addHeapObject(obj), index, addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + let v0; + if (r0 !== 0) { + v0 = getStringFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 1); + } + return v0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + * @param {any} datatype + */ + put(obj, prop, value, datatype) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_put(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value), addHeapObject(datatype)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + * @returns {any} + */ + putObject(obj, prop, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_putObject(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {any} value + */ + increment(obj, prop, value) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_increment(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), addHeapObject(value)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {Array | undefined} heads + * @returns {any} + */ + get(obj, prop, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_get(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + * @param {Array | undefined} heads + * @returns {any} + */ + getWithType(obj, prop, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getWithType(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} arg + * @param {Array | undefined} heads + * @returns {Array} + */ + getAll(obj, arg, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getAll(retptr, this.ptr, addHeapObject(obj), addHeapObject(arg), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} enable + * @returns {any} + */ + enableFreeze(enable) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_enableFreeze(retptr, this.ptr, addHeapObject(enable)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} enable + * @returns {any} + */ + enablePatches(enable) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_enablePatches(retptr, this.ptr, addHeapObject(enable)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} datatype + * @param {any} _function + */ + registerDatatype(datatype, _function) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_registerDatatype(retptr, this.ptr, addHeapObject(datatype), addHeapObject(_function)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} object + * @param {any} meta + * @param {any} callback + * @returns {any} + */ + applyPatches(object, meta, callback) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_applyPatches(retptr, this.ptr, addHeapObject(object), addHeapObject(meta), addHeapObject(callback)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Array} + */ + popPatches() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_popPatches(retptr, this.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @returns {number} + */ + length(obj, heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_length(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getFloat64Memory0()[retptr / 8 + 0]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + return r0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {any} prop + */ + delete(obj, prop) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_delete(retptr, this.ptr, addHeapObject(obj), addHeapObject(prop)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {Uint8Array} + */ + save() { + const ret = wasm.automerge_save(this.ptr); + return takeObject(ret); + } + /** + * @returns {Uint8Array} + */ + saveIncremental() { + const ret = wasm.automerge_saveIncremental(this.ptr); + return takeObject(ret); + } + /** + * @param {Uint8Array} data + * @returns {number} + */ + loadIncremental(data) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_loadIncremental(retptr, this.ptr, addHeapObject(data)); + var r0 = getFloat64Memory0()[retptr / 8 + 0]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + var r3 = getInt32Memory0()[retptr / 4 + 3]; + if (r3) { + throw takeObject(r2); + } + return r0; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} changes + */ + applyChanges(changes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_applyChanges(retptr, this.ptr, addHeapObject(changes)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} have_deps + * @returns {Array} + */ + getChanges(have_deps) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getChanges(retptr, this.ptr, addHeapObject(have_deps)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} hash + * @returns {any} + */ + getChangeByHash(hash) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getChangeByHash(retptr, this.ptr, addHeapObject(hash)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {Automerge} other + * @returns {Array} + */ + getChangesAdded(other) { + _assertClass(other, Automerge); + const ret = wasm.automerge_getChangesAdded(this.ptr, other.ptr); + return takeObject(ret); + } + /** + * @returns {Array} + */ + getHeads() { + const ret = wasm.automerge_getHeads(this.ptr); + return takeObject(ret); + } + /** + * @returns {string} + */ + getActorId() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getActorId(retptr, this.ptr); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + return getStringFromWasm0(r0, r1); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(r0, r1); + } + } + /** + * @returns {any} + */ + getLastLocalChange() { + const ret = wasm.automerge_getLastLocalChange(this.ptr); + return takeObject(ret); + } + /** + */ + dump() { + wasm.automerge_dump(this.ptr); + } + /** + * @param {Array | undefined} heads + * @returns {Array} + */ + getMissingDeps(heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_getMissingDeps(retptr, this.ptr, isLikeNone(heads) ? 0 : addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {SyncState} state + * @param {Uint8Array} message + */ + receiveSyncMessage(state, message) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + _assertClass(state, SyncState); + wasm.automerge_receiveSyncMessage(retptr, this.ptr, state.ptr, addHeapObject(message)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {SyncState} state + * @returns {any} + */ + generateSyncMessage(state) { + _assertClass(state, SyncState); + const ret = wasm.automerge_generateSyncMessage(this.ptr, state.ptr); + return takeObject(ret); + } + /** + * @param {any} meta + * @returns {any} + */ + toJS(meta) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_toJS(retptr, this.ptr, addHeapObject(meta)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} obj + * @param {Array | undefined} heads + * @param {any} meta + * @returns {any} + */ + materialize(obj, heads, meta) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.automerge_materialize(retptr, this.ptr, addHeapObject(obj), isLikeNone(heads) ? 0 : addHeapObject(heads), addHeapObject(meta)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var r2 = getInt32Memory0()[retptr / 4 + 2]; + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {string | undefined} message + * @param {number | undefined} time + * @returns {any} + */ + emptyChange(message, time) { + var ptr0 = isLikeNone(message) ? 0 : passStringToWasm0(message, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + const ret = wasm.automerge_emptyChange(this.ptr, ptr0, len0, !isLikeNone(time), isLikeNone(time) ? 0 : time); + return takeObject(ret); + } +} + +const SyncStateFinalization = new FinalizationRegistry(ptr => wasm.__wbg_syncstate_free(ptr)); +/** +*/ +export class SyncState { + + static __wrap(ptr) { + const obj = Object.create(SyncState.prototype); + obj.ptr = ptr; + SyncStateFinalization.register(obj, obj.ptr, obj); + return obj; + } + + __destroy_into_raw() { + const ptr = this.ptr; + this.ptr = 0; + SyncStateFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_syncstate_free(ptr); + } + /** + * @returns {any} + */ + get sharedHeads() { + const ret = wasm.syncstate_sharedHeads(this.ptr); + return takeObject(ret); + } + /** + * @returns {any} + */ + get lastSentHeads() { + const ret = wasm.syncstate_lastSentHeads(this.ptr); + return takeObject(ret); + } + /** + * @param {any} heads + */ + set lastSentHeads(heads) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.syncstate_set_lastSentHeads(retptr, this.ptr, addHeapObject(heads)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {any} hashes + */ + set sentHashes(hashes) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.syncstate_set_sentHashes(retptr, this.ptr, addHeapObject(hashes)); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + if (r1) { + throw takeObject(r0); + } + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @returns {SyncState} + */ + clone() { + const ret = wasm.syncstate_clone(this.ptr); + return SyncState.__wrap(ret); + } +} + +const imports = { + __wbindgen_placeholder__: { + __wbindgen_is_undefined: function(arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }, + __wbindgen_number_get: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'number' ? obj : undefined; + getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; + getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); + }, + __wbindgen_boolean_get: function(arg0) { + const v = getObject(arg0); + const ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2; + return ret; + }, + __wbindgen_is_null: function(arg0) { + const ret = getObject(arg0) === null; + return ret; + }, + __wbindgen_number_new: function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_string_new: function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, + __wbindgen_string_get: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof(obj) === 'string' ? obj : undefined; + var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_is_string: function(arg0) { + const ret = typeof(getObject(arg0)) === 'string'; + return ret; + }, + __wbindgen_object_clone_ref: function(arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }, + __wbindgen_json_serialize: function(arg0, arg1) { + const obj = getObject(arg1); + const ret = JSON.stringify(obj === undefined ? null : obj); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbg_error_f851667af71bcfc6: function(arg0, arg1) { + try { + console.error(getStringFromWasm0(arg0, arg1)); + } finally { + wasm.__wbindgen_free(arg0, arg1); + } + }, + __wbg_new_abda76e883ba8a5f: function() { + const ret = new Error(); + return addHeapObject(ret); + }, + __wbg_stack_658279fe44541cf6: function(arg0, arg1) { + const ret = getObject(arg1).stack; + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_object_drop_ref: function(arg0) { + takeObject(arg0); + }, + __wbindgen_error_new: function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbindgen_jsval_loose_eq: function(arg0, arg1) { + const ret = getObject(arg0) == getObject(arg1); + return ret; + }, + __wbindgen_is_object: function(arg0) { + const val = getObject(arg0); + const ret = typeof(val) === 'object' && val !== null; + return ret; + }, + __wbindgen_bigint_from_i64: function(arg0) { + const ret = arg0; + return addHeapObject(ret); + }, + __wbindgen_bigint_from_u64: function(arg0) { + const ret = BigInt.asUintN(64, arg0); + return addHeapObject(ret); + }, + __wbg_set_20cbc34131e76824: function(arg0, arg1, arg2) { + getObject(arg0)[takeObject(arg1)] = takeObject(arg2); + }, + __wbg_String_91fba7ded13ba54c: function(arg0, arg1) { + const ret = String(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbg_crypto_e1d53a1d73fb10b8: function(arg0) { + const ret = getObject(arg0).crypto; + return addHeapObject(ret); + }, + __wbg_msCrypto_6e7d3e1f92610cbb: function(arg0) { + const ret = getObject(arg0).msCrypto; + return addHeapObject(ret); + }, + __wbg_getRandomValues_805f1c3d65988a5a: function() { return handleError(function (arg0, arg1) { + getObject(arg0).getRandomValues(getObject(arg1)); + }, arguments) }, + __wbg_randomFillSync_6894564c2c334c42: function() { return handleError(function (arg0, arg1, arg2) { + getObject(arg0).randomFillSync(getArrayU8FromWasm0(arg1, arg2)); + }, arguments) }, + __wbg_require_78a3dcfbdba9cbce: function() { return handleError(function () { + const ret = module.require; + return addHeapObject(ret); + }, arguments) }, + __wbg_process_038c26bf42b093f8: function(arg0) { + const ret = getObject(arg0).process; + return addHeapObject(ret); + }, + __wbg_versions_ab37218d2f0b24a8: function(arg0) { + const ret = getObject(arg0).versions; + return addHeapObject(ret); + }, + __wbg_node_080f4b19d15bc1fe: function(arg0) { + const ret = getObject(arg0).node; + return addHeapObject(ret); + }, + __wbg_log_4b5638ad60bdc54a: function(arg0) { + console.log(getObject(arg0)); + }, + __wbg_log_89ca282a8a49b121: function(arg0, arg1) { + console.log(getObject(arg0), getObject(arg1)); + }, + __wbg_new_1d9a920c6bfc44a8: function() { + const ret = new Array(); + return addHeapObject(ret); + }, + __wbg_get_57245cc7d7c7619d: function(arg0, arg1) { + const ret = getObject(arg0)[arg1 >>> 0]; + return addHeapObject(ret); + }, + __wbg_set_a68214f35c417fa9: function(arg0, arg1, arg2) { + getObject(arg0)[arg1 >>> 0] = takeObject(arg2); + }, + __wbg_from_7ce3cb27cb258569: function(arg0) { + const ret = Array.from(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_isArray_27c46c67f498e15d: function(arg0) { + const ret = Array.isArray(getObject(arg0)); + return ret; + }, + __wbg_length_6e3bbe7c8bd4dbd8: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_push_740e4b286702d964: function(arg0, arg1) { + const ret = getObject(arg0).push(getObject(arg1)); + return ret; + }, + __wbg_unshift_1bf718f5eb23ad8a: function(arg0, arg1) { + const ret = getObject(arg0).unshift(getObject(arg1)); + return ret; + }, + __wbg_instanceof_ArrayBuffer_e5e48f4762c5610b: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof ArrayBuffer; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_new_8d2af00bc1e329ee: function(arg0, arg1) { + const ret = new Error(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_newnoargs_b5b063fc6c2f0376: function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_call_97ae9d8645dc388b: function() { return handleError(function (arg0, arg1) { + const ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }, arguments) }, + __wbg_call_168da88779e35f61: function() { return handleError(function (arg0, arg1, arg2) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }, + __wbg_call_e1f72c051cdab859: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) { + const ret = getObject(arg0).call(getObject(arg1), getObject(arg2), getObject(arg3), getObject(arg4)); + return addHeapObject(ret); + }, arguments) }, + __wbg_next_aaef7c8aa5e212ac: function() { return handleError(function (arg0) { + const ret = getObject(arg0).next(); + return addHeapObject(ret); + }, arguments) }, + __wbg_next_579e583d33566a86: function(arg0) { + const ret = getObject(arg0).next; + return addHeapObject(ret); + }, + __wbg_done_1b73b0672e15f234: function(arg0) { + const ret = getObject(arg0).done; + return ret; + }, + __wbg_value_1ccc36bc03462d71: function(arg0) { + const ret = getObject(arg0).value; + return addHeapObject(ret); + }, + __wbg_instanceof_Date_b979044f17219415: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Date; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_getTime_cb82adb2556ed13e: function(arg0) { + const ret = getObject(arg0).getTime(); + return ret; + }, + __wbg_new_c8631234f931e1c4: function(arg0) { + const ret = new Date(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_instanceof_Object_595a1007518cbea3: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Object; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_assign_e3deabdbb7f0913d: function(arg0, arg1) { + const ret = Object.assign(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, + __wbg_defineProperty_e47dcaf04849e02c: function(arg0, arg1, arg2) { + const ret = Object.defineProperty(getObject(arg0), getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, + __wbg_entries_65a76a413fc91037: function(arg0) { + const ret = Object.entries(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_freeze_863b0fb5229a1aa6: function(arg0) { + const ret = Object.freeze(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_keys_0702294afaeb6044: function(arg0) { + const ret = Object.keys(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_new_0b9bfdd97583284e: function() { + const ret = new Object(); + return addHeapObject(ret); + }, + __wbg_values_f72d246067c121fe: function(arg0) { + const ret = Object.values(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_length_f2ab5db52e68a619: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_concat_783dc3b16a989c3a: function(arg0, arg1) { + const ret = getObject(arg0).concat(getObject(arg1)); + return addHeapObject(ret); + }, + __wbg_slice_283900b9d91a5de8: function(arg0, arg1, arg2) { + const ret = getObject(arg0).slice(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_iterator_6f9d4f28845f426c: function() { + const ret = Symbol.iterator; + return addHeapObject(ret); + }, + __wbg_for_5dcca67bf52b18ca: function(arg0, arg1) { + const ret = Symbol.for(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, + __wbg_toString_1f0448acb8520180: function(arg0) { + const ret = getObject(arg0).toString(); + return addHeapObject(ret); + }, + __wbg_globalThis_7f206bda628d5286: function() { return handleError(function () { + const ret = globalThis.globalThis; + return addHeapObject(ret); + }, arguments) }, + __wbg_self_6d479506f72c6a71: function() { return handleError(function () { + const ret = self.self; + return addHeapObject(ret); + }, arguments) }, + __wbg_window_f2557cc78490aceb: function() { return handleError(function () { + const ret = window.window; + return addHeapObject(ret); + }, arguments) }, + __wbg_global_ba75c50d1cf384f4: function() { return handleError(function () { + const ret = global.global; + return addHeapObject(ret); + }, arguments) }, + __wbg_instanceof_Uint8Array_971eeda69eb75003: function(arg0) { + let result; + try { + result = getObject(arg0) instanceof Uint8Array; + } catch { + result = false; + } + const ret = result; + return ret; + }, + __wbg_new_8c3f0052272a457a: function(arg0) { + const ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }, + __wbg_newwithlength_f5933855e4f48a19: function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return addHeapObject(ret); + }, + __wbg_newwithbyteoffsetandlength_d9aa266703cb98be: function(arg0, arg1, arg2) { + const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_subarray_58ad4efbb5bcb886: function(arg0, arg1, arg2) { + const ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }, + __wbg_length_9e1ae1900cb0fbd5: function(arg0) { + const ret = getObject(arg0).length; + return ret; + }, + __wbg_set_83db9690f9353e79: function(arg0, arg1, arg2) { + getObject(arg0).set(getObject(arg1), arg2 >>> 0); + }, + __wbindgen_is_function: function(arg0) { + const ret = typeof(getObject(arg0)) === 'function'; + return ret; + }, + __wbg_buffer_3f3d764d4747d564: function(arg0) { + const ret = getObject(arg0).buffer; + return addHeapObject(ret); + }, + __wbg_apply_75f7334893eef4ad: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.apply(getObject(arg0), getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }, arguments) }, + __wbg_deleteProperty_424563545efc9635: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.deleteProperty(getObject(arg0), getObject(arg1)); + return ret; + }, arguments) }, + __wbg_get_765201544a2b6869: function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(getObject(arg0), getObject(arg1)); + return addHeapObject(ret); + }, arguments) }, + __wbg_ownKeys_bf24e1178641d9f0: function() { return handleError(function (arg0) { + const ret = Reflect.ownKeys(getObject(arg0)); + return addHeapObject(ret); + }, arguments) }, + __wbg_set_bf3f89b92d5a34bf: function() { return handleError(function (arg0, arg1, arg2) { + const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + return ret; + }, arguments) }, + __wbindgen_debug_string: function(arg0, arg1) { + const ret = debugString(getObject(arg1)); + const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + getInt32Memory0()[arg0 / 4 + 1] = len0; + getInt32Memory0()[arg0 / 4 + 0] = ptr0; + }, + __wbindgen_throw: function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + __wbindgen_memory: function() { + const ret = wasm.memory; + return addHeapObject(ret); + }, + }, + +}; + +const wasm_url = new URL('automerge_wasm_bg.wasm', import.meta.url); +let wasmCode = ''; +switch (wasm_url.protocol) { + case 'file:': + wasmCode = await Deno.readFile(wasm_url); + break + case 'https:': + case 'http:': + wasmCode = await (await fetch(wasm_url)).arrayBuffer(); + break + default: + throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); +} + +const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; +const wasm = wasmInstance.exports; + diff --git a/deno_wasm_dist/automerge_wasm_bg.wasm b/deno_wasm_dist/automerge_wasm_bg.wasm new file mode 100644 index 0000000000000000000000000000000000000000..8603ac894a2bbe87a682e13a2e95214b4739fbda GIT binary patch literal 2637428 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

WXIIs@+ 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�<+B|1DXhEoCf*$pMYVlg;+b+q#tT9F+=S+)?bnQiwYT6RunSMT~f$0VM`SP$x*exfGeYt zpQQ^quk$6w*hcnS8(6`)sBY=RE^(ys|I5<~ED36X@a&c0Xt2)b)LC|9JoE`gsAA?| z+Wu0fl5fe5vghFe1dH8p5;!i+VE4_hVev~%G_n^xZfa^as-*^ejjWH$sSisbaat)4 zMHb@)IP42#)wJHzHx%(raS!pZ%gf*kOipCId`P!sVcujPXY6wOsj`k>+-w+6s8SZ- zB9tfK3qy&&dli_iY60ibssLRG%b{`2od-Jcp!*>k*`I3lEN+$MAsfI9I-^4wJnd?n zR_m|E*C=JR}Bw8>-$qd z7m8|*ekEMx(>$0dKR}!0)2kk&#FSk1)Yrj%6E{+`C!!`JgN8=5ML}e2Waq95x)OUz zdyIRFk{sg-TPqMXL4bYir39_)vrZ|+<;}2mh&8hRbQM)kP_=ds$j_voo!Nie&f~6z z-0Z(s)x30k*ych9<(gz*=rDxD+JI0=lUlSG0??GYp#HNycyW&yBM ztTDik4)(zl)J)9mb0X|!DU!xPx~$D&HG_ka?}sxix-Mpj#)a!befDZXHKux8a1N!< zK?oBdd%>%2-~sg1)L%ijroX;+4pql^9^ugnf-z8x=;AS!m)GfUmqv4@MvN_5WGZ^} zF9ICS(@1rpBf89U#=Q+9`%8fb;{Z1ivFTs#V(D1yM4zGH?(7SkPtuwOgwu$DHuF$J zL58M=IgSm%RrF%mMNhy14zULMVJkhr$9fg)pg}WmLsfCl#&(hm9qNP9y{Gay;)mrl zrCxeZ#HQh>dBQA-nzdtscD$H8m`(Wu%9wJ%kA-4xH2{N}oma;y+I!8M+%s@(Sr+oF z%4vZ^^AHS-TCiE!tt7e_UGK1WU0CM?y@xNY>j!*X44e6NP^-Yeq0r|y(y(Tk@nhU$ zl|K1@NQOaBc~f~3~}`?KTe$YDe6m@ItNg&1aE_Mg}( z$?d&Do+5m2kGciCv_-%;`)&OighO@K;@!7Mf;Sc<2(=&qu@=k))l!Z7cj`RGmH^{q z9O>fO9` z2#29E9s#mIFB30jVUyo#;$azHMm%iz-zo-hi7W-( z1;?m(h<+jl8tL4>qM&mg)D69kJv_37YjOJW3%_t$_d&quVcfww4Nt=XGcpGBT{n&Y1{ibj@qVEd0O>v%{iv7BTO>n@(j+&Qh!|}+F zlT7D$1qPc}=+vl81(?9`nhF8WP4(t5$6poU4DfQSZKYNhC15VNn4M1@t|7arKA3W% z*2XEKDq4hWPQwi%_Qs|_0~mqQ0P0nVTP2C0acyrjC&p=sWu!$t!cHymx8c%Q&U6p+ zhWEaR9!8Al@h$WyzcLHXpb(e{ZO|rOYl!(iSx?pjiKpE22|xFF>2wi0`6Qfy-JFns zsdkQ{v3-j^VMIhAF6*G7k)v$2OsIqNb%dIou|wzvmhH6{*Ox5|J(7#q9~Ffs(*?=g z>tQxyXSVXelrf*LV(y94kPaC`Ke{C&a5$cF*i2)(Obk(^G~yOZTl%OhGBekxIyT6K zszA0ANM~?<_EQ=Mn}tItiG_pCiA80;Nh01&PR)$ar*QQruOqo%I_8<6W3YGk2M+1a znFm-}fJfIlSWOgFyOY*3LC(||=&_awPg08uST^EkYncRF>Sv*JD6M{`>n$)CHEYnN z2BjX^Q|ND=;~^gH3jqP z6pbr;i3gPzSkleoZgv?#!Q3B|Kx7k5ha>fsN^_GaXG6*fP+{_tfC^lY=S#{q z$_v4vyVT8%n(PlopCDqrQNuoG-P|LmX(9E{>4a}5623L4A%M$O|mk6sbTC4TDt4IugN6>4`); zR2H6SSW=ADA;xoC&lI{$y%g7XNULk+F(tK|v~{6m1nla8cM79PBaf$!;`V}mg08ss zzy*`B>ke`_zn&(AsD*_%?8&2K{!*QI{L0F}T&D99#V{PXtRyw8u33H-1phLXNoq<= zW_{2b9#m4}c;sV4wm_Hgg5lBK6-kT z0bez>2(^oyVSw|6etXoL*v}{hRu&Y3m9q*da7+a$La4!e>0_U+WM5&#dkfp`jDSrj;UO2y?EPm}@qs=VJO) zOrKUr{`r_b6Vt4JmfB`Dk`~U^O?GjQP(UfpmYPUNy0p&4`SKImVYCAu(NT*wvLOsmk+opx1 zas@coxZZ*}eD1MrEveESj7k@uR4DF>8FdUpX(p`!}Y`{eCv4S^XxR2*>KT z5Yv~C*Nwm7T25W*RjT;1`Qu^ltAnZV#o$MngVo>fS>xtKl`(;cT1!cUXp`fDA{v(lK42*>YS zLb#stRmjM%2P&VfdQZt15#gJW-XP4YYG6O(N@uFB)O0|j#fr}o`aS`k>zU5bLjP*s znnkDmrd8(KRFE^DL38)e5RdF=vAdrAW8ZpI{6?fA#W4c;ovl8W4{@~N8wuoZ6d?Dq zjMUk&2<)JXxu_9wNpPaHm+#zKT3F>M=9bt2~wKKk!n!aV*h#SWb8B3Hrs4K_udKp#c z6w<$L49_d%{Zf2?p|6HX*lcJohbG?jC#YP*kT-!gI8}@3%d%)Jc2Qw~NAyq%C@&Fe z8pXbJ^T?fnNA?FEq4vBjH2&3A%u++uVi&8RI;xNg#$$L)AswBF?r^LX;BEvccpJM_an;SgoDbg)@ujZW|oo{0< z#f!b(3+T1(nG*C?RFe6Vs+@2}A^9h~HZ-f0MwS?!V!yt=*`cW_cOca<$n7tDgn${^ zqXban?F1BhoWSfgvPFiFb38$SfAQTR^<+q4tN7`-Lds)$I6V>5lTI^H@Q6D_O4ry8 zGJIMMsr${S3Vr>FZT~I=-Hd*-@g-zTkATkIK&ymCA$uZof$Yw7Ust zXB6qs97U$NoIImx&SvYpsr#N?YEcz}oLfTabHRY1V&1b9;*DK0j{V_1UBtxGTka=~ z(`KylT91+LB@66Sp|?6w>bma)!8pNiQt0Q-V4}ojR)EVsc@2lVW&cr5ms&gR6z550 zE~A3z6VK(gXW)iAH4m%Efr9o+vpA=jN~AfQ)0)9*I_Ugy_8_u}(xB-O0o^_v(Bv76 z(8N|O1lDs8OH86eP68R8W+&MX~)+mJoGS#Pq^o2?-} zD2CwprbxIGwg6NF$l{}wSL!d8ac6Y!87+rm1>=k+R;-*b<_1oMostrM%g{0Wc&KjNaZa=--_~%=yTQnV>VPjLVxAG)+XE6QF zzPv$0t(GSLv6_R4F^Aa~H>-1YviCKs*)Lz4oo9T~>`o(%LJvL**exDl^i<kFLh@pz# zld*Ev-M$_xCtRm;*@_aG0Qpp?yzM$p#L5?`9QUKDo$`z0J;eMYR4|!WS6!SNnt-rg|B)x>A5}A=NP5%Wy?619L!lu8|LA9WAjGCp*ZI z{n?J<1W7ID8o$p*3KE)vac96P3NlIQF)-5v$aGLYhoa7agwp3c*{`2F1A7RDdiE^W z4Rz{7ikS8ft%Pg_M)Pw5Sc(bo^d1dz7^fb z)LfF2zysCnkGBZ_ZQ5`RjE~h+1u8Kt5x1!)pCAicRI;ztn~cQS^wm6L-xg!(*M`|E zP}N=m%$JS3E471=zmk7b*F6$`;idnTJ1L25Ji;xU!;O4$+@DO~99~XM-1?#dwf88I zw{R3!wsX&8gPXcu8n~YXQLNZ}@Lm7oNaeA2VH8qT^@+#wyN+xeL9srOw|}0G{L<}_ zie{1d55bJpcRfaRTqSw$jwgl(>IMj_iT1%6rx2JCYD+Wtdo%G069TRX*<8t4x7U9D z3M(F}Q1)|`?hfSFy(@3L^9WROJNNF;iyiDV;gACjG%MOXq;2K>!(N^3+g#g7Wqqly zoTS2#eS*yNp;`Hl?;NS{VH-eH>YZ<2-3#8}U1{PhP5^WD0Z^b-n)Uky`J3s+4$oqk z2Jc|60eQ2LYU4f4YF`3=kJ}w_)!tmk-XvWfs zNCvaz_5UbKixDTUI>s6tj41_MgGS25yX?#cEk)NrYd!^0j|^8K<3u1Yv;n<&{atif z_Y$+KOAZ{r1^_hx6*dlw6qu%iJd?dE#D)NMZjX<;4ezmU#gKpp5&=7gO&bukf(JhV z4VIc}RlN);O-V#>000mf2~8#EvfFA; zaxgQhPar;)8_{kRWVN6G5!_XfkqnT*S_Km(8~|TE!Uv;8E;Gw%dRu=&RO_evK@h}# zstt4`nS$c$V|k4*Bs_lyVerfjog;{u0E9rou+j9>R6H~;d67J_-LST)e|^dNPL-(& z_r$`jY14NrZkwwRY4u+ZCH6PwClZEyfdaVwC}SBrw4a22dG@Y@mEr%Fy|)e0>$>WE zpO=19t0f=XZCkNL^0clpX%|+-x5_sN$8|@C@&u=1i0hWC#vlB_b;}=Ic0QQGHP;_p zLsKK-h6tts4QW7uX3*fyAc8U|K?DJ9qL7{-h7O3N2?F|pNIHoiJwy-zs>=QS|7-94 zoTt0x_j?I9F4=vabIv|%uf6u#YpuQZ+G`Ukpn21nE)Y?#(1qXkj(x4$B!BdjvHMg{ zw=9;6`NpI+Xz-382ahZqCC$BK%|sH=(nC}6%gs zF?a`@lTn!3QJ?^0hw-u10A2~99pv6HoMb-Zrb$l2q<~SVHf-qwESSDtlUI$7XqIAZ zutt=brWb6Mwb?tQ zkZhEOdJtKXhQKCL-Y*yeHsY|m;3OGFmUda>6?q}MH~K^TVAbX_K#;`JcjCp|G|iK? zNSZD31zE2FZ^V|fC{HojDsUc=6$^Ir<%g0mQx3)8t^ zT#(hn)H{gM6*cMWZxU%GVd|QtX^=meLztOR@91Sx#?sEyXVAW+lDRwG_Lko7MD2*HUb)`R0;t^hAwYZ{A$i zjh^VuM)T&XZuCTNt~75B{~&MlL~mxAH%D}%Cwep6yg8~HJ<*%F=FKtP=!xFUH*b#X zMo;wSgl-nnjjrX*DcvllH@cQ$XLNHiz0tK4JFA=1>5ZuPr)Mn4AV}7=B7-1hA1k5^ zKXtl(URT`Go-AQJ6vqrc6j=n_1!mu|F@yQtS0&@wso<2Ijjdmf#FulZ1ia$K5wLVm zCAW9kf^>+_@zdtYda`S_)c6R6aOz{{3|Ak%SyMeZKD29nALb1zQwD_NilLX*$K@a3 z$6Z?cOzh3RamtUQGgZhbp#dbYRa6$5~1s0O8OiyJ9Qik9sCnZN65Bi{t{UhFj zpmi)GGp*$dt#An$mXOu)L-M?V$h7|4p)f%*n9GAH{~LeyS3l9tGZEdd=9K&X$uR=5 zMfGA$OTTUt+omy(2*(0VZ!*T510t=*#r5NgQlgGo-0tp?A?=R#ZhO!aM(&pp^-Baz zOMPHi(Jyfi9sP?Xpy6uq`dH1TyI(cHjZO#u0wA?G%0!YLmESYgb_HI`yjzfrsPjIO zac}ZF(plW6=Bx4Nz3FtwTR2}O+&SQiYl!I^((LPGH*?8BR^t^|9F@{DCn>^0P`CQZ zFnxs$re>BluJq}qK6$xqoF$rjMW#k>hC9oopuXsBM72Um|vy!g{dw<`Of+^-4DwY5@yWV&Q5+uOJq3i%>WSM;W8e825lM+6u~E zgc>~aSSFfco4VCtdARvA>9NezRH6oH0c2JJ5S_GmysSg`;TN>y%3 zUS-_}3*4Uy$$(hS+b?$O#hp$QQ*!2%w%Z7km02H6Ycs_?{N@6`>7rchfVejb^b{T9 z#0C33>>(LMsz3GV-)`04TCe=UZveHXBJKT07|>=tXjkt? z)Tr;<)$>lyJa%i;$f7%`qD3G6a4-Umjg5~x%`>DEg79>zxC4cu`E1e!QWJDsr>`y4 zLrD9MLBDBL{Bsgx66XixNC^x7b zf41rs2OsaqdWHA{>ar>H^NbMhvyIy?s0=<~>7~M~Ss7F6PU1|YS%IH5tLL3)R$+Qg&6=Nm z%?cZ}6llZCb>>(x=kW9m>AQ|^2f6}bcd^J8aYu<^&`k}WpT6w=*xM)^$4Vtyv)U3)b zViqyY=>uz4{tjwZ4?L^~0%XvDsg2p%c{#Ww&Dw8cXjT%Ay?VH4R<{w&%DC@q)@|B| zW~Gf;vyxIFYgQUqG^;x$X;!#golB>nSz~BzJA^{Bri=;=vsvTGaRi$Ow>2xc>=8oN ztXxC0BD4^=`f`(I%~Ax-3gNP5MITzT8ttZLRVhpQEp;G4`QwAhQ(?-BT2fUuT7bz-=g;yqWGr z6%Lho|6pT8 zOi5y^Djwx}zjoUd@y)89h1>(YnMc#fA!@Tq3dHq1z*aeZH^W<^vVPz!9%7;?zF!+G zrLNLxv{Xn^9cdkj)sDm}63X{L%!Yx0*5h}#9zpB#1^tnfenVfn8yfo3+Fb@l=u1to zSX|ZNnr*KOiA3obBQP2wfsJvj{>jHaxea@?Cwy)=8%*5DVB(@S5_-^bHXvEN=oW1P zT@)$8tb(Uo^>scQa(ABkZNB!pyT{v|?$Gea=-7^NyUul#B%rv!?P_vsVm!s4v{+H% z83sm6UUJ9fwdBxd4O`Df^oxiqd42gsNLq_SZTLl)Q%mzjhs${NAG+!pQ;becA`I1h zpe3g`&2a7u&7uXg$04OknE^SR?Bc_8sj^+1Y;SH$zE?qS``XmB5miKFGWk~_QnCi3k#j4q|l@w&Vs%{C>$OaRd-+B6qibGNDAsFq_GnRGNQ> zPfP3H-hEo+bV{jq+WB9&*s{!#>&eIoB&Je0HAY&eP>~)k^(S4kxTfPXa zoYk|PjQu>x0SR0Fd84xNzfaR`MsxLHghE?3yUzgbb!1~(R(Bvn(%+7vA|t{yubqbl zG5v{gZj_~fHLVpyH#k;5rw|V`jwLtKxZArANl@FAwg9ANKZb6_A&7Uio9MUI3mR)-vzH4tn<8#=% zSv%34Y~c^Op6-jAjIh;VMt490TI#QxWZ)?Jk(-X@ml=&$H{o$nE9N*18~&`ykLcI#f|@tu@>o`kU2+gPrqmtZ)|mZ#$!wx@WG3V_+PRwzB@zoM^^BzpCB}-4 zlhhplXxHp_N|C(pa7LaW|@!WEk6;h@w zQ{%|Q-V^&K_P6*UG$Qat>#5pyd{2GmFP|sc($1~=bo|ygQCat80ePOfqK*>%Eg__#-_58|$&1d6<8hYgE^S+nogK0KU^R_+(X zuJT2o#Mhj9FnlM{R7`uV)Wt%{`V*z`5qKppCi#4Sd$%W7N_cqmVVQ%gcyR}~13W=d55cA*_2(rhTN@pD{3X^GsmRHDWf3wrLhWg=01C*gzehvD*AO5{Y|K%SwNAkKb7Vlv54b^ zMy>E@;z3Y6t0&)>dVD*7_)5{^R;H;kD(3a z&<@$k{Kv7RAtB|+l~4qCW3{>N0aviy}gu)pq4f8C${`l_I{|2O!mATcoxXZbL* zrvE@K0yO1w{k|bJRT3wCf}0me_{L^?{kF6Mi}}_rl!54AbqR?=B9V#DCv=i4 zdqgo$Wx^ULrub?1{Y$KyX5Lkhr}&?<2*0 zl85b)CAhOh$_58_0xb) zOxpW?9bYH}_d4Gx^VxycZhkA?(SCZ@G$fylQvUIn);@{*o6A#EN|(u6NHiAUC~c># zq6yUP@WzfhPryF${w01v6%bWarG9|hB)|BwGRBI|Pp?vTR=*U@pbYI-(MgaL>D?(N zJzKZ*0@&gB2e*mfAQplIc!$D%n+JyOkV~a`U?}~}gT{fOJL-j*biU|P8vygzB2JgS zLu1^kau1I;9PQ@uCHy|~f#j7R$(*BW<>6 zs=j=-BZd%vcy&unWY?00J+h#%wop^_K;NfiaW&49L2;$!q9U%KXF(ccP4Gxti3roG zsex&ORJpaWJzj3XV7#nIcq+nYEOZ$)h2Al+1%*Mlt9--=?8BGWtGt0~88*o)($bvs z%d^K5#MHXMM*EN{HL*C-v;o-`PieG*8{dPqd8ofYaD4uK@i$0-uz@cgb7Q3CgVO9EepB8 zt`FbX_2C;kR>gARKAw&oc72dDy&hH%<#1kaaL%w*&1=-;8JhQCOw5JTP{A z^8YI*zNaqHG9BXS*-l=kBka$??`r)0fQFwJJfp?3D7PR(()m5x5YwcChNVd^DPi$7|bU@Fk}1Z zPSxe6AZUtC^DGLP*w<=aM-=9zEmCO=k49S@&a05>(iSfWk|e3w7S!n)Y&N6DXltK? z=`)t9xU|eGp}A$MuCTIdDvYK|3X8g#Gc=QfYgjX^o|8U2sH1ablB)7fTRo~u7RWnomRsOl4;y+M)E%Pt_wV;eogISNY6~^TDY(*9Ebi9DQ(F@TD)>=0XSA}p&Gg5(k*Jzza9&@;*H}FG}4ZXWt{p1^X&|U2tYz_r(gqTuW(Vf-jb^|=tykYVYI34{O z@{Hju>>HiVec?-WIrjq{&V7*&-t>>RVy-Q_sdLC_NU#^YZDfsL5sUIvIVKHJ6MRg9 z0_;h2{MZz&*AMS`r|DRZ2t6KXlRafY(v}iW_U5vCl;570Dt#{(?i7&6-K6)O1%OK5 zV=d*cDt&cdl)FE^dLZr| zujMvrS^exR1D!l83=CO?m(zioZgESv%ii=Iu@N>$<4#23-DKP;)`dIbRUjp8gGvOQ zjMmQN>XGKS<(>LXd<)aykc;P2pWZCqmmu_xq!no7n*bji;svZax5e!*1G7v z-9^JI_{B0ItUL_DRE{_t3{Y)wS}NzZ0G-_WrY3toOtLdvz434-)gUASjB*0S7{umN zcJERj9O*KPvSy$ekyE`1;XB3!d?$F!Bw~!mq*2sdiFEqv8K=g(&b0h9F$orWG{YgT zyH!Uch4&bvR*+3{O3zSQ$Alwys*mBL^eInc)yL^+jfexShw2;U5Fx_=b~yc4E3dUM z&$1wi%|*3$Ilb$RmA8;>k?kV)L6x041qenOngUM>OnFOZ9AzxL=nU5n_##>qF++1i z#6&`$!g(D}ig+;vDw8vnaV+gaHZ*CX5C>xE1V`am5Yh<_!MU!$hFVnf+ zezU8QZH7~y?5uw7`sxUgN#YEInFjGe`jbBifaEFnclPh6bzdglVNv$mYNIlr;c0j< zi|{M(#4nIOBPe=Ti{B!omTcmUW(m$5iEx2WBNE=)USj*H=E;%gMZ4gwpee#qeL+?> zD*Y>*3;1arO`wpy^2D7ayvq}J&!$Cs67O$Xrr$?el0M>=|JnpO%P*;It8=baTTyVaG?;`dpN>`uw1qh+$BL={0z?MpVJ zK$jLWiU zhcsdCNfIo|?AyS0(iQKN13(Ya8iu>`pWH3sZD-IJOAj$mMlC7)6D6l?He84Zt#3|2 z@4RH!@jft5Lgzw}ljs@R!G%!SKFR23kD|gsR$>}Og@dfb zl;^cz}s+!YJs>#z#fnucQfuIH=OS6QtFvL{Ea8d6W)b3s$ z8$xUVGN77}0X1A88x#|G=}@GeJr|>5?Od17e9oX9PnuDVeHzgyQ5_>;c_=6fqmzVV z$mw5Xt|+luvg>UN%#0>mKohe;GHU&6kc{%}6$zPbff-F70DD|B6D+d@9qWK?VAEc< z0STZ)2bu3dc9XPk4Va`To&2^~1E!|bArg*ZXH3h$5Rb}8@9}Dh#ILG#rD-%cMTs0C zR!O=4tdcTCJqj{KGg}aiiZ#f03Xtx}y=k*Nsp2)gMMj7vs!u9UiFSr4)QpY|Ex*=6 z9X7Q5BU#HYCoRwZob9yylC-=SGHjPpG0d=pJQs_BP*(I4q3Fl5qTg^!;bbX#Pom`&!BcJD(~=`YRc8?x|K=&xl|B&_7s+Ay*?gO zzAuf_u$cLKmVqiDKiJME>aVBVlm}XW%>40*-CdHfm;^W7-MTW)f%WhQfNt+z8P_nh zyNwK?<6B6AFerM$uD6EzxA#qADh8u6wV)G{?YTq^_QQzrmFuGc$PSqS%g;U;#6kgv zmO&CC@t4Fn02O`L%+=VYeU*-687b~SYyWMt6Z{&T7-3?rVhZlB!~}ys7&N&|LzxwN2=* z8C`Q*p)QQ|noxHfbjeU+>Y7<|K!lS=H)tBvt&_z%^Scej?B19PcA zFgHo5CYIX{$uv6(nP-dwvo8s=oxNS`n_|pwY6cGufIH^<5RxXfh<1>|t2|^FSHD!H zQT|0hr3Pp4D*a`Z7F9YFEhs>2H0n-?UyMU7FAPW(Xw$Ghy>tM8rZ5Ok)Upv8jfol1 z1V&THWDN)g!1L@-0Dw#sJaUBgiT=ww;Q*j0I&2AAsE7sO0xyyv?DXvmPcX+5fFO3c zp$l_OyS_s;sEsodGAeNL%Q?VYlJntNB04%8v36LjZC+zy`J_tkYkD6JqTVj-znDk5 z@o5ps*fp^68q$Cp&ud5n>n>gm#aizZ4@w$moKx63&t#ZQL!tVSH*%{nqi$oqpFWqQ zq8Ia8sb|9ds*@tjjGLRFjs9{|dh9PPB*cjO^&X80)m-Ll)@OYrkdb{9 zfRZ~J39ETjC!7FqeBO!TKpHLDrgk`lW(X_4+*6t8TJ90cjWsZb(B$#%V5a`!mnOsQ z1N3$FL9oxzHvR}}fO^SDoc=c)#lzy=XPw$wzo}cVwL%D^d{!%QL>GPTO zGEBQ(_zD-{GqG21n90^>H@~?x>JdIiN*=Z&OiBlUpX^NR=@&dz3ZB|raJ4gWa}<=j z??NefVRONm?u4R!Qo9ao-NUpv%o@r-)r;MU8~O!Ll!7NV7hLI1pfEGU^QGYV%>}P? zCln1u)lPp>3My0SK-CLF6XerVL2aBquJ|3}c%1p_K*4iE&a9Xkvw~RRr?$PHFW;YRpeY#Nsri7t1yj5S_TS4 zuIOw#gE$)_t6$S4&lov`Loe#J-x&k~1XOs8rMp|-8nq#}*ddOEv~i;b%zom<7@zFn z_L1<^>C@mjS;QcZ7%#p+Db>!$vI!s71a|O~>@5 zL_It6b4~NtsHr2E2yzY;-qL>pOzBm8Sz9xN*(XZZhs$>lD;$%fq^E=) z?8F%(Mw4?ySiy5{alUtsUOtu(sSp28(3WA(!b#ncy4i%bgqHY$zvqS?uPzzE2Qw`m zfEKBevs|5onbY|M)U*KTcCXPT>$7;4fOsD^gaee0(wk9XN>8^43zMgZGk!; z?V%_5;7T`B^4<*d5Y(WV>a@#h?yBT-edKQwqJ)5L3lsUK+>-oRDKPKJFb|SiJHb3B zm?sVMM25M14Vcc6i@y|V0dtnfR`S_rn0KP06U^g+Nqjzdp3N{9t^w28cBciCAhE=- zcVw8;^gO+xwv(R=c9Z_BFAApk@k(d=wmWYx;t^tXGM@W0%pUO;3FdjhJZ+e>wg|Yz zNxLmQodFm@2(f?}+`c!%L=duwZahy2CK3ZYj|--5$&9`&Oyv@0RNE%$4=jlXKh-4P z=94TYgr;XUV94WO=1JA>Na4^ck#}-nw2@&(#yx+|^VwI-t`jlX#`0iWqKsvNt_(IM z@YG-;i96zn`1T?pFC=(opAU%h%J}N9xBw{vUsoS}%c?LxSBs|diUc|bJ zj4*HB`N5s-uFiMb7|n^mou_~~JXQteFFNBS!sc8me zkBOvLng7ZU!5qCY|FzR#AlTA~+~*i@ws_D8fiI2{yEV$BUgXIWH~Vz+{LVlW@d!(z z;;TP*+y;AnXn>nhoh??Ns5aZlWRnCF*^YK{hE>7BXe8UP+Q_WK{YFGdnGw4-?Yy}H zQ5i=6MqycwEFgR%%Q>F|VA;TaLJuaH7YR68Tv+S} zPiv=^R<9WQFz#6Od)j8T%ma-HIPokztBH7N;*ghXm-XFgis8+#{>JTnTjz|R6IQWL zV*n)2j8=zlP`NI{qWTN6!^5i8ufGYZ{04t)Lw^2Sa3yvQX>f7`3PBiJ4*_2eJMosX zOT?wg-bGOLg#z}Gk>b%djhq#3On_+OQ1v^bz^~^&@Vup+I09_oxTEnzLmNRRafmyK zNHRG#8MfFHqKJASfK~rRce6ZCgxR|-U5M5p0Xj7_>7s3M^_r>;y37x6ri+Opf5qpC zR5EkPbD>{ac;dP=xMNNsFH55cdgF;?S{jx2*$f$JBC*IL7KG9JhogvMkyXnzR3*T{ z%5H3h@ApPM+ZdIo{>XLhVTtMmydvHGOgbc^XIQ5fnJ_9HD8o^FUgQ?>s$ad{9mrJf zffO<$3zLbIW_J(DD!ro50dqCk>kflMbBWrNX%wao$mPQ9BIJw=M1nL^yZW`;xJ4p! z?hr9{)17-!ZCnmH&JYtg!{i5c0PbPdr>Y+o7IXY* z>sd?yik?-`dhWWh|QVzfOj5NfhP<91mZ(q z-bp<6hMNej^P2sirqZdzRi}03L@BvT&iHri@6>5dEje3n##MxWJHreeB3 zm+n{6{drws#Z_H_Ge0E8ypT%G2nV1XPPibQD08^fHRHP060C~}#;k+gE^((lyfn|& zcrjAsL`KpKxaci;l38N{EkV4KsukQ<6BZYAr3coAf{!nFGav}94NZmTZeJUDV{xod zUV06p6TnVbPW3@2!j&5B%ABI_lF^EAun-3!2`k((nUeeE5DNmVn5lVrp|pKpZ6KG~o{)v#DE4|gk$_~fyDd`k0=S1IhVVk6-Xy1`aTlq+o~~Ci78Z4@xsBIc6|q8;Y}{-!BFcYWveEw2aEPb)GhNszLxkSNll$RqxH= zvf@6qn|UOEu?GK=G@jZ_0!Er(o81^iiX4q7I>Z5*oR`m3f2}wY%lP*qLV^&}_1=g$ zrNkY=Me@|9hMFwD=jYxOSV7a6X{ZyY)rVy*$}=%KjEc+LjF}h;s4wO`6T^Umk(z>T zni+x`r0KgLmX%|R%{=)EnVG(`zF^(VOOFsSR?rsPP;5x^j6t;F+jjMP*NFmSS+XG$ z@i(}cY-w@N{Y9dPB8if;h;G-1=b?)S!NS)WmAk9M>@vUL87efk3F$t5eq0Tawv$~6 z;M=<^&Kh8q+Z=Za=3R6bV_$Yd9np<=UXL}Y|DZ#3kw$~svkLqB z^g{l^BR{5RYZeSYUOIlQoqE1rQloacQHRt11194AzUIr(U_@N(9zvXI&TmY=X(5*h~(WHEP2PjO4Ts05|l z@yc)U#`aFlmzIXIYYQ*hw5IYX45oupkl2xSaUIw65G;Q>U4Kf?k#N<V3Bg)E-T(^#kAFy-^I=8vpe8P?!V!x}*B}#A+^oulDtw5KHn-2|E0A zS`ESUvv01e?M%GYsuP!LO0)53#ty7C=Kx_pt4K7)1$b%_Z*^9n<*d`Y zLCao#CGFE1FZ;BRnS4HF$3dJ_>v(sjs9sbXr)!d^cli7+vPTy!;|O|ou=TZ!gXooo zItxbXLJSj|aykh?Z%!E4-q3>WO><=^34w=JK zns=<>qaLO@=BlcWsUJ&N3@d$jR#!e;)|C&>an*qSEnT6$S#h{tgL$-NaJcV`j-79P zUo^XA4u(^e;&86^mEAM2prG8AusoWsBpYjlq z`svW*iXQd`2T3v4L=2m{EE^%P`kbk00(%P8ZKVuxVb}4baHjD%$PP8(hqDvU z7)+2ivpAk8ZwB$KBX8HREuOb)7|5*jYMTv%-Q(Ya*0#1>+dPVB9h?U|(Ze;9J-5L0k&*w}5J+xU+&;th^SojT zEr%;8Cxw8WOPM57%|_zRVLy$u z#CDBjE;5W=0RJHv20hUN0yc15!xQZ-_p&U|{Z=T9S}G7l)C&Pjo@mAg-zLJ~iQa}T zS5#`9Qcb$x8s|s?eHwI`wI{ks7kQ#JJ+dd-%w4khXyZuun6u;d$5qz8mtu1 z9xT>`F(gVDu&qk%ho#JU!7uIQI6z(Xp3>(2HY>@2EvI7@@HUmq&-+lBc_g_HKD}E zmE6pGu*T(%mQOkjzH#|`<@7E0)4HGT-@{`X8Rb2eqL3^1pe9jX?jfR3O!p?amG}GN%%T^_b3B&89N*GPdZX^{WG|A8hYq zL&LADg(6e6z(SyoB*qfvKw?|Mypp4vC-%h=;`%&t=X)xGnj>E#B8M-3MCfjy&_}EH z0`5R-qZ9K#8{C&Wl1`^0lZLY`wR_ZdwzKb-ZiGS@YoQ8@Vb~4 zH;0+N*QiUQEHBB+5ASlSRj9Xfs74vqc-3*d*;8*-@F}TGTJXENA{l;{`KA6TP;eqE55yM7^{^XU zx;?Dm>SwQKmP=ibvj^D=6WeKeDz48L6boqX5)Aw@24ty_d7Zu3Wc?n}Q?x>#XSTITV zR4>az_sm5uBWj=up{V@53}^hs&FTm1Q%Lqzdx89=JqE3J=ovK~;NlKrE{USV)5OmG zmYD3_eA`ubXwzK1bPPw@-unCp(po3AF)pRoyDDf?Q5WVv_Xb7}fXF?$@JW{?o0zzPt5qI8?~KyIJ_VT^RQVSb%jm^(c4+?#N4) zso&|8Lb>M4x#mFm!CVz#$J7-_Zm6wO_2Hk%>$>s>2#fDF!MEx%bUSLy-2US zr``SF%4Cafuf9gBBuW(;&OJisSo`hmkG69QDVZAw6e1|bG_d&a_ui2D$bOPui)n7% zs?R_Fe5?9IEUVju1%)3VHY>aeP{Xb#84SdbmAQ%?MRE^Ip|}w0QtsinSO42zuTNMj z3D1nQPO|y*Da@hRsiczhF{G0INl)xVLGk03D;POrxG@?^kc^21dO;&_K%1mzF7r$J5Ij0@8$@vn&;ak8|KC zq=nC~X~k0O2f5ZU4HKl{4QA0+y1OymxtI6?O!>l+ld%su@<`WWWCZQULL+f=;x~(i z$AsBw@dk|Y9>r7Z$Q`I7sI7O@-=~uJ_NFi>qzE@jljBW5iTSK3p3PFvYf4Rd@@zW^lxHpdB*8@T z_r?b=Po$j6)q^Vo`-of~TEiNykAUkYp>noa7+h7SrOJCl4C`ZPOJJqt3W1dj?xXDi z$ueg{CyzcpfXV4qF&+JrM2q55Em{cXpBFUVA8T81mUMU$VLBFBp2v=oh?b2x-MOOGBTEF^oLUoL6x1^% z28)qJ8U^M8tOwvwWunGfO;Qzpr^Nuf8j&itSLM+0BuhDW%Cy;0y;~<)IHdau3JGXc zwa83fYdz!CI1fgemvF&1Eu50V(; z4x_j5A&h>w<`5t};ke&~KiXlYU45)~t0X}6QL7u1O9nsY*{8DcZ_-wOO2lHKqGjCg z=GW>0y&`-{vk$<|Ne|U$o>%aAed$A7TGh|*KvkXPQT6+8bTk9B{J$s*rcc-EwiY)? z+WNA}JWIsG@9$`LG^5+NQ60eyFicqp28ck7S2R!3M-OFQGdvW=l6 zw?2VW)fK`T9tFu&90{7Kum05%jg(ud`l<}NJ2g8Ws(ukKBAvyI?H{rQ3MIy37ETpR z=q=|7yo!%w{SY@7a5aMjxR-WDHqFL0aUEn7%{z-FVFp0x!{E8k(L0SaxxT zeU*5?)=b`%Y#&69IWP=d7bqZXGEFoaNZSPAf&7kP)XLW;yK4j5)MM2rMHj7Dh%mn- z!kpFw-jop~PN(_0$?|K`cz3t{G(}1lu(s;W;e|RtA(cdNK6xCyGV*Al%ly0q=C7DQ zO1`($NfZiv6*??R%zhq9j0Yv=cqU4m5GB@e()1~@p4>F1#1}iJ#Gh?X36H$YCqQ|P zD6t-H8wMd*zG6y*+h%|g>#&C?aa@!jgO+##H-jnhUquNbs2h}+=b+!@PCwB4Nl6Ij zS7yjo|Czyg&W=~)2@ncD0O2*F!aNZwcEu|;{S{N;3Q!h?n^fpYU$Fg;K%t13slIrF zLp8mSp&*5hJUkKpge;9{2NiY90i(zP1fSRudt)Tfis_F)BUTH)h^`Do1lmwjka?#+ zEBzeJ44t2Yh>tQ$kQh@`t;N_-7Gv6#PtS}dBMD7>Q<(2d|hwMU06;fv?1vDA5;Vbb`g$h{PD(-4|m}B8xFoVv`u#M2RBCLRU{n3sos3hZ?wAXA@ar*04^#{A~sj_=iV+H~FqqkHSiSCf`w~oQC zE56Z=*RRnP(|$V5v+7s1nP}OcIbfBED6dbot3TD*Hsrnfda6d(Tn(nZ97U?;uTnJx zgt!_4LONcEz)mObnLB3W>}*jrtX}h(tHHmg4f)#nH^@2G(e~%^bn6bZ_z;kVaPmr;v;ayHb{;zB| zmD5CDW7P5YXac?naCUU*5&jka9!bE7`Tb-9&UG8$tc3xb6!(I-#p&r?B&Wxz2Q_2e=Ac~9xgFOZ&D z;JB8~Tr)Vl$HouEr%xWypGO0nc9_Vm$&-il=ivayT<2?@8g4qEtHCMg$DKy}O(HG+ zY}BejF9&E2d0{hCfR<$2MYsx2?$e+91sV*=LX{4@xBI0mOAp~6g~54G3Qf(x=rpfO zrWu)Hh2uTaI=^qM=?~TW)BDB#do5v??@{oEf4cu(F=XXEE%W>3{(DUc%6l3a1y=h7 z8X3%oYNhNj1Y~i>Pzjw91h#j>FW3gv(%zt^32E;9o!i23$aK3?k8(~iC6J0A5y4CaE>CQCr{Tx)MZsrCOY@%wQnTUqpO4V~6Y^JJ5 zG^+ZZh6LXl17`Nr($k;)fx$^DWw(=azlhKb3=j;zpMY~603Kjjrk4>GBMnyLQ^cY+ zd066T&DrK94t+hI2(a@!W_%OE(f1js7g86u#l&WEHut~+wj{;L11!+4ep6{I>NNr5 zB|q^C=OThGvt~^KVTB@iGpo;cdkP9(yoqNhctUC~{I9Wac(N=ULNBedp%BwJ zNLSe9VuZz}6&N|H53xMeIn=>}S)I6zsYY5L3&*E=yrsAgyIJh^Ok3V~aL=Fzp4zYh zF* zh3`erOSG6FKQ+Y1_*>oyCL1~9?>w@j=idSy(mYhRz*)Wngl~OLAILou!-Drr2~%&(KxT@O&PRKp^b_S$mR+CJ zI)kE>gxpiY^eW^La>6hnr@Vu%{8?YMT0r$ez^euIgOyXfT4s1S8{bce8Y6bf?PJtx zR|V~WVOCw~1#iVjD}DDoUajA#@_SWY>wO(9^}E@tZRMAr!AYby7uu2!Y}(`Z@i;A4 z>Ist4^I%HfvlhByXQT;Sx#*x9GRq10Q#C)@(j_8TyU&8Z*9(-&P@G*<Z1 zfDX-Z0{435ypUL&|4FZud!Lv!5-;}#Y4=Rg4pA6)OlIaCVrGJ*kZfPPb$|xv3a+qj z^$+9j^iQg}YVZNu`BsA7g_f&=j;f~~wB1Fn`;cMUPco)nESyMy zSa0-Qu{?bOAoV9&_2mrA{e1Lq-H8L$9o94VGyo3;#Ya;WgoTRgfR&oxzNUn=K_`Y8 zsZVWF(eZvo()}_clI~}E1`#*u{@K*~WCP95v5BOFwA28g{f{s}&fj%Cu_ep=`p~gTg6bvq- z?IWqLl3??9iGpo1N4k=Mt~#Y>E`eu)1EzK$YS&8UU20hDKveOC#28sWS?xjq(SLfz zP>OIC3bN{Dl-SJ26-6)gdrCK4Pp2!#Q;k+XwCmP*x|BXSn?CWevdgLHx%6_y_i*f4 z3^|;)x`htsJA%LJI~iu;<_j$JFbB+%O1=T}dU>!WG1V-A-&KPl6(H0({oD!jtU27) zst|<=r!IFQfpuuu@bAgb1KrYRN$<%}Q&(L}s7nuBOr2qqA~F2=(i!?%nAtQoCxEub z%?tK=0%Opn*AHKgUT;=(xnGe*`A3q$pW2l1&zyZUKIX)0U8e#>LTgZmk6LiRP(@s)I?n1N*O7!Z9R-e( zF3ExbJT%`1z4PT$*{hDZx~xZ`pwACCz(~PKwH^JtplDOM9o92rpasJhl|;u?T#{%T z@MpIHf2t2&ePA7SZHa0T5k~@MOTj=BPmI(T1upA|M8osB3Nx{X#bzCm^@As8Chobv z2T+!imw_M!aYhfL#~F|9Bq&!!$|TP&sKm!6%}_!%I@TH?mADioLRKXV{nd-nxG!X{ z3Pecz;DjSzbFFkTNWE`IgY=k~pw_NWs04Ir+WE)y%(b>P_jSD1_Uv^o;1A$@rhJW$ zSw3v|r&V9j)A{V!%)`xGvzceFCZJ7}E)}3JMNW7G403~kCT_qazGAuzwEBpiMXNVS z2)81moICVHfOv5Uab}yk{ITm|fdd*zLQqSV%8;!3jj>$Qxd5r+GEm}>o#766D>}+F^S&ya)s=wVOwSJ*#W?v}8$lRG)A*+FSG7{DI#=@#;8!-aU zn&S3q`93-JV?sO2WbUQ7EUs#%`O;h#C{N8=?Xy^l-%vsMmtq>c)N#&|G$}soZ4PsG zhF#llapWH}D`daLWn0=*7$MnrLTo+dYugk(~J$~bSWLYBN zxZaWX;%LJ0k};l#R9oB^lu*xpJ)tnu1%+^5%%ERRWEAKlECx)KeU8o6YQ?YOFKA4o z&g2#%`V{*cE5MJ|JEmu1c#B$$4g@V9Br9BCWLs78Vac1YZ!|_Pbt0t1pcG!DYTlj^C?;@Y$za4T zQ%grQ4Yu*l?-a?Mtb=p8a=>|wgsI%ow#=s!O`0@|C1>0PeR+10;rPp}$@^G98d_h~ z8&22q;{dby_$;foM04P@6|nn@<|SRK*c}BqHY0nz<0n zU7yekkwg?vg^%?Mhc#stxs^t#_?cXM3yn@SY1Ay1nsQEGHfYpuj3-u_+X=_3DmE}X zzErAIw;uT$w*k>5(^hB3j1_sv49Y^rVGMnSAgowl9*Zw+qh$uLPo#$&O~Of?*Ki`} z3lS1YNhI|Yyv-<3Y_#2O_ znzNK5yM*0~M=hmJK&mZLO5R+g)N@HnEq02OT0~G@%_x{H&Gj-y?4^_k6=R!@8%jes zv;IG&9*Z6#g*!*p6jtxQWW9pQmHa%sq)Hh9m_71rwOc1dSfgL-K58E)$3}O;~5RzB;t?Ksj<58{+rvQ-$ySlu)5KOb8*Gv&qhYe zk@;-8viGf9v&Koj73O_drd&W=@tf@M@Fv$sfU&|!;B8<)_UhB*3YGUC{?VocufEVL zTK4(Obv)%4`XBF%hR#^^3Cpniqc{hMAU=4xr#e;X3P{FRlzcxcF5w)OLt>tWV2atv-CSktf)oYwX+#1ZqQCm8YQ%0#r7%^LM56}D8 zvY73I*GHmA(&Moh`|1Fml-ouilY5%(?86lJZIO&f=RT4t@>!dWlws*gN-0=Egkk%d z6N0s_CaN$7JP_b^XajFM-3Z*_0q2p*y!dB`!87fnS@U57Q%lHxXlG9&Zk@Sz5NpIJOuTgZ~em;);y0MA|$cmGgtKlOo(n zS<#Raj=hkyYsv{HEv8>c!liZhzd|icqaNf~L@tAsM%S5yUBN2^Girw%fEjZro5U5I zsU%qI5QAryQI%HWx75wJqqdZnQIY{(mDG}U0}-j{_bO_Fvx36>N8sm`YMLqW9`3ve zln$@hgJA|4__ClihR~Bg8OrycAt@dj#?iJwzYFELb zHzN956ALm=1sRUAo7X77)quFzmNB4TYrNfr#ZC#rwphhkirO{7H9BcF^;7?(*${h9 zA)#QLBnI0l@yH=eeWak~N(zmqQ~Lxl4Sj)v=>{$Os z1`6xRM*aziLbh2(3~_RhDSBAqeWW^O_pgj6Q*KNXo0;U!K5Tb36J}niA4xh0NzWJJ zu52Z)mbBdwtb|kcLMgktP1!c{A+@?R`Op*5;)|)pGJsNxQ+K_b2BU=^={L8ubtt#h zUVkKx+lFv5pUtGU9yb1m3;r|0zi|2yzdcgk&g!ie^Bdg0)3dz;j3%{DxjHrIFl^EY zkjRbPYD0`D8dkTy6ALT0;SZ^oZV@%@)W1bF^MPUwY?bYmtv4;ru55AQa55j?sv@2g z=?5b&3kAOB^8>A0zyyGIf<%eQvN(A*g0QfmutkF-BnX&iNg>YI8R(t2iT+49TRFsS zkf%lV3`Vdv-SYT~!-JgQ5f0F}r;ob_5Lt;1arN^!WBJ$Jwp$1K#XHApcr%`uHUk0Loci0kfR$Nk7n)7^Bmx zi^JwZzQzG>6HBIJ(mKe7PSd!HgtK?@wTxQ5qE`ovzdPfQZ(`Uw)gMJ(I0?0c*M3bG z$$DV4$WP21PZA2_S>4sDxyy1=4LX!j(oje^;W66|sYe1PI~_zSEnfiZDpOJLV~fIO zG#^Px9SOPeY*n3TVup?q!=dRAIWJu68ttmJCcZ5#lra-&LexDbSEuCl-C#%rrDflw z3dm@%U9*_bnpPXpE+s^wU6-%ft~rc9&!%9i!G_JUgJa%f7}Fi{wCa}w%Co-4h$*Jy?$vMq)9AJ6} z#1mW{YR^e8u9{m+9PCoh8-+*S2e+s(C-(B_XbwBYfiDUp7X7xTx>B;P*^<)vFJPm% z0(vQ%Bt8ee2~sqh5pElRlqhlLV$H}?I`xvEmq6%xDfijH$Y2<*0cJ_v1vyv}Wy^`2 ztz6M5tc5ax@G2-RGUl`p>^Qu@A*D7lwVNa%=IMxxklQ0KfGvGdB;DJTT<9=z070^q zFanr6^jisQH(R-m@rA%*%91#3%a}lPNkuZa`+QHNauW!{26YG#C?T`c6I{9}A4{M^ zf1tPwFfMzhY&h=4q>25H?$XV4Q&Oinu@v;eZEN~1=nB1#>k9oAxyB(k611ip3Y{_g z^63U6`slaYyH5f@0R{>^QP48)vk&6iugzBdjNoO5%*o z>ECE7sV^#%6GK5y{9s<+;}=Eu$7JZG{Kd1O6~kUmT&=@E3=Kp{m@R1J_rcW8ldA6b z>>j$gJ2c$!H1nlYv0WK~ta`)WBNG~zunhLc544mPpf#sgrDX$Xb+@9mPylU@*2w{A z185~^O|+H@v@K|@3;-HHi?m}~s8s;vAtY8%TBT;u+?g8%wo6ZHHlE+jtS531{6x=B z*QfJ?0lwR{dx8`iO-^)QZ^y5jpACHVV*I=&=eDBJMwyh#w-sM3kY}) zOpfwu1ZOM$ouAkh3!ihvzk^c2|K@4476&B4^K}1c2g{Q+FNs{csO$psESlUN{^Xdi z3L!y9E$Bw`C0&8LmD$3x?fNCm(sl!g;X`cMFx5`-Ot!dkgGn-9fx&wWynbv z`=p+5gi*e%O#Ekjh-A|sbPN}p{ClF8P@NStusGstqSBmhsQRd`6q-+kj-~sBbkE7c zelLfG%Gbt1q;J#iL z^1amXmy5tU2=pK#-L|X_fftBd@cnsR>8RCof5Gp?&LdjfUQ3TJ`Z1AjzGpOgOkCm) zmsi(LFn~M~upbT38!Ah$U-mcN+2{MK0fRLpjjI`rC!lZw9T3>+X0+tC6L)F3Uud%q zAKa=dT;`>1zT%WSuw;Q-0SCcPPK?#)T{(`L+-1K@lec8hDgz>@lKqgr6FNN5zVp1~ zrjf^kG1az^a3QwGq*TlRs6M4W=6iw%R7!q15&DF$)jx481v7P1iXSwUW3BZ5>6lhO zEq{MY0N6b*N}SYp?s8zC(hX?ZSd6w!fL8daSAFPIlHl5*Goac*Hkb4+`lxv)Y_t#@{_(^iYKt$?fUh) zUV24}@tZ_1!68ik`+6zgXT6l~wRRxU7WbIC->iBGiSZ4imvZM|Z(1+q`>dDpJ)LL0 zlsZp(=+)_^PdD_^f8Q1B;d2~F73<-EKZvYGx84&5#68mK2dlHUeOKD-Q(_jCyWjt*#dmvZEtn?{Lz z=nQ7RGJz7(kt(!ZU{G#V@Egr?pkcFwU@~_e?$d%GlRm(c3S3(Vr=L}jV?A>W_8@_{IKu8g>5_ef@!f`Ds1@Cmgho-a`WTLi zq(^Jjnc zhwW{8DCQycS?vSx9EAmSo$GxxnYeQ{aW5yfJvyWwxuX47iigoqY~{2jq7ZxRPCyK8 zOK}`qSD`;Y3n;0A1))Q~hzLXMRZ_uCdzC!6AwHr29YxR9M?0m4>YR2 zAfmw<0QX1E^JBz#g8xL3oG0;b73spTfn*@SMx-DC#ANii=j!7l^r_E&hH>><=RXU| z{D?eGv#A}&Mih{8glz}v-A2-ylqp|C+gqP4n8JlR-pVPF>FHnA*2*3;Z*V{8c@)bh zqMnX~`Hw(mWyV*QUldS)??i7|g3-Mr>b-J9WVvLsga?ihFFP=VQ)H2uLh0g-Kk4Gv z)D)U}QPnPU4qKaZKR{HUuZ<9qQeW_GwQ&PabWoe40&+YC-kAvSYEH`J^cW$4g2f`q z4N6>9qs6WtJkTB+-19&aP+me|wHrf>pGi{@IeM?VW#FU<-X9lwh;eOTQs>eacLkH& z8HaiEpd@kXAR<9Rt5Ucl1)Pz$2WJlZ-!xDlm_`Ydl5=PODUQ3 zb0$(U86T%)TIZ*bb4n(BJ`!6^?pA&QYGuocuZ)K_{VtL_z`maS%~U=2C_%cQTDpbb zr=byFpaAv9DVf5%M+t@4sus9PPIE3)>Np3dWEzNi2nSu~P#1DrhD&EwKj!nq6((uq z_d6w%EhbVjQD3BF3MtXIf0UAmmnkKaza%9S!^hq%eH2Nu-5^JYyOy!kQW5gJs zAw95!l6bdSG__y5qrUs3Mk)Imp=_^S{V*sUM>WKF)R3Wdv@K;{@)vKmvEvV9=X6T; zCBeZ>E=a`SGsfEbxHp`vfci>?F72NN*SemG2>lv^nkD}d_zvV>;_l7K#4k?dQo4AI6^=7VTQCl z`lMkp`fss^KBZxz0sB(}`jlV(l|FE&rn*hsUMqTLR`1V@?i+r4`wwCbSyZ+p3o1Jo z7=6i@Sr$@Jc12|~txsoUX)WWiU-_zDd;(0jhY1_>8l|tO^e?16!nqq8pk;vCK2t6B zoTnlDVjfu#xV<~A>med7iD=zc=&(Xg{B#;H%Ih)nkpxbJtQT|#(|Yix3g=1*NpCi9AhL#1O&p^x zvBkKTY2Y4;!KOzkZBYVa(7|t1v?QR<4OVorR7AHCQTrisF}=P0nIIDl75I83Fhqm4 zs}I;Fh3C_|wRoqDO13bPNV3(kMd+%Egkh3W{@fUf6f+;)N{Wp>hE9U)j9y=$ee z5n#C3mcH*Kozao5%>!vsQWE}tIpaK(cSCHdZHx)`?KQ^)9k~tmb9(0Xxg!}m?ntO- z-4mS{RVBQo7F=PI9}GG=vS`GUQQbb~CwmkZ9aCEZU%k$;ULB8Nz&@W7yM`VITH!weJvc_^0 z8n8qBp`p`j)$ERI({Wv2Z))9Y!Hra>Zz#KYuGtr}zh*QCFt}1+pcye=)&0&YHLt>- zNZT~Cek4)k=k?E&t(5)Bp_UUpZwG>4rH-VD{xq1|IL0Z_f99JkMqp- zA78@EcG+%X9BIsFm*V%b@=Z8~hWSTi?(vF0cVzBi#xY}MX1Bz))7!;7A7cI#)7!}0 z(-oP>+{0DjH%LB3eKMKGlXp5#F+;jjpzlC%a80+)nR}SIX;-;3_jnc;nR^_00FIHl zhxmi7vs^pkoVmv+%Mto}M*qL@bC-hMo^qZ=xV zNd<;dBOet`3Gl2wDdI)V4dN)z8+(lFMZFgt8sE(4Z!&E}O{Ls}NSGgmFsX{ux%dEK z9%~S$Swe(a))x)J2m0c0GH>?cghRH%i3$`Id)N{MS7&+Tid&*WMHxy zl$~rpl1>KDxK4!L;$+(hKZ`{X#*v0OTMeC_Cx*Gx^MsS0VeZ^fxapCp*A_+Vd7u_i z6zO>w`5YcWB@#s`J�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

<0-xOvQ{r~JBXGH0tx}k0-Ta3)R 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 % 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�_n|%RtR^kQotfNLHUfqsg(G~yFMoU?Y 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


dD+J^0c~n!60%zbUh^vwMyORe5 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
1lF6*@JU=yBeslM3O#&vq_xY%Horyd??*aUb) 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%0AV0z~#6|TLsa>LoUHLYppgT<5G9z1<>0Ue$=_J$c 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

Date: Tue, 7 Mar 2023 09:49:04 -0700 Subject: [PATCH 43/45] Error instead of corrupt large op counters (#543) Since b78211ca6, OpIds have been silently truncated to 2**32. This causes corruption in the case the op id overflows. This change converts the silent error to a panic, and guards against the panic on the codepath found by the fuzzer. --- .../automerge/src/columnar/column_range/opid.rs | 6 +++--- .../src/columnar/encoding/properties.rs | 2 +- rust/automerge/src/storage/change.rs | 3 +++ .../src/storage/change/change_op_columns.rs | 2 ++ rust/automerge/src/types.rs | 6 +++--- rust/automerge/src/types/opids.rs | 2 +- .../fixtures/64bit_obj_id_change.automerge | Bin 0 -> 73 bytes .../tests/fixtures/64bit_obj_id_doc.automerge | Bin 0 -> 147 bytes rust/automerge/tests/test.rs | 16 ++++++++++++++++ 9 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 rust/automerge/tests/fixtures/64bit_obj_id_change.automerge create mode 100644 rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge diff --git a/rust/automerge/src/columnar/column_range/opid.rs b/rust/automerge/src/columnar/column_range/opid.rs index ae95d758..d2cdce79 100644 --- a/rust/automerge/src/columnar/column_range/opid.rs +++ b/rust/automerge/src/columnar/column_range/opid.rs @@ -104,11 +104,11 @@ impl<'a> OpIdIter<'a> { .transpose() .map_err(|e| DecodeColumnError::decode_raw("counter", e))?; match (actor, counter) { - (Some(Some(a)), Some(Some(c))) => match c.try_into() { - Ok(c) => Ok(Some(OpId::new(c, a as usize))), + (Some(Some(a)), Some(Some(c))) => match u32::try_from(c) { + Ok(c) => Ok(Some(OpId::new(c as u64, a as usize))), Err(_) => Err(DecodeColumnError::invalid_value( "counter", - "negative value encountered", + "negative or large value encountered", )), }, (Some(None), _) => Err(DecodeColumnError::unexpected_null("actor")), diff --git a/rust/automerge/src/columnar/encoding/properties.rs b/rust/automerge/src/columnar/encoding/properties.rs index a3bf1ed0..30f1169d 100644 --- a/rust/automerge/src/columnar/encoding/properties.rs +++ b/rust/automerge/src/columnar/encoding/properties.rs @@ -139,7 +139,7 @@ pub(crate) fn option_splice_scenario< } pub(crate) fn opid() -> impl Strategy + Clone { - (0..(i64::MAX as usize), 0..(i64::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) + (0..(u32::MAX as usize), 0..(u32::MAX as u64)).prop_map(|(actor, ctr)| OpId::new(ctr, actor)) } pub(crate) fn elemid() -> impl Strategy + Clone { diff --git a/rust/automerge/src/storage/change.rs b/rust/automerge/src/storage/change.rs index ff3cc9ab..61db0b00 100644 --- a/rust/automerge/src/storage/change.rs +++ b/rust/automerge/src/storage/change.rs @@ -177,6 +177,9 @@ impl<'a> Change<'a, Unverified> { for op in self.iter_ops() { f(op?); } + if u32::try_from(u64::from(self.start_op)).is_err() { + return Err(ReadChangeOpError::CounterTooLarge); + } Ok(Change { bytes: self.bytes, header: self.header, diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index cd1cb150..86ec59c2 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -283,6 +283,8 @@ pub enum ReadChangeOpError { DecodeError(#[from] DecodeColumnError), #[error(transparent)] InvalidOpType(#[from] InvalidOpType), + #[error("counter too large")] + CounterTooLarge, } #[derive(Clone)] diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 2978aa97..468986ec 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -439,17 +439,17 @@ pub(crate) struct OpId(u32, u32); impl OpId { pub(crate) fn new(counter: u64, actor: usize) -> Self { - Self(counter as u32, actor as u32) + Self(counter.try_into().unwrap(), actor.try_into().unwrap()) } #[inline] pub(crate) fn counter(&self) -> u64 { - self.0 as u64 + self.0.into() } #[inline] pub(crate) fn actor(&self) -> usize { - self.1 as usize + self.1.try_into().unwrap() } #[inline] diff --git a/rust/automerge/src/types/opids.rs b/rust/automerge/src/types/opids.rs index eaeed471..a81ccb36 100644 --- a/rust/automerge/src/types/opids.rs +++ b/rust/automerge/src/types/opids.rs @@ -129,7 +129,7 @@ mod tests { fn gen_opid(actors: Vec) -> impl Strategy { (0..actors.len()).prop_flat_map(|actor_idx| { - (Just(actor_idx), 0..u64::MAX) + (Just(actor_idx), 0..(u32::MAX as u64)) .prop_map(|(actor_idx, counter)| OpId::new(counter, actor_idx)) }) } diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge new file mode 100644 index 0000000000000000000000000000000000000000..700342a2df71772d78f0373385f44aae9eb88c7b GIT binary patch literal 73 zcmZq8_i9cmO}NHr&meG%DY?GbS?DGk_of5L_6# literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge b/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge new file mode 100644 index 0000000000000000000000000000000000000000..6beb57fe9ad7d5428d5b854c0e39f8bb57dcfdf7 GIT binary patch literal 147 zcmZq8_i7GNJ@|p4gOO3-7FS4!le1?_E5p*)57*rEWlSnfxVFRCa9`J~6^(1kJz9Nc zT|0UFe#&#R(pL)KvpP?)GcqwV33Dj3n{qiYg)y; Date: Thu, 9 Mar 2023 08:09:43 -0700 Subject: [PATCH 44/45] smaller automerge c (#545) * Fix automerge-c tests on mac * Generate significantly smaller automerge-c builds This cuts the size of libautomerge_core.a from 25Mb to 1.6Mb on macOS and 53Mb to 2.7Mb on Linux. As a side-effect of setting codegen-units = 1 for all release builds the optimized wasm files are also 100kb smaller. --- .github/workflows/ci.yaml | 8 +++++--- README.md | 5 ++++- rust/Cargo.toml | 9 ++------- rust/automerge-c/CMakeLists.txt | 26 +++++++++++++++++++++---- rust/automerge-c/test/byte_span_tests.c | 1 + 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0263f408..8519ac5e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,10 +2,10 @@ name: CI on: push: branches: - - main + - main pull_request: branches: - - main + - main jobs: fmt: runs-on: ubuntu-latest @@ -118,7 +118,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.67.0 + toolchain: nightly-2023-01-26 default: true - uses: Swatinem/rust-cache@v1 - name: Install CMocka @@ -127,6 +127,8 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.12 with: cmake-version: latest + - name: Install rust-src + run: rustup component add rust-src - name: Build and test C bindings run: ./scripts/ci/cmake-build Release Static shell: bash diff --git a/README.md b/README.md index 76d48ddd..ad174da4 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,6 @@ to figure out how to use it. If you are looking to build rust applications which use automerge you may want to look into [autosurgeon](https://github.com/alexjg/autosurgeon) - ## Repository Organisation - `./rust` - the rust rust implementation and also the Rust components of @@ -119,6 +118,10 @@ yarn --cwd ./javascript # install rust dependencies cargo install wasm-bindgen-cli wasm-opt cargo-deny +# get nightly rust to produce optimized automerge-c builds +rustup toolchain install nightly +rustup component add rust-src --toolchain nightly + # add wasm target in addition to current architecture rustup target add wasm32-unknown-unknown diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 938100cf..5d29fc9f 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,13 +10,8 @@ members = [ resolver = "2" [profile.release] -debug = true lto = true -opt-level = 3 +codegen-units = 1 [profile.bench] -debug = true - -[profile.release.package.automerge-wasm] -debug = false -opt-level = 3 +debug = true \ No newline at end of file diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt index 056d111b..0c35eebd 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -43,19 +43,37 @@ endif() string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) +# In order to build with -Z build-std, we need to pass target explicitly. +# https://doc.rust-lang.org/cargo/reference/unstable.html#build-std +execute_process ( + COMMAND rustc -vV + OUTPUT_VARIABLE RUSTC_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(REGEX REPLACE ".*host: ([^ \n]*).*" "\\1" + CARGO_TARGET + ${RUSTC_VERSION} +) + if(BUILD_TYPE_LOWER STREQUAL debug) set(CARGO_BUILD_TYPE "debug") - set(CARGO_FLAG "") + set(CARGO_FLAG --target=${CARGO_TARGET}) else() set(CARGO_BUILD_TYPE "release") - set(CARGO_FLAG "--release") + if (NOT RUSTC_VERSION MATCHES "nightly") + set(RUSTUP_TOOLCHAIN nightly) + endif() + + set(RUSTFLAGS -C\ panic=abort) + + set(CARGO_FLAG -Z build-std=std,panic_abort --release --target=${CARGO_TARGET}) endif() set(CARGO_FEATURES "") -set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") +set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_TARGET}/${CARGO_BUILD_TYPE}") set(BINDINGS_NAME "${LIBRARY_NAME}_core") @@ -90,7 +108,7 @@ add_custom_command( # configuration file has been updated. ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml COMMAND - ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} + ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} RUSTUP_TOOLCHAIN=${RUSTUP_TOOLCHAIN} RUSTFLAGS=${RUSTFLAGS} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} COMMAND # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h diff --git a/rust/automerge-c/test/byte_span_tests.c b/rust/automerge-c/test/byte_span_tests.c index 43856f3b..0b1c86a1 100644 --- a/rust/automerge-c/test/byte_span_tests.c +++ b/rust/automerge-c/test/byte_span_tests.c @@ -3,6 +3,7 @@ #include #include #include +#include /* third-party */ #include From cb409b6ffe2cec15ce7724c291cf91d383b4c19b Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 9 Mar 2023 18:10:23 +0000 Subject: [PATCH 45/45] docs: timestamp -> time in automerge.change examples (#548) --- javascript/src/stable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 74410346..e83b127f 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -305,7 +305,7 @@ export function from>( * @example A change with a message and a timestamp * * ``` - * doc1 = automerge.change(doc1, {message: "add another value", timestamp: 1640995200}, d => { + * doc1 = automerge.change(doc1, {message: "add another value", time: 1640995200}, d => { * d.key2 = "value2" * }) * ``` @@ -316,7 +316,7 @@ export function from>( * let patchCallback = patch => { * patchedPath = patch.path * } - * doc1 = automerge.change(doc1, {message, "add another value", timestamp: 1640995200, patchCallback}, d => { + * doc1 = automerge.change(doc1, {message, "add another value", time: 1640995200, patchCallback}, d => { * d.key2 = "value2" * }) * assert.equal(patchedPath, ["key2"])

?)v2k*P`B)6SOU7*|G%aeT?z571)pw<=c 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±u6530NCg@!8lo&otz@0`RGBG6K6>dj$+{ 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⪻cE8j431Cj==Z9z)~3eK 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 | Value +export type ObjType = string | Array | { [key: string]: ObjType | Value } +export type FullValue = + ["str", string] | + ["int", number] | + ["uint", number] | + ["f64", number] | + ["boolean", boolean] | + ["timestamp", Date] | + ["counter", number] | + ["bytes", Uint8Array] | + ["null", null] | + ["map", ObjID] | + ["list", ObjID] | + ["text", ObjID] | + ["table", ObjID] + +export type FullValueWithId = + ["str", string, ObjID ] | + ["int", number, ObjID ] | + ["uint", number, ObjID ] | + ["f64", number, ObjID ] | + ["boolean", boolean, ObjID ] | + ["timestamp", Date, ObjID ] | + ["counter", number, ObjID ] | + ["bytes", Uint8Array, ObjID ] | + ["null", null, ObjID ] | + ["map", ObjID ] | + ["list", ObjID] | + ["text", ObjID] | + ["table", ObjID] + +export enum ObjTypeName { + list = "list", + map = "map", + table = "table", + text = "text", +} + +export type Datatype = + "boolean" | + "str" | + "int" | + "uint" | + "f64" | + "null" | + "timestamp" | + "counter" | + "bytes" | + "map" | + "text" | + "list"; + +export type SyncHave = { + lastSync: Heads, + bloom: Uint8Array, +} + +export type DecodedSyncMessage = { + heads: Heads, + need: Heads, + have: SyncHave[] + changes: Change[] +} + +export type DecodedChange = { + actor: Actor, + seq: number + startOp: number, + time: number, + message: string | null, + deps: Heads, + hash: Hash, + ops: Op[] +} + +type PartialBy = Omit & Partial> +export type ChangeToEncode = PartialBy + +export type Op = { + action: string, + obj: ObjID, + key: string, + value?: string | number | boolean, + datatype?: string, + pred: string[], +} + +export type Patch = PutPatch | DelPatch | SplicePatch | IncPatch; + +export type PutPatch = { + action: 'put' + path: Prop[], + value: Value + conflict: boolean +} + +export type IncPatch = { + action: 'inc' + path: Prop[], + value: number +} + +export type DelPatch = { + action: 'del' + path: Prop[], + length?: number, +} + +export type SplicePatch = { + action: 'splice' + path: Prop[], + values: Value[], +} + +export function encodeChange(change: ChangeToEncode): Change; +export function create(text_v2: boolean, actor?: Actor): Automerge; +export function load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge; +export function decodeChange(change: Change): DecodedChange; +export function initSyncState(): SyncState; +export function encodeSyncMessage(message: DecodedSyncMessage): SyncMessage; +export function decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage; +export function encodeSyncState(state: SyncState): Uint8Array; +export function decodeSyncState(data: Uint8Array): SyncState; +export function exportSyncState(state: SyncState): JsSyncState; +export function importSyncState(state: JsSyncState): SyncState; + +export interface API { + create(text_v2: boolean, actor?: Actor): Automerge; + load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge; + encodeChange(change: ChangeToEncode): Change; + decodeChange(change: Change): DecodedChange; + initSyncState(): SyncState; + encodeSyncMessage(message: DecodedSyncMessage): SyncMessage; + decodeSyncMessage(msg: SyncMessage): DecodedSyncMessage; + encodeSyncState(state: SyncState): Uint8Array; + decodeSyncState(data: Uint8Array): SyncState; + exportSyncState(state: SyncState): JsSyncState; + importSyncState(state: JsSyncState): SyncState; +} + +export class Automerge { + // change state + put(obj: ObjID, prop: Prop, value: Value, datatype?: Datatype): void; + putObject(obj: ObjID, prop: Prop, value: ObjType): ObjID; + insert(obj: ObjID, index: number, value: Value, datatype?: Datatype): void; + insertObject(obj: ObjID, index: number, value: ObjType): ObjID; + push(obj: ObjID, value: Value, datatype?: Datatype): void; + pushObject(obj: ObjID, value: ObjType): ObjID; + splice(obj: ObjID, start: number, delete_count: number, text?: string | Array): ObjID[] | undefined; + increment(obj: ObjID, prop: Prop, value: number): void; + delete(obj: ObjID, prop: Prop): void; + + // returns a single value - if there is a conflict return the winner + get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined; + getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null; + // return all values in case of a conflict + getAll(obj: ObjID, arg: Prop, heads?: Heads): FullValueWithId[]; + keys(obj: ObjID, heads?: Heads): string[]; + text(obj: ObjID, heads?: Heads): string; + length(obj: ObjID, heads?: Heads): number; + materialize(obj?: ObjID, heads?: Heads, metadata?: unknown): MaterializeValue; + toJS(): MaterializeValue; + + // transactions + commit(message?: string, time?: number): Hash | null; + emptyChange(message?: string, time?: number): Hash; + merge(other: Automerge): Heads; + getActorId(): Actor; + pendingOps(): number; + rollback(): number; + + // patches + enablePatches(enable: boolean): boolean; + enableFreeze(enable: boolean): boolean; + registerDatatype(datatype: string, callback: Function): void; + popPatches(): Patch[]; + + // save and load to local store + save(): Uint8Array; + saveIncremental(): Uint8Array; + loadIncremental(data: Uint8Array): number; + + // sync over network + receiveSyncMessage(state: SyncState, message: SyncMessage): void; + generateSyncMessage(state: SyncState): SyncMessage | null; + + // low level change functions + applyChanges(changes: Change[]): void; + getChanges(have_deps: Heads): Change[]; + getChangeByHash(hash: Hash): Change | null; + getChangesAdded(other: Automerge): Change[]; + getHeads(): Heads; + getLastLocalChange(): Change | null; + getMissingDeps(heads?: Heads): Heads; + + // memory management + free(): void; // only needed if weak-refs are unsupported + clone(actor?: string): Automerge; // TODO - remove, this is dangerous + fork(actor?: string, heads?: Heads): Automerge; + + // dump internal state to console.log - for debugging + dump(): void; + + // experimental api can go here + applyPatches(obj: Doc, meta?: unknown, callback?: (patch: Array, before: Doc, after: Doc) => void): Doc; +} + +export interface JsSyncState { + sharedHeads: Heads; + lastSentHeads: Heads; + theirHeads: Heads | undefined; + theirHeed: Heads | undefined; + theirHave: SyncHave[] | undefined; + sentHashes: Heads; +} + +export class SyncState { + free(): void; + clone(): SyncState; + lastSentHeads: Heads; + sentHashes: Heads; + readonly sharedHeads: Heads; +} From 2d8df125224a251da729efb149dda7f8bb255d26 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 12 Jan 2023 11:35:48 +0000 Subject: [PATCH 02/45] re-enable version check for WASM release --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 530f07c7..15495233 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -24,7 +24,7 @@ jobs: needs: - check_if_wasm_version_upgraded # We create release only if the version in the package.json has been upgraded - #if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true' + if: needs.check_if_wasm_version_upgraded.outputs.wasm_has_updated == 'true' steps: - uses: actions/setup-node@v3 with: From 22e9915fac632adb213e4675c6169953167d3349 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 12 Jan 2023 12:32:53 +0000 Subject: [PATCH 03/45] automerge-wasm: publish release build in Github Action --- .github/workflows/release.yaml | 6 ++++++ javascript/package.json | 2 +- rust/automerge-wasm/package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 15495233..b3c0aed1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -50,12 +50,18 @@ jobs: - name: run wasm deno tests id: wasm_deno_tests run: ./scripts/ci/deno_tests + - name: build release + id: build_release + run: | + npm --prefix $GITHUB_WORKSPACE/rust/automerge-wasm run release - name: Collate deno release files if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' run: | mkdir $GITHUB_WORKSPACE/deno_wasm_dist cp $GITHUB_WORKSPACE/rust/automerge-wasm/deno/* $GITHUB_WORKSPACE/deno_wasm_dist cp $GITHUB_WORKSPACE/rust/automerge-wasm/index.d.ts $GITHUB_WORKSPACE/deno_wasm_dist + cp $GITHUB_WORKSPACE/rust/automerge-wasm/README.md $GITHUB_WORKSPACE/deno_wasm_dist + cp $GITHUB_WORKSPACE/rust/automerge-wasm/LICENSE $GITHUB_WORKSPACE/deno_wasm_dist sed -i '1i /// ' $GITHUB_WORKSPACE/deno_wasm_dist/automerge_wasm.js - name: Create npm release if: steps.wasm_js_tests.outcome == 'success' && steps.wasm_deno_tests.outcome == 'success' diff --git a/javascript/package.json b/javascript/package.json index 5e2efbda..53cc6fdc 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -44,7 +44,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.20", + "@automerge/automerge-wasm": "0.1.21", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 47dd7f32..76167a3e 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.20", + "version": "0.1.21", "license": "MIT", "files": [ "README.md", From 681a3f1f3fd6161cb7733e07cdfe46d68b6967fe Mon Sep 17 00:00:00 2001 From: Alex Currie-Clark Date: Thu, 12 Jan 2023 07:04:40 +0000 Subject: [PATCH 04/45] Add github action to deploy deno package --- .github/workflows/release.yaml | 110 +++++++++++++++++++++++- javascript/.denoifyrc.json | 3 + javascript/.gitignore | 1 + javascript/config/cjs.json | 7 +- javascript/config/declonly.json | 7 +- javascript/config/mjs.json | 7 +- javascript/deno-tests/deno.ts | 10 +++ javascript/package.json | 5 +- javascript/scripts/deno-prefixer.mjs | 9 ++ javascript/scripts/denoify-replacer.mjs | 42 +++++++++ javascript/src/constants.ts | 2 +- javascript/src/counter.ts | 2 +- javascript/src/internal_state.ts | 4 +- javascript/src/low_level.ts | 20 ++--- javascript/src/numbers.ts | 2 +- javascript/src/proxies.ts | 9 +- javascript/src/stable.ts | 45 +++++----- javascript/src/text.ts | 8 +- javascript/src/unstable.ts | 12 ++- javascript/src/uuid.deno.ts | 26 ++++++ javascript/tsconfig.json | 2 +- scripts/ci/deno_tests | 13 ++- 22 files changed, 296 insertions(+), 50 deletions(-) create mode 100644 javascript/.denoifyrc.json create mode 100644 javascript/deno-tests/deno.ts create mode 100644 javascript/scripts/deno-prefixer.mjs create mode 100644 javascript/scripts/denoify-replacer.mjs create mode 100644 javascript/src/uuid.deno.ts diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b3c0aed1..762671ff 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -103,4 +103,112 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Remove tmp_branch run: git push origin :tmp_branch - + check_if_js_version_upgraded: + name: Check if JS version has been upgraded + runs-on: ubuntu-latest + outputs: + js_version: ${{ steps.version-updated.outputs.current-package-version }} + js_has_updated: ${{ steps.version-updated.outputs.has-updated }} + steps: + - uses: JiPaix/package-json-updated-action@v1.0.5 + id: version-updated + with: + path: javascript/package.json + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + publish-js: + name: Publish JS package + runs-on: ubuntu-latest + needs: + - check_if_js_version_upgraded + - check_if_wasm_version_upgraded + - publish-wasm + # We create release only if the version in the package.json has been upgraded and after the WASM release + if: | + (always() && ! cancelled()) && + (needs.publish-wasm.result == 'success' || needs.publish-wasm.result == 'skipped') && + needs.check_if_js_version_upgraded.outputs.js_has_updated == 'true' + steps: + - uses: actions/setup-node@v3 + with: + node-version: '16.x' + registry-url: 'https://registry.npmjs.org' + - uses: denoland/setup-deno@v1 + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + ref: ${{ github.ref }} + - name: Get rid of local github workflows + run: rm -r .github/workflows + - name: Remove js_tmp_branch if it exists + run: git push origin :js_tmp_branch || true + - run: git checkout -b js_tmp_branch + - name: check js formatting + run: | + yarn global add prettier + prettier -c javascript/.prettierrc javascript + - name: run js tests + id: js_tests + run: | + cargo install wasm-bindgen-cli wasm-opt + rustup target add wasm32-unknown-unknown + ./scripts/ci/js_tests + - name: build js release + id: build_release + run: | + npm --prefix $GITHUB_WORKSPACE/javascript run build + - name: build js deno release + id: build_deno_release + run: | + VERSION=$WASM_VERSION npm --prefix $GITHUB_WORKSPACE/javascript run deno:build + env: + WASM_VERSION: ${{ needs.check_if_wasm_version_upgraded.outputs.wasm_version }} + - name: run deno tests + id: deno_tests + run: | + npm --prefix $GITHUB_WORKSPACE/javascript run deno:test + - name: Collate deno release files + if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' + run: | + mkdir $GITHUB_WORKSPACE/deno_js_dist + cp $GITHUB_WORKSPACE/javascript/deno_dist/* $GITHUB_WORKSPACE/deno_js_dist + - name: Create npm release + if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' + run: | + if [ "$(npm --prefix $GITHUB_WORKSPACE/javascript show . version)" = "$VERSION" ]; then + echo "This version is already published" + exit 0 + fi + EXTRA_ARGS="--access public" + if [[ $VERSION == *"alpha."* ]] || [[ $VERSION == *"beta."* ]] || [[ $VERSION == *"rc."* ]]; then + echo "Is pre-release version" + EXTRA_ARGS="$EXTRA_ARGS --tag next" + fi + if [ "$NODE_AUTH_TOKEN" = "" ]; then + echo "Can't publish on NPM, You need a NPM_TOKEN secret." + false + fi + npm publish $GITHUB_WORKSPACE/javascript $EXTRA_ARGS + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + VERSION: ${{ needs.check_if_js_version_upgraded.outputs.js_version }} + - name: Commit js deno release files + run: | + git config --global user.name "actions" + git config --global user.email actions@github.com + git add $GITHUB_WORKSPACE/deno_js_dist + git commit -am "Add deno js release files" + git push origin js_tmp_branch + - name: Tag JS release + if: steps.js_tests.outcome == 'success' && steps.deno_tests.outcome == 'success' + uses: softprops/action-gh-release@v1 + with: + name: Automerge v${{ needs.check_if_js_version_upgraded.outputs.js_version }} + tag_name: js/automerge-${{ needs.check_if_js_version_upgraded.outputs.js_version }} + target_commitish: js_tmp_branch + generate_release_notes: false + draft: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Remove js_tmp_branch + run: git push origin :js_tmp_branch diff --git a/javascript/.denoifyrc.json b/javascript/.denoifyrc.json new file mode 100644 index 00000000..9453a31f --- /dev/null +++ b/javascript/.denoifyrc.json @@ -0,0 +1,3 @@ +{ + "replacer": "scripts/denoify-replacer.mjs" +} diff --git a/javascript/.gitignore b/javascript/.gitignore index ab4ec70d..f98d9db2 100644 --- a/javascript/.gitignore +++ b/javascript/.gitignore @@ -3,3 +3,4 @@ dist docs/ .vim +deno_dist/ diff --git a/javascript/config/cjs.json b/javascript/config/cjs.json index fc500311..0b135067 100644 --- a/javascript/config/cjs.json +++ b/javascript/config/cjs.json @@ -1,6 +1,11 @@ { "extends": "../tsconfig.json", - "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"], + "exclude": [ + "../dist/**/*", + "../node_modules", + "../test/**/*", + "../src/**/*.deno.ts" + ], "compilerOptions": { "outDir": "../dist/cjs" } diff --git a/javascript/config/declonly.json b/javascript/config/declonly.json index df615930..7c1df687 100644 --- a/javascript/config/declonly.json +++ b/javascript/config/declonly.json @@ -1,6 +1,11 @@ { "extends": "../tsconfig.json", - "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"], + "exclude": [ + "../dist/**/*", + "../node_modules", + "../test/**/*", + "../src/**/*.deno.ts" + ], "emitDeclarationOnly": true, "compilerOptions": { "outDir": "../dist" diff --git a/javascript/config/mjs.json b/javascript/config/mjs.json index 2ee7a8b8..ecf3ce36 100644 --- a/javascript/config/mjs.json +++ b/javascript/config/mjs.json @@ -1,6 +1,11 @@ { "extends": "../tsconfig.json", - "exclude": ["../dist/**/*", "../node_modules", "../test/**/*"], + "exclude": [ + "../dist/**/*", + "../node_modules", + "../test/**/*", + "../src/**/*.deno.ts" + ], "compilerOptions": { "target": "es6", "module": "es6", diff --git a/javascript/deno-tests/deno.ts b/javascript/deno-tests/deno.ts new file mode 100644 index 00000000..fc0a4dad --- /dev/null +++ b/javascript/deno-tests/deno.ts @@ -0,0 +1,10 @@ +import * as Automerge from "../deno_dist/index.ts" + +Deno.test("It should create, clone and free", () => { + let doc1 = Automerge.init() + let doc2 = Automerge.clone(doc1) + + // this is only needed if weakrefs are not supported + Automerge.free(doc1) + Automerge.free(doc2) +}) diff --git a/javascript/package.json b/javascript/package.json index 53cc6fdc..39464fac 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.1-alpha.3", + "version": "2.0.1-alpha.4", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -25,6 +25,8 @@ "lint": "eslint src", "build": "tsc -p config/mjs.json && tsc -p config/cjs.json && tsc -p config/declonly.json --emitDeclarationOnly", "test": "ts-mocha test/*.ts", + "deno:build": "denoify && node ./scripts/deno-prefixer.mjs", + "deno:test": "deno test ./deno-tests/deno.ts --allow-read --allow-net", "watch-docs": "typedoc src/index.ts --watch --readme none" }, "devDependencies": { @@ -33,6 +35,7 @@ "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", + "denoify": "^1.4.5", "eslint": "^8.29.0", "fast-sha256": "^1.3.0", "mocha": "^10.2.0", diff --git a/javascript/scripts/deno-prefixer.mjs b/javascript/scripts/deno-prefixer.mjs new file mode 100644 index 00000000..28544102 --- /dev/null +++ b/javascript/scripts/deno-prefixer.mjs @@ -0,0 +1,9 @@ +import * as fs from "fs" + +const files = ["./deno_dist/proxies.ts"] +for (const filepath of files) { + const data = fs.readFileSync(filepath) + fs.writeFileSync(filepath, "// @ts-nocheck \n" + data) + + console.log('Prepended "// @ts-nocheck" to ' + filepath) +} diff --git a/javascript/scripts/denoify-replacer.mjs b/javascript/scripts/denoify-replacer.mjs new file mode 100644 index 00000000..fcf4bc45 --- /dev/null +++ b/javascript/scripts/denoify-replacer.mjs @@ -0,0 +1,42 @@ +// @denoify-ignore + +import { makeThisModuleAnExecutableReplacer } from "denoify" +// import { assert } from "tsafe"; +// import * as path from "path"; + +makeThisModuleAnExecutableReplacer( + async ({ parsedImportExportStatement, destDirPath, version }) => { + version = process.env.VERSION || version + + switch (parsedImportExportStatement.parsedArgument.nodeModuleName) { + case "@automerge/automerge-wasm": + { + const moduleRoot = + process.env.MODULE_ROOT || + `https://deno.land/x/automerge_wasm@${version}` + /* + *We expect not to run against statements like + *import(..).then(...) + *or + *export * from "..." + *in our code. + */ + if ( + !parsedImportExportStatement.isAsyncImport && + (parsedImportExportStatement.statementType === "import" || + parsedImportExportStatement.statementType === "export") + ) { + if (parsedImportExportStatement.isTypeOnly) { + return `${parsedImportExportStatement.statementType} type ${parsedImportExportStatement.target} from "${moduleRoot}/index.d.ts";` + } else { + return `${parsedImportExportStatement.statementType} ${parsedImportExportStatement.target} from "${moduleRoot}/automerge_wasm.js";` + } + } + } + break + } + + //The replacer should return undefined when we want to let denoify replace the statement + return undefined + } +) diff --git a/javascript/src/constants.ts b/javascript/src/constants.ts index d3bd8138..7b714772 100644 --- a/javascript/src/constants.ts +++ b/javascript/src/constants.ts @@ -2,7 +2,7 @@ export const STATE = Symbol.for("_am_meta") // symbol used to hide application metadata on automerge objects export const TRACE = Symbol.for("_am_trace") // used for debugging -export const OBJECT_ID = Symbol.for("_am_objectId") // synbol used to hide the object id on automerge objects +export const OBJECT_ID = Symbol.for("_am_objectId") // symbol used to hide the object id on automerge objects export const IS_PROXY = Symbol.for("_am_isProxy") // symbol used to test if the document is a proxy object export const UINT = Symbol.for("_am_uint") diff --git a/javascript/src/counter.ts b/javascript/src/counter.ts index 6b9ad277..873fa157 100644 --- a/javascript/src/counter.ts +++ b/javascript/src/counter.ts @@ -1,4 +1,4 @@ -import { Automerge, ObjID, Prop } from "@automerge/automerge-wasm" +import { Automerge, type ObjID, type Prop } from "@automerge/automerge-wasm" import { COUNTER } from "./constants" /** * The most basic CRDT: an integer value that can be changed only by diff --git a/javascript/src/internal_state.ts b/javascript/src/internal_state.ts index 92ab648e..f3da49b1 100644 --- a/javascript/src/internal_state.ts +++ b/javascript/src/internal_state.ts @@ -1,8 +1,8 @@ -import { ObjID, Heads, Automerge } from "@automerge/automerge-wasm" +import { type ObjID, type Heads, Automerge } from "@automerge/automerge-wasm" import { STATE, OBJECT_ID, TRACE, IS_PROXY } from "./constants" -import { type Doc, PatchCallback } from "./types" +import type { Doc, PatchCallback } from "./types" export interface InternalState { handle: Automerge diff --git a/javascript/src/low_level.ts b/javascript/src/low_level.ts index 94ac63db..63ef5546 100644 --- a/javascript/src/low_level.ts +++ b/javascript/src/low_level.ts @@ -1,20 +1,20 @@ import { + type API, Automerge, - Change, - DecodedChange, - Actor, + type Change, + type DecodedChange, + type Actor, SyncState, - SyncMessage, - JsSyncState, - DecodedSyncMessage, - ChangeToEncode, + type SyncMessage, + type JsSyncState, + type DecodedSyncMessage, + type ChangeToEncode, } from "@automerge/automerge-wasm" -export { ChangeToEncode } from "@automerge/automerge-wasm" -import { API } from "@automerge/automerge-wasm" +export type { ChangeToEncode } from "@automerge/automerge-wasm" export function UseApi(api: API) { for (const k in api) { - ApiHandler[k] = api[k] + ;(ApiHandler as any)[k] = (api as any)[k] } } diff --git a/javascript/src/numbers.ts b/javascript/src/numbers.ts index d52a36c5..7ad95998 100644 --- a/javascript/src/numbers.ts +++ b/javascript/src/numbers.ts @@ -1,4 +1,4 @@ -// Convience classes to allow users to stricly specify the number type they want +// Convenience classes to allow users to strictly specify the number type they want import { INT, UINT, F64 } from "./constants" diff --git a/javascript/src/proxies.ts b/javascript/src/proxies.ts index 3fb3a825..7a99cf80 100644 --- a/javascript/src/proxies.ts +++ b/javascript/src/proxies.ts @@ -1,7 +1,12 @@ import { Text } from "./text" -import { Automerge, Heads, ObjID } from "@automerge/automerge-wasm" -import { Prop } from "@automerge/automerge-wasm" import { + Automerge, + type Heads, + type ObjID, + type Prop, +} from "@automerge/automerge-wasm" + +import type { AutomergeValue, ScalarValue, MapValue, diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index c52d0a4c..1f38cb27 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -4,47 +4,50 @@ export { /** @hidden */ uuid } from "./uuid" import { rootProxy, listProxy, mapProxy, textProxy } from "./proxies" import { STATE } from "./constants" -import { AutomergeValue, Counter, Doc, PatchCallback } from "./types" -export { - AutomergeValue, +import { + type AutomergeValue, Counter, - Doc, + type Doc, + type PatchCallback, +} from "./types" +export { + type AutomergeValue, + Counter, + type Doc, Int, Uint, Float64, - Patch, - PatchCallback, - ScalarValue, + type Patch, + type PatchCallback, + type ScalarValue, Text, } from "./types" import { Text } from "./text" -import { type API } from "@automerge/automerge-wasm" -export { - PutPatch, - DelPatch, - SplicePatch, - IncPatch, - SyncMessage, -} from "@automerge/automerge-wasm" -import { ApiHandler, ChangeToEncode, UseApi } from "./low_level" - -import { +import type { + API, Actor as ActorId, Prop, ObjID, Change, DecodedChange, Heads, - Automerge, MaterializeValue, -} from "@automerge/automerge-wasm" -import { JsSyncState as SyncState, SyncMessage, DecodedSyncMessage, } from "@automerge/automerge-wasm" +export type { + PutPatch, + DelPatch, + SplicePatch, + IncPatch, + SyncMessage, +} from "@automerge/automerge-wasm" +import { ApiHandler, type ChangeToEncode, UseApi } from "./low_level" + +import { Automerge } from "@automerge/automerge-wasm" import { RawString } from "./raw_string" diff --git a/javascript/src/text.ts b/javascript/src/text.ts index bb0a868d..f87af891 100644 --- a/javascript/src/text.ts +++ b/javascript/src/text.ts @@ -1,10 +1,12 @@ -import { Value } from "@automerge/automerge-wasm" +import type { Value } from "@automerge/automerge-wasm" import { TEXT, STATE } from "./constants" +import type { InternalState } from "./internal_state" export class Text { elems: Array str: string | undefined - spans: Array | undefined + spans: Array | undefined; + [STATE]?: InternalState constructor(text?: string | string[] | Value[]) { if (typeof text === "string") { @@ -208,7 +210,7 @@ export class Text { new Text(this.elems.slice(start, end)) } - some(test: (Value) => boolean): boolean { + some(test: (arg: Value) => boolean): boolean { return this.elems.some(test) } diff --git a/javascript/src/unstable.ts b/javascript/src/unstable.ts index 3ee18dbc..b448d955 100644 --- a/javascript/src/unstable.ts +++ b/javascript/src/unstable.ts @@ -37,7 +37,15 @@ */ import { Counter } from "./types" -export { Counter, Doc, Int, Uint, Float64, Patch, PatchCallback } from "./types" +export { + Counter, + type Doc, + Int, + Uint, + Float64, + type Patch, + type PatchCallback, +} from "./types" import type { PatchCallback } from "./stable" @@ -59,7 +67,7 @@ export type ScalarValue = export type Conflicts = { [key: string]: AutomergeValue } -export { +export type { PutPatch, DelPatch, SplicePatch, diff --git a/javascript/src/uuid.deno.ts b/javascript/src/uuid.deno.ts new file mode 100644 index 00000000..04c9b93d --- /dev/null +++ b/javascript/src/uuid.deno.ts @@ -0,0 +1,26 @@ +import * as v4 from "https://deno.land/x/uuid@v0.1.2/mod.ts" + +// this file is a deno only port of the uuid module + +function defaultFactory() { + return v4.uuid().replace(/-/g, "") +} + +let factory = defaultFactory + +interface UUIDFactory extends Function { + setFactory(f: typeof factory): void + reset(): void +} + +export const uuid: UUIDFactory = () => { + return factory() +} + +uuid.setFactory = newFactory => { + factory = newFactory +} + +uuid.reset = () => { + factory = defaultFactory +} diff --git a/javascript/tsconfig.json b/javascript/tsconfig.json index c6684ca0..628aea8e 100644 --- a/javascript/tsconfig.json +++ b/javascript/tsconfig.json @@ -15,5 +15,5 @@ "outDir": "./dist" }, "include": ["src/**/*", "test/**/*"], - "exclude": ["./dist/**/*", "./node_modules"] + "exclude": ["./dist/**/*", "./node_modules", "./src/**/*.deno.ts"] } diff --git a/scripts/ci/deno_tests b/scripts/ci/deno_tests index bc655468..bdec9b95 100755 --- a/scripts/ci/deno_tests +++ b/scripts/ci/deno_tests @@ -1,6 +1,17 @@ THIS_SCRIPT=$(dirname "$0"); WASM_PROJECT=$THIS_SCRIPT/../../rust/automerge-wasm; +JS_PROJECT=$THIS_SCRIPT/../../javascript; +echo "Running Wasm Deno tests"; yarn --cwd $WASM_PROJECT install; yarn --cwd $WASM_PROJECT build; -deno test $WASM_PROJECT/deno-tests/deno.ts --allow-read +deno test $WASM_PROJECT/deno-tests/deno.ts --allow-read; + +cp $WASM_PROJECT/index.d.ts $WASM_PROJECT/deno; +sed -i '1i /// ' $WASM_PROJECT/deno/automerge_wasm.js; + +echo "Running JS Deno tests"; +yarn --cwd $JS_PROJECT install; +ROOT_MODULE=$WASM_PROJECT/deno yarn --cwd $JS_PROJECT deno:build; +yarn --cwd $JS_PROJECT deno:test; + From d8df1707d903497417a74d6febf7675b8f8695c4 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Sat, 14 Jan 2023 11:06:58 +0000 Subject: [PATCH 05/45] Update rust toolchain for "linux" step --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a5d42010..c2d469d5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -136,7 +136,7 @@ jobs: strategy: matrix: toolchain: - - 1.60.0 + - 1.66.0 - nightly continue-on-error: ${{ matrix.toolchain == 'nightly' }} steps: From 964ae2bd818bd3176092aa35083bfeaee4eeca84 Mon Sep 17 00:00:00 2001 From: alexjg Date: Sat, 14 Jan 2023 11:27:48 +0000 Subject: [PATCH 06/45] Fix SeekOpWithPatch on optrees with only internal optrees (#496) In #480 we fixed an issue where `SeekOp` calculated an incorrect insertion index on optrees where the only visible ops were on internal nodes. We forgot to port this fix to `SeekOpWithPatch`, which has almost the same logic just with additional work done in order to notify an `OpObserver` of changes. Add a test and fix to `SeekOpWithPatch` --- rust/automerge/src/query/seek_op.rs | 75 +++++++++++-------- .../automerge/src/query/seek_op_with_patch.rs | 34 ++++++++- 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/rust/automerge/src/query/seek_op.rs b/rust/automerge/src/query/seek_op.rs index 4d955f96..22d1f58d 100644 --- a/rust/automerge/src/query/seek_op.rs +++ b/rust/automerge/src/query/seek_op.rs @@ -161,7 +161,7 @@ impl<'a> TreeQuery<'a> for SeekOp<'a> { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use crate::{ op_set::OpSet, op_tree::B, @@ -170,36 +170,43 @@ mod tests { ActorId, ScalarValue, }; - #[test] - fn seek_on_page_boundary() { - // Create an optree in which the only visible ops are on the boundaries of the nodes, - // i.e. the visible elements are in the internal nodes. Like so - // - // .----------------------. - // | id | key | succ | - // | B | "a" | | - // | 2B | "b" | | - // '----------------------' - // / | \ - // ;------------------------. | `------------------------------------. - // | id | op | succ | | | id | op | succ | - // | 0 |set "a" | 1 | | | 2B + 1 |set "c" | 2B + 2 | - // | 1 |set "a" | 2 | | | 2B + 2 |set "c" | 2B + 3 | - // | 2 |set "a" | 3 | | ... - // ... | | 3B |set "c" | | - // | B - 1 |set "a" | B | | '------------------------------------' - // '--------'--------'------' | - // | - // .-----------------------------. - // | id | key | succ | - // | B + 1 | "b" | B + 2 | - // | B + 2 | "b" | B + 3 | - // .... - // | B + (B - 1 | "b" | 2B | - // '-----------------------------' - // - // The important point here is that the leaf nodes contain no visible ops for keys "a" and - // "b". + /// Create an optree in which the only visible ops are on the boundaries of the nodes, + /// i.e. the visible elements are in the internal nodes. Like so + /// + /// ```notrust + /// + /// .----------------------. + /// | id | key | succ | + /// | B | "a" | | + /// | 2B | "b" | | + /// '----------------------' + /// / | \ + /// ;------------------------. | `------------------------------------. + /// | id | op | succ | | | id | op | succ | + /// | 0 |set "a" | 1 | | | 2B + 1 |set "c" | 2B + 2 | + /// | 1 |set "a" | 2 | | | 2B + 2 |set "c" | 2B + 3 | + /// | 2 |set "a" | 3 | | ... + /// ... | | 3B |set "c" | | + /// | B - 1 |set "a" | B | | '------------------------------------' + /// '--------'--------'------' | + /// | + /// .-----------------------------. + /// | id | key | succ | + /// | B + 1 | "b" | B + 2 | + /// | B + 2 | "b" | B + 3 | + /// .... + /// | B + (B - 1 | "b" | 2B | + /// '-----------------------------' + /// ``` + /// + /// The important point here is that the leaf nodes contain no visible ops for keys "a" and + /// "b". + /// + /// # Returns + /// + /// The opset in question and an op which should be inserted at the next position after the + /// internally visible ops. + pub(crate) fn optree_with_only_internally_visible_ops() -> (OpSet, Op) { let mut set = OpSet::new(); let actor = set.m.actors.cache(ActorId::random()); let a = set.m.props.cache("a".to_string()); @@ -255,6 +262,12 @@ mod tests { .sorted_opids(std::iter::once(OpId::new(B as u64 - 1, actor))), insert: false, }; + (set, new_op) + } + + #[test] + fn seek_on_page_boundary() { + let (set, new_op) = optree_with_only_internally_visible_ops(); let q = SeekOp::new(&new_op); let q = set.search(&ObjId::root(), q); diff --git a/rust/automerge/src/query/seek_op_with_patch.rs b/rust/automerge/src/query/seek_op_with_patch.rs index 0cc48b37..7cacb032 100644 --- a/rust/automerge/src/query/seek_op_with_patch.rs +++ b/rust/automerge/src/query/seek_op_with_patch.rs @@ -136,8 +136,18 @@ impl<'a> TreeQuery<'a> for SeekOpWithPatch<'a> { if self.pos + child.len() >= start { // skip empty nodes if child.index.visible_len(self.encoding) == 0 { - self.pos += child.len(); - QueryResult::Next + let child_contains_key = + child.elements.iter().any(|e| ops[*e].key == self.op.key); + if !child_contains_key { + // If we are in a node which has no visible ops, but none of the + // elements of the node match the key of the op, then we must have + // finished processing and so we can just return. + // See https://github.com/automerge/automerge-rs/pull/480 + QueryResult::Finish + } else { + self.pos += child.len(); + QueryResult::Next + } } else { QueryResult::Descend } @@ -291,3 +301,23 @@ impl<'a> TreeQuery<'a> for SeekOpWithPatch<'a> { } } } + +#[cfg(test)] +mod tests { + use super::{super::seek_op::tests::optree_with_only_internally_visible_ops, SeekOpWithPatch}; + use crate::{ + op_tree::B, + types::{ListEncoding, ObjId}, + }; + + #[test] + fn test_insert_on_internal_only_nodes() { + let (set, new_op) = optree_with_only_internally_visible_ops(); + + let q = SeekOpWithPatch::new(&new_op, ListEncoding::List); + let q = set.search(&ObjId::root(), q); + + // we've inserted `B - 1` elements for "a", so the index should be `B` + assert_eq!(q.pos, B); + } +} From 5629a7bec4ccf5be72bd38776c26167ba54bea4c Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 19 Jan 2023 15:38:27 +0000 Subject: [PATCH 07/45] Various CI script fixes (#501) Some of the scripts in scripts/ci were not reliable detecting the path they were operating in. Additionally the deno_tests script was not correctly picking up the ROOT_MODULE environment variable. Add more robust path handling and fix the deno_tests script. --- javascript/.prettierignore | 1 + javascript/scripts/denoify-replacer.mjs | 2 +- scripts/ci/cmake-build | 3 ++- scripts/ci/deno_tests | 20 ++++++++++++-------- scripts/ci/fmt_js | 4 +++- scripts/ci/js_tests | 6 ++++-- scripts/ci/lint | 5 ++++- scripts/ci/rust-docs | 4 +++- scripts/ci/wasm_tests | 3 ++- 9 files changed, 32 insertions(+), 16 deletions(-) diff --git a/javascript/.prettierignore b/javascript/.prettierignore index c2dcd4bb..6ab2f796 100644 --- a/javascript/.prettierignore +++ b/javascript/.prettierignore @@ -1,3 +1,4 @@ e2e/verdacciodb dist docs +deno_dist diff --git a/javascript/scripts/denoify-replacer.mjs b/javascript/scripts/denoify-replacer.mjs index fcf4bc45..e183ba0d 100644 --- a/javascript/scripts/denoify-replacer.mjs +++ b/javascript/scripts/denoify-replacer.mjs @@ -12,7 +12,7 @@ makeThisModuleAnExecutableReplacer( case "@automerge/automerge-wasm": { const moduleRoot = - process.env.MODULE_ROOT || + process.env.ROOT_MODULE || `https://deno.land/x/automerge_wasm@${version}` /* *We expect not to run against statements like diff --git a/scripts/ci/cmake-build b/scripts/ci/cmake-build index 3924dc4a..f6f9f9b1 100755 --- a/scripts/ci/cmake-build +++ b/scripts/ci/cmake-build @@ -1,7 +1,8 @@ #!/usr/bin/env bash set -eoux pipefail -THIS_SCRIPT=$(dirname "$0"); +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" # \note CMake's default build types are "Debug", "MinSizeRel", "Release" and # "RelWithDebInfo" but custom ones can also be defined so we pass it verbatim. BUILD_TYPE=$1; diff --git a/scripts/ci/deno_tests b/scripts/ci/deno_tests index bdec9b95..9f297557 100755 --- a/scripts/ci/deno_tests +++ b/scripts/ci/deno_tests @@ -1,17 +1,21 @@ -THIS_SCRIPT=$(dirname "$0"); +#!/usr/bin/env bash +set -eou pipefail +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" WASM_PROJECT=$THIS_SCRIPT/../../rust/automerge-wasm; JS_PROJECT=$THIS_SCRIPT/../../javascript; +E2E_PROJECT=$THIS_SCRIPT/../../javascript/e2e; -echo "Running Wasm Deno tests"; -yarn --cwd $WASM_PROJECT install; -yarn --cwd $WASM_PROJECT build; -deno test $WASM_PROJECT/deno-tests/deno.ts --allow-read; - -cp $WASM_PROJECT/index.d.ts $WASM_PROJECT/deno; +echo "building wasm and js" +yarn --cwd $E2E_PROJECT install; +yarn --cwd $E2E_PROJECT e2e buildjs; +cp $WASM_PROJECT/index.d.ts $WASM_PROJECT/deno/; sed -i '1i /// ' $WASM_PROJECT/deno/automerge_wasm.js; +echo "Running Wasm Deno tests"; +deno test $WASM_PROJECT/deno-tests/deno.ts --allow-read; + echo "Running JS Deno tests"; -yarn --cwd $JS_PROJECT install; ROOT_MODULE=$WASM_PROJECT/deno yarn --cwd $JS_PROJECT deno:build; yarn --cwd $JS_PROJECT deno:test; diff --git a/scripts/ci/fmt_js b/scripts/ci/fmt_js index acaf1e08..8f387b6a 100755 --- a/scripts/ci/fmt_js +++ b/scripts/ci/fmt_js @@ -1,5 +1,7 @@ #!/usr/bin/env bash set -eoux pipefail -yarn --cwd javascript prettier -c . +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +yarn --cwd $THIS_SCRIPT/../../javascript prettier -c . diff --git a/scripts/ci/js_tests b/scripts/ci/js_tests index b05edd1c..68205a33 100755 --- a/scripts/ci/js_tests +++ b/scripts/ci/js_tests @@ -1,6 +1,8 @@ -set -e +#!/usr/bin/env bash +set -eoux pipefail -THIS_SCRIPT=$(dirname "$0"); +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" WASM_PROJECT=$THIS_SCRIPT/../../rust/automerge-wasm; JS_PROJECT=$THIS_SCRIPT/../../javascript; E2E_PROJECT=$THIS_SCRIPT/../../javascript/e2e; diff --git a/scripts/ci/lint b/scripts/ci/lint index 15a0228d..87a16765 100755 --- a/scripts/ci/lint +++ b/scripts/ci/lint @@ -1,7 +1,10 @@ #!/usr/bin/env bash set -eoux pipefail -cd rust +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +cd $THIS_SCRIPT/../../rust # Force clippy to consider all local sources # https://github.com/rust-lang/rust-clippy/issues/4612 find . -name "*.rs" -not -path "./target/*" -exec touch "{}" + diff --git a/scripts/ci/rust-docs b/scripts/ci/rust-docs index bbbc4fe1..4be0ed9a 100755 --- a/scripts/ci/rust-docs +++ b/scripts/ci/rust-docs @@ -1,6 +1,8 @@ #!/usr/bin/env bash set -eoux pipefail -cd rust +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +cd $THIS_SCRIPT/../../rust RUSTDOCFLAGS="-D rustdoc::broken-intra-doc-links -D warnings" \ cargo doc --no-deps --workspace --document-private-items diff --git a/scripts/ci/wasm_tests b/scripts/ci/wasm_tests index 2f273d99..fac344d8 100755 --- a/scripts/ci/wasm_tests +++ b/scripts/ci/wasm_tests @@ -1,4 +1,5 @@ -THIS_SCRIPT=$(dirname "$0"); +# see https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself +THIS_SCRIPT="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" WASM_PROJECT=$THIS_SCRIPT/../../rust/automerge-wasm; yarn --cwd $WASM_PROJECT install; From d8baa116e7bc6f1f25e56bbbd75fc2ffc7140170 Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 19 Jan 2023 17:02:47 +0000 Subject: [PATCH 08/45] automerge-rs: Add `ExId::to_bytes` (#491) The `ExId` structure has some internal details which make lookups for object IDs which were produced by the document doing the looking up faster. These internal details are quite specific to the implementation so we don't want to expose them as a public API. On the other hand, we need to be able to serialize `ExId`s so that FFI clients can hold on to them without referencing memory which is owned by the document (ahem, looking at you Java). Introduce `ExId::to_bytes` and `TryFrom<&[u8]> ExId` implementing a canonical serialization which includes a version tag, giveing us compatibility options if we decide to change the implementation. --- rust/automerge/src/exid.rs | 135 +++++++++++++++++++++++++++++++++++++ rust/automerge/src/lib.rs | 2 +- 2 files changed, 136 insertions(+), 1 deletion(-) diff --git a/rust/automerge/src/exid.rs b/rust/automerge/src/exid.rs index 2c174e28..3ff8fbb5 100644 --- a/rust/automerge/src/exid.rs +++ b/rust/automerge/src/exid.rs @@ -1,3 +1,4 @@ +use crate::storage::parse; use crate::ActorId; use serde::Serialize; use serde::Serializer; @@ -11,6 +12,102 @@ pub enum ExId { Id(u64, ActorId, usize), } +const SERIALIZATION_VERSION_TAG: u8 = 0; +const TYPE_ROOT: u8 = 0; +const TYPE_ID: u8 = 1; + +impl ExId { + /// Serialize the ExId to a byte array. + pub fn to_bytes(&self) -> Vec { + // The serialized format is + // + // .--------------------------------. + // | version | type | data | + // +--------------------------------+ + // | 4 bytes |4 bytes | variable | + // '--------------------------------' + // + // Version is currently always `0` + // + // `data` depends on the type + // + // * If the type is `TYPE_ROOT` (0) then there is no data + // * If the type is `TYPE_ID` (1) then the data is + // + // .-------------------------------------------------------. + // | actor ID len | actor ID bytes | counter | actor index | + // '-------------------------------------------------------' + // + // Where the actor ID len, counter, and actor index are all uLEB encoded + // integers. The actor ID bytes is just an array of bytes. + // + match self { + ExId::Root => { + let val: u8 = SERIALIZATION_VERSION_TAG | (TYPE_ROOT << 4); + vec![val] + } + ExId::Id(id, actor, counter) => { + let actor_bytes = actor.to_bytes(); + let mut bytes = Vec::with_capacity(actor_bytes.len() + 4 + 4); + let tag = SERIALIZATION_VERSION_TAG | (TYPE_ID << 4); + bytes.push(tag); + leb128::write::unsigned(&mut bytes, actor_bytes.len() as u64).unwrap(); + bytes.extend_from_slice(actor_bytes); + leb128::write::unsigned(&mut bytes, *counter as u64).unwrap(); + leb128::write::unsigned(&mut bytes, *id).unwrap(); + bytes + } + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ObjIdFromBytesError { + #[error("no version tag")] + NoVersion, + #[error("invalid version tag")] + InvalidVersion(u8), + #[error("invalid type tag")] + InvalidType(u8), + #[error("invalid Actor ID length: {0}")] + ParseActorLen(String), + #[error("Not enough bytes in actor ID")] + ParseActor, + #[error("invalid counter: {0}")] + ParseCounter(String), + #[error("invalid actor index hint: {0}")] + ParseActorIdxHint(String), +} + +impl<'a> TryFrom<&'a [u8]> for ExId { + type Error = ObjIdFromBytesError; + + fn try_from(value: &'a [u8]) -> Result { + let i = parse::Input::new(value); + let (i, tag) = parse::take1::<()>(i).map_err(|_| ObjIdFromBytesError::NoVersion)?; + let version = tag & 0b1111; + if version != SERIALIZATION_VERSION_TAG { + return Err(ObjIdFromBytesError::InvalidVersion(version)); + } + let type_tag = tag >> 4; + match type_tag { + TYPE_ROOT => Ok(ExId::Root), + TYPE_ID => { + let (i, len) = parse::leb128_u64::(i) + .map_err(|e| ObjIdFromBytesError::ParseActorLen(e.to_string()))?; + let (i, actor) = parse::take_n::<()>(len as usize, i) + .map_err(|_| ObjIdFromBytesError::ParseActor)?; + let (i, counter) = parse::leb128_u64::(i) + .map_err(|e| ObjIdFromBytesError::ParseCounter(e.to_string()))?; + let (_i, actor_idx_hint) = parse::leb128_u64::(i) + .map_err(|e| ObjIdFromBytesError::ParseActorIdxHint(e.to_string()))?; + Ok(Self::Id(actor_idx_hint, actor.into(), counter as usize)) + } + other => Err(ObjIdFromBytesError::InvalidType(other)), + } + } +} + impl PartialEq for ExId { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -80,3 +177,41 @@ impl AsRef for ExId { self } } + +#[cfg(test)] +mod tests { + use super::ExId; + use proptest::prelude::*; + + use crate::ActorId; + + fn gen_actorid() -> impl Strategy { + proptest::collection::vec(any::(), 0..100).prop_map(ActorId::from) + } + + prop_compose! { + fn gen_non_root_objid()(actor in gen_actorid(), counter in any::(), idx in any::()) -> ExId { + ExId::Id(idx as u64, actor, counter) + } + } + + fn gen_obji() -> impl Strategy { + prop_oneof![Just(ExId::Root), gen_non_root_objid()] + } + + proptest! { + #[test] + fn objid_roundtrip(objid in gen_obji()) { + let bytes = objid.to_bytes(); + let objid2 = ExId::try_from(&bytes[..]).unwrap(); + assert_eq!(objid, objid2); + } + } + + #[test] + fn test_root_roundtrip() { + let bytes = ExId::Root.to_bytes(); + let objid2 = ExId::try_from(&bytes[..]).unwrap(); + assert_eq!(ExId::Root, objid2); + } +} diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index 97ff0650..58f5b263 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -93,7 +93,7 @@ pub use change::{Change, LoadError as LoadChangeError}; pub use error::AutomergeError; pub use error::InvalidActorId; pub use error::InvalidChangeHashSlice; -pub use exid::ExId as ObjId; +pub use exid::{ExId as ObjId, ObjIdFromBytesError}; pub use keys::Keys; pub use keys_at::KeysAt; pub use legacy::Change as ExpandedChange; From 9b44a75f69e0b6bcca7a8054395ff887bda92b7e Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 19 Jan 2023 21:11:36 +0000 Subject: [PATCH 09/45] fix: don't panic when generating parents for hidden objects (#500) Problem: the `OpSet::export_key` method uses `query::ElemIdPos` to determine the index of sequence elements when exporting a key. This query returned `None` for invisible elements. The `Parents` iterator which is used to generate paths to objects in patches in `automerge-wasm` used `export_key`. The end result is that applying a remote change which deletes an object in a sequence would panic as it tries to generate a path for an invisible object. Solution: modify `query::ElemIdPos` to include invisible objects. This does mean that the path generated will refer to the previous visible object in the sequence as it's index, but this is probably fine as for an invisible object the path shouldn't be used anyway. While we're here also change the return value of `OpSet::export_key` to an `Option` and make `query::Index::ops` private as obeisance to the Lady of the Golden Blade. --- rust/automerge/src/op_set.rs | 16 +++++---- rust/automerge/src/parents.rs | 44 ++++++++++++++++++++++++- rust/automerge/src/query.rs | 7 +++- rust/automerge/src/query/elem_id_pos.rs | 35 ++++++++++++++------ 4 files changed, 83 insertions(+), 19 deletions(-) diff --git a/rust/automerge/src/op_set.rs b/rust/automerge/src/op_set.rs index 1f5a4486..5b50d2b0 100644 --- a/rust/automerge/src/op_set.rs +++ b/rust/automerge/src/op_set.rs @@ -89,15 +89,17 @@ impl OpSetInternal { }) } - pub(crate) fn export_key(&self, obj: ObjId, key: Key, encoding: ListEncoding) -> Prop { + pub(crate) fn export_key(&self, obj: ObjId, key: Key, encoding: ListEncoding) -> Option { match key { - Key::Map(m) => Prop::Map(self.m.props.get(m).into()), + Key::Map(m) => self.m.props.safe_get(m).map(|s| Prop::Map(s.to_string())), Key::Seq(opid) => { - let i = self - .search(&obj, query::ElemIdPos::new(opid, encoding)) - .index() - .unwrap(); - Prop::Seq(i) + if opid.is_head() { + Some(Prop::Seq(0)) + } else { + self.search(&obj, query::ElemIdPos::new(opid, encoding)) + .index() + .map(Prop::Seq) + } } } } diff --git a/rust/automerge/src/parents.rs b/rust/automerge/src/parents.rs index 1d01ffbf..76c4bba1 100644 --- a/rust/automerge/src/parents.rs +++ b/rust/automerge/src/parents.rs @@ -47,7 +47,10 @@ impl<'a> Iterator for Parents<'a> { self.obj = obj; Some(Parent { obj: self.ops.id_to_exid(self.obj.0), - prop: self.ops.export_key(self.obj, key, ListEncoding::List), + prop: self + .ops + .export_key(self.obj, key, ListEncoding::List) + .unwrap(), visible, }) } else { @@ -62,3 +65,42 @@ pub struct Parent { pub prop: Prop, pub visible: bool, } + +#[cfg(test)] +mod tests { + use super::Parent; + use crate::{transaction::Transactable, Prop}; + + #[test] + fn test_invisible_parents() { + // Create a document with a list of objects, then delete one of the objects, then generate + // a path to the deleted object. + + let mut doc = crate::AutoCommit::new(); + let list = doc + .put_object(crate::ROOT, "list", crate::ObjType::List) + .unwrap(); + let obj1 = doc.insert_object(&list, 0, crate::ObjType::Map).unwrap(); + let _obj2 = doc.insert_object(&list, 1, crate::ObjType::Map).unwrap(); + doc.put(&obj1, "key", "value").unwrap(); + doc.delete(&list, 0).unwrap(); + + let mut parents = doc.parents(&obj1).unwrap().collect::>(); + parents.reverse(); + assert_eq!( + parents, + vec![ + Parent { + obj: crate::ROOT, + prop: Prop::Map("list".to_string()), + visible: true, + }, + Parent { + obj: list, + prop: Prop::Seq(0), + visible: false, + }, + ] + ); + } +} diff --git a/rust/automerge/src/query.rs b/rust/automerge/src/query.rs index 9707da33..721756c1 100644 --- a/rust/automerge/src/query.rs +++ b/rust/automerge/src/query.rs @@ -114,7 +114,7 @@ pub(crate) struct Index { pub(crate) visible16: usize, pub(crate) visible8: usize, /// Set of opids found in this node and below. - pub(crate) ops: HashSet, + ops: HashSet, } impl Index { @@ -140,6 +140,11 @@ impl Index { self.visible.contains_key(seen) } + /// Whether `opid` is in this node or any below it + pub(crate) fn has_op(&self, opid: &OpId) -> bool { + self.ops.contains(opid) + } + pub(crate) fn change_vis<'a>( &mut self, change_vis: ChangeVisibility<'a>, diff --git a/rust/automerge/src/query/elem_id_pos.rs b/rust/automerge/src/query/elem_id_pos.rs index 8eecd7e0..cb559216 100644 --- a/rust/automerge/src/query/elem_id_pos.rs +++ b/rust/automerge/src/query/elem_id_pos.rs @@ -1,14 +1,14 @@ use crate::{ op_tree::OpTreeNode, - types::{ElemId, Key, ListEncoding, Op}, + types::{ElemId, ListEncoding, Op, OpId}, }; use super::{QueryResult, TreeQuery}; -/// Lookup the index in the list that this elemid occupies. +/// Lookup the index in the list that this elemid occupies, includes hidden elements. #[derive(Clone, Debug)] pub(crate) struct ElemIdPos { - elemid: ElemId, + elem_opid: OpId, pos: usize, found: bool, encoding: ListEncoding, @@ -16,11 +16,20 @@ pub(crate) struct ElemIdPos { impl ElemIdPos { pub(crate) fn new(elemid: ElemId, encoding: ListEncoding) -> Self { - Self { - elemid, - pos: 0, - found: false, - encoding, + if elemid.is_head() { + Self { + elem_opid: elemid.0, + pos: 0, + found: true, + encoding, + } + } else { + Self { + elem_opid: elemid.0, + pos: 0, + found: false, + encoding, + } } } @@ -35,8 +44,11 @@ impl ElemIdPos { impl<'a> TreeQuery<'a> for ElemIdPos { fn query_node(&mut self, child: &OpTreeNode, _ops: &[Op]) -> QueryResult { + if self.found { + return QueryResult::Finish; + } // if index has our element then we can continue - if child.index.has_visible(&Key::Seq(self.elemid)) { + if child.index.has_op(&self.elem_opid) { // element is in this node somewhere QueryResult::Descend } else { @@ -47,7 +59,10 @@ impl<'a> TreeQuery<'a> for ElemIdPos { } fn query_element(&mut self, element: &crate::types::Op) -> QueryResult { - if element.elemid() == Some(self.elemid) { + if self.found { + return QueryResult::Finish; + } + if element.elemid() == Some(ElemId(self.elem_opid)) { // this is it self.found = true; return QueryResult::Finish; From 6b0ee6da2e7e0dfe9341c6fa4d3cc8c4b9b87549 Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 19 Jan 2023 22:15:06 +0000 Subject: [PATCH 10/45] Bump js to 2.0.1-alpha.5 and automerge-wasm to 0.1.22 (#497) --- javascript/package.json | 4 ++-- rust/automerge-wasm/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/package.json b/javascript/package.json index 39464fac..caeeb647 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.1-alpha.4", + "version": "2.0.1-alpha.5", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -47,7 +47,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.21", + "@automerge/automerge-wasm": "0.1.22", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 76167a3e..0f133468 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.21", + "version": "0.1.22", "license": "MIT", "files": [ "README.md", From 98e755106f5d44e6cff2897921138ac3f95de3d0 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 23 Jan 2023 04:01:05 -0700 Subject: [PATCH 11/45] Fix and simplify lebsize calculations (#503) Before this change numbits_i64() was incorrect for every value of the form 0 - 2^x. This only manifested in a visible error if x%7 == 6 (so for -64, -8192, etc.) at which point `lebsize` would return a value one too large, causing a panic in commit(). --- .../automerge/src/columnar/encoding/leb128.rs | 47 +++++++++++-------- rust/automerge/tests/test.rs | 6 +++ 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/rust/automerge/src/columnar/encoding/leb128.rs b/rust/automerge/src/columnar/encoding/leb128.rs index 036cfba8..cbb82c31 100644 --- a/rust/automerge/src/columnar/encoding/leb128.rs +++ b/rust/automerge/src/columnar/encoding/leb128.rs @@ -1,29 +1,22 @@ /// The number of bytes required to encode `val` as a LEB128 integer -pub(crate) fn lebsize(val: i64) -> u64 { - let numbits = numbits_i64(val); - (numbits as f64 / 7.0).floor() as u64 + 1 +pub(crate) fn lebsize(mut val: i64) -> u64 { + if val < 0 { + val = !val + } + // 1 extra for the sign bit + leb_bytes(1 + 64 - val.leading_zeros() as u64) } /// The number of bytes required to encode `val` as a uLEB128 integer pub(crate) fn ulebsize(val: u64) -> u64 { - if val <= 1 { + if val == 0 { return 1; } - let numbits = numbits_u64(val); - let mut numblocks = (numbits as f64 / 7.0).floor() as u64; - if numbits % 7 != 0 { - numblocks += 1; - } - numblocks + leb_bytes(64 - val.leading_zeros() as u64) } -fn numbits_i64(val: i64) -> u64 { - // Is this right? This feels like it's not right - (std::mem::size_of::() as u32 * 8 - val.abs().leading_zeros()) as u64 -} - -fn numbits_u64(val: u64) -> u64 { - (std::mem::size_of::() as u32 * 8 - val.leading_zeros()) as u64 +fn leb_bytes(bits: u64) -> u64 { + (bits + 6) / 7 } #[cfg(test)] @@ -51,7 +44,7 @@ mod tests { #[test] fn ulebsize_examples() { - let scenarios = vec![0, 1, 127, 128, 129, 169]; + let scenarios = vec![0, 1, 127, 128, 129, 169, u64::MAX]; for val in scenarios { let mut out = Vec::new(); leb128::write::unsigned(&mut out, val).unwrap(); @@ -62,7 +55,23 @@ mod tests { #[test] fn lebsize_examples() { - let scenarios = vec![0, 1, -1, 127, 128, -127, -128, -2097152, 169]; + let scenarios = vec![ + 0, + 1, + -1, + 63, + 64, + -64, + -65, + 127, + 128, + -127, + -128, + -2097152, + 169, + i64::MIN, + i64::MAX, + ]; for val in scenarios { let mut out = Vec::new(); leb128::write::signed(&mut out, val).unwrap(); diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index 6ab797f0..4648cf87 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1412,6 +1412,12 @@ fn invalid_deflate_stream() { assert!(Automerge::load(&bytes).is_err()); } +#[test] +fn negative_64() { + let mut doc = Automerge::new(); + assert!(doc.transact(|d| { d.put(ROOT, "a", -64_i64) }).is_ok()) +} + #[test] fn bad_change_on_optree_node_boundary() { let mut doc = Automerge::new(); From 1f7b109dcdb735366c5eff8ff0736738e740fee4 Mon Sep 17 00:00:00 2001 From: Andrew Jeffery Date: Mon, 23 Jan 2023 17:01:41 +0000 Subject: [PATCH 12/45] Add From for ScalarValue::Str (#506) --- rust/automerge/src/value.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/automerge/src/value.rs b/rust/automerge/src/value.rs index b3142bdf..d8429f4e 100644 --- a/rust/automerge/src/value.rs +++ b/rust/automerge/src/value.rs @@ -266,6 +266,12 @@ impl<'a> From for Value<'a> { } } +impl<'a> From for Value<'a> { + fn from(s: SmolStr) -> Self { + Value::Scalar(Cow::Owned(ScalarValue::Str(s))) + } +} + impl<'a> From for Value<'a> { fn from(c: char) -> Self { Value::Scalar(Cow::Owned(ScalarValue::Str(SmolStr::new(c.to_string())))) From 78adbc4ff94b8ff62df0e02de1cd4fb519c8e9a9 Mon Sep 17 00:00:00 2001 From: Alex Currie-Clark <1306728+acurrieclark@users.noreply.github.com> Date: Mon, 23 Jan 2023 17:02:02 +0000 Subject: [PATCH 13/45] Update patch types (#499) * Update `Patch` types * Clarify that the splice patch applies to text * Add Splice patch type to exports * Add new patches to javascript --- javascript/src/stable.ts | 3 ++- javascript/src/unstable.ts | 3 ++- rust/automerge-wasm/index.d.ts | 10 ++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 1f38cb27..9db4d0e2 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -41,7 +41,8 @@ import type { export type { PutPatch, DelPatch, - SplicePatch, + SpliceTextPatch, + InsertPatch, IncPatch, SyncMessage, } from "@automerge/automerge-wasm" diff --git a/javascript/src/unstable.ts b/javascript/src/unstable.ts index b448d955..21b5be08 100644 --- a/javascript/src/unstable.ts +++ b/javascript/src/unstable.ts @@ -70,7 +70,8 @@ export type Conflicts = { [key: string]: AutomergeValue } export type { PutPatch, DelPatch, - SplicePatch, + SpliceTextPatch, + InsertPatch, IncPatch, SyncMessage, } from "@automerge/automerge-wasm" diff --git a/rust/automerge-wasm/index.d.ts b/rust/automerge-wasm/index.d.ts index 29586b47..be12e4c1 100644 --- a/rust/automerge-wasm/index.d.ts +++ b/rust/automerge-wasm/index.d.ts @@ -94,7 +94,7 @@ export type Op = { pred: string[], } -export type Patch = PutPatch | DelPatch | SplicePatch | IncPatch; +export type Patch = PutPatch | DelPatch | SpliceTextPatch | IncPatch | InsertPatch; export type PutPatch = { action: 'put' @@ -115,9 +115,15 @@ export type DelPatch = { length?: number, } -export type SplicePatch = { +export type SpliceTextPatch = { action: 'splice' path: Prop[], + value: string, +} + +export type InsertPatch = { + action: 'insert' + path: Prop[], values: Value[], } From 819767cc3327ed6e5724970aae39173775c9e5c1 Mon Sep 17 00:00:00 2001 From: alexjg Date: Mon, 23 Jan 2023 19:19:55 +0000 Subject: [PATCH 14/45] fix: use saturating_sub when updating cached text width (#505) Problem: In `automerge::query::Index::change_vis` we use `-=` to subtract the width of an operation which is being hidden from the text widths which we store on the index of each node in the optree. This index represents the width of all the visible text operations in this node and below. This was causing an integer underflow error when encountering some list operations. More specifically, when a `ScalarValue::Str` in a list was made invisible by a later operation which contained a _shorter_ string, the width subtracted from the indexed text widths could be longer than the current index. Solution: use `saturating_sub` instead. This is technically papering over the problem because really the width should never go below zero, but the text widths are only relevant for text objects where the existing logic works as advertised because we don't have a `set` operation for text indices. A more robust solution would be to track the type of the Index (and consequently of the `OpTree`) at the type level, but time is limited and problems are infinite. Also, add a lengthy description of the reason we are using `saturating_sub` so that when I read it in about a month I don't have to redo the painful debugging process that got me to this commit. --- rust/automerge/src/query.rs | 81 +++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 17 deletions(-) diff --git a/rust/automerge/src/query.rs b/rust/automerge/src/query.rs index 721756c1..640ecf8d 100644 --- a/rust/automerge/src/query.rs +++ b/rust/automerge/src/query.rs @@ -107,12 +107,65 @@ pub(crate) enum QueryResult { Finish, } +#[derive(Clone, Debug, PartialEq)] +struct TextWidth { + utf8: usize, + utf16: usize, +} + +impl TextWidth { + fn add_op(&mut self, op: &Op) { + self.utf8 += op.width(ListEncoding::Text(TextEncoding::Utf8)); + self.utf16 += op.width(ListEncoding::Text(TextEncoding::Utf16)); + } + + fn remove_op(&mut self, op: &Op) { + // Why are we using saturating_sub here? Shouldn't this always be greater than 0? + // + // In the case of objects which are _not_ `Text` we may end up subtracting more than the + // current width. This can happen if the elements in a list are `ScalarValue::str` and + // there are conflicting elements for the same index in the list. Like so: + // + // ```notrust + // [ + // "element", + // ["conflict1", "conflict2_longer"], + // "element" + // ] + // ``` + // + // Where there are two conflicted elements at index 1 + // + // in `Index::insert` and `Index::change_visibility` we add the width of the inserted op in + // utf8 and utf16 to the current width, but only if there was not a previous element for + // that index. Imagine that we encounter the "conflict1" op first, then we will add the + // length of 'conflict1' to the text widths. When 'conflict2_longer' is added we don't do + // anything because we've already seen an op for this index. Imagine that later we remove + // the `conflict2_longer` op, then we will end up subtracting the length of + // 'conflict2_longer' from the text widths, hence, `saturating_sub`. This isn't a problem + // because for non text objects we don't need the text widths to be accurate anyway. + // + // Really this is a sign that we should be tracking the type of the Index (List or Text) at + // the type level, but for now we just look the other way. + self.utf8 = self + .utf8 + .saturating_sub(op.width(ListEncoding::Text(TextEncoding::Utf8))); + self.utf16 = self + .utf16 + .saturating_sub(op.width(ListEncoding::Text(TextEncoding::Utf16))); + } + + fn merge(&mut self, other: &TextWidth) { + self.utf8 += other.utf8; + self.utf16 += other.utf16; + } +} + #[derive(Clone, Debug, PartialEq)] pub(crate) struct Index { /// The map of visible keys to the number of visible operations for that key. - pub(crate) visible: HashMap, - pub(crate) visible16: usize, - pub(crate) visible8: usize, + visible: HashMap, + visible_text: TextWidth, /// Set of opids found in this node and below. ops: HashSet, } @@ -121,8 +174,7 @@ impl Index { pub(crate) fn new() -> Self { Index { visible: Default::default(), - visible16: 0, - visible8: 0, + visible_text: TextWidth { utf8: 0, utf16: 0 }, ops: Default::default(), } } @@ -131,8 +183,8 @@ impl Index { pub(crate) fn visible_len(&self, encoding: ListEncoding) -> usize { match encoding { ListEncoding::List => self.visible.len(), - ListEncoding::Text(TextEncoding::Utf8) => self.visible8, - ListEncoding::Text(TextEncoding::Utf16) => self.visible16, + ListEncoding::Text(TextEncoding::Utf8) => self.visible_text.utf8, + ListEncoding::Text(TextEncoding::Utf16) => self.visible_text.utf16, } } @@ -159,8 +211,7 @@ impl Index { (true, false) => match self.visible.get(&key).copied() { Some(n) if n == 1 => { self.visible.remove(&key); - self.visible8 -= op.width(ListEncoding::Text(TextEncoding::Utf8)); - self.visible16 -= op.width(ListEncoding::Text(TextEncoding::Utf16)); + self.visible_text.remove_op(op); } Some(n) => { self.visible.insert(key, n - 1); @@ -172,8 +223,7 @@ impl Index { self.visible.insert(key, n + 1); } else { self.visible.insert(key, 1); - self.visible8 += op.width(ListEncoding::Text(TextEncoding::Utf8)); - self.visible16 += op.width(ListEncoding::Text(TextEncoding::Utf16)); + self.visible_text.add_op(op); } } _ => {} @@ -189,8 +239,7 @@ impl Index { self.visible.insert(key, n + 1); } else { self.visible.insert(key, 1); - self.visible8 += op.width(ListEncoding::Text(TextEncoding::Utf8)); - self.visible16 += op.width(ListEncoding::Text(TextEncoding::Utf16)); + self.visible_text.add_op(op); } } } @@ -202,8 +251,7 @@ impl Index { match self.visible.get(&key).copied() { Some(n) if n == 1 => { self.visible.remove(&key); - self.visible8 -= op.width(ListEncoding::Text(TextEncoding::Utf8)); - self.visible16 -= op.width(ListEncoding::Text(TextEncoding::Utf16)); + self.visible_text.remove_op(op); } Some(n) => { self.visible.insert(key, n - 1); @@ -223,8 +271,7 @@ impl Index { .and_modify(|len| *len += *other_len) .or_insert(*other_len); } - self.visible16 += other.visible16; - self.visible8 += other.visible8; + self.visible_text.merge(&other.visible_text); } } From 931ee7e77bd83d5c8b52c79fc2c99143171a33a5 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 25 Jan 2023 09:03:05 -0700 Subject: [PATCH 15/45] Add Fuzz Testing (#498) * Add fuzz testing for document load * Fix fuzz crashers and add to test suite --- rust/automerge/fuzz/.gitignore | 3 ++ rust/automerge/fuzz/Cargo.toml | 29 ++++++++++++++ rust/automerge/fuzz/fuzz_targets/load.rs | 37 ++++++++++++++++++ .../src/columnar/column_range/deps.rs | 6 ++- .../src/columnar/column_range/opid_list.rs | 7 +++- .../src/storage/columns/raw_column.rs | 5 ++- .../src/storage/load/change_collector.rs | 15 ++++++- ...h-da39a3ee5e6b4b0d3255bfef95601890afd80709 | Bin 0 -> 10 bytes .../fuzz-crashers/incorrect_max_op.automerge | Bin 0 -> 126 bytes .../invalid_deflate_stream.automerge | Bin 0 -> 123 bytes .../fuzz-crashers/missing_actor.automerge | Bin 0 -> 126 bytes .../overflow_in_length.automerge | Bin 0 -> 182 bytes .../fuzz-crashers/too_many_deps.automerge | Bin 0 -> 134 bytes .../fuzz-crashers/too_many_ops.automerge | Bin 0 -> 134 bytes rust/automerge/tests/test.rs | 20 +++++----- 15 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 rust/automerge/fuzz/.gitignore create mode 100644 rust/automerge/fuzz/Cargo.toml create mode 100644 rust/automerge/fuzz/fuzz_targets/load.rs create mode 100644 rust/automerge/tests/fuzz-crashers/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 create mode 100644 rust/automerge/tests/fuzz-crashers/incorrect_max_op.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/invalid_deflate_stream.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_actor.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/overflow_in_length.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/too_many_deps.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/too_many_ops.automerge diff --git a/rust/automerge/fuzz/.gitignore b/rust/automerge/fuzz/.gitignore new file mode 100644 index 00000000..2eb15f8e --- /dev/null +++ b/rust/automerge/fuzz/.gitignore @@ -0,0 +1,3 @@ +target +corpus +coverage diff --git a/rust/automerge/fuzz/Cargo.toml b/rust/automerge/fuzz/Cargo.toml new file mode 100644 index 00000000..3461e9f3 --- /dev/null +++ b/rust/automerge/fuzz/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "automerge-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +leb128 = "^0.2.5" +sha2 = "^0.10.0" + +[dependencies.automerge] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "load" +path = "fuzz_targets/load.rs" +test = false +doc = false \ No newline at end of file diff --git a/rust/automerge/fuzz/fuzz_targets/load.rs b/rust/automerge/fuzz/fuzz_targets/load.rs new file mode 100644 index 00000000..0dea2624 --- /dev/null +++ b/rust/automerge/fuzz/fuzz_targets/load.rs @@ -0,0 +1,37 @@ +#![no_main] + +use sha2::{Sha256, Digest}; +use automerge::{Automerge}; +use libfuzzer_sys::arbitrary::{Arbitrary, Result, Unstructured}; +use libfuzzer_sys::fuzz_target; + +#[derive(Debug)] +struct DocumentChunk { + bytes: Vec, +} + +fn add_header(typ: u8, data: &[u8]) -> Vec { + let mut input = vec![u8::from(typ)]; + leb128::write::unsigned(&mut input, data.len() as u64).unwrap(); + input.extend(data.as_ref()); + let hash_result = Sha256::digest(input.clone()); + let array: [u8; 32] = hash_result.into(); + + let mut out = vec![133, 111, 74, 131, array[0], array[1], array[2], array[3]]; + out.extend(input); + out +} + +impl<'a> Arbitrary<'a> for DocumentChunk +{ + fn arbitrary(u: &mut Unstructured<'a>) -> Result { + let input = u.bytes(u.len())?; + let contents = add_header(0, input); + + return Ok(DocumentChunk{bytes: contents}) + } +} + +fuzz_target!(|doc: DocumentChunk| { + Automerge::load(&doc.bytes); +}); diff --git a/rust/automerge/src/columnar/column_range/deps.rs b/rust/automerge/src/columnar/column_range/deps.rs index df49192a..1956acd1 100644 --- a/rust/automerge/src/columnar/column_range/deps.rs +++ b/rust/automerge/src/columnar/column_range/deps.rs @@ -62,7 +62,11 @@ impl<'a> DepsIter<'a> { } None => return Ok(None), }; - let mut result = Vec::with_capacity(num); + // We cannot trust `num` because it is provided over the network, + // but in the common case it will be correct and small (so we + // use with_capacity to make sure the vector is precisely the right + // size). + let mut result = Vec::with_capacity(std::cmp::min(num, 100)); while result.len() < num { match self .deps diff --git a/rust/automerge/src/columnar/column_range/opid_list.rs b/rust/automerge/src/columnar/column_range/opid_list.rs index 12279c08..6a9c8a38 100644 --- a/rust/automerge/src/columnar/column_range/opid_list.rs +++ b/rust/automerge/src/columnar/column_range/opid_list.rs @@ -189,7 +189,12 @@ impl<'a> OpIdListIter<'a> { Some(None) => return Err(DecodeColumnError::unexpected_null("num")), None => return Ok(None), }; - let mut p = Vec::with_capacity(num as usize); + + // We cannot trust `num` because it is provided over the network, + // but in the common case it will be correct and small (so we + // use with_capacity to make sure the vector is precisely the right + // size). + let mut p = Vec::with_capacity(std::cmp::min(num, 100) as usize); for _ in 0..num { let actor = self .actor diff --git a/rust/automerge/src/storage/columns/raw_column.rs b/rust/automerge/src/storage/columns/raw_column.rs index 808b53cf..ac9a5759 100644 --- a/rust/automerge/src/storage/columns/raw_column.rs +++ b/rust/automerge/src/storage/columns/raw_column.rs @@ -219,7 +219,10 @@ impl RawColumns { let columns: Vec> = specs_and_lens .into_iter() .scan(0_usize, |offset, (spec, len)| { - let end = *offset + len as usize; + // Note: we use a saturating add here as len was passed over the network + // and so could be anything. If the addition does every saturate we would + // expect parsing to fail later (but at least it won't panic!). + let end = offset.saturating_add(len as usize); let data = *offset..end; *offset = end; Some(RawColumn { diff --git a/rust/automerge/src/storage/load/change_collector.rs b/rust/automerge/src/storage/load/change_collector.rs index 75ef98f1..d05367a9 100644 --- a/rust/automerge/src/storage/load/change_collector.rs +++ b/rust/automerge/src/storage/load/change_collector.rs @@ -26,6 +26,8 @@ pub(crate) enum Error { MissingChange, #[error("unable to read change metadata: {0}")] ReadChange(Box), + #[error("incorrect max op")] + IncorrectMaxOp, #[error("missing ops")] MissingOps, } @@ -180,7 +182,18 @@ impl<'a> PartialChange<'a> { .ops .iter() .map(|(obj, op)| op_as_actor_id(obj, op, metadata)); - let actor = metadata.actors.get(self.actor).clone(); + let actor = metadata + .actors + .safe_get(self.actor) + .ok_or_else(|| { + tracing::error!(actor_index = self.actor, "actor out of bounds"); + Error::MissingActor + })? + .clone(); + + if num_ops > self.max_op { + return Err(Error::IncorrectMaxOp); + } let change = match StoredChange::builder() .with_dependencies(deps) diff --git a/rust/automerge/tests/fuzz-crashers/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 b/rust/automerge/tests/fuzz-crashers/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 new file mode 100644 index 0000000000000000000000000000000000000000..bcb12cddc6980d44c13dd0351899abe297817f70 GIT binary patch literal 10 RcmZq8_iCQDXxb$P1^^m_1Y!UH literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/incorrect_max_op.automerge b/rust/automerge/tests/fuzz-crashers/incorrect_max_op.automerge new file mode 100644 index 0000000000000000000000000000000000000000..05cc2c82681529ae087bc4ab88c3ebc7ffbf73a7 GIT binary patch literal 126 zcmZq8_iFy6Eq;Zegi(Mga9P2Di~d0kS!`#NOG_3rZg0ucpBfVWKQ9lyTY8rUT zb)+h5Oppy)Q?ugCCKWbDCT1pKCS@iErZ6TBQ8q;&(}d9p$O&g@U}UOisAmLX5M-}s S$!CP{K$KfPLqTyp0|Nj9lO`qr literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/invalid_deflate_stream.automerge b/rust/automerge/tests/fuzz-crashers/invalid_deflate_stream.automerge new file mode 100644 index 0000000000000000000000000000000000000000..21e869eb4bafd66b9f2a3bb7f856fd2b312c61fa GIT binary patch literal 123 zcmZq8_i8o(0)|3H0T7K07?C;H*ldhU%uEVQ226%P$f3ZZ2x2lCGFdW(GdD0Y)icyH Z0wDtsvez@ERe|^*0kik}_trBo004Sr7)}5H literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_actor.automerge b/rust/automerge/tests/fuzz-crashers/missing_actor.automerge new file mode 100644 index 0000000000000000000000000000000000000000..cc8c61b14d4873ab1a117ad4d1b6eb39d9037e25 GIT binary patch literal 126 zcmZq8_iAP@etLtUgi+xAhCLyj-A82@#BJP1t8G;SXSckWBrGZ zVG=09K&AgdKpjk?5Kt>X6i6IIbASji09nky!obAH9t+h3vXupFBO?P)mWhcymXQGf DM*mhW literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/too_many_deps.automerge b/rust/automerge/tests/fuzz-crashers/too_many_deps.automerge new file mode 100644 index 0000000000000000000000000000000000000000..657ce9930f000a3b8d4585e3889220b3b48e1db0 GIT binary patch literal 134 zcmZq8_iCP-9<9Jo!zl26!=8}NZl_EAt>0%B6p6fGoFREZtGfJf_fnlJA6~mF{yla} zlIf?86Z5p}SdGt-$7i!KGBGm=GbuAUaD_2(h_WdHnI?=*OkqsnEDelI^$hilK*&)4 b3JMtN+3Q*I848L)GK{+!>)rD6K^z7E|5`CV literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/too_many_ops.automerge b/rust/automerge/tests/fuzz-crashers/too_many_ops.automerge new file mode 100644 index 0000000000000000000000000000000000000000..661258b0933e854bde60d741b6a47c731029de3b GIT binary patch literal 134 zcmZq8_i7G3?{Jo(hEd@ChCLyjvz;#Ww|<{lP$cq#afajtt?Kf_-Ai?@e0c4y`1jZ? zNv5AVPR!G?V>LcU9-qy|$i&Pf%%sfZz!b*BA Result<(), AutomergeError> { } #[test] -fn invalid_deflate_stream() { - let bytes: [u8; 123] = [ - 133, 111, 74, 131, 48, 48, 48, 48, 0, 113, 1, 16, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 1, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, - 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 6, 1, 2, 3, 2, 32, 2, 48, - 2, 49, 2, 49, 2, 8, 32, 4, 33, 2, 48, 2, 49, 1, 49, 2, 57, 2, 87, 3, 128, 1, 2, 127, 0, - 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, 2, 102, 122, 127, 0, 127, 1, 1, 127, 1, 127, - 54, 239, 191, 189, 127, 0, 0, - ]; +fn fuzz_crashers() { + let paths = fs::read_dir("./tests/fuzz-crashers").unwrap(); - assert!(Automerge::load(&bytes).is_err()); + for path in paths { + // uncomment this line to figure out which fixture is crashing: + // println!("{:?}", path.as_ref().unwrap().path().display()); + let bytes = fs::read(path.as_ref().unwrap().path()); + let res = Automerge::load(&bytes.unwrap()); + assert!(res.is_err()); + } } #[test] From f428fe0169434782254b9f4320e9b4e7269c7bdb Mon Sep 17 00:00:00 2001 From: alexjg Date: Fri, 27 Jan 2023 17:23:13 +0000 Subject: [PATCH 16/45] Improve typescript types (#508) --- javascript/.eslintrc.cjs | 9 + javascript/src/conflicts.ts | 100 ++++++++ javascript/src/counter.ts | 2 +- javascript/src/low_level.ts | 1 + javascript/src/proxies.ts | 268 ++++++++++++++------- javascript/src/stable.ts | 102 +++----- javascript/src/text.ts | 10 +- javascript/src/types.ts | 3 +- javascript/src/unstable.ts | 45 ++-- javascript/src/unstable_types.ts | 30 +++ javascript/test/basic_test.ts | 1 - javascript/test/legacy_tests.ts | 7 +- javascript/test/stable_unstable_interop.ts | 58 +++++ 13 files changed, 450 insertions(+), 186 deletions(-) create mode 100644 javascript/src/conflicts.ts create mode 100644 javascript/src/unstable_types.ts diff --git a/javascript/.eslintrc.cjs b/javascript/.eslintrc.cjs index 5d11eb94..88776271 100644 --- a/javascript/.eslintrc.cjs +++ b/javascript/.eslintrc.cjs @@ -3,4 +3,13 @@ module.exports = { parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + rules: { + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], + }, } diff --git a/javascript/src/conflicts.ts b/javascript/src/conflicts.ts new file mode 100644 index 00000000..52af23e1 --- /dev/null +++ b/javascript/src/conflicts.ts @@ -0,0 +1,100 @@ +import { Counter, type AutomergeValue } from "./types" +import { Text } from "./text" +import { type AutomergeValue as UnstableAutomergeValue } from "./unstable_types" +import { type Target, Text1Target, Text2Target } from "./proxies" +import { mapProxy, listProxy, ValueType } from "./proxies" +import type { Prop, ObjID } from "@automerge/automerge-wasm" +import { Automerge } from "@automerge/automerge-wasm" + +export type ConflictsF = { [key: string]: ValueType } +export type Conflicts = ConflictsF +export type UnstableConflicts = ConflictsF + +export function stableConflictAt( + context: Automerge, + objectId: ObjID, + prop: Prop +): Conflicts | undefined { + return conflictAt( + context, + objectId, + prop, + true, + (context: Automerge, conflictId: ObjID): AutomergeValue => { + return new Text(context.text(conflictId)) + } + ) +} + +export function unstableConflictAt( + context: Automerge, + objectId: ObjID, + prop: Prop +): UnstableConflicts | undefined { + return conflictAt( + context, + objectId, + prop, + true, + (context: Automerge, conflictId: ObjID): UnstableAutomergeValue => { + return context.text(conflictId) + } + ) +} + +function conflictAt( + context: Automerge, + objectId: ObjID, + prop: Prop, + textV2: boolean, + handleText: (a: Automerge, conflictId: ObjID) => ValueType +): ConflictsF | undefined { + const values = context.getAll(objectId, prop) + if (values.length <= 1) { + return + } + const result: ConflictsF = {} + for (const fullVal of values) { + switch (fullVal[0]) { + case "map": + result[fullVal[1]] = mapProxy( + context, + fullVal[1], + textV2, + [prop], + true + ) + break + case "list": + result[fullVal[1]] = listProxy( + context, + fullVal[1], + textV2, + [prop], + true + ) + break + case "text": + result[fullVal[1]] = handleText(context, fullVal[1] as ObjID) + break + case "str": + case "uint": + case "int": + case "f64": + case "boolean": + case "bytes": + case "null": + result[fullVal[2]] = fullVal[1] as ValueType + break + case "counter": + result[fullVal[2]] = new Counter(fullVal[1]) as ValueType + break + case "timestamp": + result[fullVal[2]] = new Date(fullVal[1]) as ValueType + break + default: + throw RangeError(`datatype ${fullVal[0]} unimplemented`) + } + } + return result +} diff --git a/javascript/src/counter.ts b/javascript/src/counter.ts index 873fa157..88adb840 100644 --- a/javascript/src/counter.ts +++ b/javascript/src/counter.ts @@ -100,7 +100,7 @@ export function getWriteableCounter( path: Prop[], objectId: ObjID, key: Prop -) { +): WriteableCounter { return new WriteableCounter(value, context, path, objectId, key) } diff --git a/javascript/src/low_level.ts b/javascript/src/low_level.ts index 63ef5546..f44f3a32 100644 --- a/javascript/src/low_level.ts +++ b/javascript/src/low_level.ts @@ -14,6 +14,7 @@ export type { ChangeToEncode } from "@automerge/automerge-wasm" export function UseApi(api: API) { for (const k in api) { + // eslint-disable-next-line @typescript-eslint/no-extra-semi,@typescript-eslint/no-explicit-any ;(ApiHandler as any)[k] = (api as any)[k] } } diff --git a/javascript/src/proxies.ts b/javascript/src/proxies.ts index 7a99cf80..54a8dd71 100644 --- a/javascript/src/proxies.ts +++ b/javascript/src/proxies.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Text } from "./text" import { Automerge, @@ -6,13 +7,12 @@ import { type Prop, } from "@automerge/automerge-wasm" -import type { - AutomergeValue, - ScalarValue, - MapValue, - ListValue, - TextValue, -} from "./types" +import type { AutomergeValue, ScalarValue, MapValue, ListValue } from "./types" +import { + type AutomergeValue as UnstableAutomergeValue, + MapValue as UnstableMapValue, + ListValue as UnstableListValue, +} from "./unstable_types" import { Counter, getWriteableCounter } from "./counter" import { STATE, @@ -26,19 +26,38 @@ import { } from "./constants" import { RawString } from "./raw_string" -type Target = { +type TargetCommon = { context: Automerge objectId: ObjID path: Array readonly: boolean heads?: Array - cache: {} + cache: object trace?: any frozen: boolean - textV2: boolean } -function parseListIndex(key) { +export type Text2Target = TargetCommon & { textV2: true } +export type Text1Target = TargetCommon & { textV2: false } +export type Target = Text1Target | Text2Target + +export type ValueType = T extends Text2Target + ? UnstableAutomergeValue + : T extends Text1Target + ? AutomergeValue + : never +type MapValueType = T extends Text2Target + ? UnstableMapValue + : T extends Text1Target + ? MapValue + : never +type ListValueType = T extends Text2Target + ? UnstableListValue + : T extends Text1Target + ? ListValue + : never + +function parseListIndex(key: any) { if (typeof key === "string" && /^[0-9]+$/.test(key)) key = parseInt(key, 10) if (typeof key !== "number") { return key @@ -49,7 +68,10 @@ function parseListIndex(key) { return key } -function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { +function valueAt( + target: T, + prop: Prop +): ValueType | undefined { const { context, objectId, path, readonly, heads, textV2 } = target const value = context.getWithType(objectId, prop, heads) if (value === null) { @@ -61,7 +83,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { case undefined: return case "map": - return mapProxy( + return mapProxy( context, val as ObjID, textV2, @@ -70,7 +92,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { heads ) case "list": - return listProxy( + return listProxy( context, val as ObjID, textV2, @@ -80,7 +102,7 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { ) case "text": if (textV2) { - return context.text(val as ObjID, heads) + return context.text(val as ObjID, heads) as ValueType } else { return textProxy( context, @@ -88,29 +110,36 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { [...path, prop], readonly, heads - ) + ) as unknown as ValueType } case "str": - return val + return val as ValueType case "uint": - return val + return val as ValueType case "int": - return val + return val as ValueType case "f64": - return val + return val as ValueType case "boolean": - return val + return val as ValueType case "null": - return null + return null as ValueType case "bytes": - return val + return val as ValueType case "timestamp": - return val + return val as ValueType case "counter": { if (readonly) { - return new Counter(val as number) + return new Counter(val as number) as ValueType } else { - return getWriteableCounter(val as number, context, path, objectId, prop) + const counter: Counter = getWriteableCounter( + val as number, + context, + path, + objectId, + prop + ) + return counter as ValueType } } default: @@ -118,7 +147,21 @@ function valueAt(target: Target, prop: Prop): AutomergeValue | undefined { } } -function import_value(value: any, textV2: boolean) { +type ImportedValue = + | [null, "null"] + | [number, "uint"] + | [number, "int"] + | [number, "f64"] + | [number, "counter"] + | [number, "timestamp"] + | [string, "str"] + | [Text | string, "text"] + | [Uint8Array, "bytes"] + | [Array, "list"] + | [Record, "map"] + | [boolean, "boolean"] + +function import_value(value: any, textV2: boolean): ImportedValue { switch (typeof value) { case "object": if (value == null) { @@ -170,7 +213,10 @@ function import_value(value: any, textV2: boolean) { } const MapHandler = { - get(target: Target, key): AutomergeValue | { handle: Automerge } { + get( + target: T, + key: any + ): ValueType | ObjID | boolean | { handle: Automerge } { const { context, objectId, cache } = target if (key === Symbol.toStringTag) { return target[Symbol.toStringTag] @@ -185,7 +231,7 @@ const MapHandler = { return cache[key] }, - set(target: Target, key, val) { + set(target: Target, key: any, val: any) { const { context, objectId, path, readonly, frozen, textV2 } = target target.cache = {} // reset cache on set if (val && val[OBJECT_ID]) { @@ -221,8 +267,10 @@ const MapHandler = { } case "text": { if (textV2) { + assertString(value) context.putObject(objectId, key, value) } else { + assertText(value) const text = context.putObject(objectId, key, "") const proxyText = textProxy(context, text, [...path, key], readonly) for (let i = 0; i < value.length; i++) { @@ -251,7 +299,7 @@ const MapHandler = { return true }, - deleteProperty(target: Target, key) { + deleteProperty(target: Target, key: any) { const { context, objectId, readonly } = target target.cache = {} // reset cache on delete if (readonly) { @@ -261,12 +309,12 @@ const MapHandler = { return true }, - has(target: Target, key) { + has(target: Target, key: any) { const value = this.get(target, key) return value !== undefined }, - getOwnPropertyDescriptor(target: Target, key) { + getOwnPropertyDescriptor(target: Target, key: any) { // const { context, objectId } = target const value = this.get(target, key) if (typeof value !== "undefined") { @@ -287,11 +335,20 @@ const MapHandler = { } const ListHandler = { - get(target: Target, index) { + get( + target: T, + index: any + ): + | ValueType + | boolean + | ObjID + | { handle: Automerge } + | number + | ((_: any) => boolean) { const { context, objectId, heads } = target index = parseListIndex(index) if (index === Symbol.hasInstance) { - return instance => { + return (instance: any) => { return Array.isArray(instance) } } @@ -304,13 +361,13 @@ const ListHandler = { if (index === STATE) return { handle: context } if (index === "length") return context.length(objectId, heads) if (typeof index === "number") { - return valueAt(target, index) + return valueAt(target, index) as ValueType } else { return listMethods(target)[index] } }, - set(target: Target, index, val) { + set(target: Target, index: any, val: any) { const { context, objectId, path, readonly, frozen, textV2 } = target index = parseListIndex(index) if (val && val[OBJECT_ID]) { @@ -334,7 +391,7 @@ const ListHandler = { } switch (datatype) { case "list": { - let list + let list: ObjID if (index >= context.length(objectId)) { list = context.insertObject(objectId, index, []) } else { @@ -352,13 +409,15 @@ const ListHandler = { } case "text": { if (textV2) { + assertString(value) if (index >= context.length(objectId)) { context.insertObject(objectId, index, value) } else { context.putObject(objectId, index, value) } } else { - let text + let text: ObjID + assertText(value) if (index >= context.length(objectId)) { text = context.insertObject(objectId, index, "") } else { @@ -370,7 +429,7 @@ const ListHandler = { break } case "map": { - let map + let map: ObjID if (index >= context.length(objectId)) { map = context.insertObject(objectId, index, {}) } else { @@ -398,7 +457,7 @@ const ListHandler = { return true }, - deleteProperty(target: Target, index) { + deleteProperty(target: Target, index: any) { const { context, objectId } = target index = parseListIndex(index) const elem = context.get(objectId, index) @@ -411,7 +470,7 @@ const ListHandler = { return true }, - has(target: Target, index) { + has(target: Target, index: any) { const { context, objectId, heads } = target index = parseListIndex(index) if (typeof index === "number") { @@ -420,7 +479,7 @@ const ListHandler = { return index === "length" }, - getOwnPropertyDescriptor(target: Target, index) { + getOwnPropertyDescriptor(target: Target, index: any) { const { context, objectId, heads } = target if (index === "length") @@ -434,7 +493,7 @@ const ListHandler = { return { configurable: true, enumerable: true, value } }, - getPrototypeOf(target) { + getPrototypeOf(target: Target) { return Object.getPrototypeOf(target) }, ownKeys(/*target*/): string[] { @@ -476,14 +535,14 @@ const TextHandler = Object.assign({}, ListHandler, { }, }) -export function mapProxy( +export function mapProxy( context: Automerge, objectId: ObjID, textV2: boolean, path?: Prop[], readonly?: boolean, heads?: Heads -): MapValue { +): MapValueType { const target: Target = { context, objectId, @@ -496,19 +555,19 @@ export function mapProxy( } const proxied = {} Object.assign(proxied, target) - let result = new Proxy(proxied, MapHandler) + const result = new Proxy(proxied, MapHandler) // conversion through unknown is necessary because the types are so different - return result as unknown as MapValue + return result as unknown as MapValueType } -export function listProxy( +export function listProxy( context: Automerge, objectId: ObjID, textV2: boolean, path?: Prop[], readonly?: boolean, heads?: Heads -): ListValue { +): ListValueType { const target: Target = { context, objectId, @@ -521,17 +580,22 @@ export function listProxy( } const proxied = [] Object.assign(proxied, target) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return new Proxy(proxied, ListHandler) as unknown as ListValue } +interface TextProxy extends Text { + splice: (index: any, del: any, ...vals: any[]) => void +} + export function textProxy( context: Automerge, objectId: ObjID, path?: Prop[], readonly?: boolean, heads?: Heads -): TextValue { +): TextProxy { const target: Target = { context, objectId, @@ -542,7 +606,9 @@ export function textProxy( cache: {}, textV2: false, } - return new Proxy(target, TextHandler) as unknown as TextValue + const proxied = {} + Object.assign(proxied, target) + return new Proxy(proxied, TextHandler) as unknown as TextProxy } export function rootProxy( @@ -554,10 +620,10 @@ export function rootProxy( return mapProxy(context, "_root", textV2, [], !!readonly) } -function listMethods(target: Target) { +function listMethods(target: T) { const { context, objectId, path, readonly, frozen, heads, textV2 } = target const methods = { - deleteAt(index, numDelete) { + deleteAt(index: number, numDelete: number) { if (typeof numDelete === "number") { context.splice(objectId, index, numDelete) } else { @@ -572,8 +638,20 @@ function listMethods(target: Target) { start = parseListIndex(start || 0) end = parseListIndex(end || length) for (let i = start; i < Math.min(end, length); i++) { - if (datatype === "text" || datatype === "list" || datatype === "map") { + if (datatype === "list" || datatype === "map") { context.putObject(objectId, i, value) + } else if (datatype === "text") { + if (textV2) { + assertString(value) + context.putObject(objectId, i, value) + } else { + assertText(value) + const text = context.putObject(objectId, i, "") + const proxyText = textProxy(context, text, [...path, i], readonly) + for (let i = 0; i < value.length; i++) { + proxyText[i] = value.get(i) + } + } } else { context.put(objectId, i, value, datatype) } @@ -581,7 +659,7 @@ function listMethods(target: Target) { return this }, - indexOf(o, start = 0) { + indexOf(o: any, start = 0) { const length = context.length(objectId) for (let i = start; i < length; i++) { const value = context.getWithType(objectId, i, heads) @@ -592,7 +670,7 @@ function listMethods(target: Target) { return -1 }, - insertAt(index, ...values) { + insertAt(index: number, ...values: any[]) { this.splice(index, 0, ...values) return this }, @@ -607,7 +685,7 @@ function listMethods(target: Target) { return last }, - push(...values) { + push(...values: any[]) { const len = context.length(objectId) this.splice(len, 0, ...values) return context.length(objectId) @@ -620,7 +698,7 @@ function listMethods(target: Target) { return first }, - splice(index, del, ...vals) { + splice(index: any, del: any, ...vals: any[]) { index = parseListIndex(index) del = parseListIndex(del) for (const val of vals) { @@ -638,9 +716,9 @@ function listMethods(target: Target) { "Sequence object cannot be modified outside of a change block" ) } - const result: AutomergeValue[] = [] + const result: ValueType[] = [] for (let i = 0; i < del; i++) { - const value = valueAt(target, index) + const value = valueAt(target, index) if (value !== undefined) { result.push(value) } @@ -663,6 +741,7 @@ function listMethods(target: Target) { } case "text": { if (textV2) { + assertString(value) context.insertObject(objectId, index, value) } else { const text = context.insertObject(objectId, index, "") @@ -698,7 +777,7 @@ function listMethods(target: Target) { return result }, - unshift(...values) { + unshift(...values: any) { this.splice(0, 0, ...values) return context.length(objectId) }, @@ -749,11 +828,11 @@ function listMethods(target: Target) { return iterator }, - toArray(): AutomergeValue[] { - const list: AutomergeValue = [] - let value + toArray(): ValueType[] { + const list: Array> = [] + let value: ValueType | undefined do { - value = valueAt(target, list.length) + value = valueAt(target, list.length) if (value !== undefined) { list.push(value) } @@ -762,7 +841,7 @@ function listMethods(target: Target) { return list }, - map(f: (AutomergeValue, number) => T): T[] { + map(f: (_a: ValueType, _n: number) => U): U[] { return this.toArray().map(f) }, @@ -774,24 +853,26 @@ function listMethods(target: Target) { return this.toArray().toLocaleString() }, - forEach(f: (AutomergeValue, number) => undefined) { + forEach(f: (_a: ValueType, _n: number) => undefined) { return this.toArray().forEach(f) }, // todo: real concat function is different - concat(other: AutomergeValue[]): AutomergeValue[] { + concat(other: ValueType[]): ValueType[] { return this.toArray().concat(other) }, - every(f: (AutomergeValue, number) => boolean): boolean { + every(f: (_a: ValueType, _n: number) => boolean): boolean { return this.toArray().every(f) }, - filter(f: (AutomergeValue, number) => boolean): AutomergeValue[] { + filter(f: (_a: ValueType, _n: number) => boolean): ValueType[] { return this.toArray().filter(f) }, - find(f: (AutomergeValue, number) => boolean): AutomergeValue | undefined { + find( + f: (_a: ValueType, _n: number) => boolean + ): ValueType | undefined { let index = 0 for (const v of this) { if (f(v, index)) { @@ -801,7 +882,7 @@ function listMethods(target: Target) { } }, - findIndex(f: (AutomergeValue, number) => boolean): number { + findIndex(f: (_a: ValueType, _n: number) => boolean): number { let index = 0 for (const v of this) { if (f(v, index)) { @@ -812,7 +893,7 @@ function listMethods(target: Target) { return -1 }, - includes(elem: AutomergeValue): boolean { + includes(elem: ValueType): boolean { return this.find(e => e === elem) !== undefined }, @@ -820,29 +901,30 @@ function listMethods(target: Target) { return this.toArray().join(sep) }, - // todo: remove the any - reduce(f: (any, AutomergeValue) => T, initalValue?: T): T | undefined { - return this.toArray().reduce(f, initalValue) + reduce( + f: (acc: U, currentValue: ValueType) => U, + initialValue: U + ): U | undefined { + return this.toArray().reduce(f, initialValue) }, - // todo: remove the any - reduceRight( - f: (any, AutomergeValue) => T, - initalValue?: T - ): T | undefined { - return this.toArray().reduceRight(f, initalValue) + reduceRight( + f: (acc: U, item: ValueType) => U, + initialValue: U + ): U | undefined { + return this.toArray().reduceRight(f, initialValue) }, - lastIndexOf(search: AutomergeValue, fromIndex = +Infinity): number { + lastIndexOf(search: ValueType, fromIndex = +Infinity): number { // this can be faster return this.toArray().lastIndexOf(search, fromIndex) }, - slice(index?: number, num?: number): AutomergeValue[] { + slice(index?: number, num?: number): ValueType[] { return this.toArray().slice(index, num) }, - some(f: (AutomergeValue, number) => boolean): boolean { + some(f: (v: ValueType, i: number) => boolean): boolean { let index = 0 for (const v of this) { if (f(v, index)) { @@ -869,7 +951,7 @@ function listMethods(target: Target) { function textMethods(target: Target) { const { context, objectId, heads } = target const methods = { - set(index: number, value) { + set(index: number, value: any) { return (this[index] = value) }, get(index: number): AutomergeValue { @@ -902,10 +984,22 @@ function textMethods(target: Target) { toJSON(): string { return this.toString() }, - indexOf(o, start = 0) { + indexOf(o: any, start = 0) { const text = context.text(objectId) return text.indexOf(o, start) }, } return methods } + +function assertText(value: Text | string): asserts value is Text { + if (!(value instanceof Text)) { + throw new Error("value was not a Text instance") + } +} + +function assertString(value: Text | string): asserts value is string { + if (typeof value !== "string") { + throw new Error("value was not a string") + } +} diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 9db4d0e2..3b328240 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -1,7 +1,7 @@ /** @hidden **/ export { /** @hidden */ uuid } from "./uuid" -import { rootProxy, listProxy, mapProxy, textProxy } from "./proxies" +import { rootProxy } from "./proxies" import { STATE } from "./constants" import { @@ -20,10 +20,10 @@ export { type Patch, type PatchCallback, type ScalarValue, - Text, } from "./types" import { Text } from "./text" +export { Text } from "./text" import type { API, @@ -54,6 +54,8 @@ import { RawString } from "./raw_string" import { _state, _is_proxy, _trace, _obj } from "./internal_state" +import { stableConflictAt } from "./conflicts" + /** Options passed to {@link change}, and {@link emptyChange} * @typeParam T - The type of value contained in the document */ @@ -71,13 +73,36 @@ export type ChangeOptions = { */ export type ApplyOptions = { patchCallback?: PatchCallback } +/** + * A List is an extended Array that adds the two helper methods `deleteAt` and `insertAt`. + */ +export interface List extends Array { + insertAt(index: number, ...args: T[]): List + deleteAt(index: number, numDelete?: number): List +} + +/** + * To extend an arbitrary type, we have to turn any arrays that are part of the type's definition into Lists. + * So we recurse through the properties of T, turning any Arrays we find into Lists. + */ +export type Extend = + // is it an array? make it a list (we recursively extend the type of the array's elements as well) + T extends Array + ? List> + : // is it an object? recursively extend all of its properties + // eslint-disable-next-line @typescript-eslint/ban-types + T extends Object + ? { [P in keyof T]: Extend } + : // otherwise leave the type alone + T + /** * Function which is called by {@link change} when making changes to a `Doc` * @typeParam T - The type of value contained in the document * * This function may mutate `doc` */ -export type ChangeFn = (doc: T) => void +export type ChangeFn = (doc: Extend) => void /** @hidden **/ export interface State { @@ -136,11 +161,12 @@ export function init(_opts?: ActorId | InitOptions): Doc { const handle = ApiHandler.create(opts.enableTextV2 || false, opts.actor) handle.enablePatches(true) handle.enableFreeze(!!opts.freeze) - handle.registerDatatype("counter", (n: any) => new Counter(n)) - let textV2 = opts.enableTextV2 || false + handle.registerDatatype("counter", (n: number) => new Counter(n)) + const textV2 = opts.enableTextV2 || false if (textV2) { handle.registerDatatype("str", (n: string) => new RawString(n)) } else { + // eslint-disable-next-line @typescript-eslint/no-explicit-any handle.registerDatatype("text", (n: any) => new Text(n)) } const doc = handle.materialize("/", undefined, { @@ -204,7 +230,7 @@ export function clone( // `change` uses the presence of state.heads to determine if we are in a view // set it to undefined to indicate that this is a full fat document - const { heads: oldHeads, ...stateSansHeads } = state + const { heads: _oldHeads, ...stateSansHeads } = state return handle.applyPatches(doc, { ...stateSansHeads, handle }) } @@ -343,7 +369,7 @@ function _change( try { state.heads = heads const root: T = rootProxy(state.handle, state.textV2) - callback(root) + callback(root as Extend) if (state.handle.pendingOps() === 0) { state.heads = undefined return doc @@ -541,62 +567,6 @@ export function getActorId(doc: Doc): ActorId { */ type Conflicts = { [key: string]: AutomergeValue } -function conflictAt( - context: Automerge, - objectId: ObjID, - prop: Prop, - textV2: boolean -): Conflicts | undefined { - const values = context.getAll(objectId, prop) - if (values.length <= 1) { - return - } - const result: Conflicts = {} - for (const fullVal of values) { - switch (fullVal[0]) { - case "map": - result[fullVal[1]] = mapProxy(context, fullVal[1], textV2, [prop], true) - break - case "list": - result[fullVal[1]] = listProxy( - context, - fullVal[1], - textV2, - [prop], - true - ) - break - case "text": - if (textV2) { - result[fullVal[1]] = context.text(fullVal[1]) - } else { - result[fullVal[1]] = textProxy(context, objectId, [prop], true) - } - break - //case "table": - //case "cursor": - case "str": - case "uint": - case "int": - case "f64": - case "boolean": - case "bytes": - case "null": - result[fullVal[2]] = fullVal[1] - break - case "counter": - result[fullVal[2]] = new Counter(fullVal[1]) - break - case "timestamp": - result[fullVal[2]] = new Date(fullVal[1]) - break - default: - throw RangeError(`datatype ${fullVal[0]} unimplemented`) - } - } - return result -} - /** * Get the conflicts associated with a property * @@ -646,9 +616,12 @@ export function getConflicts( prop: Prop ): Conflicts | undefined { const state = _state(doc, false) + if (state.textV2) { + throw new Error("use unstable.getConflicts for an unstable document") + } const objectId = _obj(doc) if (objectId != null) { - return conflictAt(state.handle, objectId, prop, state.textV2) + return stableConflictAt(state.handle, objectId, prop) } else { return undefined } @@ -672,6 +645,7 @@ export function getLastLocalChange(doc: Doc): Change | undefined { * This is useful to determine if something is actually an automerge document, * if `doc` is not an automerge document this will return null. */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function getObjectId(doc: any, prop?: Prop): ObjID | null { if (prop) { const state = _state(doc, false) diff --git a/javascript/src/text.ts b/javascript/src/text.ts index f87af891..b01bd7db 100644 --- a/javascript/src/text.ts +++ b/javascript/src/text.ts @@ -3,9 +3,12 @@ import { TEXT, STATE } from "./constants" import type { InternalState } from "./internal_state" export class Text { + //eslint-disable-next-line @typescript-eslint/no-explicit-any elems: Array str: string | undefined + //eslint-disable-next-line @typescript-eslint/no-explicit-any spans: Array | undefined; + //eslint-disable-next-line @typescript-eslint/no-explicit-any [STATE]?: InternalState constructor(text?: string | string[] | Value[]) { @@ -25,6 +28,7 @@ export class Text { return this.elems.length } + //eslint-disable-next-line @typescript-eslint/no-explicit-any get(index: number): any { return this.elems[index] } @@ -73,7 +77,7 @@ export class Text { * For example, the value `['a', 'b', {x: 3}, 'c', 'd']` has spans: * `=> ['ab', {x: 3}, 'cd']` */ - toSpans(): Array { + toSpans(): Array { if (!this.spans) { this.spans = [] let chars = "" @@ -118,7 +122,7 @@ export class Text { /** * Inserts new list items `values` starting at position `index`. */ - insertAt(index: number, ...values: Array) { + insertAt(index: number, ...values: Array) { if (this[STATE]) { throw new RangeError( "object cannot be modified outside of a change block" @@ -140,7 +144,7 @@ export class Text { this.elems.splice(index, numDelete) } - map(callback: (e: Value | Object) => T) { + map(callback: (e: Value | object) => T) { this.elems.map(callback) } diff --git a/javascript/src/types.ts b/javascript/src/types.ts index e3cb81f8..beb5cf70 100644 --- a/javascript/src/types.ts +++ b/javascript/src/types.ts @@ -1,4 +1,5 @@ export { Text } from "./text" +import { Text } from "./text" export { Counter } from "./counter" export { Int, Uint, Float64 } from "./numbers" @@ -10,9 +11,9 @@ export type AutomergeValue = | ScalarValue | { [key: string]: AutomergeValue } | Array + | Text export type MapValue = { [key: string]: AutomergeValue } export type ListValue = Array -export type TextValue = Array export type ScalarValue = | string | number diff --git a/javascript/src/unstable.ts b/javascript/src/unstable.ts index 21b5be08..7c73afb9 100644 --- a/javascript/src/unstable.ts +++ b/javascript/src/unstable.ts @@ -22,9 +22,9 @@ * This leads to the following differences from `stable`: * * * There is no `unstable.Text` class, all strings are text objects - * * Reading strings in a `future` document is the same as reading any other + * * Reading strings in an `unstable` document is the same as reading any other * javascript string - * * To modify strings in a `future` document use {@link splice} + * * To modify strings in an `unstable` document use {@link splice} * * The {@link AutomergeValue} type does not include the {@link Text} * class but the {@link RawString} class is included in the {@link ScalarValue} * type @@ -35,7 +35,6 @@ * * @module */ -import { Counter } from "./types" export { Counter, @@ -45,27 +44,14 @@ export { Float64, type Patch, type PatchCallback, -} from "./types" + type AutomergeValue, + type ScalarValue, +} from "./unstable_types" import type { PatchCallback } from "./stable" -export type AutomergeValue = - | ScalarValue - | { [key: string]: AutomergeValue } - | Array -export type MapValue = { [key: string]: AutomergeValue } -export type ListValue = Array -export type ScalarValue = - | string - | number - | null - | boolean - | Date - | Counter - | Uint8Array - | RawString - -export type Conflicts = { [key: string]: AutomergeValue } +import { type UnstableConflicts as Conflicts } from "./conflicts" +import { unstableConflictAt } from "./conflicts" export type { PutPatch, @@ -125,7 +111,6 @@ export { RawString } from "./raw_string" export const getBackend = stable.getBackend import { _is_proxy, _state, _obj } from "./internal_state" -import { RawString } from "./raw_string" /** * Create a new automerge document @@ -137,7 +122,7 @@ import { RawString } from "./raw_string" * random actor ID */ export function init(_opts?: ActorId | InitOptions): Doc { - let opts = importOpts(_opts) + const opts = importOpts(_opts) opts.enableTextV2 = true return stable.init(opts) } @@ -161,7 +146,7 @@ export function clone( doc: Doc, _opts?: ActorId | InitOptions ): Doc { - let opts = importOpts(_opts) + const opts = importOpts(_opts) opts.enableTextV2 = true return stable.clone(doc, opts) } @@ -296,6 +281,14 @@ export function getConflicts( doc: Doc, prop: stable.Prop ): Conflicts | undefined { - // this function only exists to get the types to line up with future.AutomergeValue - return stable.getConflicts(doc, prop) + const state = _state(doc, false) + if (!state.textV2) { + throw new Error("use getConflicts for a stable document") + } + const objectId = _obj(doc) + if (objectId != null) { + return unstableConflictAt(state.handle, objectId, prop) + } else { + return undefined + } } diff --git a/javascript/src/unstable_types.ts b/javascript/src/unstable_types.ts new file mode 100644 index 00000000..071e2cc4 --- /dev/null +++ b/javascript/src/unstable_types.ts @@ -0,0 +1,30 @@ +import { Counter } from "./types" + +export { + Counter, + type Doc, + Int, + Uint, + Float64, + type Patch, + type PatchCallback, +} from "./types" + +import { RawString } from "./raw_string" +export { RawString } from "./raw_string" + +export type AutomergeValue = + | ScalarValue + | { [key: string]: AutomergeValue } + | Array +export type MapValue = { [key: string]: AutomergeValue } +export type ListValue = Array +export type ScalarValue = + | string + | number + | null + | boolean + | Date + | Counter + | Uint8Array + | RawString diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 90e7a99d..5aa1ac34 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -267,7 +267,6 @@ describe("Automerge", () => { }) assert.deepEqual(doc5, { list: [2, 1, 9, 10, 3, 11, 12] }) let doc6 = Automerge.change(doc5, d => { - // @ts-ignore d.list.insertAt(3, 100, 101) }) assert.deepEqual(doc6, { list: [2, 1, 9, 100, 101, 10, 3, 11, 12] }) diff --git a/javascript/test/legacy_tests.ts b/javascript/test/legacy_tests.ts index a423b51f..90c731d9 100644 --- a/javascript/test/legacy_tests.ts +++ b/javascript/test/legacy_tests.ts @@ -461,12 +461,12 @@ describe("Automerge", () => { s1 = Automerge.change(s1, "set foo", doc => { doc.foo = "bar" }) - let deleted + let deleted: any s1 = Automerge.change(s1, "del foo", doc => { deleted = delete doc.foo }) assert.strictEqual(deleted, true) - let deleted2 + let deleted2: any assert.doesNotThrow(() => { s1 = Automerge.change(s1, "del baz", doc => { deleted2 = delete doc.baz @@ -515,7 +515,7 @@ describe("Automerge", () => { s1 = Automerge.change(s1, doc => { doc.nested = {} }) - let id = Automerge.getObjectId(s1.nested) + Automerge.getObjectId(s1.nested) assert.strictEqual( OPID_PATTERN.test(Automerge.getObjectId(s1.nested)!), true @@ -975,6 +975,7 @@ describe("Automerge", () => { it("should allow adding and removing list elements in the same change callback", () => { let s1 = Automerge.change( Automerge.init<{ noodles: Array }>(), + // @ts-ignore doc => (doc.noodles = []) ) s1 = Automerge.change(s1, doc => { diff --git a/javascript/test/stable_unstable_interop.ts b/javascript/test/stable_unstable_interop.ts index 2f58c256..dc57f338 100644 --- a/javascript/test/stable_unstable_interop.ts +++ b/javascript/test/stable_unstable_interop.ts @@ -38,4 +38,62 @@ describe("stable/unstable interop", () => { stableDoc = unstable.merge(stableDoc, unstableDoc) assert.deepStrictEqual(stableDoc.text, "abc") }) + + it("should show conflicts on text objects", () => { + let doc1 = stable.from({ text: new stable.Text("abc") }, "bb") + let doc2 = stable.from({ text: new stable.Text("def") }, "aa") + doc1 = stable.merge(doc1, doc2) + let conflicts = stable.getConflicts(doc1, "text")! + assert.equal(conflicts["1@bb"]!.toString(), "abc") + assert.equal(conflicts["1@aa"]!.toString(), "def") + + let unstableDoc = unstable.init() + unstableDoc = unstable.merge(unstableDoc, doc1) + let conflicts2 = unstable.getConflicts(unstableDoc, "text")! + assert.equal(conflicts2["1@bb"]!.toString(), "abc") + assert.equal(conflicts2["1@aa"]!.toString(), "def") + }) + + it("should allow filling a list with text in stable", () => { + let doc = stable.from<{ list: Array }>({ + list: [null, null, null], + }) + doc = stable.change(doc, doc => { + doc.list.fill(new stable.Text("abc"), 0, 3) + }) + assert.deepStrictEqual(doc.list, [ + new stable.Text("abc"), + new stable.Text("abc"), + new stable.Text("abc"), + ]) + }) + + it("should allow filling a list with text in unstable", () => { + let doc = unstable.from<{ list: Array }>({ + list: [null, null, null], + }) + doc = stable.change(doc, doc => { + doc.list.fill("abc", 0, 3) + }) + assert.deepStrictEqual(doc.list, ["abc", "abc", "abc"]) + }) + + it("should allow splicing text into a list on stable", () => { + let doc = stable.from<{ list: Array }>({ list: [] }) + doc = stable.change(doc, doc => { + doc.list.splice(0, 0, new stable.Text("abc"), new stable.Text("def")) + }) + assert.deepStrictEqual(doc.list, [ + new stable.Text("abc"), + new stable.Text("def"), + ]) + }) + + it("should allow splicing text into a list on unstable", () => { + let doc = unstable.from<{ list: Array }>({ list: [] }) + doc = unstable.change(doc, doc => { + doc.list.splice(0, 0, "abc", "def") + }) + assert.deepStrictEqual(doc.list, ["abc", "def"]) + }) }) From 58a7a06b754f58bee961012a96485634c9efa854 Mon Sep 17 00:00:00 2001 From: alexjg Date: Fri, 27 Jan 2023 20:27:11 +0000 Subject: [PATCH 17/45] @automerge/automerge-wasm@0.1.23 and @automerge/automerge@2.0.1-alpha.6 (#509) --- javascript/package.json | 4 ++-- rust/automerge-wasm/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/package.json b/javascript/package.json index caeeb647..05358703 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.1-alpha.5", + "version": "2.0.1-alpha.6", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -47,7 +47,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.22", + "@automerge/automerge-wasm": "0.1.23", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 0f133468..cce3199f 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.22", + "version": "0.1.23", "license": "MIT", "files": [ "README.md", From 9b6a3c8691de47f1751c916776555db18e012f80 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Sat, 28 Jan 2023 09:32:21 +0000 Subject: [PATCH 18/45] Update README --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d11e9d1c..94e1bbb8 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,10 @@ In general we try and respect semver. ### JavaScript -An alpha release of the javascript package is currently available as -`@automerge/automerge@2.0.0-alpha.n` where `n` is an integer. We are gathering -feedback on the API and looking to release a `2.0.0` in the next few weeks. +A stable release of the javascript package is currently available as +`@automerge/automerge@2.0.0` where. pre-release verisions of the `2.0.1` are +available as `2.0.1-alpha.n`. `2.0.1*` packages are also available for Deno at +https://deno.land/x/automerge ### Rust @@ -52,7 +53,10 @@ The rust codebase is currently oriented around producing a performant backend for the Javascript wrapper and as such the API for Rust code is low level and not well documented. We will be returning to this over the next few months but for now you will need to be comfortable reading the tests and asking questions -to figure out how to use it. +to figure out how to use it. If you are looking to build rust applications which +use automerge you may want to look into +[autosurgeon](https://github.com/alexjg/autosurgeon) + ## Repository Organisation From 89a0866272502f6360221d6585e93990f932de24 Mon Sep 17 00:00:00 2001 From: alexjg Date: Sat, 28 Jan 2023 21:22:45 +0000 Subject: [PATCH 19/45] @automerge/automerge@2.0.1 (#510) --- javascript/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/package.json b/javascript/package.json index 05358703..017c5a54 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.1-alpha.6", + "version": "2.0.1", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", From 08801ab580e31df472f5c33858aa85b94d99d0fe Mon Sep 17 00:00:00 2001 From: alexjg Date: Mon, 30 Jan 2023 19:37:03 +0000 Subject: [PATCH 20/45] automerge-rs: Introduce ReadDoc and SyncDoc traits and add documentation (#511) The Rust API has so far grown somewhat organically driven by the needs of the javascript implementation. This has led to an API which is quite awkward and unfamiliar to Rust programmers. Additionally there is no documentation to speak of. This commit is the first movement towards cleaning things up a bit. We touch a lot of files but the changes are all very mechanical. We introduce a few traits to abstract over the common operations between `Automerge` and `AutoCommit`, and add a whole bunch of documentation. * Add a `ReadDoc` trait to describe methods which read value from a document. make `Transactable` extend `ReadDoc` * Add a `SyncDoc` trait to describe methods necessary for synchronizing documents. * Put the `SyncDoc` implementation for `AutoCommit` behind `AutoCommit::sync` to ensure that any open transactions are closed before taking part in the sync protocol * Split `OpObserver` into two traits: `OpObserver` + `BranchableObserver`. `BranchableObserver` captures the methods which are only needed for observing transactions. * Add a whole bunch of documentation. The main changes Rust users will need to make is: * Import the `ReadDoc` trait wherever you are using the methods which have been moved to it. Optionally change concrete paramters on functions to `ReadDoc` constraints. * Likewise import the `SyncDoc` trait wherever you are doing synchronisation work * If you are using the `AutoCommit::*_sync_message` methods you will need to add a call to `AutoCommit::sync()` first. E.g. `doc.generate_sync_message` becomes `doc.sync().generate_sync_message` * If you have an implementation of `OpObserver` which you are using in an `AutoCommit` then split it into an implementation of `OpObserver` and `BranchableObserver` --- rust/automerge-c/src/doc.rs | 9 +- rust/automerge-c/src/doc/list.rs | 1 + rust/automerge-c/src/doc/map.rs | 1 + rust/automerge-cli/src/export.rs | 1 + rust/automerge-test/src/lib.rs | 21 +- rust/automerge-wasm/src/interop.rs | 2 +- rust/automerge-wasm/src/lib.rs | 8 +- rust/automerge-wasm/src/observer.rs | 42 +- rust/automerge/Cargo.toml | 1 + rust/automerge/README.md | 5 + rust/automerge/benches/range.rs | 18 +- rust/automerge/benches/sync.rs | 6 +- rust/automerge/examples/quickstart.rs | 2 +- rust/automerge/examples/watch.rs | 1 + rust/automerge/src/autocommit.rs | 286 +++++-- rust/automerge/src/automerge.rs | 810 +++++++++--------- rust/automerge/src/automerge/tests.rs | 2 +- rust/automerge/src/autoserde.rs | 45 +- rust/automerge/src/exid.rs | 9 +- rust/automerge/src/keys.rs | 4 + rust/automerge/src/keys_at.rs | 4 + rust/automerge/src/lib.rs | 193 ++++- rust/automerge/src/list_range.rs | 3 + rust/automerge/src/list_range_at.rs | 3 + rust/automerge/src/map_range.rs | 3 + rust/automerge/src/map_range_at.rs | 3 + rust/automerge/src/op_observer.rs | 135 +-- rust/automerge/src/op_observer/compose.rs | 102 +++ rust/automerge/src/parents.rs | 31 +- rust/automerge/src/read.rs | 199 +++++ rust/automerge/src/sync.rs | 278 ++++-- rust/automerge/src/sync/state.rs | 10 + rust/automerge/src/transaction/inner.rs | 2 +- .../src/transaction/manual_transaction.rs | 199 +++-- rust/automerge/src/transaction/observation.rs | 14 +- .../automerge/src/transaction/transactable.rs | 109 +-- rust/automerge/src/types.rs | 19 + rust/automerge/src/value.rs | 10 +- rust/automerge/src/values.rs | 9 +- rust/automerge/tests/test.rs | 72 +- rust/edit-trace/src/main.rs | 1 + 41 files changed, 1720 insertions(+), 953 deletions(-) create mode 100644 rust/automerge/README.md create mode 100644 rust/automerge/src/op_observer/compose.rs create mode 100644 rust/automerge/src/read.rs diff --git a/rust/automerge-c/src/doc.rs b/rust/automerge-c/src/doc.rs index 58625798..f02c01bf 100644 --- a/rust/automerge-c/src/doc.rs +++ b/rust/automerge-c/src/doc.rs @@ -1,5 +1,7 @@ use automerge as am; +use automerge::sync::SyncDoc; use automerge::transaction::{CommitOptions, Transactable}; +use automerge::ReadDoc; use std::ops::{Deref, DerefMut}; use crate::actor_id::{to_actor_id, AMactorId}; @@ -291,7 +293,7 @@ pub unsafe extern "C" fn AMgenerateSyncMessage( ) -> *mut AMresult { let doc = to_doc_mut!(doc); let sync_state = to_sync_state_mut!(sync_state); - to_result(doc.generate_sync_message(sync_state.as_mut())) + to_result(doc.sync().generate_sync_message(sync_state.as_mut())) } /// \memberof AMdoc @@ -708,7 +710,10 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( let doc = to_doc_mut!(doc); let sync_state = to_sync_state_mut!(sync_state); let sync_message = to_sync_message!(sync_message); - to_result(doc.receive_sync_message(sync_state.as_mut(), sync_message.as_ref().clone())) + to_result( + doc.sync() + .receive_sync_message(sync_state.as_mut(), sync_message.as_ref().clone()), + ) } /// \memberof AMdoc diff --git a/rust/automerge-c/src/doc/list.rs b/rust/automerge-c/src/doc/list.rs index 48f26c21..6bcdeabf 100644 --- a/rust/automerge-c/src/doc/list.rs +++ b/rust/automerge-c/src/doc/list.rs @@ -1,5 +1,6 @@ use automerge as am; use automerge::transaction::Transactable; +use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; use crate::change_hashes::AMchangeHashes; diff --git a/rust/automerge-c/src/doc/map.rs b/rust/automerge-c/src/doc/map.rs index a5801323..86c6b4a2 100644 --- a/rust/automerge-c/src/doc/map.rs +++ b/rust/automerge-c/src/doc/map.rs @@ -1,5 +1,6 @@ use automerge as am; use automerge::transaction::Transactable; +use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; use crate::change_hashes::AMchangeHashes; diff --git a/rust/automerge-cli/src/export.rs b/rust/automerge-cli/src/export.rs index 45fd7b3b..45f39101 100644 --- a/rust/automerge-cli/src/export.rs +++ b/rust/automerge-cli/src/export.rs @@ -1,5 +1,6 @@ use anyhow::Result; use automerge as am; +use automerge::ReadDoc; use crate::{color_json::print_colored_json, SkipVerifyFlag}; diff --git a/rust/automerge-test/src/lib.rs b/rust/automerge-test/src/lib.rs index b2af72e1..a1d4ea89 100644 --- a/rust/automerge-test/src/lib.rs +++ b/rust/automerge-test/src/lib.rs @@ -4,6 +4,8 @@ use std::{ hash::Hash, }; +use automerge::ReadDoc; + use serde::ser::{SerializeMap, SerializeSeq}; pub fn new_doc() -> automerge::AutoCommit { @@ -48,7 +50,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { /// let title = doc.put(todo, "title", "water plants").unwrap(); /// /// assert_doc!( -/// &doc.document(), +/// &doc, /// map!{ /// "todos" => { /// list![ @@ -67,6 +69,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { /// ```rust /// # use automerge_test::{assert_doc, map}; /// # use automerge::transaction::Transactable; +/// # use automerge::ReadDoc; /// /// let mut doc1 = automerge::AutoCommit::new(); /// let mut doc2 = automerge::AutoCommit::new(); @@ -74,7 +77,7 @@ pub fn sorted_actors() -> (automerge::ActorId, automerge::ActorId) { /// doc2.put(automerge::ROOT, "field", "two").unwrap(); /// doc1.merge(&mut doc2); /// assert_doc!( -/// &doc1.document(), +/// doc1.document(), /// map!{ /// "field" => { /// "one", @@ -330,12 +333,12 @@ impl serde::Serialize for RealizedObject { } } -pub fn realize(doc: &automerge::Automerge) -> RealizedObject { +pub fn realize(doc: &R) -> RealizedObject { realize_obj(doc, &automerge::ROOT, automerge::ObjType::Map) } -pub fn realize_prop>( - doc: &automerge::Automerge, +pub fn realize_prop>( + doc: &R, obj_id: &automerge::ObjId, prop: P, ) -> RealizedObject { @@ -346,8 +349,8 @@ pub fn realize_prop>( } } -pub fn realize_obj( - doc: &automerge::Automerge, +pub fn realize_obj( + doc: &R, obj_id: &automerge::ObjId, objtype: automerge::ObjType, ) -> RealizedObject { @@ -370,8 +373,8 @@ pub fn realize_obj( } } -fn realize_values>( - doc: &automerge::Automerge, +fn realize_values>( + doc: &R, obj_id: &automerge::ObjId, key: K, ) -> BTreeSet { diff --git a/rust/automerge-wasm/src/interop.rs b/rust/automerge-wasm/src/interop.rs index 2881209a..1546ff10 100644 --- a/rust/automerge-wasm/src/interop.rs +++ b/rust/automerge-wasm/src/interop.rs @@ -2,7 +2,7 @@ use crate::error::InsertObject; use crate::value::Datatype; use crate::{Automerge, TextRepresentation}; use automerge as am; -use automerge::transaction::Transactable; +use automerge::ReadDoc; use automerge::ROOT; use automerge::{Change, ChangeHash, ObjType, Prop}; use js_sys::{Array, Function, JsString, Object, Reflect, Symbol, Uint8Array}; diff --git a/rust/automerge-wasm/src/lib.rs b/rust/automerge-wasm/src/lib.rs index d6ccc8c8..b53bf3b9 100644 --- a/rust/automerge-wasm/src/lib.rs +++ b/rust/automerge-wasm/src/lib.rs @@ -29,7 +29,7 @@ use am::transaction::CommitOptions; use am::transaction::{Observed, Transactable, UnObserved}; use am::ScalarValue; use automerge as am; -use automerge::{Change, ObjId, Prop, TextEncoding, Value, ROOT}; +use automerge::{sync::SyncDoc, Change, ObjId, Prop, ReadDoc, TextEncoding, Value, ROOT}; use js_sys::{Array, Function, Object, Uint8Array}; use serde::ser::Serialize; use std::borrow::Cow; @@ -746,13 +746,15 @@ impl Automerge { ) -> Result<(), error::ReceiveSyncMessage> { let message = message.to_vec(); let message = am::sync::Message::decode(message.as_slice())?; - self.doc.receive_sync_message(&mut state.0, message)?; + self.doc + .sync() + .receive_sync_message(&mut state.0, message)?; Ok(()) } #[wasm_bindgen(js_name = generateSyncMessage)] pub fn generate_sync_message(&mut self, state: &mut SyncState) -> JsValue { - if let Some(message) = self.doc.generate_sync_message(&mut state.0) { + if let Some(message) = self.doc.sync().generate_sync_message(&mut state.0) { Uint8Array::from(message.encode().as_slice()).into() } else { JsValue::null() diff --git a/rust/automerge-wasm/src/observer.rs b/rust/automerge-wasm/src/observer.rs index 83516597..c0b462a6 100644 --- a/rust/automerge-wasm/src/observer.rs +++ b/rust/automerge-wasm/src/observer.rs @@ -6,7 +6,7 @@ use crate::{ interop::{self, alloc, js_set}, TextRepresentation, }; -use automerge::{Automerge, ObjId, OpObserver, Prop, ScalarValue, SequenceTree, Value}; +use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, SequenceTree, Value}; use js_sys::{Array, Object}; use wasm_bindgen::prelude::*; @@ -30,9 +30,9 @@ impl Observer { old_enabled } - fn get_path(&mut self, doc: &Automerge, obj: &ObjId) -> Option> { + fn get_path(&mut self, doc: &R, obj: &ObjId) -> Option> { match doc.parents(obj) { - Ok(mut parents) => parents.visible_path(), + Ok(parents) => parents.visible_path(), Err(e) => { automerge::log!("error generating patch : {:?}", e); None @@ -98,9 +98,9 @@ pub(crate) enum Patch { } impl OpObserver for Observer { - fn insert( + fn insert( &mut self, - doc: &Automerge, + doc: &R, obj: ObjId, index: usize, tagged_value: (Value<'_>, ObjId), @@ -134,7 +134,7 @@ impl OpObserver for Observer { } } - fn splice_text(&mut self, doc: &Automerge, obj: ObjId, index: usize, value: &str) { + fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { if self.enabled { if self.text_rep == TextRepresentation::Array { for (i, c) in value.chars().enumerate() { @@ -182,7 +182,7 @@ impl OpObserver for Observer { } } - fn delete_seq(&mut self, doc: &Automerge, obj: ObjId, index: usize, length: usize) { + fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { if self.enabled { match self.patches.last_mut() { Some(Patch::SpliceText { @@ -244,7 +244,7 @@ impl OpObserver for Observer { } } - fn delete_map(&mut self, doc: &Automerge, obj: ObjId, key: &str) { + fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { if self.enabled { if let Some(path) = self.get_path(doc, &obj) { let patch = Patch::DeleteMap { @@ -257,9 +257,9 @@ impl OpObserver for Observer { } } - fn put( + fn put( &mut self, - doc: &Automerge, + doc: &R, obj: ObjId, prop: Prop, tagged_value: (Value<'_>, ObjId), @@ -290,9 +290,9 @@ impl OpObserver for Observer { } } - fn expose( + fn expose( &mut self, - doc: &Automerge, + doc: &R, obj: ObjId, prop: Prop, tagged_value: (Value<'_>, ObjId), @@ -323,7 +323,13 @@ impl OpObserver for Observer { } } - fn increment(&mut self, doc: &Automerge, obj: ObjId, prop: Prop, tagged_value: (i64, ObjId)) { + fn increment( + &mut self, + doc: &R, + obj: ObjId, + prop: Prop, + tagged_value: (i64, ObjId), + ) { if self.enabled { if let Some(path) = self.get_path(doc, &obj) { let value = tagged_value.0; @@ -337,6 +343,12 @@ impl OpObserver for Observer { } } + fn text_as_seq(&self) -> bool { + self.text_rep == TextRepresentation::Array + } +} + +impl automerge::op_observer::BranchableObserver for Observer { fn merge(&mut self, other: &Self) { self.patches.extend_from_slice(other.patches.as_slice()) } @@ -348,10 +360,6 @@ impl OpObserver for Observer { text_rep: self.text_rep, } } - - fn text_as_seq(&self) -> bool { - self.text_rep == TextRepresentation::Array - } } fn prop_to_js(p: &Prop) -> JsValue { diff --git a/rust/automerge/Cargo.toml b/rust/automerge/Cargo.toml index 89b48020..578878ae 100644 --- a/rust/automerge/Cargo.toml +++ b/rust/automerge/Cargo.toml @@ -7,6 +7,7 @@ repository = "https://github.com/automerge/automerge-rs" documentation = "https://automerge.org/automerge-rs/automerge/" rust-version = "1.57.0" description = "A JSON-like data structure (a CRDT) that can be modified concurrently by different users, and merged again automatically" +readme = "./README.md" [features] optree-visualisation = ["dot", "rand"] diff --git a/rust/automerge/README.md b/rust/automerge/README.md new file mode 100644 index 00000000..97dbe4f8 --- /dev/null +++ b/rust/automerge/README.md @@ -0,0 +1,5 @@ +# Automerge + +Automerge is a library of data structures for building collaborative +[local-first](https://www.inkandswitch.com/local-first/) applications. This is +the Rust implementation. See [automerge.org](https://automerge.org/) diff --git a/rust/automerge/benches/range.rs b/rust/automerge/benches/range.rs index aec5c293..008ae159 100644 --- a/rust/automerge/benches/range.rs +++ b/rust/automerge/benches/range.rs @@ -1,4 +1,4 @@ -use automerge::{transaction::Transactable, Automerge, ROOT}; +use automerge::{transaction::Transactable, Automerge, ReadDoc, ROOT}; use criterion::{black_box, criterion_group, criterion_main, Criterion}; fn doc(n: u64) -> Automerge { @@ -16,36 +16,20 @@ fn range(doc: &Automerge) { range.for_each(drop); } -fn range_rev(doc: &Automerge) { - let range = doc.values(ROOT).rev(); - range.for_each(drop); -} - fn range_at(doc: &Automerge) { let range = doc.values_at(ROOT, &doc.get_heads()); range.for_each(drop); } -fn range_at_rev(doc: &Automerge) { - let range = doc.values_at(ROOT, &doc.get_heads()).rev(); - range.for_each(drop); -} - fn criterion_benchmark(c: &mut Criterion) { let n = 100_000; let doc = doc(n); c.bench_function(&format!("range {}", n), |b| { b.iter(|| range(black_box(&doc))) }); - c.bench_function(&format!("range rev {}", n), |b| { - b.iter(|| range_rev(black_box(&doc))) - }); c.bench_function(&format!("range_at {}", n), |b| { b.iter(|| range_at(black_box(&doc))) }); - c.bench_function(&format!("range_at rev {}", n), |b| { - b.iter(|| range_at_rev(black_box(&doc))) - }); } criterion_group!(benches, criterion_benchmark); diff --git a/rust/automerge/benches/sync.rs b/rust/automerge/benches/sync.rs index 483fd2b4..13965792 100644 --- a/rust/automerge/benches/sync.rs +++ b/rust/automerge/benches/sync.rs @@ -1,4 +1,8 @@ -use automerge::{sync, transaction::Transactable, Automerge, ROOT}; +use automerge::{ + sync::{self, SyncDoc}, + transaction::Transactable, + Automerge, ROOT, +}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; #[derive(Default)] diff --git a/rust/automerge/examples/quickstart.rs b/rust/automerge/examples/quickstart.rs index 76ef0470..fcb23d5e 100644 --- a/rust/automerge/examples/quickstart.rs +++ b/rust/automerge/examples/quickstart.rs @@ -2,7 +2,7 @@ use automerge::transaction::CommitOptions; use automerge::transaction::Transactable; use automerge::AutomergeError; use automerge::ObjType; -use automerge::{Automerge, ROOT}; +use automerge::{Automerge, ReadDoc, ROOT}; // Based on https://automerge.github.io/docs/quickstart fn main() { diff --git a/rust/automerge/examples/watch.rs b/rust/automerge/examples/watch.rs index 1618d6c4..4cd8f4ea 100644 --- a/rust/automerge/examples/watch.rs +++ b/rust/automerge/examples/watch.rs @@ -3,6 +3,7 @@ use automerge::transaction::Transactable; use automerge::Automerge; use automerge::AutomergeError; use automerge::Patch; +use automerge::ReadDoc; use automerge::VecOpObserver; use automerge::ROOT; diff --git a/rust/automerge/src/autocommit.rs b/rust/automerge/src/autocommit.rs index 2258fa2e..2c1c3adf 100644 --- a/rust/automerge/src/autocommit.rs +++ b/rust/automerge/src/autocommit.rs @@ -1,10 +1,12 @@ use std::ops::RangeBounds; use crate::exid::ExId; -use crate::op_observer::OpObserver; +use crate::op_observer::{BranchableObserver, OpObserver}; +use crate::sync::SyncDoc; use crate::transaction::{CommitOptions, Transactable}; use crate::{ - sync, Keys, KeysAt, ListRange, ListRangeAt, MapRange, MapRangeAt, ObjType, Parents, ScalarValue, + sync, Keys, KeysAt, ListRange, ListRangeAt, MapRange, MapRangeAt, ObjType, Parents, ReadDoc, + ScalarValue, }; use crate::{ transaction::{Observation, Observed, TransactionInner, UnObserved}, @@ -12,6 +14,41 @@ use crate::{ }; /// An automerge document that automatically manages transactions. +/// +/// An `AutoCommit` can optionally manage an [`OpObserver`]. This observer will be notified of all +/// changes made by both remote and local changes. The type parameter `O` tracks whether this +/// document is observed or not. +/// +/// ## Creating, loading, merging and forking documents +/// +/// A new document can be created with [`Self::new`], which will create a document with a random +/// [`ActorId`]. Existing documents can be loaded with [`Self::load`]. +/// +/// If you have two documents and you want to merge the changes from one into the other you can use +/// [`Self::merge`]. +/// +/// If you have a document you want to split into two concurrent threads of execution you can use +/// [`Self::fork`]. If you want to split a document from ealier in its history you can use +/// [`Self::fork_at`]. +/// +/// ## Reading values +/// +/// [`Self`] implements [`ReadDoc`], which provides methods for reading values from the document. +/// +/// ## Modifying a document +/// +/// This type implements [`Transactable`] directly, so you can modify it using methods from [`Transactable`]. +/// +/// ## Synchronization +/// +/// To synchronise call [`Self::sync`] which returns an implementation of [`SyncDoc`] +/// +/// ## Observers +/// +/// An `AutoCommit` can optionally manage an [`OpObserver`]. [`Self::new`] will return a document +/// with no observer but you can set an observer using [`Self::with_observer`]. The observer must +/// implement both [`OpObserver`] and [`BranchableObserver`]. If you have an observed autocommit +/// then you can obtain a mutable reference to the observer with [`Self::observer`] #[derive(Debug, Clone)] pub struct AutoCommitWithObs { doc: Automerge, @@ -19,19 +56,12 @@ pub struct AutoCommitWithObs { observation: Obs, } +/// An autocommit document with no observer +/// +/// See [`AutoCommitWithObs`] pub type AutoCommit = AutoCommitWithObs; -impl AutoCommitWithObs { - pub fn unobserved() -> AutoCommitWithObs { - AutoCommitWithObs { - doc: Automerge::new(), - transaction: None, - observation: UnObserved::new(), - } - } -} - -impl Default for AutoCommitWithObs> { +impl Default for AutoCommitWithObs> { fn default() -> Self { let op_observer = O::default(); AutoCommitWithObs { @@ -61,7 +91,7 @@ impl AutoCommit { } } -impl AutoCommitWithObs> { +impl AutoCommitWithObs> { pub fn observer(&mut self) -> &mut Obs { self.ensure_transaction_closed(); self.observation.observer() @@ -89,7 +119,7 @@ impl AutoCommitWithObs { } impl AutoCommitWithObs { - pub fn with_observer( + pub fn with_observer( self, op_observer: Obs2, ) -> AutoCommitWithObs> { @@ -125,6 +155,9 @@ impl AutoCommitWithObs { self.doc.get_actor() } + /// Change the text encoding of this view of the document + /// + /// This is a cheap operation, it just changes the way indexes are calculated pub fn with_encoding(mut self, encoding: TextEncoding) -> Self { self.doc.text_encoding = encoding; self @@ -145,6 +178,13 @@ impl AutoCommitWithObs { } } + /// Load an incremental save of a document. + /// + /// Unlike `load` this imports changes into an existing document. It will work with both the + /// output of [`Self::save`] and [`Self::save_incremental`] + /// + /// The return value is the number of ops which were applied, this is not useful and will + /// change in future. pub fn load_incremental(&mut self, data: &[u8]) -> Result { self.ensure_transaction_closed(); // TODO - would be nice to pass None here instead of &mut () @@ -181,17 +221,24 @@ impl AutoCommitWithObs { } } + /// Save the entirety of this document in a compact form. pub fn save(&mut self) -> Vec { self.ensure_transaction_closed(); self.doc.save() } + /// Save this document, but don't run it through DEFLATE afterwards pub fn save_nocompress(&mut self) -> Vec { self.ensure_transaction_closed(); self.doc.save_nocompress() } - // should this return an empty vec instead of None? + /// Save the changes since the last call to [Self::save`] + /// + /// The output of this will not be a compressed document format, but a series of individual + /// changes. This is useful if you know you have only made a small change since the last `save` + /// and you want to immediately send it somewhere (e.g. you've inserted a single character in a + /// text object). pub fn save_incremental(&mut self) -> Vec { self.ensure_transaction_closed(); self.doc.save_incremental() @@ -202,6 +249,7 @@ impl AutoCommitWithObs { self.doc.get_missing_deps(heads) } + /// Get the last change made by this documents actor ID pub fn get_last_local_change(&mut self) -> Option<&Change> { self.ensure_transaction_closed(); self.doc.get_last_local_change() @@ -220,40 +268,24 @@ impl AutoCommitWithObs { self.doc.get_change_by_hash(hash) } + /// Get changes in `other` that are not in `self pub fn get_changes_added<'a>(&mut self, other: &'a mut Self) -> Vec<&'a Change> { self.ensure_transaction_closed(); other.ensure_transaction_closed(); self.doc.get_changes_added(&other.doc) } + #[doc(hidden)] pub fn import(&self, s: &str) -> Result<(ExId, ObjType), AutomergeError> { self.doc.import(s) } + #[doc(hidden)] pub fn dump(&mut self) { self.ensure_transaction_closed(); self.doc.dump() } - pub fn generate_sync_message(&mut self, sync_state: &mut sync::State) -> Option { - self.ensure_transaction_closed(); - self.doc.generate_sync_message(sync_state) - } - - pub fn receive_sync_message( - &mut self, - sync_state: &mut sync::State, - message: sync::Message, - ) -> Result<(), AutomergeError> { - self.ensure_transaction_closed(); - if let Some(observer) = self.observation.observer() { - self.doc - .receive_sync_message_with(sync_state, message, Some(observer)) - } else { - self.doc.receive_sync_message(sync_state, message) - } - } - /// Return a graphviz representation of the opset. /// /// # Arguments @@ -305,6 +337,7 @@ impl AutoCommitWithObs { tx.commit(&mut self.doc, options.message, options.time) } + /// Remove any changes that have been made in the current transaction from the document pub fn rollback(&mut self) -> usize { self.transaction .take() @@ -326,14 +359,24 @@ impl AutoCommitWithObs { let args = self.doc.transaction_args(); TransactionInner::empty(&mut self.doc, args, options.message, options.time) } + + /// An implementation of [`crate::sync::SyncDoc`] for this autocommit + /// + /// This ensures that any outstanding transactions for this document are committed before + /// taking part in the sync protocol + pub fn sync(&mut self) -> impl SyncDoc + '_ { + self.ensure_transaction_closed(); + SyncWrapper { inner: self } + } } -impl Transactable for AutoCommitWithObs { - fn pending_ops(&self) -> usize { - self.transaction - .as_ref() - .map(|(_, t)| t.pending_ops()) - .unwrap_or(0) +impl ReadDoc for AutoCommitWithObs { + fn parents>(&self, obj: O) -> Result, AutomergeError> { + self.doc.parents(obj) + } + + fn path_to_object>(&self, obj: O) -> Result, AutomergeError> { + self.doc.path_to_object(obj) } fn keys>(&self, obj: O) -> Keys<'_, '_> { @@ -398,6 +441,69 @@ impl Transactable for AutoCommitWithObs { self.doc.object_type(obj) } + fn text>(&self, obj: O) -> Result { + self.doc.text(obj) + } + + fn text_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result { + self.doc.text_at(obj, heads) + } + + fn get, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError> { + self.doc.get(obj, prop) + } + + fn get_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError> { + self.doc.get_at(obj, prop, heads) + } + + fn get_all, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError> { + self.doc.get_all(obj, prop) + } + + fn get_all_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError> { + self.doc.get_all_at(obj, prop, heads) + } + + fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec { + self.doc.get_missing_deps(heads) + } + + fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&Change> { + self.doc.get_change_by_hash(hash) + } +} + +impl Transactable for AutoCommitWithObs { + fn pending_ops(&self) -> usize { + self.transaction + .as_ref() + .map(|(_, t)| t.pending_ops()) + .unwrap_or(0) + } + fn put, P: Into, V: Into>( &mut self, obj: O, @@ -515,60 +621,52 @@ impl Transactable for AutoCommitWithObs { ) } - fn text>(&self, obj: O) -> Result { - self.doc.text(obj) - } - - fn text_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result { - self.doc.text_at(obj, heads) - } - - // TODO - I need to return these OpId's here **only** to get - // the legacy conflicts format of { [opid]: value } - // Something better? - fn get, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError> { - self.doc.get(obj, prop) - } - - fn get_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError> { - self.doc.get_at(obj, prop, heads) - } - - fn get_all, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError> { - self.doc.get_all(obj, prop) - } - - fn get_all_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError> { - self.doc.get_all_at(obj, prop, heads) - } - - fn parents>(&self, obj: O) -> Result, AutomergeError> { - self.doc.parents(obj) - } - fn base_heads(&self) -> Vec { self.doc.get_heads() } } + +// A wrapper we return from `AutoCommit::sync` to ensure that transactions are closed before we +// start syncing +struct SyncWrapper<'a, Obs: Observation> { + inner: &'a mut AutoCommitWithObs, +} + +impl<'a, Obs: Observation> SyncDoc for SyncWrapper<'a, Obs> { + fn generate_sync_message(&self, sync_state: &mut sync::State) -> Option { + self.inner.doc.generate_sync_message(sync_state) + } + + fn receive_sync_message( + &mut self, + sync_state: &mut sync::State, + message: sync::Message, + ) -> Result<(), AutomergeError> { + self.inner.ensure_transaction_closed(); + if let Some(observer) = self.inner.observation.observer() { + self.inner + .doc + .receive_sync_message_with(sync_state, message, observer) + } else { + self.inner.doc.receive_sync_message(sync_state, message) + } + } + + fn receive_sync_message_with( + &mut self, + sync_state: &mut sync::State, + message: sync::Message, + op_observer: &mut Obs2, + ) -> Result<(), AutomergeError> { + if let Some(our_observer) = self.inner.observation.observer() { + let mut composed = crate::op_observer::compose(our_observer, op_observer); + self.inner + .doc + .receive_sync_message_with(sync_state, message, &mut composed) + } else { + self.inner + .doc + .receive_sync_message_with(sync_state, message, op_observer) + } + } +} diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 584f761d..86aa5f63 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -9,7 +9,7 @@ use crate::clocks::Clocks; use crate::columnar::Key as EncodedKey; use crate::exid::ExId; use crate::keys::Keys; -use crate::op_observer::OpObserver; +use crate::op_observer::{BranchableObserver, OpObserver}; use crate::op_set::OpSet; use crate::parents::Parents; use crate::storage::{self, load, CompressConfig, VerificationMode}; @@ -22,7 +22,7 @@ use crate::types::{ }; use crate::{ query, AutomergeError, Change, KeysAt, ListRange, ListRangeAt, MapRange, MapRangeAt, ObjType, - Prop, Values, + Prop, ReadDoc, Values, }; use serde::Serialize; @@ -35,7 +35,39 @@ pub(crate) enum Actor { Cached(usize), } -/// An automerge document. +/// An automerge document which does not manage transactions for you. +/// +/// ## Creating, loading, merging and forking documents +/// +/// A new document can be created with [`Self::new`], which will create a document with a random +/// [`ActorId`]. Existing documents can be loaded with [`Self::load`], or [`Self::load_with`]. +/// +/// If you have two documents and you want to merge the changes from one into the other you can use +/// [`Self::merge`] or [`Self::merge_with`]. +/// +/// If you have a document you want to split into two concurrent threads of execution you can use +/// [`Self::fork`]. If you want to split a document from ealier in its history you can use +/// [`Self::fork_at`]. +/// +/// ## Reading values +/// +/// [`Self`] implements [`ReadDoc`], which provides methods for reading values from the document. +/// +/// ## Modifying a document (Transactions) +/// +/// [`Automerge`] provides an interface for viewing and modifying automerge documents which does +/// not manage transactions for you. To create changes you use either [`Automerge::transaction`] or +/// [`Automerge::transact`] (or the `_with` variants). +/// +/// ## Sync +/// +/// This type implements [`crate::sync::SyncDoc`] +/// +/// ## Observers +/// +/// Many of the methods on this type have an `_with` or `_observed` variant +/// which allow you to pass in an [`OpObserver`] to observe any changes which +/// occur. #[derive(Debug, Clone)] pub struct Automerge { /// The list of unapplied changes that are not causally ready. @@ -79,6 +111,9 @@ impl Automerge { } } + /// Change the text encoding of this view of the document + /// + /// This is a cheap operation, it just changes the way indexes are calculated pub fn with_encoding(mut self, encoding: TextEncoding) -> Self { self.text_encoding = encoding; self @@ -125,7 +160,8 @@ impl Automerge { Transaction::new(self, args, UnObserved) } - pub fn transaction_with_observer( + /// Start a transaction with an observer + pub fn transaction_with_observer( &mut self, op_observer: Obs, ) -> Transaction<'_, Observed> { @@ -172,7 +208,6 @@ impl Automerge { self.transact_with_impl(Some(c), f) } - /// Like [`Self::transact`] but with a function for generating the commit options. fn transact_with_impl( &mut self, c: Option, @@ -210,7 +245,7 @@ impl Automerge { pub fn transact_observed(&mut self, f: F) -> transaction::Result where F: FnOnce(&mut Transaction<'_, Observed>) -> Result, - Obs: OpObserver + Default, + Obs: OpObserver + BranchableObserver + Default, { self.transact_observed_with_impl(None::<&dyn Fn(&O) -> CommitOptions>, f) } @@ -224,7 +259,7 @@ impl Automerge { where F: FnOnce(&mut Transaction<'_, Observed>) -> Result, C: FnOnce(&O) -> CommitOptions, - Obs: OpObserver + Default, + Obs: OpObserver + BranchableObserver + Default, { self.transact_observed_with_impl(Some(c), f) } @@ -237,7 +272,7 @@ impl Automerge { where F: FnOnce(&mut Transaction<'_, Observed>) -> Result, C: FnOnce(&O) -> CommitOptions, - Obs: OpObserver + Default, + Obs: OpObserver + BranchableObserver + Default, { let observer = Obs::default(); let mut tx = self.transaction_with_observer(observer); @@ -273,13 +308,17 @@ impl Automerge { } /// Fork this document at the current point for use by a different actor. + /// + /// This will create a new actor ID for the forked document pub fn fork(&self) -> Self { let mut f = self.clone(); f.set_actor(ActorId::random()); f } - /// Fork this document at the give heads + /// Fork this document at the given heads + /// + /// This will create a new actor ID for the forked document pub fn fork_at(&self, heads: &[ChangeHash]) -> Result { let mut seen = heads.iter().cloned().collect::>(); let mut heads = heads.to_vec(); @@ -304,182 +343,6 @@ impl Automerge { Ok(f) } - // KeysAt::() - // LenAt::() - // PropAt::() - // NthAt::() - - /// Get the parents of an object in the document tree. - /// - /// ### Errors - /// - /// Returns an error when the id given is not the id of an object in this document. - /// This function does not get the parents of scalar values contained within objects. - /// - /// ### Experimental - /// - /// This function may in future be changed to allow getting the parents from the id of a scalar - /// value. - pub fn parents>(&self, obj: O) -> Result, AutomergeError> { - let (obj_id, _) = self.exid_to_obj(obj.as_ref())?; - Ok(self.ops.parents(obj_id)) - } - - pub fn path_to_object>( - &self, - obj: O, - ) -> Result, AutomergeError> { - Ok(self.parents(obj.as_ref().clone())?.path()) - } - - /// Get the keys of the object `obj`. - /// - /// For a map this returns the keys of the map. - /// For a list this returns the element ids (opids) encoded as strings. - pub fn keys>(&self, obj: O) -> Keys<'_, '_> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - let iter_keys = self.ops.keys(obj); - Keys::new(self, iter_keys) - } else { - Keys::new(self, None) - } - } - - /// Historical version of [`keys`](Self::keys). - pub fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return KeysAt::new(self, self.ops.keys_at(obj, clock)); - } - } - KeysAt::new(self, None) - } - - /// Iterate over the keys and values of the map `obj` in the given range. - pub fn map_range, R: RangeBounds>( - &self, - obj: O, - range: R, - ) -> MapRange<'_, R> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - MapRange::new(self, self.ops.map_range(obj, range)) - } else { - MapRange::new(self, None) - } - } - - /// Historical version of [`map_range`](Self::map_range). - pub fn map_range_at, R: RangeBounds>( - &self, - obj: O, - range: R, - heads: &[ChangeHash], - ) -> MapRangeAt<'_, R> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - let iter_range = self.ops.map_range_at(obj, range, clock); - return MapRangeAt::new(self, iter_range); - } - } - MapRangeAt::new(self, None) - } - - /// Iterate over the indexes and values of the list `obj` in the given range. - pub fn list_range, R: RangeBounds>( - &self, - obj: O, - range: R, - ) -> ListRange<'_, R> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - ListRange::new(self, self.ops.list_range(obj, range)) - } else { - ListRange::new(self, None) - } - } - - /// Historical version of [`list_range`](Self::list_range). - pub fn list_range_at, R: RangeBounds>( - &self, - obj: O, - range: R, - heads: &[ChangeHash], - ) -> ListRangeAt<'_, R> { - if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - let iter_range = self.ops.list_range_at(obj, range, clock); - return ListRangeAt::new(self, iter_range); - } - } - ListRangeAt::new(self, None) - } - - pub fn values>(&self, obj: O) -> Values<'_> { - if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if obj_type.is_sequence() { - Values::new(self, self.ops.list_range(obj, ..)) - } else { - Values::new(self, self.ops.map_range(obj, ..)) - } - } else { - Values::empty(self) - } - } - - pub fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { - if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return match obj_type { - ObjType::Map | ObjType::Table => { - let iter_range = self.ops.map_range_at(obj, .., clock); - Values::new(self, iter_range) - } - ObjType::List | ObjType::Text => { - let iter_range = self.ops.list_range_at(obj, .., clock); - Values::new(self, iter_range) - } - }; - } - } - Values::empty(self) - } - - /// Get the length of the given object. - pub fn length>(&self, obj: O) -> usize { - if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if obj_type == ObjType::Map || obj_type == ObjType::Table { - self.keys(obj).count() - } else { - let encoding = ListEncoding::new(obj_type, self.text_encoding); - self.ops.search(&inner_obj, query::Len::new(encoding)).len - } - } else { - 0 - } - } - - /// Historical version of [`length`](Self::length). - pub fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { - if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return if obj_type == ObjType::Map || obj_type == ObjType::Table { - self.keys_at(obj, heads).count() - } else { - let encoding = ListEncoding::new(obj_type, self.text_encoding); - self.ops - .search(&inner_obj, query::LenAt::new(clock, encoding)) - .len - }; - } - } - 0 - } - - /// Get the type of this object, if it is an object. - pub fn object_type>(&self, obj: O) -> Result { - let (_, obj_type) = self.exid_to_obj(obj.as_ref())?; - Ok(obj_type) - } - pub(crate) fn exid_to_obj(&self, id: &ExId) -> Result<(ObjId, ObjType), AutomergeError> { match id { ExId::Root => Ok((ObjId::root(), ObjType::Map)), @@ -511,153 +374,19 @@ impl Automerge { self.ops.id_to_exid(id) } - /// Get the string represented by the given text object. - pub fn text>(&self, obj: O) -> Result { - let obj = self.exid_to_obj(obj.as_ref())?.0; - let query = self.ops.search(&obj, query::ListVals::new()); - let mut buffer = String::new(); - for q in &query.ops { - buffer.push_str(q.to_str()); - } - Ok(buffer) - } - - /// Historical version of [`text`](Self::text). - pub fn text_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result { - let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads)?; - let query = self.ops.search(&obj, query::ListValsAt::new(clock)); - let mut buffer = String::new(); - for q in &query.ops { - if let OpType::Put(ScalarValue::Str(s)) = &q.action { - buffer.push_str(s); - } else { - buffer.push('\u{fffc}'); - } - } - Ok(buffer) - } - - // TODO - I need to return these OpId's here **only** to get - // the legacy conflicts format of { [opid]: value } - // Something better? - /// Get a value out of the document. - /// - /// Returns both the value and the id of the operation that created it, useful for handling - /// conflicts and serves as the object id if the value is an object. - pub fn get, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError> { - Ok(self.get_all(obj, prop.into())?.last().cloned()) - } - - /// Historical version of [`get`](Self::get). - pub fn get_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError> { - Ok(self.get_all_at(obj, prop, heads)?.last().cloned()) - } - - /// Get all conflicting values out of the document at this prop that conflict. - /// - /// Returns both the value and the id of the operation that created it, useful for handling - /// conflicts and serves as the object id if the value is an object. - pub fn get_all, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError> { - let obj = self.exid_to_obj(obj.as_ref())?.0; - let mut result = match prop.into() { - Prop::Map(p) => { - let prop = self.ops.m.props.lookup(&p); - if let Some(p) = prop { - self.ops - .search(&obj, query::Prop::new(p)) - .ops - .into_iter() - .map(|o| (o.value(), self.id_to_exid(o.id))) - .collect() - } else { - vec![] - } - } - Prop::Seq(n) => { - let obj_type = self.ops.object_type(&obj); - let encoding = obj_type - .map(|o| ListEncoding::new(o, self.text_encoding)) - .unwrap_or_default(); - self.ops - .search(&obj, query::Nth::new(n, encoding)) - .ops - .into_iter() - .map(|o| (o.value(), self.id_to_exid(o.id))) - .collect() - } - }; - result.sort_by(|a, b| b.1.cmp(&a.1)); - Ok(result) - } - - /// Historical version of [`get_all`](Self::get_all). - pub fn get_all_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError> { - let prop = prop.into(); - let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads)?; - let result = match prop { - Prop::Map(p) => { - let prop = self.ops.m.props.lookup(&p); - if let Some(p) = prop { - self.ops - .search(&obj, query::PropAt::new(p, clock)) - .ops - .into_iter() - .map(|o| (o.clone_value(), self.id_to_exid(o.id))) - .collect() - } else { - vec![] - } - } - Prop::Seq(n) => { - let obj_type = self.ops.object_type(&obj); - let encoding = obj_type - .map(|o| ListEncoding::new(o, self.text_encoding)) - .unwrap_or_default(); - self.ops - .search(&obj, query::NthAt::new(n, clock, encoding)) - .ops - .into_iter() - .map(|o| (o.clone_value(), self.id_to_exid(o.id))) - .collect() - } - }; - Ok(result) - } - /// Load a document. pub fn load(data: &[u8]) -> Result { Self::load_with::<()>(data, VerificationMode::Check, None) } + /// Load a document without verifying the head hashes + /// + /// This is useful for debugging as it allows you to examine a corrupted document. pub fn load_unverified_heads(data: &[u8]) -> Result { Self::load_with::<()>(data, VerificationMode::DontCheck, None) } - /// Load a document. + /// Load a document with an observer #[tracing::instrument(skip(data, observer), err)] pub fn load_with( data: &[u8], @@ -749,11 +478,17 @@ impl Automerge { } /// Load an incremental save of a document. + /// + /// Unlike `load` this imports changes into an existing document. It will work with both the + /// output of [`Self::save`] and [`Self::save_incremental`] + /// + /// The return value is the number of ops which were applied, this is not useful and will + /// change in future. pub fn load_incremental(&mut self, data: &[u8]) -> Result { self.load_incremental_with::<()>(data, None) } - /// Load an incremental save of a document. + /// Like [`Self::load_incremental`] but with an observer pub fn load_incremental_with( &mut self, data: &[u8], @@ -783,6 +518,9 @@ impl Automerge { } /// Apply changes to this document. + /// + /// This is idemptotent in the sense that if a change has already been applied it will be + /// ignored. pub fn apply_changes( &mut self, changes: impl IntoIterator, @@ -790,7 +528,7 @@ impl Automerge { self.apply_changes_with::<_, ()>(changes, None) } - /// Apply changes to this document. + /// Like [`Self::apply_changes`] but with an observer pub fn apply_changes_with, Obs: OpObserver>( &mut self, changes: I, @@ -925,6 +663,10 @@ impl Automerge { } /// Save the entirety of this document in a compact form. + /// + /// This takes a mutable reference to self because it saves the heads of the last save so that + /// `save_incremental` can be used to produce only the changes since the last `save`. This API + /// will be changing in future. pub fn save(&mut self) -> Vec { let heads = self.get_heads(); let c = self.history.iter(); @@ -940,6 +682,7 @@ impl Automerge { bytes } + /// Save this document, but don't run it through DEFLATE afterwards pub fn save_nocompress(&mut self) -> Vec { let heads = self.get_heads(); let c = self.history.iter(); @@ -955,7 +698,12 @@ impl Automerge { bytes } - /// Save the changes since last save in a compact form. + /// Save the changes since the last call to [Self::save`] + /// + /// The output of this will not be a compressed document format, but a series of individual + /// changes. This is useful if you know you have only made a small change since the last `save` + /// and you want to immediately send it somewhere (e.g. you've inserted a single character in a + /// text object). pub fn save_incremental(&mut self) -> Vec { let changes = self .get_changes(self.saved.as_slice()) @@ -997,33 +745,6 @@ impl Automerge { Ok(()) } - /// Get the hashes of the changes in this document that aren't transitive dependencies of the - /// given `heads`. - pub fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec { - let in_queue: HashSet<_> = self.queue.iter().map(|change| change.hash()).collect(); - let mut missing = HashSet::new(); - - for head in self.queue.iter().flat_map(|change| change.deps()) { - if !self.history_index.contains_key(head) { - missing.insert(head); - } - } - - for head in heads { - if !self.history_index.contains_key(head) { - missing.insert(head); - } - } - - let mut missing = missing - .into_iter() - .filter(|hash| !in_queue.contains(hash)) - .copied() - .collect::>(); - missing.sort(); - missing - } - /// Get the changes since `have_deps` in this document using a clock internally. fn get_changes_clock(&self, have_deps: &[ChangeHash]) -> Result, AutomergeError> { // get the clock for the given deps @@ -1052,10 +773,6 @@ impl Automerge { .collect()) } - pub fn get_changes(&self, have_deps: &[ChangeHash]) -> Result, AutomergeError> { - self.get_changes_clock(have_deps) - } - /// Get the last change this actor made to the document. pub fn get_last_local_change(&self) -> Option<&Change> { return self @@ -1087,47 +804,6 @@ impl Automerge { } } - /// Get a change by its hash. - pub fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&Change> { - self.history_index - .get(hash) - .and_then(|index| self.history.get(*index)) - } - - /// Get the changes that the other document added compared to this document. - #[tracing::instrument(skip(self, other))] - pub fn get_changes_added<'a>(&self, other: &'a Self) -> Vec<&'a Change> { - // Depth-first traversal from the heads through the dependency graph, - // until we reach a change that is already present in other - let mut stack: Vec<_> = other.get_heads(); - tracing::trace!(their_heads=?stack, "finding changes to merge"); - let mut seen_hashes = HashSet::new(); - let mut added_change_hashes = Vec::new(); - while let Some(hash) = stack.pop() { - if !seen_hashes.contains(&hash) && self.get_change_by_hash(&hash).is_none() { - seen_hashes.insert(hash); - added_change_hashes.push(hash); - if let Some(change) = other.get_change_by_hash(&hash) { - stack.extend(change.deps()); - } - } - } - // Return those changes in the reverse of the order in which the depth-first search - // found them. This is not necessarily a topological sort, but should usually be close. - added_change_hashes.reverse(); - added_change_hashes - .into_iter() - .filter_map(|h| other.get_change_by_hash(&h)) - .collect() - } - - /// Get the heads of this document. - pub fn get_heads(&self) -> Vec { - let mut deps: Vec<_> = self.deps.iter().copied().collect(); - deps.sort_unstable(); - deps - } - fn get_hash(&self, actor: usize, seq: u64) -> Result { self.states .get(&actor) @@ -1181,6 +857,7 @@ impl Automerge { self.deps.insert(change.hash()); } + #[doc(hidden)] pub fn import(&self, s: &str) -> Result<(ExId, ObjType), AutomergeError> { if s == "_root" { Ok((ExId::Root, ObjType::Map)) @@ -1367,6 +1044,343 @@ impl Automerge { op } + + /// Get the heads of this document. + pub fn get_heads(&self) -> Vec { + let mut deps: Vec<_> = self.deps.iter().copied().collect(); + deps.sort_unstable(); + deps + } + + pub fn get_changes(&self, have_deps: &[ChangeHash]) -> Result, AutomergeError> { + self.get_changes_clock(have_deps) + } + + /// Get changes in `other` that are not in `self + pub fn get_changes_added<'a>(&self, other: &'a Self) -> Vec<&'a Change> { + // Depth-first traversal from the heads through the dependency graph, + // until we reach a change that is already present in other + let mut stack: Vec<_> = other.get_heads(); + tracing::trace!(their_heads=?stack, "finding changes to merge"); + let mut seen_hashes = HashSet::new(); + let mut added_change_hashes = Vec::new(); + while let Some(hash) = stack.pop() { + if !seen_hashes.contains(&hash) && self.get_change_by_hash(&hash).is_none() { + seen_hashes.insert(hash); + added_change_hashes.push(hash); + if let Some(change) = other.get_change_by_hash(&hash) { + stack.extend(change.deps()); + } + } + } + // Return those changes in the reverse of the order in which the depth-first search + // found them. This is not necessarily a topological sort, but should usually be close. + added_change_hashes.reverse(); + added_change_hashes + .into_iter() + .filter_map(|h| other.get_change_by_hash(&h)) + .collect() + } +} + +impl ReadDoc for Automerge { + fn parents>(&self, obj: O) -> Result, AutomergeError> { + let (obj_id, _) = self.exid_to_obj(obj.as_ref())?; + Ok(self.ops.parents(obj_id)) + } + + fn path_to_object>(&self, obj: O) -> Result, AutomergeError> { + Ok(self.parents(obj.as_ref().clone())?.path()) + } + + fn keys>(&self, obj: O) -> Keys<'_, '_> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + let iter_keys = self.ops.keys(obj); + Keys::new(self, iter_keys) + } else { + Keys::new(self, None) + } + } + + fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + if let Ok(clock) = self.clock_at(heads) { + return KeysAt::new(self, self.ops.keys_at(obj, clock)); + } + } + KeysAt::new(self, None) + } + + fn map_range, R: RangeBounds>( + &self, + obj: O, + range: R, + ) -> MapRange<'_, R> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + MapRange::new(self, self.ops.map_range(obj, range)) + } else { + MapRange::new(self, None) + } + } + + fn map_range_at, R: RangeBounds>( + &self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> MapRangeAt<'_, R> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + if let Ok(clock) = self.clock_at(heads) { + let iter_range = self.ops.map_range_at(obj, range, clock); + return MapRangeAt::new(self, iter_range); + } + } + MapRangeAt::new(self, None) + } + + fn list_range, R: RangeBounds>( + &self, + obj: O, + range: R, + ) -> ListRange<'_, R> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + ListRange::new(self, self.ops.list_range(obj, range)) + } else { + ListRange::new(self, None) + } + } + + fn list_range_at, R: RangeBounds>( + &self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> ListRangeAt<'_, R> { + if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { + if let Ok(clock) = self.clock_at(heads) { + let iter_range = self.ops.list_range_at(obj, range, clock); + return ListRangeAt::new(self, iter_range); + } + } + ListRangeAt::new(self, None) + } + + fn values>(&self, obj: O) -> Values<'_> { + if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { + if obj_type.is_sequence() { + Values::new(self, self.ops.list_range(obj, ..)) + } else { + Values::new(self, self.ops.map_range(obj, ..)) + } + } else { + Values::empty(self) + } + } + + fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { + if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { + if let Ok(clock) = self.clock_at(heads) { + return match obj_type { + ObjType::Map | ObjType::Table => { + let iter_range = self.ops.map_range_at(obj, .., clock); + Values::new(self, iter_range) + } + ObjType::List | ObjType::Text => { + let iter_range = self.ops.list_range_at(obj, .., clock); + Values::new(self, iter_range) + } + }; + } + } + Values::empty(self) + } + + fn length>(&self, obj: O) -> usize { + if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { + if obj_type == ObjType::Map || obj_type == ObjType::Table { + self.keys(obj).count() + } else { + let encoding = ListEncoding::new(obj_type, self.text_encoding); + self.ops.search(&inner_obj, query::Len::new(encoding)).len + } + } else { + 0 + } + } + + fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { + if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { + if let Ok(clock) = self.clock_at(heads) { + return if obj_type == ObjType::Map || obj_type == ObjType::Table { + self.keys_at(obj, heads).count() + } else { + let encoding = ListEncoding::new(obj_type, self.text_encoding); + self.ops + .search(&inner_obj, query::LenAt::new(clock, encoding)) + .len + }; + } + } + 0 + } + + fn object_type>(&self, obj: O) -> Result { + let (_, obj_type) = self.exid_to_obj(obj.as_ref())?; + Ok(obj_type) + } + + fn text>(&self, obj: O) -> Result { + let obj = self.exid_to_obj(obj.as_ref())?.0; + let query = self.ops.search(&obj, query::ListVals::new()); + let mut buffer = String::new(); + for q in &query.ops { + buffer.push_str(q.to_str()); + } + Ok(buffer) + } + + fn text_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result { + let obj = self.exid_to_obj(obj.as_ref())?.0; + let clock = self.clock_at(heads)?; + let query = self.ops.search(&obj, query::ListValsAt::new(clock)); + let mut buffer = String::new(); + for q in &query.ops { + if let OpType::Put(ScalarValue::Str(s)) = &q.action { + buffer.push_str(s); + } else { + buffer.push('\u{fffc}'); + } + } + Ok(buffer) + } + + fn get, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError> { + Ok(self.get_all(obj, prop.into())?.last().cloned()) + } + + fn get_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError> { + Ok(self.get_all_at(obj, prop, heads)?.last().cloned()) + } + + fn get_all, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError> { + let obj = self.exid_to_obj(obj.as_ref())?.0; + let mut result = match prop.into() { + Prop::Map(p) => { + let prop = self.ops.m.props.lookup(&p); + if let Some(p) = prop { + self.ops + .search(&obj, query::Prop::new(p)) + .ops + .into_iter() + .map(|o| (o.value(), self.id_to_exid(o.id))) + .collect() + } else { + vec![] + } + } + Prop::Seq(n) => { + let obj_type = self.ops.object_type(&obj); + let encoding = obj_type + .map(|o| ListEncoding::new(o, self.text_encoding)) + .unwrap_or_default(); + self.ops + .search(&obj, query::Nth::new(n, encoding)) + .ops + .into_iter() + .map(|o| (o.value(), self.id_to_exid(o.id))) + .collect() + } + }; + result.sort_by(|a, b| b.1.cmp(&a.1)); + Ok(result) + } + + fn get_all_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError> { + let prop = prop.into(); + let obj = self.exid_to_obj(obj.as_ref())?.0; + let clock = self.clock_at(heads)?; + let result = match prop { + Prop::Map(p) => { + let prop = self.ops.m.props.lookup(&p); + if let Some(p) = prop { + self.ops + .search(&obj, query::PropAt::new(p, clock)) + .ops + .into_iter() + .map(|o| (o.clone_value(), self.id_to_exid(o.id))) + .collect() + } else { + vec![] + } + } + Prop::Seq(n) => { + let obj_type = self.ops.object_type(&obj); + let encoding = obj_type + .map(|o| ListEncoding::new(o, self.text_encoding)) + .unwrap_or_default(); + self.ops + .search(&obj, query::NthAt::new(n, clock, encoding)) + .ops + .into_iter() + .map(|o| (o.clone_value(), self.id_to_exid(o.id))) + .collect() + } + }; + Ok(result) + } + + fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec { + let in_queue: HashSet<_> = self.queue.iter().map(|change| change.hash()).collect(); + let mut missing = HashSet::new(); + + for head in self.queue.iter().flat_map(|change| change.deps()) { + if !self.history_index.contains_key(head) { + missing.insert(head); + } + } + + for head in heads { + if !self.history_index.contains_key(head) { + missing.insert(head); + } + } + + let mut missing = missing + .into_iter() + .filter(|hash| !in_queue.contains(hash)) + .copied() + .collect::>(); + missing.sort(); + missing + } + + fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&Change> { + self.history_index + .get(hash) + .and_then(|index| self.history.get(*index)) + } } impl Default for Automerge { diff --git a/rust/automerge/src/automerge/tests.rs b/rust/automerge/src/automerge/tests.rs index 7eadaedd..8d533fed 100644 --- a/rust/automerge/src/automerge/tests.rs +++ b/rust/automerge/src/automerge/tests.rs @@ -1539,7 +1539,7 @@ fn observe_counter_change_application() { #[test] fn get_changes_heads_empty() { - let mut doc = AutoCommit::unobserved(); + let mut doc = AutoCommit::new(); doc.put(ROOT, "key1", 1).unwrap(); doc.commit(); doc.put(ROOT, "key2", 1).unwrap(); diff --git a/rust/automerge/src/autoserde.rs b/rust/automerge/src/autoserde.rs index 63b0848a..ccfc6ae6 100644 --- a/rust/automerge/src/autoserde.rs +++ b/rust/automerge/src/autoserde.rs @@ -1,18 +1,33 @@ use serde::ser::{SerializeMap, SerializeSeq}; -use crate::{Automerge, ObjId, ObjType, Value}; +use crate::{ObjId, ObjType, ReadDoc, Value}; -/// A wrapper type which implements [`serde::Serialize`] for an [`Automerge`]. +/// A wrapper type which implements [`serde::Serialize`] for a [`ReadDoc`]. +/// +/// # Example +/// +/// ``` +/// # fn main() -> Result<(), Box> { +/// use automerge::{AutoCommit, AutomergeError, Value, transaction::Transactable}; +/// let mut doc = AutoCommit::new(); +/// doc.put(automerge::ROOT, "key", "value")?; +/// +/// let serialized = serde_json::to_string(&automerge::AutoSerde::from(&doc)).unwrap(); +/// +/// assert_eq!(serialized, r#"{"key":"value"}"#); +/// # Ok(()) +/// # } +/// ``` #[derive(Debug)] -pub struct AutoSerde<'a>(&'a Automerge); +pub struct AutoSerde<'a, R: crate::ReadDoc>(&'a R); -impl<'a> From<&'a Automerge> for AutoSerde<'a> { - fn from(a: &'a Automerge) -> Self { +impl<'a, R: ReadDoc> From<&'a R> for AutoSerde<'a, R> { + fn from(a: &'a R) -> Self { AutoSerde(a) } } -impl<'a> serde::Serialize for AutoSerde<'a> { +impl<'a, R: crate::ReadDoc> serde::Serialize for AutoSerde<'a, R> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -25,12 +40,12 @@ impl<'a> serde::Serialize for AutoSerde<'a> { } } -struct AutoSerdeMap<'a> { - doc: &'a Automerge, +struct AutoSerdeMap<'a, R> { + doc: &'a R, obj: ObjId, } -impl<'a> serde::Serialize for AutoSerdeMap<'a> { +impl<'a, R: crate::ReadDoc> serde::Serialize for AutoSerdeMap<'a, R> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -51,12 +66,12 @@ impl<'a> serde::Serialize for AutoSerdeMap<'a> { } } -struct AutoSerdeSeq<'a> { - doc: &'a Automerge, +struct AutoSerdeSeq<'a, R> { + doc: &'a R, obj: ObjId, } -impl<'a> serde::Serialize for AutoSerdeSeq<'a> { +impl<'a, R: crate::ReadDoc> serde::Serialize for AutoSerdeSeq<'a, R> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -77,13 +92,13 @@ impl<'a> serde::Serialize for AutoSerdeSeq<'a> { } } -struct AutoSerdeVal<'a> { - doc: &'a Automerge, +struct AutoSerdeVal<'a, R> { + doc: &'a R, val: Value<'a>, obj: ObjId, } -impl<'a> serde::Serialize for AutoSerdeVal<'a> { +impl<'a, R: crate::ReadDoc> serde::Serialize for AutoSerdeVal<'a, R> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, diff --git a/rust/automerge/src/exid.rs b/rust/automerge/src/exid.rs index 3ff8fbb5..3a5a2ca2 100644 --- a/rust/automerge/src/exid.rs +++ b/rust/automerge/src/exid.rs @@ -6,6 +6,10 @@ use std::cmp::{Ord, Ordering}; use std::fmt; use std::hash::{Hash, Hasher}; +/// An identifier for an object in a document +/// +/// This can be persisted using `to_bytes` and `TryFrom<&[u8]>` breaking changes to the +/// serialization format will be considered breaking changes for this library version. #[derive(Debug, Clone)] pub enum ExId { Root, @@ -17,7 +21,10 @@ const TYPE_ROOT: u8 = 0; const TYPE_ID: u8 = 1; impl ExId { - /// Serialize the ExId to a byte array. + /// Serialize this object ID to a byte array. + /// + /// This serialization format is versioned and incompatible changes to it will be considered a + /// breaking change for the version of this library. pub fn to_bytes(&self) -> Vec { // The serialized format is // diff --git a/rust/automerge/src/keys.rs b/rust/automerge/src/keys.rs index f8e0c676..838015ef 100644 --- a/rust/automerge/src/keys.rs +++ b/rust/automerge/src/keys.rs @@ -1,5 +1,9 @@ use crate::{query, Automerge}; +/// An iterator over the keys of an object +/// +/// This is returned by [`crate::ReadDoc::keys`] and method. The returned item is either +/// the keys of a map, or the encoded element IDs of a sequence. #[derive(Debug)] pub struct Keys<'a, 'k> { keys: Option>, diff --git a/rust/automerge/src/keys_at.rs b/rust/automerge/src/keys_at.rs index c957e175..fd747bbc 100644 --- a/rust/automerge/src/keys_at.rs +++ b/rust/automerge/src/keys_at.rs @@ -1,5 +1,9 @@ use crate::{query, Automerge}; +/// An iterator over the keys of an object at a particular point in history +/// +/// This is returned by [`crate::ReadDoc::keys_at`] method. The returned item is either the keys of a map, +/// or the encoded element IDs of a sequence. #[derive(Debug)] pub struct KeysAt<'a, 'k> { keys: Option>, diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index 58f5b263..bafd8983 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -1,3 +1,190 @@ +//! # Automerge +//! +//! Automerge is a library of data structures for building collaborative, +//! [local-first](https://www.inkandswitch.com/local-first/) applications. The +//! idea of automerge is to provide a data structure which is quite general, +//! \- consisting of nested key/value maps and/or lists - which can be modified +//! entirely locally but which can at any time be merged with other instances of +//! the same data structure. +//! +//! In addition to the core data structure (which we generally refer to as a +//! "document"), we also provide an implementation of a sync protocol (in +//! [`crate::sync`]) which can be used over any reliable in-order transport; and +//! an efficient binary storage format. +//! +//! This crate is organised around two representations of a document - +//! [`Automerge`] and [`AutoCommit`]. The difference between the two is that +//! [`AutoCommit`] manages transactions for you. Both of these representations +//! implement [`ReadDoc`] for reading values from a document and +//! [`sync::SyncDoc`] for taking part in the sync protocol. [`AutoCommit`] +//! directly implements [`transaction::Transactable`] for making changes to a +//! document, whilst [`Automerge`] requires you to explicitly create a +//! [`transaction::Transaction`]. +//! +//! NOTE: The API this library provides for modifying data is quite low level +//! (somewhat analogous to directly creating JSON values rather than using +//! `serde` derive macros or equivalent). If you're writing a Rust application which uses automerge +//! you may want to look at [autosurgeon](https://github.com/automerge/autosurgeon). +//! +//! ## Data Model +//! +//! An automerge document is a map from strings to values +//! ([`Value`]) where values can be either +//! +//! * A nested composite value which is either +//! * A map from strings to values ([`ObjType::Map`]) +//! * A list of values ([`ObjType::List`]) +//! * A text object (a sequence of unicode characters) ([`ObjType::Text`]) +//! * A primitive value ([`ScalarValue`]) which is one of +//! * A string +//! * A 64 bit floating point number +//! * A signed 64 bit integer +//! * An unsigned 64 bit integer +//! * A boolean +//! * A counter object (a 64 bit integer which merges by addition) +//! ([`ScalarValue::Counter`]) +//! * A timestamp (a 64 bit integer which is milliseconds since the unix epoch) +//! +//! All composite values have an ID ([`ObjId`]) which is created when the value +//! is inserted into the document or is the root object ID [`ROOT`]. Values in +//! the document are then referred to by the pair (`object ID`, `key`). The +//! `key` is represented by the [`Prop`] type and is either a string for a maps, +//! or an index for sequences. +//! +//! ### Conflicts +//! +//! There are some things automerge cannot merge sensibly. For example, two +//! actors concurrently setting the key "name" to different values. In this case +//! automerge will pick a winning value in a random but deterministic way, but +//! the conflicting value is still available via the [`ReadDoc::get_all`] method. +//! +//! ### Change hashes and historical values +//! +//! Like git, points in the history of a document are identified by hash. Unlike +//! git there can be multiple hashes representing a particular point (because +//! automerge supports concurrent changes). These hashes can be obtained using +//! either [`Automerge::get_heads`] or [`AutoCommit::get_heads`] (note these +//! methods are not part of [`ReadDoc`] because in the case of [`AutoCommit`] it +//! requires a mutable reference to the document). +//! +//! These hashes can be used to read values from the document at a particular +//! point in history using the various `*_at` methods on [`ReadDoc`] which take a +//! slice of [`ChangeHash`] as an argument. +//! +//! ### Actor IDs +//! +//! Any change to an automerge document is made by an actor, represented by an +//! [`ActorId`]. An actor ID is any random sequence of bytes but each change by +//! the same actor ID must be sequential. This often means you will want to +//! maintain at least one actor ID per device. It is fine to generate a new +//! actor ID for each change, but be aware that each actor ID takes up space in +//! a document so if you expect a document to be long lived and/or to have many +//! changes then you should try to reuse actor IDs where possible. +//! +//! ### Text Encoding +//! +//! Both [`Automerge`] and [`AutoCommit`] provide a `with_encoding` method which +//! allows you to specify the [`crate::TextEncoding`] which is used for +//! interpreting the indexes passed to methods like [`ReadDoc::list_range`] or +//! [`transaction::Transactable::splice`]. The default encoding is UTF-8, but +//! you can switch to UTF-16. +//! +//! ## Sync Protocol +//! +//! See the [`sync`] module. +//! +//! ## Serde serialization +//! +//! Sometimes you just want to get the JSON value of an automerge document. For +//! this you can use [`AutoSerde`], which implements `serde::Serialize` for an +//! automerge document. +//! +//! ## Example +//! +//! Let's create a document representing an address book. +//! +//! ``` +//! use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc}; +//! +//! # fn main() -> Result<(), Box> { +//! let mut doc = AutoCommit::new(); +//! +//! // `put_object` creates a nested object in the root key/value map and +//! // returns the ID of the new object, in this case a list. +//! let contacts = doc.put_object(automerge::ROOT, "contacts", ObjType::List)?; +//! +//! // Now we can insert objects into the list +//! let alice = doc.insert_object(&contacts, 0, ObjType::Map)?; +//! +//! // Finally we can set keys in the "alice" map +//! doc.put(&alice, "name", "Alice")?; +//! doc.put(&alice, "email", "alice@example.com")?; +//! +//! // Create another contact +//! let bob = doc.insert_object(&contacts, 1, ObjType::Map)?; +//! doc.put(&bob, "name", "Bob")?; +//! doc.put(&bob, "email", "bob@example.com")?; +//! +//! // Now we save the address book, we can put this in a file +//! let data: Vec = doc.save(); +//! # Ok(()) +//! # } +//! ``` +//! +//! Now modify this document on two separate devices and merge the modifications. +//! +//! ``` +//! use std::borrow::Cow; +//! use automerge::{ObjType, AutoCommit, transaction::Transactable, ReadDoc}; +//! +//! # fn main() -> Result<(), Box> { +//! # let mut doc = AutoCommit::new(); +//! # let contacts = doc.put_object(automerge::ROOT, "contacts", ObjType::List)?; +//! # let alice = doc.insert_object(&contacts, 0, ObjType::Map)?; +//! # doc.put(&alice, "name", "Alice")?; +//! # doc.put(&alice, "email", "alice@example.com")?; +//! # let bob = doc.insert_object(&contacts, 1, ObjType::Map)?; +//! # doc.put(&bob, "name", "Bob")?; +//! # doc.put(&bob, "email", "bob@example.com")?; +//! # let saved: Vec = doc.save(); +//! +//! // Load the document on the first device and change alices email +//! let mut doc1 = AutoCommit::load(&saved)?; +//! let contacts = match doc1.get(automerge::ROOT, "contacts")? { +//! Some((automerge::Value::Object(ObjType::List), contacts)) => contacts, +//! _ => panic!("contacts should be a list"), +//! }; +//! let alice = match doc1.get(&contacts, 0)? { +//! Some((automerge::Value::Object(ObjType::Map), alice)) => alice, +//! _ => panic!("alice should be a map"), +//! }; +//! doc1.put(&alice, "email", "alicesnewemail@example.com")?; +//! +//! +//! // Load the document on the second device and change bobs name +//! let mut doc2 = AutoCommit::load(&saved)?; +//! let contacts = match doc2.get(automerge::ROOT, "contacts")? { +//! Some((automerge::Value::Object(ObjType::List), contacts)) => contacts, +//! _ => panic!("contacts should be a list"), +//! }; +//! let bob = match doc2.get(&contacts, 1)? { +//! Some((automerge::Value::Object(ObjType::Map), bob)) => bob, +//! _ => panic!("bob should be a map"), +//! }; +//! doc2.put(&bob, "name", "Robert")?; +//! +//! // Finally, we can merge the changes from the two devices +//! doc1.merge(&mut doc2)?; +//! let bobsname: Option = doc1.get(&bob, "name")?.map(|(v, _)| v); +//! assert_eq!(bobsname, Some(automerge::Value::Scalar(Cow::Owned("Robert".into())))); +//! +//! let alices_email: Option = doc1.get(&alice, "email")?.map(|(v, _)| v); +//! assert_eq!(alices_email, Some(automerge::Value::Scalar(Cow::Owned("alicesnewemail@example.com".into())))); +//! # Ok(()) +//! # } +//! ``` +//! + #![doc( html_logo_url = "https://raw.githubusercontent.com/automerge/automerge-rs/main/img/brandmark.svg", html_favicon_url = "https:///raw.githubusercontent.com/automerge/automerge-rs/main/img/favicon.ico" @@ -71,11 +258,12 @@ mod list_range; mod list_range_at; mod map_range; mod map_range_at; -mod op_observer; +pub mod op_observer; mod op_set; mod op_tree; mod parents; mod query; +mod read; mod sequence_tree; mod storage; pub mod sync; @@ -105,9 +293,12 @@ pub use op_observer::OpObserver; pub use op_observer::Patch; pub use op_observer::VecOpObserver; pub use parents::{Parent, Parents}; +pub use read::ReadDoc; +#[doc(hidden)] pub use sequence_tree::SequenceTree; pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding}; pub use value::{ScalarValue, Value}; pub use values::Values; +/// The object ID for the root map of a document pub const ROOT: ObjId = ObjId::Root; diff --git a/rust/automerge/src/list_range.rs b/rust/automerge/src/list_range.rs index ae7b2aa5..a043da72 100644 --- a/rust/automerge/src/list_range.rs +++ b/rust/automerge/src/list_range.rs @@ -3,6 +3,9 @@ use crate::{exid::ExId, Value}; use crate::{query, Automerge}; use std::ops::RangeBounds; +/// An iterator over the elements of a list object +/// +/// This is returned by the [`crate::ReadDoc::list_range`] method #[derive(Debug)] pub struct ListRange<'a, R: RangeBounds> { range: Option>, diff --git a/rust/automerge/src/list_range_at.rs b/rust/automerge/src/list_range_at.rs index 37db9677..ce8f5a46 100644 --- a/rust/automerge/src/list_range_at.rs +++ b/rust/automerge/src/list_range_at.rs @@ -3,6 +3,9 @@ use std::ops::RangeBounds; use crate::{query, Automerge}; +/// An iterator over the elements of a list object at a particular set of heads +/// +/// This is returned by the [`crate::ReadDoc::list_range_at`] method #[derive(Debug)] pub struct ListRangeAt<'a, R: RangeBounds> { range: Option>, diff --git a/rust/automerge/src/map_range.rs b/rust/automerge/src/map_range.rs index 8029b84d..ad33ebf5 100644 --- a/rust/automerge/src/map_range.rs +++ b/rust/automerge/src/map_range.rs @@ -3,6 +3,9 @@ use std::ops::RangeBounds; use crate::{query, Automerge}; +/// An iterator over the keys and values of a map object +/// +/// This is returned by the [`crate::ReadDoc::map_range`] method #[derive(Debug)] pub struct MapRange<'a, R: RangeBounds> { range: Option>, diff --git a/rust/automerge/src/map_range_at.rs b/rust/automerge/src/map_range_at.rs index b2eb3fb2..8d008e89 100644 --- a/rust/automerge/src/map_range_at.rs +++ b/rust/automerge/src/map_range_at.rs @@ -3,6 +3,9 @@ use std::ops::RangeBounds; use crate::{query, Automerge}; +/// An iterator over the keys and values of a map object as at a particuar heads +/// +/// This is returned by the [`crate::ReadDoc::map_range_at`] method #[derive(Debug)] pub struct MapRangeAt<'a, R: RangeBounds> { range: Option>, diff --git a/rust/automerge/src/op_observer.rs b/rust/automerge/src/op_observer.rs index 0d082219..5b33c21f 100644 --- a/rust/automerge/src/op_observer.rs +++ b/rust/automerge/src/op_observer.rs @@ -1,8 +1,11 @@ use crate::exid::ExId; -use crate::Automerge; use crate::Prop; +use crate::ReadDoc; use crate::Value; +mod compose; +pub use compose::compose; + /// An observer of operations applied to the document. pub trait OpObserver { /// A new value has been inserted into the given object. @@ -12,15 +15,16 @@ pub trait OpObserver { /// - `index`: the index the new value has been inserted at. /// - `tagged_value`: the value that has been inserted and the id of the operation that did the /// insert. - fn insert( + fn insert( &mut self, - doc: &Automerge, + doc: &R, objid: ExId, index: usize, tagged_value: (Value<'_>, ExId), ); - fn splice_text(&mut self, _doc: &Automerge, _objid: ExId, _index: usize, _value: &str); + /// Some text has been spliced into a text object + fn splice_text(&mut self, _doc: &R, _objid: ExId, _index: usize, _value: &str); /// A new value has been put into the given object. /// @@ -30,9 +34,9 @@ pub trait OpObserver { /// - `tagged_value`: the value that has been put into the object and the id of the operation /// that did the put. /// - `conflict`: whether this put conflicts with other operations. - fn put( + fn put( &mut self, - doc: &Automerge, + doc: &R, objid: ExId, prop: Prop, tagged_value: (Value<'_>, ExId), @@ -49,9 +53,9 @@ pub trait OpObserver { /// - `tagged_value`: the value that has been put into the object and the id of the operation /// that did the put. /// - `conflict`: whether this put conflicts with other operations. - fn expose( + fn expose( &mut self, - doc: &Automerge, + doc: &R, objid: ExId, prop: Prop, tagged_value: (Value<'_>, ExId), @@ -63,7 +67,7 @@ pub trait OpObserver { /// - `doc`: a handle to the doc after the op has been inserted, can be used to query information /// - `objid`: the object that has been put into. /// - `prop`: the prop that the value as been put at. - fn flag_conflict(&mut self, _doc: &Automerge, _objid: ExId, _prop: Prop) {} + fn flag_conflict(&mut self, _doc: &R, _objid: ExId, _prop: Prop) {} /// A counter has been incremented. /// @@ -72,14 +76,20 @@ pub trait OpObserver { /// - `prop`: they prop that the chounter is at. /// - `tagged_value`: the amount the counter has been incremented by, and the the id of the /// increment operation. - fn increment(&mut self, doc: &Automerge, objid: ExId, prop: Prop, tagged_value: (i64, ExId)); + fn increment( + &mut self, + doc: &R, + objid: ExId, + prop: Prop, + tagged_value: (i64, ExId), + ); /// A map value has beeen deleted. /// /// - `doc`: a handle to the doc after the op has been inserted, can be used to query information /// - `objid`: the object that has been deleted in. /// - `prop`: the prop to be deleted - fn delete(&mut self, doc: &Automerge, objid: ExId, prop: Prop) { + fn delete(&mut self, doc: &R, objid: ExId, prop: Prop) { match prop { Prop::Map(k) => self.delete_map(doc, objid, &k), Prop::Seq(i) => self.delete_seq(doc, objid, i, 1), @@ -91,7 +101,7 @@ pub trait OpObserver { /// - `doc`: a handle to the doc after the op has been inserted, can be used to query information /// - `objid`: the object that has been deleted in. /// - `key`: the map key to be deleted - fn delete_map(&mut self, doc: &Automerge, objid: ExId, key: &str); + fn delete_map(&mut self, doc: &R, objid: ExId, key: &str); /// A one or more list values have beeen deleted. /// @@ -99,21 +109,7 @@ pub trait OpObserver { /// - `objid`: the object that has been deleted in. /// - `index`: the index of the deletion /// - `num`: the number of sequential elements deleted - fn delete_seq(&mut self, doc: &Automerge, objid: ExId, index: usize, num: usize); - - /// Branch of a new op_observer later to be merged - /// - /// Called by AutoCommit when creating a new transaction. Observer branch - /// will be merged on `commit()` or thrown away on `rollback()` - /// - fn branch(&self) -> Self; - - /// Merge observed information from a transaction. - /// - /// Called by AutoCommit on `commit()` - /// - /// - `other`: Another Op Observer of the same type - fn merge(&mut self, other: &Self); + fn delete_seq(&mut self, doc: &R, objid: ExId, index: usize, num: usize); /// Whether to call sequence methods or `splice_text` when encountering changes in text /// @@ -123,21 +119,41 @@ pub trait OpObserver { } } +/// An observer which can be branched +/// +/// This is used when observing operations in a transaction. In this case `branch` will be called +/// at the beginning of the transaction to return a new observer and then `merge` will be called +/// with the branched observer as `other` when the transaction is comitted. +pub trait BranchableObserver { + /// Branch of a new op_observer later to be merged + /// + /// Called when creating a new transaction. Observer branch will be merged on `commit()` or + /// thrown away on `rollback()` + fn branch(&self) -> Self; + + /// Merge observed information from a transaction. + /// + /// Called by AutoCommit on `commit()` + /// + /// - `other`: Another Op Observer of the same type + fn merge(&mut self, other: &Self); +} + impl OpObserver for () { - fn insert( + fn insert( &mut self, - _doc: &Automerge, + _doc: &R, _objid: ExId, _index: usize, _tagged_value: (Value<'_>, ExId), ) { } - fn splice_text(&mut self, _doc: &Automerge, _objid: ExId, _index: usize, _value: &str) {} + fn splice_text(&mut self, _doc: &R, _objid: ExId, _index: usize, _value: &str) {} - fn put( + fn put( &mut self, - _doc: &Automerge, + _doc: &R, _objid: ExId, _prop: Prop, _tagged_value: (Value<'_>, ExId), @@ -145,9 +161,9 @@ impl OpObserver for () { ) { } - fn expose( + fn expose( &mut self, - _doc: &Automerge, + _doc: &R, _objid: ExId, _prop: Prop, _tagged_value: (Value<'_>, ExId), @@ -155,21 +171,22 @@ impl OpObserver for () { ) { } - fn increment( + fn increment( &mut self, - _doc: &Automerge, + _doc: &R, _objid: ExId, _prop: Prop, _tagged_value: (i64, ExId), ) { } - fn delete_map(&mut self, _doc: &Automerge, _objid: ExId, _key: &str) {} + fn delete_map(&mut self, _doc: &R, _objid: ExId, _key: &str) {} - fn delete_seq(&mut self, _doc: &Automerge, _objid: ExId, _index: usize, _num: usize) {} + fn delete_seq(&mut self, _doc: &R, _objid: ExId, _index: usize, _num: usize) {} +} +impl BranchableObserver for () { fn merge(&mut self, _other: &Self) {} - fn branch(&self) -> Self {} } @@ -188,8 +205,14 @@ impl VecOpObserver { } impl OpObserver for VecOpObserver { - fn insert(&mut self, doc: &Automerge, obj: ExId, index: usize, (value, id): (Value<'_>, ExId)) { - if let Ok(mut p) = doc.parents(&obj) { + fn insert( + &mut self, + doc: &R, + obj: ExId, + index: usize, + (value, id): (Value<'_>, ExId), + ) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Insert { obj, path: p.path(), @@ -199,8 +222,8 @@ impl OpObserver for VecOpObserver { } } - fn splice_text(&mut self, doc: &Automerge, obj: ExId, index: usize, value: &str) { - if let Ok(mut p) = doc.parents(&obj) { + fn splice_text(&mut self, doc: &R, obj: ExId, index: usize, value: &str) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Splice { obj, path: p.path(), @@ -210,15 +233,15 @@ impl OpObserver for VecOpObserver { } } - fn put( + fn put( &mut self, - doc: &Automerge, + doc: &R, obj: ExId, prop: Prop, (value, id): (Value<'_>, ExId), conflict: bool, ) { - if let Ok(mut p) = doc.parents(&obj) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Put { obj, path: p.path(), @@ -229,15 +252,15 @@ impl OpObserver for VecOpObserver { } } - fn expose( + fn expose( &mut self, - doc: &Automerge, + doc: &R, obj: ExId, prop: Prop, (value, id): (Value<'_>, ExId), conflict: bool, ) { - if let Ok(mut p) = doc.parents(&obj) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Expose { obj, path: p.path(), @@ -248,8 +271,8 @@ impl OpObserver for VecOpObserver { } } - fn increment(&mut self, doc: &Automerge, obj: ExId, prop: Prop, tagged_value: (i64, ExId)) { - if let Ok(mut p) = doc.parents(&obj) { + fn increment(&mut self, doc: &R, obj: ExId, prop: Prop, tagged_value: (i64, ExId)) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Increment { obj, path: p.path(), @@ -259,8 +282,8 @@ impl OpObserver for VecOpObserver { } } - fn delete_map(&mut self, doc: &Automerge, obj: ExId, key: &str) { - if let Ok(mut p) = doc.parents(&obj) { + fn delete_map(&mut self, doc: &R, obj: ExId, key: &str) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Delete { obj, path: p.path(), @@ -270,8 +293,8 @@ impl OpObserver for VecOpObserver { } } - fn delete_seq(&mut self, doc: &Automerge, obj: ExId, index: usize, num: usize) { - if let Ok(mut p) = doc.parents(&obj) { + fn delete_seq(&mut self, doc: &R, obj: ExId, index: usize, num: usize) { + if let Ok(p) = doc.parents(&obj) { self.patches.push(Patch::Delete { obj, path: p.path(), @@ -280,7 +303,9 @@ impl OpObserver for VecOpObserver { }) } } +} +impl BranchableObserver for VecOpObserver { fn merge(&mut self, other: &Self) { self.patches.extend_from_slice(other.patches.as_slice()) } diff --git a/rust/automerge/src/op_observer/compose.rs b/rust/automerge/src/op_observer/compose.rs new file mode 100644 index 00000000..92fe3b1e --- /dev/null +++ b/rust/automerge/src/op_observer/compose.rs @@ -0,0 +1,102 @@ +use super::OpObserver; + +pub fn compose<'a, O1: OpObserver, O2: OpObserver>( + obs1: &'a mut O1, + obs2: &'a mut O2, +) -> impl OpObserver + 'a { + ComposeObservers { obs1, obs2 } +} + +struct ComposeObservers<'a, O1: OpObserver, O2: OpObserver> { + obs1: &'a mut O1, + obs2: &'a mut O2, +} + +impl<'a, O1: OpObserver, O2: OpObserver> OpObserver for ComposeObservers<'a, O1, O2> { + fn insert( + &mut self, + doc: &R, + objid: crate::ObjId, + index: usize, + tagged_value: (crate::Value<'_>, crate::ObjId), + ) { + self.obs1 + .insert(doc, objid.clone(), index, tagged_value.clone()); + self.obs2.insert(doc, objid, index, tagged_value); + } + + fn splice_text( + &mut self, + doc: &R, + objid: crate::ObjId, + index: usize, + value: &str, + ) { + self.obs1.splice_text(doc, objid.clone(), index, value); + self.obs2.splice_text(doc, objid, index, value); + } + + fn put( + &mut self, + doc: &R, + objid: crate::ObjId, + prop: crate::Prop, + tagged_value: (crate::Value<'_>, crate::ObjId), + conflict: bool, + ) { + self.obs1.put( + doc, + objid.clone(), + prop.clone(), + tagged_value.clone(), + conflict, + ); + self.obs2.put(doc, objid, prop, tagged_value, conflict); + } + + fn expose( + &mut self, + doc: &R, + objid: crate::ObjId, + prop: crate::Prop, + tagged_value: (crate::Value<'_>, crate::ObjId), + conflict: bool, + ) { + self.obs1.expose( + doc, + objid.clone(), + prop.clone(), + tagged_value.clone(), + conflict, + ); + self.obs2.expose(doc, objid, prop, tagged_value, conflict); + } + + fn increment( + &mut self, + doc: &R, + objid: crate::ObjId, + prop: crate::Prop, + tagged_value: (i64, crate::ObjId), + ) { + self.obs1 + .increment(doc, objid.clone(), prop.clone(), tagged_value.clone()); + self.obs2.increment(doc, objid, prop, tagged_value); + } + + fn delete_map(&mut self, doc: &R, objid: crate::ObjId, key: &str) { + self.obs1.delete_map(doc, objid.clone(), key); + self.obs2.delete_map(doc, objid, key); + } + + fn delete_seq( + &mut self, + doc: &R, + objid: crate::ObjId, + index: usize, + num: usize, + ) { + self.obs2.delete_seq(doc, objid.clone(), index, num); + self.obs2.delete_seq(doc, objid, index, num); + } +} diff --git a/rust/automerge/src/parents.rs b/rust/automerge/src/parents.rs index 76c4bba1..e1c5cc66 100644 --- a/rust/automerge/src/parents.rs +++ b/rust/automerge/src/parents.rs @@ -3,6 +3,14 @@ use crate::op_set::OpSet; use crate::types::{ListEncoding, ObjId}; use crate::{exid::ExId, Prop}; +/// An iterator over the "parents" of an object +/// +/// The "parent" of an object in this context is the ([`ExId`], [`Prop`]) pair which specifies the +/// location of this object in the composite object which contains it. Each element in the iterator +/// is a [`Parent`], yielded in reverse order. This means that once the iterator returns `None` you +/// have reached the root of the document. +/// +/// This is returned by [`crate::ReadDoc::parents`] #[derive(Debug)] pub struct Parents<'a> { pub(crate) obj: ObjId, @@ -10,9 +18,10 @@ pub struct Parents<'a> { } impl<'a> Parents<'a> { - // returns the path to the object - // works even if the object or a parent has been deleted - pub fn path(&mut self) -> Vec<(ExId, Prop)> { + /// Return the path this `Parents` represents + /// + /// This is _not_ in reverse order. + pub fn path(self) -> Vec<(ExId, Prop)> { let mut path = self .map(|Parent { obj, prop, .. }| (obj, prop)) .collect::>(); @@ -20,10 +29,8 @@ impl<'a> Parents<'a> { path } - // returns the path to the object - // if the object or one of its parents has been deleted or conflicted out - // returns none - pub fn visible_path(&mut self) -> Option> { + /// Like `path` but returns `None` if the target is not visible + pub fn visible_path(self) -> Option> { let mut path = Vec::new(); for Parent { obj, prop, visible } in self { if !visible { @@ -59,17 +66,25 @@ impl<'a> Iterator for Parents<'a> { } } +/// A component of a path to an object #[derive(Debug, PartialEq, Eq)] pub struct Parent { + /// The object ID this component refers to pub obj: ExId, + /// The property within `obj` this component refers to pub prop: Prop, + /// Whether this component is "visible" + /// + /// An "invisible" component is one where the property is hidden, either because it has been + /// deleted or because there is a conflict on this (object, property) pair and this value does + /// not win the conflict. pub visible: bool, } #[cfg(test)] mod tests { use super::Parent; - use crate::{transaction::Transactable, Prop}; + use crate::{transaction::Transactable, Prop, ReadDoc}; #[test] fn test_invisible_parents() { diff --git a/rust/automerge/src/read.rs b/rust/automerge/src/read.rs new file mode 100644 index 00000000..6d479718 --- /dev/null +++ b/rust/automerge/src/read.rs @@ -0,0 +1,199 @@ +use crate::{ + error::AutomergeError, exid::ExId, keys::Keys, keys_at::KeysAt, list_range::ListRange, + list_range_at::ListRangeAt, map_range::MapRange, map_range_at::MapRangeAt, parents::Parents, + values::Values, Change, ChangeHash, ObjType, Prop, Value, +}; + +use std::ops::RangeBounds; + +/// Methods for reading values from an automerge document +/// +/// Many of the methods on this trait have an alternate `*_at` version which +/// takes an additional argument of `&[ChangeHash]`. This allows you to retrieve +/// the value at a particular point in the document history identified by the +/// given change hashes. +pub trait ReadDoc { + /// Get the parents of an object in the document tree. + /// + /// See the documentation for [`Parents`] for more details. + /// + /// ### Errors + /// + /// Returns an error when the id given is not the id of an object in this document. + /// This function does not get the parents of scalar values contained within objects. + /// + /// ### Experimental + /// + /// This function may in future be changed to allow getting the parents from the id of a scalar + /// value. + fn parents>(&self, obj: O) -> Result, AutomergeError>; + + /// Get the path to an object + /// + /// "path" here means the sequence of `(object Id, key)` pairs which leads + /// to the object in question. + /// + /// ### Errors + /// + /// * If the object ID `obj` is not in the document + fn path_to_object>(&self, obj: O) -> Result, AutomergeError>; + + /// Get the keys of the object `obj`. + /// + /// For a map this returns the keys of the map. + /// For a list this returns the element ids (opids) encoded as strings. + fn keys>(&self, obj: O) -> Keys<'_, '_>; + + /// Get the keys of the object `obj` as at `heads` + /// + /// See [`Self::keys`] + fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_>; + + /// Iterate over the keys and values of the map `obj` in the given range. + /// + /// If the object correspoding to `obj` is a list then this will return an empty iterator + /// + /// The returned iterator yields `(key, value, exid)` tuples, where the + /// third element is the ID of the operation which created the value. + fn map_range, R: RangeBounds>( + &self, + obj: O, + range: R, + ) -> MapRange<'_, R>; + + /// Iterate over the keys and values of the map `obj` in the given range as + /// at `heads` + /// + /// If the object correspoding to `obj` is a list then this will return an empty iterator + /// + /// The returned iterator yields `(key, value, exid)` tuples, where the + /// third element is the ID of the operation which created the value. + /// + /// See [`Self::map_range`] + fn map_range_at, R: RangeBounds>( + &self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> MapRangeAt<'_, R>; + + /// Iterate over the indexes and values of the list or text `obj` in the given range. + /// + /// The reuturned iterator yields `(index, value, exid)` tuples, where the third + /// element is the ID of the operation which created the value. + fn list_range, R: RangeBounds>( + &self, + obj: O, + range: R, + ) -> ListRange<'_, R>; + + /// Iterate over the indexes and values of the list or text `obj` in the given range as at `heads` + /// + /// The returned iterator yields `(index, value, exid)` tuples, where the third + /// element is the ID of the operation which created the value. + /// + /// See [`Self::list_range`] + fn list_range_at, R: RangeBounds>( + &self, + obj: O, + range: R, + heads: &[ChangeHash], + ) -> ListRangeAt<'_, R>; + + /// Iterate over the values in a map, list, or text object + /// + /// The returned iterator yields `(value, exid)` tuples, where the second element + /// is the ID of the operation which created the value. + fn values>(&self, obj: O) -> Values<'_>; + + /// Iterate over the values in a map, list, or text object as at `heads` + /// + /// The returned iterator yields `(value, exid)` tuples, where the second element + /// is the ID of the operation which created the value. + /// + /// See [`Self::values`] + fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_>; + + /// Get the length of the given object. + /// + /// If the given object is not in this document this method will return `0` + fn length>(&self, obj: O) -> usize; + + /// Get the length of the given object as at `heads` + /// + /// If the given object is not in this document this method will return `0` + /// + /// See [`Self::length`] + fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize; + + /// Get the type of this object, if it is an object. + fn object_type>(&self, obj: O) -> Result; + + /// Get the string represented by the given text object. + fn text>(&self, obj: O) -> Result; + + /// Get the string represented by the given text object as at `heads`, see + /// [`Self::text`] + fn text_at>( + &self, + obj: O, + heads: &[ChangeHash], + ) -> Result; + + /// Get a value out of the document. + /// + /// This returns a tuple of `(value, object ID)`. This is for two reasons: + /// + /// 1. If `value` is an object (represented by `Value::Object`) then the ID + /// is the ID of that object. This can then be used to retrieve nested + /// values from the document. + /// 2. Even if `value` is a scalar, the ID represents the operation which + /// created the value. This is useful if there are conflicting values for + /// this key as each value is tagged with the ID. + /// + /// In the case of a key which has conflicting values, this method will + /// return a single arbitrarily chosen value. This value will be chosen + /// deterministically on all nodes. If you want to get all the values for a + /// key use [`Self::get_all`]. + fn get, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError>; + + /// Get the value of the given key as at `heads`, see `[Self::get]` + fn get_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError>; + + /// Get all conflicting values out of the document at this prop that conflict. + /// + /// If there are multiple conflicting values for a given key this method + /// will return all of them, with each value tagged by the ID of the + /// operation which created it. + fn get_all, P: Into>( + &self, + obj: O, + prop: P, + ) -> Result, ExId)>, AutomergeError>; + + /// Get all possibly conflicting values for a key as at `heads` + /// + /// See `[Self::get_all]` + fn get_all_at, P: Into>( + &self, + obj: O, + prop: P, + heads: &[ChangeHash], + ) -> Result, ExId)>, AutomergeError>; + + /// Get the hashes of the changes in this document that aren't transitive dependencies of the + /// given `heads`. + fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec; + + /// Get a change by its hash. + fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&Change>; +} diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index 1545f954..5d71d989 100644 --- a/rust/automerge/src/sync.rs +++ b/rust/automerge/src/sync.rs @@ -1,10 +1,79 @@ +//! # Sync Protocol +//! +//! The sync protocol is based on this paper: +//! , it assumes a reliable in-order stream +//! between two peers who are synchronizing a document. +//! +//! Each peer maintains a [`State`] for each peer they are synchronizing with. +//! This state tracks things like what the heads of the other peer are and +//! whether there are in-flight messages. Anything which implements [`SyncDoc`] +//! can take part in the sync protocol. The flow goes something like this: +//! +//! * The initiating peer creates an empty [`State`] and then calls +//! [`SyncDoc::generate_sync_message`] to generate new sync message and sends +//! it to the receiving peer. +//! * The receiving peer receives a message from the initiator, creates a new +//! [`State`], and calls [`SyncDoc::receive_sync_message`] on it's view of the +//! document +//! * The receiving peer then calls [`SyncDoc::generate_sync_message`] to generate +//! a new sync message and send it back to the initiator +//! * From this point on each peer operates in a loop, receiving a sync message +//! from the other peer and then generating a new message to send back. +//! +//! ## Example +//! +//! ``` +//! use automerge::{transaction::Transactable, sync::{self, SyncDoc}, ReadDoc}; +//! # fn main() -> Result<(), automerge::AutomergeError> { +//! // Create a document on peer1 +//! let mut peer1 = automerge::AutoCommit::new(); +//! peer1.put(automerge::ROOT, "key", "value")?; +//! +//! // Create a state to track our sync with peer2 +//! let mut peer1_state = sync::State::new(); +//! // Generate the initial message to send to peer2, unwrap for brevity +//! let message1to2 = peer1.sync().generate_sync_message(&mut peer1_state).unwrap(); +//! +//! // We receive the message on peer2. We don't have a document at all yet +//! // so we create one +//! let mut peer2 = automerge::AutoCommit::new(); +//! // We don't have a state for peer1 (it's a new connection), so we create one +//! let mut peer2_state = sync::State::new(); +//! // Now receive the message from peer 1 +//! peer2.sync().receive_sync_message(&mut peer2_state, message1to2)?; +//! +//! // Now we loop, sending messages from one to two and two to one until +//! // neither has anything new to send +//! +//! loop { +//! let two_to_one = peer2.sync().generate_sync_message(&mut peer2_state); +//! if let Some(message) = two_to_one.as_ref() { +//! println!("two to one"); +//! peer1.sync().receive_sync_message(&mut peer1_state, message.clone())?; +//! } +//! let one_to_two = peer1.sync().generate_sync_message(&mut peer1_state); +//! if let Some(message) = one_to_two.as_ref() { +//! println!("one to two"); +//! peer2.sync().receive_sync_message(&mut peer2_state, message.clone())?; +//! } +//! if two_to_one.is_none() && one_to_two.is_none() { +//! break; +//! } +//! } +//! +//! assert_eq!(peer2.get(automerge::ROOT, "key")?.unwrap().0.to_str(), Some("value")); +//! +//! # Ok(()) +//! # } +//! ``` + use itertools::Itertools; use serde::ser::SerializeMap; use std::collections::{HashMap, HashSet}; use crate::{ storage::{parse, Change as StoredChange, ReadChangeOpError}, - Automerge, AutomergeError, Change, ChangeHash, OpObserver, + Automerge, AutomergeError, Change, ChangeHash, OpObserver, ReadDoc, }; mod bloom; @@ -14,10 +83,38 @@ pub use bloom::{BloomFilter, DecodeError as DecodeBloomError}; pub use state::DecodeError as DecodeStateError; pub use state::{Have, State}; +/// A document which can take part in the sync protocol +/// +/// See the [module level documentation](crate::sync) for more details. +pub trait SyncDoc { + /// Generate a sync message for the remote peer represented by `sync_state` + /// + /// If this returns `None` then there are no new messages to send, either because we are + /// waiting for an acknolwedgement of an in-flight message, or because the remote is up to + /// date. + fn generate_sync_message(&self, sync_state: &mut State) -> Option; + + /// Apply a received sync message to this document and `sync_state` + fn receive_sync_message( + &mut self, + sync_state: &mut State, + message: Message, + ) -> Result<(), AutomergeError>; + + /// Apply a received sync message to this document and `sync_state`, observing any changes with + /// `op_observer` + fn receive_sync_message_with( + &mut self, + sync_state: &mut State, + message: Message, + op_observer: &mut Obs, + ) -> Result<(), AutomergeError>; +} + const MESSAGE_TYPE_SYNC: u8 = 0x42; // first byte of a sync message, for identification -impl Automerge { - pub fn generate_sync_message(&self, sync_state: &mut State) -> Option { +impl SyncDoc for Automerge { + fn generate_sync_message(&self, sync_state: &mut State) -> Option { let our_heads = self.get_heads(); let our_need = self.get_missing_deps(sync_state.their_heads.as_ref().unwrap_or(&vec![])); @@ -106,80 +203,25 @@ impl Automerge { Some(sync_message) } - pub fn receive_sync_message( + fn receive_sync_message( &mut self, sync_state: &mut State, message: Message, ) -> Result<(), AutomergeError> { - self.receive_sync_message_with::<()>(sync_state, message, None) + self.do_receive_sync_message::<()>(sync_state, message, None) } - pub fn receive_sync_message_with( + fn receive_sync_message_with( &mut self, sync_state: &mut State, message: Message, - op_observer: Option<&mut Obs>, + op_observer: &mut Obs, ) -> Result<(), AutomergeError> { - let before_heads = self.get_heads(); - - let Message { - heads: message_heads, - changes: message_changes, - need: message_need, - have: message_have, - } = message; - - let changes_is_empty = message_changes.is_empty(); - if !changes_is_empty { - self.apply_changes_with(message_changes, op_observer)?; - sync_state.shared_heads = advance_heads( - &before_heads.iter().collect(), - &self.get_heads().into_iter().collect(), - &sync_state.shared_heads, - ); - } - - // trim down the sent hashes to those that we know they haven't seen - self.filter_changes(&message_heads, &mut sync_state.sent_hashes)?; - - if changes_is_empty && message_heads == before_heads { - sync_state.last_sent_heads = message_heads.clone(); - } - - if sync_state.sent_hashes.is_empty() { - sync_state.in_flight = false; - } - - let known_heads = message_heads - .iter() - .filter(|head| self.get_change_by_hash(head).is_some()) - .collect::>(); - if known_heads.len() == message_heads.len() { - sync_state.shared_heads = message_heads.clone(); - sync_state.in_flight = false; - // If the remote peer has lost all its data, reset our state to perform a full resync - if message_heads.is_empty() { - sync_state.last_sent_heads = Default::default(); - sync_state.sent_hashes = Default::default(); - } - } else { - sync_state.shared_heads = sync_state - .shared_heads - .iter() - .chain(known_heads) - .copied() - .unique() - .sorted() - .collect::>(); - } - - sync_state.their_have = Some(message_have); - sync_state.their_heads = Some(message_heads); - sync_state.their_need = Some(message_need); - - Ok(()) + self.do_receive_sync_message(sync_state, message, Some(op_observer)) } +} +impl Automerge { fn make_bloom_filter(&self, last_sync: Vec) -> Have { let new_changes = self .get_changes(&last_sync) @@ -261,6 +303,72 @@ impl Automerge { Ok(changes_to_send) } } + + fn do_receive_sync_message( + &mut self, + sync_state: &mut State, + message: Message, + op_observer: Option<&mut Obs>, + ) -> Result<(), AutomergeError> { + let before_heads = self.get_heads(); + + let Message { + heads: message_heads, + changes: message_changes, + need: message_need, + have: message_have, + } = message; + + let changes_is_empty = message_changes.is_empty(); + if !changes_is_empty { + self.apply_changes_with(message_changes, op_observer)?; + sync_state.shared_heads = advance_heads( + &before_heads.iter().collect(), + &self.get_heads().into_iter().collect(), + &sync_state.shared_heads, + ); + } + + // trim down the sent hashes to those that we know they haven't seen + self.filter_changes(&message_heads, &mut sync_state.sent_hashes)?; + + if changes_is_empty && message_heads == before_heads { + sync_state.last_sent_heads = message_heads.clone(); + } + + if sync_state.sent_hashes.is_empty() { + sync_state.in_flight = false; + } + + let known_heads = message_heads + .iter() + .filter(|head| self.get_change_by_hash(head).is_some()) + .collect::>(); + if known_heads.len() == message_heads.len() { + sync_state.shared_heads = message_heads.clone(); + sync_state.in_flight = false; + // If the remote peer has lost all its data, reset our state to perform a full resync + if message_heads.is_empty() { + sync_state.last_sent_heads = Default::default(); + sync_state.sent_hashes = Default::default(); + } + } else { + sync_state.shared_heads = sync_state + .shared_heads + .iter() + .chain(known_heads) + .copied() + .unique() + .sorted() + .collect::>(); + } + + sync_state.their_have = Some(message_have); + sync_state.their_heads = Some(message_heads); + sync_state.their_need = Some(message_need); + + Ok(()) + } } #[derive(Debug, thiserror::Error)] @@ -545,8 +653,8 @@ mod tests { doc.put(crate::ROOT, "key", "value").unwrap(); let mut sync_state = State::new(); - assert!(doc.generate_sync_message(&mut sync_state).is_some()); - assert!(doc.generate_sync_message(&mut sync_state).is_none()); + assert!(doc.sync().generate_sync_message(&mut sync_state).is_some()); + assert!(doc.sync().generate_sync_message(&mut sync_state).is_none()); } #[test] @@ -556,11 +664,12 @@ mod tests { let mut s1 = State::new(); let mut s2 = State::new(); let m1 = doc1 + .sync() .generate_sync_message(&mut s1) .expect("message was none"); - doc2.receive_sync_message(&mut s2, m1).unwrap(); - let m2 = doc2.generate_sync_message(&mut s2); + doc2.sync().receive_sync_message(&mut s2, m1).unwrap(); + let m2 = doc2.sync().generate_sync_message(&mut s2); assert!(m2.is_none()); } @@ -584,9 +693,11 @@ mod tests { //// both sides report what they have but have no shared peer state let msg1to2 = doc1 + .sync() .generate_sync_message(&mut s1) .expect("initial sync from 1 to 2 was None"); let msg2to1 = doc2 + .sync() .generate_sync_message(&mut s2) .expect("initial sync message from 2 to 1 was None"); assert_eq!(msg1to2.changes.len(), 0); @@ -595,52 +706,57 @@ mod tests { assert_eq!(msg2to1.have[0].last_sync.len(), 0); //// doc1 and doc2 receive that message and update sync state - doc1.receive_sync_message(&mut s1, msg2to1).unwrap(); - doc2.receive_sync_message(&mut s2, msg1to2).unwrap(); + doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap(); + doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap(); //// now both reply with their local changes the other lacks //// (standard warning that 1% of the time this will result in a "need" message) let msg1to2 = doc1 + .sync() .generate_sync_message(&mut s1) .expect("first reply from 1 to 2 was None"); assert_eq!(msg1to2.changes.len(), 5); let msg2to1 = doc2 + .sync() .generate_sync_message(&mut s2) .expect("first reply from 2 to 1 was None"); assert_eq!(msg2to1.changes.len(), 5); //// both should now apply the changes - doc1.receive_sync_message(&mut s1, msg2to1).unwrap(); + doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap(); assert_eq!(doc1.get_missing_deps(&[]), Vec::new()); - doc2.receive_sync_message(&mut s2, msg1to2).unwrap(); + doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap(); assert_eq!(doc2.get_missing_deps(&[]), Vec::new()); //// The response acknowledges the changes received and sends no further changes let msg1to2 = doc1 + .sync() .generate_sync_message(&mut s1) .expect("second reply from 1 to 2 was None"); assert_eq!(msg1to2.changes.len(), 0); let msg2to1 = doc2 + .sync() .generate_sync_message(&mut s2) .expect("second reply from 2 to 1 was None"); assert_eq!(msg2to1.changes.len(), 0); //// After receiving acknowledgements, their shared heads should be equal - doc1.receive_sync_message(&mut s1, msg2to1).unwrap(); - doc2.receive_sync_message(&mut s2, msg1to2).unwrap(); + doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap(); + doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap(); assert_eq!(s1.shared_heads, s2.shared_heads); //// We're in sync, no more messages required - assert!(doc1.generate_sync_message(&mut s1).is_none()); - assert!(doc2.generate_sync_message(&mut s2).is_none()); + assert!(doc1.sync().generate_sync_message(&mut s1).is_none()); + assert!(doc2.sync().generate_sync_message(&mut s2).is_none()); //// If we make one more change and start another sync then its lastSync should be updated doc1.put(crate::ROOT, "x", 5).unwrap(); doc1.commit(); let msg1to2 = doc1 + .sync() .generate_sync_message(&mut s1) .expect("third reply from 1 to 2 was None"); let mut expected_heads = vec![head1, head2]; @@ -782,8 +898,8 @@ mod tests { let mut iterations = 0; loop { - let a_to_b = a.generate_sync_message(a_sync_state); - let b_to_a = b.generate_sync_message(b_sync_state); + let a_to_b = a.sync().generate_sync_message(a_sync_state); + let b_to_a = b.sync().generate_sync_message(b_sync_state); if a_to_b.is_none() && b_to_a.is_none() { break; } @@ -791,10 +907,10 @@ mod tests { panic!("failed to sync in {} iterations", MAX_ITER); } if let Some(msg) = a_to_b { - b.receive_sync_message(b_sync_state, msg).unwrap() + b.sync().receive_sync_message(b_sync_state, msg).unwrap() } if let Some(msg) = b_to_a { - a.receive_sync_message(a_sync_state, msg).unwrap() + a.sync().receive_sync_message(a_sync_state, msg).unwrap() } iterations += 1; } diff --git a/rust/automerge/src/sync/state.rs b/rust/automerge/src/sync/state.rs index 00775196..354c605f 100644 --- a/rust/automerge/src/sync/state.rs +++ b/rust/automerge/src/sync/state.rs @@ -23,13 +23,23 @@ impl From for DecodeError { } /// The state of synchronisation with a peer. +/// +/// This should be persisted using [`Self::encode`] when you know you will be interacting with the +/// same peer in multiple sessions. [`Self::encode`] only encodes state which should be reused +/// across connections. #[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] pub struct State { + /// The hashes which we know both peers have pub shared_heads: Vec, + /// The heads we last sent pub last_sent_heads: Vec, + /// The heads we last received from them pub their_heads: Option>, + /// Any specific changes they last said they needed pub their_need: Option>, + /// The bloom filters summarising what they said they have pub their_have: Option>, + /// The hashes we have sent in this session pub sent_hashes: BTreeSet, /// `generate_sync_message` should return `None` if there are no new changes to send. In diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index cba4e723..7e7db17d 100644 --- a/rust/automerge/src/transaction/inner.rs +++ b/rust/automerge/src/transaction/inner.rs @@ -717,7 +717,7 @@ struct SpliceArgs<'a> { #[cfg(test)] mod tests { - use crate::{transaction::Transactable, ROOT}; + use crate::{transaction::Transactable, ReadDoc, ROOT}; use super::*; diff --git a/rust/automerge/src/transaction/manual_transaction.rs b/rust/automerge/src/transaction/manual_transaction.rs index 22115aab..fa5f6340 100644 --- a/rust/automerge/src/transaction/manual_transaction.rs +++ b/rust/automerge/src/transaction/manual_transaction.rs @@ -1,7 +1,10 @@ use std::ops::RangeBounds; use crate::exid::ExId; -use crate::{Automerge, ChangeHash, KeysAt, ObjType, OpObserver, Prop, ScalarValue, Value, Values}; +use crate::op_observer::BranchableObserver; +use crate::{ + Automerge, ChangeHash, KeysAt, ObjType, OpObserver, Prop, ReadDoc, ScalarValue, Value, Values, +}; use crate::{AutomergeError, Keys}; use crate::{ListRange, ListRangeAt, MapRange, MapRangeAt}; @@ -49,7 +52,7 @@ impl<'a> Transaction<'a, observation::UnObserved> { } } -impl<'a, Obs: OpObserver> Transaction<'a, observation::Observed> { +impl<'a, Obs: OpObserver + BranchableObserver> Transaction<'a, observation::Observed> { pub fn observer(&mut self) -> &mut Obs { self.observation.as_mut().unwrap().observer() } @@ -112,95 +115,7 @@ impl<'a, Obs: observation::Observation> Transaction<'a, Obs> { } } -impl<'a, Obs: observation::Observation> Transactable for Transaction<'a, Obs> { - /// Get the number of pending operations in this transaction. - fn pending_ops(&self) -> usize { - self.inner.as_ref().unwrap().pending_ops() - } - - /// Set the value of property `P` to value `V` in object `obj`. - /// - /// # Errors - /// - /// This will return an error if - /// - The object does not exist - /// - The key is the wrong type for the object - /// - The key does not exist in the object - fn put, P: Into, V: Into>( - &mut self, - obj: O, - prop: P, - value: V, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.put(doc, obs, obj.as_ref(), prop, value)) - } - - fn put_object, P: Into>( - &mut self, - obj: O, - prop: P, - value: ObjType, - ) -> Result { - self.do_tx(|tx, doc, obs| tx.put_object(doc, obs, obj.as_ref(), prop, value)) - } - - fn insert, V: Into>( - &mut self, - obj: O, - index: usize, - value: V, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.insert(doc, obs, obj.as_ref(), index, value)) - } - - fn insert_object>( - &mut self, - obj: O, - index: usize, - value: ObjType, - ) -> Result { - self.do_tx(|tx, doc, obs| tx.insert_object(doc, obs, obj.as_ref(), index, value)) - } - - fn increment, P: Into>( - &mut self, - obj: O, - prop: P, - value: i64, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.increment(doc, obs, obj.as_ref(), prop, value)) - } - - fn delete, P: Into>( - &mut self, - obj: O, - prop: P, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.delete(doc, obs, obj.as_ref(), prop)) - } - - /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert - /// the new elements - fn splice, V: IntoIterator>( - &mut self, - obj: O, - pos: usize, - del: usize, - vals: V, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.splice(doc, obs, obj.as_ref(), pos, del, vals)) - } - - fn splice_text>( - &mut self, - obj: O, - pos: usize, - del: usize, - text: &str, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.splice_text(doc, obs, obj.as_ref(), pos, del, text)) - } - +impl<'a, Obs: observation::Observation> ReadDoc for Transaction<'a, Obs> { fn keys>(&self, obj: O) -> Keys<'_, '_> { self.doc.keys(obj) } @@ -313,6 +228,108 @@ impl<'a, Obs: observation::Observation> Transactable for Transaction<'a, Obs> { self.doc.parents(obj) } + fn path_to_object>(&self, obj: O) -> Result, AutomergeError> { + self.doc.path_to_object(obj) + } + + fn get_missing_deps(&self, heads: &[ChangeHash]) -> Vec { + self.doc.get_missing_deps(heads) + } + + fn get_change_by_hash(&self, hash: &ChangeHash) -> Option<&crate::Change> { + self.doc.get_change_by_hash(hash) + } +} + +impl<'a, Obs: observation::Observation> Transactable for Transaction<'a, Obs> { + /// Get the number of pending operations in this transaction. + fn pending_ops(&self) -> usize { + self.inner.as_ref().unwrap().pending_ops() + } + + /// Set the value of property `P` to value `V` in object `obj`. + /// + /// # Errors + /// + /// This will return an error if + /// - The object does not exist + /// - The key is the wrong type for the object + /// - The key does not exist in the object + fn put, P: Into, V: Into>( + &mut self, + obj: O, + prop: P, + value: V, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.put(doc, obs, obj.as_ref(), prop, value)) + } + + fn put_object, P: Into>( + &mut self, + obj: O, + prop: P, + value: ObjType, + ) -> Result { + self.do_tx(|tx, doc, obs| tx.put_object(doc, obs, obj.as_ref(), prop, value)) + } + + fn insert, V: Into>( + &mut self, + obj: O, + index: usize, + value: V, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.insert(doc, obs, obj.as_ref(), index, value)) + } + + fn insert_object>( + &mut self, + obj: O, + index: usize, + value: ObjType, + ) -> Result { + self.do_tx(|tx, doc, obs| tx.insert_object(doc, obs, obj.as_ref(), index, value)) + } + + fn increment, P: Into>( + &mut self, + obj: O, + prop: P, + value: i64, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.increment(doc, obs, obj.as_ref(), prop, value)) + } + + fn delete, P: Into>( + &mut self, + obj: O, + prop: P, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.delete(doc, obs, obj.as_ref(), prop)) + } + + /// Splice new elements into the given sequence. Returns a vector of the OpIds used to insert + /// the new elements + fn splice, V: IntoIterator>( + &mut self, + obj: O, + pos: usize, + del: usize, + vals: V, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.splice(doc, obs, obj.as_ref(), pos, del, vals)) + } + + fn splice_text>( + &mut self, + obj: O, + pos: usize, + del: usize, + text: &str, + ) -> Result<(), AutomergeError> { + self.do_tx(|tx, doc, obs| tx.splice_text(doc, obs, obj.as_ref(), pos, del, text)) + } + fn base_heads(&self) -> Vec { self.doc.get_heads() } diff --git a/rust/automerge/src/transaction/observation.rs b/rust/automerge/src/transaction/observation.rs index 974004cf..53723711 100644 --- a/rust/automerge/src/transaction/observation.rs +++ b/rust/automerge/src/transaction/observation.rs @@ -1,15 +1,17 @@ //! This module is essentially a type level Option. It is used in sitations where we know at //! compile time whether an `OpObserver` is available to track changes in a transaction. -use crate::{ChangeHash, OpObserver}; +use crate::{op_observer::BranchableObserver, ChangeHash, OpObserver}; mod private { + use crate::op_observer::BranchableObserver; + pub trait Sealed {} - impl Sealed for super::Observed {} + impl Sealed for super::Observed {} impl Sealed for super::UnObserved {} } pub trait Observation: private::Sealed { - type Obs: OpObserver; + type Obs: OpObserver + BranchableObserver; type CommitResult; fn observer(&mut self) -> Option<&mut Self::Obs>; @@ -19,9 +21,9 @@ pub trait Observation: private::Sealed { } #[derive(Clone, Debug)] -pub struct Observed(Obs); +pub struct Observed(Obs); -impl Observed { +impl Observed { pub(crate) fn new(o: O) -> Self { Self(o) } @@ -31,7 +33,7 @@ impl Observed { } } -impl Observation for Observed { +impl Observation for Observed { type Obs = Obs; type CommitResult = (Obs, Option); fn observer(&mut self) -> Option<&mut Self::Obs> { diff --git a/rust/automerge/src/transaction/transactable.rs b/rust/automerge/src/transaction/transactable.rs index 7f38edbe..05c48c79 100644 --- a/rust/automerge/src/transaction/transactable.rs +++ b/rust/automerge/src/transaction/transactable.rs @@ -1,13 +1,8 @@ -use std::ops::RangeBounds; - use crate::exid::ExId; -use crate::{ - AutomergeError, ChangeHash, Keys, KeysAt, ListRange, ListRangeAt, MapRange, MapRangeAt, - ObjType, Parents, Prop, ScalarValue, Value, Values, -}; +use crate::{AutomergeError, ChangeHash, ObjType, Prop, ReadDoc, ScalarValue}; /// A way of mutating a document within a single change. -pub trait Transactable { +pub trait Transactable: ReadDoc { /// Get the number of pending operations in this transaction. fn pending_ops(&self) -> usize; @@ -93,106 +88,6 @@ pub trait Transactable { text: &str, ) -> Result<(), AutomergeError>; - /// Get the keys of the given object, it should be a map. - fn keys>(&self, obj: O) -> Keys<'_, '_>; - - /// Get the keys of the given object at a point in history. - fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_>; - - fn map_range, R: RangeBounds>( - &self, - obj: O, - range: R, - ) -> MapRange<'_, R>; - - fn map_range_at, R: RangeBounds>( - &self, - obj: O, - range: R, - heads: &[ChangeHash], - ) -> MapRangeAt<'_, R>; - - fn list_range, R: RangeBounds>( - &self, - obj: O, - range: R, - ) -> ListRange<'_, R>; - - fn list_range_at, R: RangeBounds>( - &self, - obj: O, - range: R, - heads: &[ChangeHash], - ) -> ListRangeAt<'_, R>; - - fn values>(&self, obj: O) -> Values<'_>; - - fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_>; - - /// Get the length of the given object. - fn length>(&self, obj: O) -> usize; - - /// Get the length of the given object at a point in history. - fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize; - - /// Get type for object - fn object_type>(&self, obj: O) -> Result; - - /// Get the string that this text object represents. - fn text>(&self, obj: O) -> Result; - - /// Get the string that this text object represents at a point in history. - fn text_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result; - - /// Get the value at this prop in the object. - fn get, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError>; - - /// Get the value at this prop in the object at a point in history. - fn get_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError>; - - fn get_all, P: Into>( - &self, - obj: O, - prop: P, - ) -> Result, ExId)>, AutomergeError>; - - fn get_all_at, P: Into>( - &self, - obj: O, - prop: P, - heads: &[ChangeHash], - ) -> Result, ExId)>, AutomergeError>; - - /// Get the parents of an object in the document tree. - /// - /// ### Errors - /// - /// Returns an error when the id given is not the id of an object in this document. - /// This function does not get the parents of scalar values contained within objects. - /// - /// ### Experimental - /// - /// This function may in future be changed to allow getting the parents from the id of a scalar - /// value. - fn parents>(&self, obj: O) -> Result, AutomergeError>; - - fn path_to_object>(&self, obj: O) -> Result, AutomergeError> { - Ok(self.parents(obj.as_ref().clone())?.path()) - } - /// The heads this transaction will be based on fn base_heads(&self) -> Vec; } diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 7bbf4353..870569e9 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -143,12 +143,17 @@ impl fmt::Display for ActorId { } } +/// The type of an object #[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Copy, Hash)] #[serde(rename_all = "camelCase", untagged)] pub enum ObjType { + /// A map Map, + /// Retained for backwards compatibility, tables are identical to maps Table, + /// A sequence of arbitrary values List, + /// A sequence of characters Text, } @@ -378,9 +383,15 @@ pub(crate) enum Key { Seq(ElemId), } +/// A property of an object +/// +/// This is either a string representing a property in a map, or an integer +/// which is the index into a sequence #[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)] pub enum Prop { + /// A property in a map Map(String), + /// An index into a sequence Seq(usize), } @@ -454,9 +465,17 @@ impl ObjId { } } +/// How indexes into text sequeces are calculated +/// +/// Automerge text objects are internally sequences of utf8 characters. This +/// means that in environments (such as javascript) which use a different +/// encoding the indexes into the text sequence will be different. This enum +/// represents the different ways indexes can be calculated. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum TextEncoding { + /// The indexes are calculated using the utf8 encoding Utf8, + /// The indexes are calculated using the utf16 encoding Utf16, } diff --git a/rust/automerge/src/value.rs b/rust/automerge/src/value.rs index d8429f4e..be128787 100644 --- a/rust/automerge/src/value.rs +++ b/rust/automerge/src/value.rs @@ -5,9 +5,12 @@ use smol_str::SmolStr; use std::borrow::Cow; use std::fmt; +/// The type of values in an automerge document #[derive(Debug, Clone, PartialEq)] pub enum Value<'a> { + /// An composite object of type `ObjType` Object(ObjType), + /// A non composite value // TODO: if we don't have to store this in patches any more then it might be able to be just a // &'a ScalarValue rather than a Cow Scalar(Cow<'a, ScalarValue>), @@ -431,6 +434,7 @@ impl From<&Counter> for f64 { } } +/// A value which is not a composite value #[derive(Serialize, PartialEq, Debug, Clone)] #[serde(untagged)] pub enum ScalarValue { @@ -442,7 +446,11 @@ pub enum ScalarValue { Counter(Counter), Timestamp(i64), Boolean(bool), - Unknown { type_code: u8, bytes: Vec }, + /// A value from a future version of automerge + Unknown { + type_code: u8, + bytes: Vec, + }, Null, } diff --git a/rust/automerge/src/values.rs b/rust/automerge/src/values.rs index 90f596f3..15ccb4cb 100644 --- a/rust/automerge/src/values.rs +++ b/rust/automerge/src/values.rs @@ -2,6 +2,9 @@ use crate::exid::ExId; use crate::{Automerge, Value}; use std::fmt; +/// An iterator over the values in an object +/// +/// This is returned by the [`crate::ReadDoc::values`] and [`crate::ReadDoc::values_at`] methods pub struct Values<'a> { range: Box>, doc: &'a Automerge, @@ -52,9 +55,3 @@ impl<'a> Iterator for Values<'a> { self.range.next_value(self.doc) } } - -impl<'a> DoubleEndedIterator for Values<'a> { - fn next_back(&mut self) -> Option { - unimplemented!() - } -} diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index df0e4cff..ca6c64c0 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1,7 +1,7 @@ use automerge::transaction::Transactable; use automerge::{ - ActorId, AutoCommit, Automerge, AutomergeError, Change, ExpandedChange, ObjType, ScalarValue, - VecOpObserver, ROOT, + ActorId, AutoCommit, Automerge, AutomergeError, Change, ExpandedChange, ObjType, ReadDoc, + ScalarValue, VecOpObserver, ROOT, }; use std::fs; @@ -21,7 +21,7 @@ fn no_conflict_on_repeated_assignment() { doc.put(&automerge::ROOT, "foo", 1).unwrap(); doc.put(&automerge::ROOT, "foo", 2).unwrap(); assert_doc!( - doc.document(), + &doc, map! { "foo" => { 2 }, } @@ -41,7 +41,7 @@ fn repeated_map_assignment_which_resolves_conflict_not_ignored() { doc1.put(&automerge::ROOT, "field", 123).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "field" => { 123 } } @@ -62,7 +62,7 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() { doc1.put(&list_id, 0, 789).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "list" => { list![ @@ -84,7 +84,7 @@ fn list_deletion() { doc.insert(&list_id, 2, 789).unwrap(); doc.delete(&list_id, 1).unwrap(); assert_doc!( - doc.document(), + &doc, map! { "list" => { list![ { 123 }, @@ -106,7 +106,7 @@ fn merge_concurrent_map_prop_updates() { "bar".into() ); assert_doc!( - doc1.document(), + &doc1, map! { "foo" => { "bar" }, "hello" => { "world" }, @@ -114,7 +114,7 @@ fn merge_concurrent_map_prop_updates() { ); doc2.merge(&mut doc1).unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "foo" => { "bar" }, "hello" => { "world" }, @@ -134,7 +134,7 @@ fn add_concurrent_increments_of_same_property() { doc2.increment(&automerge::ROOT, "counter", 2).unwrap(); doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "counter" => { mk_counter(3) @@ -161,7 +161,7 @@ fn add_increments_only_to_preceeded_values() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "counter" => { mk_counter(1), @@ -181,7 +181,7 @@ fn concurrent_updates_of_same_field() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "field" => { "one", @@ -206,7 +206,7 @@ fn concurrent_updates_of_same_list_element() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => { list![{ @@ -232,7 +232,7 @@ fn assignment_conflicts_of_different_types() { doc1.merge(&mut doc3).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "field" => { "string", @@ -255,7 +255,7 @@ fn changes_within_conflicting_map_field() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "field" => { "string", @@ -292,7 +292,7 @@ fn changes_within_conflicting_list_element() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "list" => { list![ @@ -330,7 +330,7 @@ fn concurrently_assigned_nested_maps_should_not_merge() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "config" => { map!{ @@ -364,7 +364,7 @@ fn concurrent_insertions_at_different_list_positions() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "list" => { list![ @@ -396,7 +396,7 @@ fn concurrent_insertions_at_same_list_position() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => { list![ @@ -427,7 +427,7 @@ fn concurrent_assignment_and_deletion_of_a_map_entry() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "bestBird" => { "magpie", @@ -451,7 +451,7 @@ fn concurrent_assignment_and_deletion_of_list_entry() { doc2.delete(&list_id, 1).unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "birds" => {list![ {"blackbird"}, @@ -461,7 +461,7 @@ fn concurrent_assignment_and_deletion_of_list_entry() { ); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => {list![ { "blackbird" }, @@ -474,7 +474,7 @@ fn concurrent_assignment_and_deletion_of_list_entry() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => {list![ { "blackbird" }, @@ -507,7 +507,7 @@ fn insertion_after_a_deleted_list_element() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => {list![ { "blackbird" }, @@ -518,7 +518,7 @@ fn insertion_after_a_deleted_list_element() { doc2.merge(&mut doc1).unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "birds" => {list![ { "blackbird" }, @@ -549,7 +549,7 @@ fn concurrent_deletion_of_same_list_element() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => {list![ { "albatross" }, @@ -560,7 +560,7 @@ fn concurrent_deletion_of_same_list_element() { doc2.merge(&mut doc1).unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "birds" => {list![ { "albatross" }, @@ -593,7 +593,7 @@ fn concurrent_updates_at_different_levels() { doc1.merge(&mut doc2).unwrap(); assert_obj!( - doc1.document(), + &doc1, &automerge::ROOT, "animals", map! { @@ -635,7 +635,7 @@ fn concurrent_updates_of_concurrently_deleted_objects() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "birds" => { map!{}, @@ -686,7 +686,7 @@ fn does_not_interleave_sequence_insertions_at_same_position() { doc1.merge(&mut doc2).unwrap(); assert_doc!( - doc1.document(), + &doc1, map! { "wisdom" => {list![ {"to"}, @@ -719,7 +719,7 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id( doc2.insert(&list, 0, "one").unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "list" => { list![ { "one" }, @@ -744,7 +744,7 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id() doc2.insert(&list, 0, "one").unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "list" => { list![ { "one" }, @@ -771,7 +771,7 @@ fn insertion_consistent_with_causality() { doc2.insert(&list, 0, "one").unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "list" => { list![ {"one"}, @@ -1129,7 +1129,7 @@ fn test_merging_test_conflicts_then_saving_and_loading() { let mut doc2 = AutoCommit::load(&doc1.save()).unwrap(); doc2.set_actor(actor2); - assert_doc! {doc2.document(), map!{ + assert_doc! {&doc2, map!{ "text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"o"}]}, }}; @@ -1139,16 +1139,16 @@ fn test_merging_test_conflicts_then_saving_and_loading() { doc2.splice_text(&text, 6, 0, "world").unwrap(); assert_doc!( - doc2.document(), + &doc2, map! { "text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"!"}, {" "}, {"w"} , {"o"}, {"r"}, {"l"}, {"d"}]} } ); - let mut doc3 = AutoCommit::load(&doc2.save()).unwrap(); + let doc3 = AutoCommit::load(&doc2.save()).unwrap(); assert_doc!( - doc3.document(), + &doc3, map! { "text" => { list![{"h"}, {"e"}, {"l"}, {"l"}, {"!"}, {" "}, {"w"} , {"o"}, {"r"}, {"l"}, {"d"}]} } diff --git a/rust/edit-trace/src/main.rs b/rust/edit-trace/src/main.rs index debe52db..9724a109 100644 --- a/rust/edit-trace/src/main.rs +++ b/rust/edit-trace/src/main.rs @@ -1,4 +1,5 @@ use automerge::ObjType; +use automerge::ReadDoc; use automerge::{transaction::Transactable, Automerge, AutomergeError, ROOT}; use std::time::Instant; From de5af2fffa957a0dda7cfb388a57389e216621aa Mon Sep 17 00:00:00 2001 From: alexjg Date: Mon, 30 Jan 2023 19:58:35 +0000 Subject: [PATCH 21/45] automerge-rs 0.3.0 and automerge-test 0.2.0 (#512) --- rust/automerge-test/Cargo.toml | 4 ++-- rust/automerge/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/automerge-test/Cargo.toml b/rust/automerge-test/Cargo.toml index 4fba0379..9290d7ac 100644 --- a/rust/automerge-test/Cargo.toml +++ b/rust/automerge-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "automerge-test" -version = "0.1.0" +version = "0.2.0" edition = "2021" license = "MIT" repository = "https://github.com/automerge/automerge-rs" @@ -10,7 +10,7 @@ description = "Utilities for testing automerge libraries" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -automerge = { version = "^0.2", path = "../automerge" } +automerge = { version = "^0.3", path = "../automerge" } smol_str = { version = "^0.1.21", features=["serde"] } serde = { version = "^1.0", features=["derive"] } decorum = "0.3.1" diff --git a/rust/automerge/Cargo.toml b/rust/automerge/Cargo.toml index 578878ae..e5a9125d 100644 --- a/rust/automerge/Cargo.toml +++ b/rust/automerge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "automerge" -version = "0.2.0" +version = "0.3.0" edition = "2021" license = "MIT" repository = "https://github.com/automerge/automerge-rs" From a6959e70e87aa9d882f68683144ede925ce62042 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 31 Jan 2023 10:54:54 -0700 Subject: [PATCH 22/45] More robust leb128 parsing (#515) Before this change i64 decoding did not work for negative numbers (not a real problem because it is only used for the timestamp of a change), and both u64 and i64 would allow overlong LEB encodings. --- rust/automerge/src/storage/parse.rs | 2 +- rust/automerge/src/storage/parse/leb128.rs | 292 +++++++++++++++++---- 2 files changed, 239 insertions(+), 55 deletions(-) diff --git a/rust/automerge/src/storage/parse.rs b/rust/automerge/src/storage/parse.rs index 64419fda..54668da4 100644 --- a/rust/automerge/src/storage/parse.rs +++ b/rust/automerge/src/storage/parse.rs @@ -110,7 +110,7 @@ use crate::{ActorId, ChangeHash}; const HASH_SIZE: usize = 32; // 256 bits = 32 bytes #[allow(unused_imports)] -pub(crate) use self::leb128::{leb128_i32, leb128_i64, leb128_u32, leb128_u64, nonzero_leb128_u64}; +pub(crate) use self::leb128::{leb128_i64, leb128_u32, leb128_u64, nonzero_leb128_u64}; pub(crate) type ParseResult<'a, O, E> = Result<(Input<'a>, O), ParseError>; diff --git a/rust/automerge/src/storage/parse/leb128.rs b/rust/automerge/src/storage/parse/leb128.rs index 800253c9..9f5e72a2 100644 --- a/rust/automerge/src/storage/parse/leb128.rs +++ b/rust/automerge/src/storage/parse/leb128.rs @@ -1,4 +1,3 @@ -use core::mem::size_of; use std::num::NonZeroU64; use super::{take1, Input, ParseError, ParseResult}; @@ -7,44 +6,83 @@ use super::{take1, Input, ParseError, ParseResult}; pub(crate) enum Error { #[error("leb128 was too large for the destination type")] Leb128TooLarge, + #[error("leb128 was improperly encoded")] + Leb128Overlong, #[error("leb128 was zero when it was expected to be nonzero")] UnexpectedZero, } -macro_rules! impl_leb { - ($parser_name: ident, $ty: ty) => { - #[allow(dead_code)] - pub(crate) fn $parser_name<'a, E>(input: Input<'a>) -> ParseResult<'a, $ty, E> - where - E: From, - { - let mut res = 0; - let mut shift = 0; +pub(crate) fn leb128_u64(input: Input<'_>) -> ParseResult<'_, u64, E> +where + E: From, +{ + let mut res = 0; + let mut shift = 0; + let mut input = input; - let mut input = input; - let mut pos = 0; - loop { - let (i, byte) = take1(input)?; - input = i; - if (byte & 0x80) == 0 { - res |= (byte as $ty) << shift; - return Ok((input, res)); - } else if pos == leb128_size::<$ty>() - 1 { - return Err(ParseError::Error(Error::Leb128TooLarge.into())); - } else { - res |= ((byte & 0x7F) as $ty) << shift; - } - pos += 1; - shift += 7; + loop { + let (i, byte) = take1(input)?; + input = i; + res |= ((byte & 0x7F) as u64) << shift; + shift += 7; + + if (byte & 0x80) == 0 { + if shift > 64 && byte > 1 { + return Err(ParseError::Error(Error::Leb128TooLarge.into())); + } else if shift > 7 && byte == 0 { + return Err(ParseError::Error(Error::Leb128Overlong.into())); } + return Ok((input, res)); + } else if shift > 64 { + return Err(ParseError::Error(Error::Leb128TooLarge.into())); } - }; + } } -impl_leb!(leb128_u64, u64); -impl_leb!(leb128_u32, u32); -impl_leb!(leb128_i64, i64); -impl_leb!(leb128_i32, i32); +pub(crate) fn leb128_i64(input: Input<'_>) -> ParseResult<'_, i64, E> +where + E: From, +{ + let mut res = 0; + let mut shift = 0; + + let mut input = input; + let mut prev = 0; + loop { + let (i, byte) = take1(input)?; + input = i; + res |= ((byte & 0x7F) as i64) << shift; + shift += 7; + + if (byte & 0x80) == 0 { + if shift > 64 && byte != 0 && byte != 0x7f { + // the 10th byte (if present) must contain only the sign-extended sign bit + return Err(ParseError::Error(Error::Leb128TooLarge.into())); + } else if shift > 7 + && ((byte == 0 && prev & 0x40 == 0) || (byte == 0x7f && prev & 0x40 > 0)) + { + // overlong if the sign bit of penultimate byte has been extended + return Err(ParseError::Error(Error::Leb128Overlong.into())); + } else if shift < 64 && byte & 0x40 > 0 { + // sign extend negative numbers + res |= -1 << shift; + } + return Ok((input, res)); + } else if shift > 64 { + return Err(ParseError::Error(Error::Leb128TooLarge.into())); + } + prev = byte; + } +} + +pub(crate) fn leb128_u32(input: Input<'_>) -> ParseResult<'_, u32, E> +where + E: From, +{ + let (i, num) = leb128_u64(input)?; + let result = u32::try_from(num).map_err(|_| ParseError::Error(Error::Leb128TooLarge.into()))?; + Ok((i, result)) +} /// Parse a LEB128 encoded u64 from the input, throwing an error if it is `0` pub(crate) fn nonzero_leb128_u64(input: Input<'_>) -> ParseResult<'_, NonZeroU64, E> @@ -57,38 +95,27 @@ where Ok((input, result)) } -/// Maximum LEB128-encoded size of an integer type -const fn leb128_size() -> usize { - let bits = size_of::() * 8; - (bits + 6) / 7 // equivalent to ceil(bits/7) w/o floats -} - #[cfg(test)] mod tests { use super::super::Needed; use super::*; - use std::{convert::TryFrom, num::NonZeroUsize}; + use std::num::NonZeroUsize; const NEED_ONE: Needed = Needed::Size(unsafe { NonZeroUsize::new_unchecked(1) }); #[test] - fn leb_128_unsigned() { + fn leb_128_u64() { let one = &[0b00000001_u8]; let one_two_nine = &[0b10000001, 0b00000001]; let one_and_more = &[0b00000001, 0b00000011]; let scenarios: Vec<(&'static [u8], ParseResult<'_, u64, Error>)> = vec![ (one, Ok((Input::with_position(one, 1), 1))), - (&[0b10000001_u8], Err(ParseError::Incomplete(NEED_ONE))), ( one_two_nine, Ok((Input::with_position(one_two_nine, 2), 129)), ), (one_and_more, Ok((Input::with_position(one_and_more, 1), 1))), - ( - &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], - Err(ParseError::Error(Error::Leb128TooLarge)), - ), ]; for (index, (input, expected)) in scenarios.clone().into_iter().enumerate() { let result = leb128_u64(Input::new(input)); @@ -102,17 +129,174 @@ mod tests { } } - for (index, (input, expected)) in scenarios.into_iter().enumerate() { - let u32_expected = expected.map(|(i, e)| (i, u32::try_from(e).unwrap())); - let result = leb128_u32(Input::new(input)); - if result != u32_expected { - panic!( - "Scenario {} failed for u32: expected {:?} got {:?}", - index + 1, - u32_expected, - result - ); + let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ + ( + "too many bytes", + &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "too many bits", + &[129, 129, 129, 129, 129, 129, 129, 129, 129, 2], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "overlong encoding", + &[129, 0], + ParseError::Error(Error::Leb128Overlong), + ), + ("missing data", &[255], ParseError::Incomplete(NEED_ONE)), + ]; + error_cases.into_iter().for_each(|(desc, input, expected)| { + match leb128_u64::(Input::new(input)) { + Ok((_, x)) => panic!("leb128_u64 should fail with {}, got {}", desc, x), + Err(error) => { + if error != expected { + panic!("leb128_u64 should fail with {}, got {}", expected, error) + } + } } - } + }); + + let success_cases: Vec<(&'static [u8], u64)> = vec![ + (&[0], 0), + (&[0x7f], 127), + (&[0x80, 0x01], 128), + (&[0xff, 0x7f], 16383), + ( + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1], + u64::MAX, + ), + ]; + success_cases.into_iter().for_each(|(input, expected)| { + match leb128_u64::(Input::new(input)) { + Ok((_, x)) => { + if x != expected { + panic!("leb128_u64 should succeed with {}, got {}", expected, x) + } + } + Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), + } + }); + } + + #[test] + fn leb_128_u32() { + let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ + ( + "too many bytes", + &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "too many bits", + &[0xff, 0xff, 0xff, 0xff, 0x1f], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "overlong encoding", + &[129, 0], + ParseError::Error(Error::Leb128Overlong), + ), + ("missing data", &[0xaa], ParseError::Incomplete(NEED_ONE)), + ]; + error_cases.into_iter().for_each(|(desc, input, expected)| { + match leb128_u32::(Input::new(input)) { + Ok((_, x)) => panic!("leb128_u32 should fail with {}, got {}", desc, x), + Err(error) => { + if error != expected { + panic!("leb128_u32 should fail with {}, got {}", expected, error) + } + } + } + }); + + let success_cases: Vec<(&'static [u8], u32)> = vec![ + (&[0], 0), + (&[0x7f], 127), + (&[0x80, 0x01], 128), + (&[0xff, 0x7f], 16383), + (&[0xff, 0xff, 0xff, 0xff, 0x0f], u32::MAX), + ]; + success_cases.into_iter().for_each(|(input, expected)| { + match leb128_u32::(Input::new(input)) { + Ok((_, x)) => { + if x != expected { + panic!("leb128_u32 should succeed with {}, got {}", expected, x) + } + } + Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), + } + }); + } + + #[test] + fn leb_128_i64() { + let error_cases: Vec<(&'static str, &'static [u8], ParseError<_>)> = vec![ + ( + "too many bytes", + &[129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129, 129], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "too many positive bits", + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "too many negative bits", + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7e], + ParseError::Error(Error::Leb128TooLarge), + ), + ( + "overlong positive encoding", + &[0xbf, 0], + ParseError::Error(Error::Leb128Overlong), + ), + ( + "overlong negative encoding", + &[0x81, 0xff, 0x7f], + ParseError::Error(Error::Leb128Overlong), + ), + ("missing data", &[0x90], ParseError::Incomplete(NEED_ONE)), + ]; + error_cases.into_iter().for_each(|(desc, input, expected)| { + match leb128_i64::(Input::new(input)) { + Ok((_, x)) => panic!("leb128_i64 should fail with {}, got {}", desc, x), + Err(error) => { + if error != expected { + panic!("leb128_i64 should fail with {}, got {}", expected, error) + } + } + } + }); + + let success_cases: Vec<(&'static [u8], i64)> = vec![ + (&[0], 0), + (&[0x7f], -1), + (&[0x3f], 63), + (&[0x40], -64), + (&[0x80, 0x01], 128), + (&[0xff, 0x3f], 8191), + (&[0x80, 0x40], -8192), + ( + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0], + i64::MAX, + ), + ( + &[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f], + i64::MIN, + ), + ]; + success_cases.into_iter().for_each(|(input, expected)| { + match leb128_i64::(Input::new(input)) { + Ok((_, x)) => { + if x != expected { + panic!("leb128_i64 should succeed with {}, got {}", expected, x) + } + } + Err(error) => panic!("leb128_u64 should succeed with {}, got {}", expected, error), + } + }); } } From 2a9652e642fbf7296a85180d790d4e297559f93f Mon Sep 17 00:00:00 2001 From: alexjg Date: Wed, 1 Feb 2023 09:15:00 +0000 Subject: [PATCH 23/45] typescript: Hide API type and make SyncState opaque (#514) --- javascript/src/stable.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 3b328240..74410346 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -26,7 +26,7 @@ import { Text } from "./text" export { Text } from "./text" import type { - API, + API as WasmAPI, Actor as ActorId, Prop, ObjID, @@ -34,7 +34,7 @@ import type { DecodedChange, Heads, MaterializeValue, - JsSyncState as SyncState, + JsSyncState, SyncMessage, DecodedSyncMessage, } from "@automerge/automerge-wasm" @@ -46,6 +46,17 @@ export type { IncPatch, SyncMessage, } from "@automerge/automerge-wasm" + +/** @hidden **/ +type API = WasmAPI + +const SyncStateSymbol = Symbol("_syncstate") + +/** + * An opaque type tracking the state of sync with a remote peer + */ +type SyncState = JsSyncState & { _opaque: typeof SyncStateSymbol } + import { ApiHandler, type ChangeToEncode, UseApi } from "./low_level" import { Automerge } from "@automerge/automerge-wasm" @@ -772,7 +783,7 @@ export function decodeSyncState(state: Uint8Array): SyncState { const sync = ApiHandler.decodeSyncState(state) const result = ApiHandler.exportSyncState(sync) sync.free() - return result + return result as SyncState } /** @@ -793,7 +804,7 @@ export function generateSyncMessage( const state = _state(doc) const syncState = ApiHandler.importSyncState(inState) const message = state.handle.generateSyncMessage(syncState) - const outState = ApiHandler.exportSyncState(syncState) + const outState = ApiHandler.exportSyncState(syncState) as SyncState return [outState, message] } @@ -835,7 +846,7 @@ export function receiveSyncMessage( } const heads = state.handle.getHeads() state.handle.receiveSyncMessage(syncState, message) - const outSyncState = ApiHandler.exportSyncState(syncState) + const outSyncState = ApiHandler.exportSyncState(syncState) as SyncState return [ progressDocument(doc, heads, opts.patchCallback || state.patchCallback), outSyncState, @@ -852,7 +863,7 @@ export function receiveSyncMessage( * @group sync */ export function initSyncState(): SyncState { - return ApiHandler.exportSyncState(ApiHandler.initSyncState()) + return ApiHandler.exportSyncState(ApiHandler.initSyncState()) as SyncState } /** @hidden */ From f8d5a8ea989580ab54d0dc541859a79b31a70107 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Feb 2023 09:15:54 +0000 Subject: [PATCH 24/45] Bump json5 from 1.0.1 to 1.0.2 in /javascript/examples/create-react-app (#487) Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2. in javascript/examples/create-react-app --- javascript/examples/create-react-app/yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/javascript/examples/create-react-app/yarn.lock b/javascript/examples/create-react-app/yarn.lock index d6e5d93f..ec83af3b 100644 --- a/javascript/examples/create-react-app/yarn.lock +++ b/javascript/examples/create-react-app/yarn.lock @@ -5845,9 +5845,9 @@ json-stable-stringify-without-jsonify@^1.0.1: integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== json5@^1.0.1: - version "1.0.1" - resolved "http://localhost:4873/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== dependencies: minimist "^1.2.0" @@ -6165,9 +6165,9 @@ minimatch@^5.0.1: brace-expansion "^2.0.1" minimist@^1.2.0, minimist@^1.2.6: - version "1.2.6" - resolved "http://localhost:4873/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + version "1.2.7" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" + integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== mkdirp@~0.5.1: version "0.5.6" From 9195e9cb7628ad380650d4e6ec727fbd481bfb7a Mon Sep 17 00:00:00 2001 From: alexjg Date: Thu, 2 Feb 2023 15:02:53 +0000 Subject: [PATCH 25/45] Fix deny errors (#518) * Ignore deny errors on duplicate windows-sys * Delete spurious lockfile in automerge-cli --- rust/automerge-cli/Cargo.lock | 857 ---------------------------------- rust/deny.toml | 6 + 2 files changed, 6 insertions(+), 857 deletions(-) delete mode 100644 rust/automerge-cli/Cargo.lock diff --git a/rust/automerge-cli/Cargo.lock b/rust/automerge-cli/Cargo.lock deleted file mode 100644 index a330ee89..00000000 --- a/rust/automerge-cli/Cargo.lock +++ /dev/null @@ -1,857 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anyhow" -version = "1.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "159bb86af3a200e19a068f4224eae4c8bb2d0fa054c7e5d1cacd5cef95e684cd" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "automerge" -version = "0.1.0" -dependencies = [ - "flate2", - "fxhash", - "hex", - "itertools", - "js-sys", - "leb128", - "nonzero_ext", - "rand", - "serde", - "sha2", - "smol_str", - "thiserror", - "tinyvec", - "tracing", - "unicode-segmentation", - "uuid", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "automerge-cli" -version = "0.1.0" -dependencies = [ - "anyhow", - "atty", - "automerge", - "clap", - "colored_json", - "combine", - "duct", - "maplit", - "serde_json", - "thiserror", - "tracing-subscriber", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "clap" -version = "3.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced1892c55c910c1219e98d6fc8d71f6bddba7905866ce740066d8bfea859312" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap_derive" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "colored_json" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd32eb54d016e203b7c2600e3a7802c75843a92e38ccc4869aefeca21771a64" -dependencies = [ - "ansi_term", - "atty", - "libc", - "serde", - "serde_json", -] - -[[package]] -name = "combine" -version = "4.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "cpufeatures" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crypto-common" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "duct" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc6a0a59ed0888e0041cf708e66357b7ae1a82f1c67247e1f93b5e0818f7d8d" -dependencies = [ - "libc", - "once_cell", - "os_pipe", - "shared_child", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "flate2" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" -dependencies = [ - "cfg-if", - "crc32fast", - "libc", - "miniz_oxide", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cd93900197114fa1fcb7ae84ca742095eed9442088988ae74fa744e930e77" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "heck" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "indexmap" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" -dependencies = [ - "autocfg", - "hashbrown", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "js-sys" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "libc" -version = "0.2.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" - -[[package]] -name = "log" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "memchr" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" - -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "nonzero_ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44a1290799eababa63ea60af0cbc3f03363e328e58f32fb0294798ed3e85f444" - -[[package]] -name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "os_pipe" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "os_str_bytes" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "serde" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.136" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared_child" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6be9f7d5565b1483af3e72975e2dee33879b3b86bd48c0929fccf6585d79e65a" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "smallvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" - -[[package]] -name = "smol_str" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d15c83e300cce35b7c8cd39ff567c1ef42dde6d4a1a38dbdbf9a59902261bd" -dependencies = [ - "serde", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "textwrap" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" - -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "tracing" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" -dependencies = [ - "cfg-if", - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" -dependencies = [ - "lazy_static", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" -dependencies = [ - "ansi_term", - "sharded-slab", - "smallvec", - "thread_local", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "uuid" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" -dependencies = [ - "getrandom", - "serde", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasm-bindgen" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" - -[[package]] -name = "web-sys" -version = "0.3.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust/deny.toml b/rust/deny.toml index 54a68a60..12a562ce 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -172,6 +172,12 @@ deny = [ ] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [ + # duct, which we only depend on for integration tests in automerge-cli, + # pulls in a version of os_pipe which in turn pulls in a version of + # windows-sys which is different to the version in pulled in by is-terminal. + # This is fine to ignore for now because it doesn't end up in downstream + # dependencies. + { name = "windows-sys", version = "0.42.0" } ] # Similarly to `skip` allows you to skip certain crates during duplicate # detection. Unlike skip, it also includes the entire tree of transitive From da55dfac7ae3baa0892d98b64fcd41be61733c37 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Mon, 23 Jan 2023 18:30:54 +0000 Subject: [PATCH 26/45] refactor: make fields of Automerge private The fields of `automerge::Automerge` were crate public, which made it hard to change the structure of `Automerge` with confidence. Make all fields private and put them behind accessors where necessary to allow for easy internal changes. --- rust/automerge/src/autocommit.rs | 2 +- rust/automerge/src/automerge.rs | 65 +++++++++++++++++++---- rust/automerge/src/op_set/load.rs | 6 +-- rust/automerge/src/transaction/inner.rs | 69 ++++++++++++------------- 4 files changed, 92 insertions(+), 50 deletions(-) diff --git a/rust/automerge/src/autocommit.rs b/rust/automerge/src/autocommit.rs index 2c1c3adf..ae28596e 100644 --- a/rust/automerge/src/autocommit.rs +++ b/rust/automerge/src/autocommit.rs @@ -159,7 +159,7 @@ impl AutoCommitWithObs { /// /// This is a cheap operation, it just changes the way indexes are calculated pub fn with_encoding(mut self, encoding: TextEncoding) -> Self { - self.doc.text_encoding = encoding; + self.doc = self.doc.with_encoding(encoding); self } diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 86aa5f63..1b789337 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -71,26 +71,26 @@ pub(crate) enum Actor { #[derive(Debug, Clone)] pub struct Automerge { /// The list of unapplied changes that are not causally ready. - pub(crate) queue: Vec, + queue: Vec, /// The history of changes that form this document, topologically sorted too. - pub(crate) history: Vec, + history: Vec, /// Mapping from change hash to index into the history list. - pub(crate) history_index: HashMap, + history_index: HashMap, /// Mapping from change hash to vector clock at this state. - pub(crate) clocks: HashMap, + clocks: HashMap, /// Mapping from actor index to list of seqs seen for them. - pub(crate) states: HashMap>, + states: HashMap>, /// Current dependencies of this document (heads hashes). - pub(crate) deps: HashSet, + deps: HashSet, /// Heads at the last save. - pub(crate) saved: Vec, + saved: Vec, /// The set of operations that form this document. - pub(crate) ops: OpSet, + ops: OpSet, /// The current actor. - pub(crate) actor: Actor, + actor: Actor, /// The maximum operation counter this document has seen. - pub(crate) max_op: u64, - pub(crate) text_encoding: TextEncoding, + max_op: u64, + text_encoding: TextEncoding, } impl Automerge { @@ -111,6 +111,49 @@ impl Automerge { } } + pub(crate) fn ops_mut(&mut self) -> &mut OpSet { + &mut self.ops + } + + pub(crate) fn ops(&self) -> &OpSet { + &self.ops + } + + pub(crate) fn into_ops(self) -> OpSet { + self.ops + } + + pub(crate) fn actor_id(&self) -> &ActorId { + match &self.actor { + Actor::Unused(id) => id, + Actor::Cached(idx) => self.ops.m.actors.get(*idx), + } + } + + /// Remove the current actor from the opset if it has no ops + /// + /// If the current actor ID has no ops in the opset then remove it from the cache of actor IDs. + /// This us used when rolling back a transaction. If the rolled back ops are the only ops for + /// the current actor then we want to remove that actor from the opset so it doesn't end up in + /// any saved version of the document. + /// + /// # Panics + /// + /// If the last actor in the OpSet is not the actor ID of this document + pub(crate) fn rollback_last_actor(&mut self) { + if let Actor::Cached(actor_idx) = self.actor { + if self.states.get(&actor_idx).is_none() && self.ops.m.actors.len() > 0 { + assert!(self.ops.m.actors.len() == actor_idx + 1); + let actor = self.ops.m.actors.remove_last(); + self.actor = Actor::Unused(actor); + } + } + } + + pub(crate) fn text_encoding(&self) -> TextEncoding { + self.text_encoding + } + /// Change the text encoding of this view of the document /// /// This is a cheap operation, it just changes the way indexes are calculated diff --git a/rust/automerge/src/op_set/load.rs b/rust/automerge/src/op_set/load.rs index 6cc64e79..0df7f6ef 100644 --- a/rust/automerge/src/op_set/load.rs +++ b/rust/automerge/src/op_set/load.rs @@ -79,10 +79,10 @@ impl<'a, O: OpObserver> DocObserver for ObservedOpSetBuilder<'a, O> { } fn finish(self, _metadata: super::OpSetMetadata) -> Self::Output { - let mut opset = Automerge::new(); + let mut doc = Automerge::new(); for (obj, op) in self.ops { - opset.insert_op_with_observer(&obj, op, self.observer); + doc.insert_op_with_observer(&obj, op, self.observer); } - opset.ops + doc.into_ops() } } diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index 7e7db17d..95f922f3 100644 --- a/rust/automerge/src/transaction/inner.rs +++ b/rust/automerge/src/transaction/inner.rs @@ -98,7 +98,7 @@ impl TransactionInner { } let num_ops = self.pending_ops(); - let change = self.export(&doc.ops.m); + let change = self.export(&doc.ops().m); let hash = change.hash(); #[cfg(not(debug_assertions))] tracing::trace!(commit=?hash, deps=?change.deps(), "committing transaction"); @@ -153,20 +153,16 @@ impl TransactionInner { // remove in reverse order so sets are removed before makes etc... for (obj, op) in self.operations.into_iter().rev() { for pred_id in &op.pred { - if let Some(p) = doc.ops.search(&obj, OpIdSearch::new(*pred_id)).index() { - doc.ops.change_vis(&obj, p, |o| o.remove_succ(&op)); + if let Some(p) = doc.ops().search(&obj, OpIdSearch::new(*pred_id)).index() { + doc.ops_mut().change_vis(&obj, p, |o| o.remove_succ(&op)); } } - if let Some(pos) = doc.ops.search(&obj, OpIdSearch::new(op.id)).index() { - doc.ops.remove(&obj, pos); + if let Some(pos) = doc.ops().search(&obj, OpIdSearch::new(op.id)).index() { + doc.ops_mut().remove(&obj, pos); } } - // remove the actor from the cache so that it doesn't end up in the saved document - if doc.states.get(&self.actor).is_none() && doc.ops.m.actors.len() > 0 { - let actor = doc.ops.m.actors.remove_last(); - doc.actor = Actor::Unused(actor); - } + doc.rollback_last_actor(); num } @@ -277,10 +273,10 @@ impl TransactionInner { obj: ObjId, succ_pos: &[usize], ) { - doc.ops.add_succ(&obj, succ_pos, &op); + doc.ops_mut().add_succ(&obj, succ_pos, &op); if !op.is_delete() { - doc.ops.insert(pos, &obj, op.clone()); + doc.ops_mut().insert(pos, &obj, op.clone()); } self.finalize_op(doc, op_observer, obj, prop, op); @@ -332,7 +328,7 @@ impl TransactionInner { let id = self.next_id(); let query = doc - .ops + .ops() .search(&obj, query::InsertNth::new(index, ListEncoding::List)); let key = query.key()?; @@ -346,7 +342,7 @@ impl TransactionInner { insert: true, }; - doc.ops.insert(query.pos(), &obj, op.clone()); + doc.ops_mut().insert(query.pos(), &obj, op.clone()); self.finalize_op(doc, op_observer, obj, Prop::Seq(index), op); @@ -380,8 +376,8 @@ impl TransactionInner { } let id = self.next_id(); - let prop_index = doc.ops.m.props.cache(prop.clone()); - let query = doc.ops.search(&obj, query::Prop::new(prop_index)); + let prop_index = doc.ops_mut().m.props.cache(prop.clone()); + let query = doc.ops().search(&obj, query::Prop::new(prop_index)); // no key present to delete if query.ops.is_empty() && action == OpType::Delete { @@ -398,7 +394,7 @@ impl TransactionInner { return Err(AutomergeError::MissingCounter); } - let pred = doc.ops.m.sorted_opids(query.ops.iter().map(|o| o.id)); + let pred = doc.ops().m.sorted_opids(query.ops.iter().map(|o| o.id)); let op = Op { id, @@ -425,11 +421,11 @@ impl TransactionInner { action: OpType, ) -> Result, AutomergeError> { let query = doc - .ops + .ops() .search(&obj, query::Nth::new(index, ListEncoding::List)); let id = self.next_id(); - let pred = doc.ops.m.sorted_opids(query.ops.iter().map(|o| o.id)); + let pred = doc.ops().m.sorted_opids(query.ops.iter().map(|o| o.id)); let key = query.key()?; if query.ops.len() == 1 && query.ops[0].is_noop(&action) { @@ -490,7 +486,7 @@ impl TransactionInner { index, del: 1, values: vec![], - splice_type: SpliceType::Text("", doc.text_encoding), + splice_type: SpliceType::Text("", doc.text_encoding()), }, )?; } else { @@ -551,7 +547,7 @@ impl TransactionInner { index, del, values, - splice_type: SpliceType::Text(text, doc.text_encoding), + splice_type: SpliceType::Text(text, doc.text_encoding()), }, ) } @@ -568,13 +564,13 @@ impl TransactionInner { splice_type, }: SpliceArgs<'_>, ) -> Result<(), AutomergeError> { - let ex_obj = doc.ops.id_to_exid(obj.0); + let ex_obj = doc.ops().id_to_exid(obj.0); let encoding = splice_type.encoding(); // delete `del` items - performing the query for each one let mut deleted = 0; while deleted < del { // TODO: could do this with a single custom query - let query = doc.ops.search(&obj, query::Nth::new(index, encoding)); + let query = doc.ops().search(&obj, query::Nth::new(index, encoding)); // if we delete in the middle of a multi-character // move cursor back to the beginning and expand the del width @@ -590,9 +586,10 @@ impl TransactionInner { break; }; - let op = self.next_delete(query.key()?, query.pred(&doc.ops)); + let op = self.next_delete(query.key()?, query.pred(doc.ops())); - doc.ops.add_succ(&obj, &query.ops_pos, &op); + let ops_pos = query.ops_pos; + doc.ops_mut().add_succ(&obj, &ops_pos, &op); self.operations.push((obj, op)); @@ -608,7 +605,9 @@ impl TransactionInner { // do the insert query for the first item and then // insert the remaining ops one after the other if !values.is_empty() { - let query = doc.ops.search(&obj, query::InsertNth::new(index, encoding)); + let query = doc + .ops() + .search(&obj, query::InsertNth::new(index, encoding)); let mut pos = query.pos(); let mut key = query.key()?; let mut cursor = index; @@ -617,7 +616,7 @@ impl TransactionInner { for v in &values { let op = self.next_insert(key, v.clone()); - doc.ops.insert(pos, &obj, op.clone()); + doc.ops_mut().insert(pos, &obj, op.clone()); width = op.width(encoding); cursor += width; @@ -627,7 +626,7 @@ impl TransactionInner { self.operations.push((obj, op)); } - doc.ops.hint(&obj, cursor - width, pos - 1); + doc.ops_mut().hint(&obj, cursor - width, pos - 1); // handle the observer if let Some(obs) = op_observer.as_mut() { @@ -639,7 +638,7 @@ impl TransactionInner { let start = self.operations.len() - values.len(); for (offset, v) in values.iter().enumerate() { let op = &self.operations[start + offset].1; - let value = (v.clone().into(), doc.ops.id_to_exid(op.id)); + let value = (v.clone().into(), doc.ops().id_to_exid(op.id)); obs.insert(doc, ex_obj.clone(), index + offset, value) } } @@ -660,19 +659,19 @@ impl TransactionInner { ) { // TODO - id_to_exid should be a noop if not used - change type to Into? if let Some(op_observer) = op_observer { - let ex_obj = doc.ops.id_to_exid(obj.0); + let ex_obj = doc.ops().id_to_exid(obj.0); if op.insert { - let obj_type = doc.ops.object_type(&obj); + let obj_type = doc.ops().object_type(&obj); assert!(obj_type.unwrap().is_sequence()); match (obj_type, prop) { (Some(ObjType::List), Prop::Seq(index)) => { - let value = (op.value(), doc.ops.id_to_exid(op.id)); + let value = (op.value(), doc.ops().id_to_exid(op.id)); op_observer.insert(doc, ex_obj, index, value) } (Some(ObjType::Text), Prop::Seq(index)) => { // FIXME if op_observer.text_as_seq() { - let value = (op.value(), doc.ops.id_to_exid(op.id)); + let value = (op.value(), doc.ops().id_to_exid(op.id)); op_observer.insert(doc, ex_obj, index, value) } else { op_observer.splice_text(doc, ex_obj, index, op.to_str()) @@ -683,9 +682,9 @@ impl TransactionInner { } else if op.is_delete() { op_observer.delete(doc, ex_obj, prop); } else if let Some(value) = op.get_increment_value() { - op_observer.increment(doc, ex_obj, prop, (value, doc.ops.id_to_exid(op.id))); + op_observer.increment(doc, ex_obj, prop, (value, doc.ops().id_to_exid(op.id))); } else { - let value = (op.value(), doc.ops.id_to_exid(op.id)); + let value = (op.value(), doc.ops().id_to_exid(op.id)); op_observer.put(doc, ex_obj, prop, value, false); } } From c3c04128f5f1703007f650ea3104d98334334aab Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 26 Jan 2023 09:45:26 +0000 Subject: [PATCH 27/45] Only observe the current state on load Problem: When loading a document whilst passing an `OpObserver` we call the OpObserver for every change in the loaded document. This slows down the loading process for two reasons: 1) we have to make a call to the observer for every op 2) we cannot just stream the ops into the OpSet in topological order but must instead buffer them to pass to the observer. Solution: Construct the OpSet first, then only traverse the visible ops in the OpSet, calling the observer. For documents with a deep history this results in vastly fewer calls to the observer and also allows us to construct the OpSet much more quickly. It is slightly different semantically because the observer never gets notified of changes which are not visible, but that shouldn't matter to most observers. --- rust/automerge/Cargo.toml | 1 + rust/automerge/src/automerge.rs | 31 +- rust/automerge/src/automerge/current_state.rs | 890 ++++++++++++++++++ rust/automerge/src/op_set.rs | 55 +- rust/automerge/src/op_set/load.rs | 38 +- rust/automerge/src/storage/chunk.rs | 2 +- rust/automerge/src/sync.rs | 2 +- rust/automerge/src/transaction/inner.rs | 1 - rust/deny.toml | 3 + 9 files changed, 944 insertions(+), 79 deletions(-) create mode 100644 rust/automerge/src/automerge/current_state.rs diff --git a/rust/automerge/Cargo.toml b/rust/automerge/Cargo.toml index e5a9125d..0c10cc2b 100644 --- a/rust/automerge/Cargo.toml +++ b/rust/automerge/Cargo.toml @@ -47,6 +47,7 @@ criterion = "0.4.0" test-log = { version = "0.2.10", features=["trace"], default-features = false} tracing-subscriber = {version = "0.3.9", features = ["fmt", "env-filter"] } automerge-test = { path = "../automerge-test" } +prettytable = "0.10.0" [[bench]] name = "range" diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 1b789337..e0db8b5a 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -26,6 +26,8 @@ use crate::{ }; use serde::Serialize; +mod current_state; + #[cfg(test)] mod tests; @@ -119,17 +121,6 @@ impl Automerge { &self.ops } - pub(crate) fn into_ops(self) -> OpSet { - self.ops - } - - pub(crate) fn actor_id(&self) -> &ActorId { - match &self.actor { - Actor::Unused(id) => id, - Actor::Cached(idx) => self.ops.m.actors.get(*idx), - } - } - /// Remove the current actor from the opset if it has no ops /// /// If the current actor ID has no ops in the opset then remove it from the cache of actor IDs. @@ -455,13 +446,8 @@ impl Automerge { result: op_set, changes, heads, - } = match &mut observer { - Some(o) => { - storage::load::reconstruct_document(&d, mode, OpSet::observed_builder(*o)) - } - None => storage::load::reconstruct_document(&d, mode, OpSet::builder()), - } - .map_err(|e| load::Error::InflateDocument(Box::new(e)))?; + } = storage::load::reconstruct_document(&d, mode, OpSet::builder()) + .map_err(|e| load::Error::InflateDocument(Box::new(e)))?; let mut hashes_by_index = HashMap::new(); let mut actor_to_history: HashMap> = HashMap::new(); let mut clocks = Clocks::new(); @@ -517,6 +503,9 @@ impl Automerge { } load::LoadedChanges::Partial { error, .. } => return Err(error.into()), } + if let Some(observer) = &mut observer { + current_state::observe_current_state(&am, *observer); + } Ok(am) } @@ -715,7 +704,7 @@ impl Automerge { let c = self.history.iter(); let bytes = crate::storage::save::save_document( c, - self.ops.iter(), + self.ops.iter().map(|(objid, _, op)| (objid, op)), &self.ops.m.actors, &self.ops.m.props, &heads, @@ -731,7 +720,7 @@ impl Automerge { let c = self.history.iter(); let bytes = crate::storage::save::save_document( c, - self.ops.iter(), + self.ops.iter().map(|(objid, _, op)| (objid, op)), &self.ops.m.actors, &self.ops.m.props, &heads, @@ -944,7 +933,7 @@ impl Automerge { "pred", "succ" ); - for (obj, op) in self.ops.iter() { + for (obj, _, op) in self.ops.iter() { let id = self.to_string(op.id); let obj = self.to_string(obj); let key = match op.key { diff --git a/rust/automerge/src/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs new file mode 100644 index 00000000..1c1bceed --- /dev/null +++ b/rust/automerge/src/automerge/current_state.rs @@ -0,0 +1,890 @@ +use std::{borrow::Cow, collections::HashSet, iter::Peekable}; + +use itertools::Itertools; + +use crate::{ + types::{ElemId, Key, ListEncoding, ObjId, Op, OpId}, + ObjType, OpObserver, OpType, ScalarValue, Value, +}; + +/// Traverse the "current" state of the document, notifying `observer` +/// +/// The "current" state of the document is the set of visible operations. This function will +/// traverse that set of operations and call the corresponding methods on the `observer` as it +/// encounters values. The `observer` methods will be called in the order in which they appear in +/// the document. That is to say that the observer will be notified of parent objects before the +/// objects they contain and elements of a sequence will be notified in the order they occur. +/// +/// Due to only notifying of visible operations the observer will only be called with `put`, +/// `insert`, and `splice`, operations. +pub(super) fn observe_current_state(doc: &crate::Automerge, observer: &mut O) { + // The OpSet already exposes operations in the order they appear in the document. + // `OpSet::iter_objs` iterates over the objects in causal order, this means that parent objects + // will always appear before their children. Furthermore, the operations within each object are + // ordered by key (which means by their position in a sequence for sequences). + // + // Effectively then we iterate over each object, then we group the operations in the object by + // key and for each key find the visible operations for that key. Then we notify the observer + // for each of those visible operations. + let mut visible_objs = HashSet::new(); + visible_objs.insert(ObjId::root()); + for (obj, typ, ops) in doc.ops().iter_objs() { + if !visible_objs.contains(obj) { + continue; + } + let ops_by_key = ops.group_by(|o| o.key); + let actions = ops_by_key + .into_iter() + .flat_map(|(key, key_ops)| key_actions(key, key_ops)); + if typ == ObjType::Text && !observer.text_as_seq() { + track_new_objs_and_notify( + &mut visible_objs, + doc, + obj, + typ, + observer, + text_actions(actions), + ) + } else if typ == ObjType::List { + track_new_objs_and_notify( + &mut visible_objs, + doc, + obj, + typ, + observer, + list_actions(actions), + ) + } else { + track_new_objs_and_notify(&mut visible_objs, doc, obj, typ, observer, actions) + } + } +} + +fn track_new_objs_and_notify, O: OpObserver>( + visible_objs: &mut HashSet, + doc: &crate::Automerge, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + actions: I, +) { + let exid = doc.id_to_exid(obj.0); + for action in actions { + if let Some(obj) = action.made_object() { + visible_objs.insert(obj); + } + action.notify_observer(doc, &exid, obj, typ, observer); + } +} + +trait Action { + /// Notify an observer of whatever this action does + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ); + + /// If this action created an object, return the ID of that object + fn made_object(&self) -> Option; +} + +fn key_actions<'a, I: Iterator>( + key: Key, + key_ops: I, +) -> impl Iterator> { + #[derive(Clone)] + enum CurrentOp<'a> { + Put { + value: Value<'a>, + id: OpId, + conflicted: bool, + }, + Insert(Value<'a>, OpId), + } + let current_ops = key_ops + .filter(|o| o.visible()) + .filter_map(|o| match o.action { + OpType::Make(obj_type) => { + let value = Value::Object(obj_type); + if o.insert { + Some(CurrentOp::Insert(value, o.id)) + } else { + Some(CurrentOp::Put { + value, + id: o.id, + conflicted: false, + }) + } + } + OpType::Put(ref value) => { + let value = Value::Scalar(Cow::Borrowed(value)); + if o.insert { + Some(CurrentOp::Insert(value, o.id)) + } else { + Some(CurrentOp::Put { + value, + id: o.id, + conflicted: false, + }) + } + } + _ => None, + }); + current_ops + .coalesce(|previous, current| match (previous, current) { + (CurrentOp::Put { .. }, CurrentOp::Put { value, id, .. }) => Ok(CurrentOp::Put { + value, + id, + conflicted: true, + }), + (previous, current) => Err((previous, current)), + }) + .map(move |op| match op { + CurrentOp::Put { + value, + id, + conflicted, + } => SimpleAction::Put { + prop: key, + tagged_value: (value, id), + conflict: conflicted, + }, + CurrentOp::Insert(val, id) => SimpleAction::Insert { + elem_id: ElemId(id), + tagged_value: (val, id), + }, + }) +} + +/// Either a "put" or "insert" action. i.e. not splicing for text values +enum SimpleAction<'a> { + Put { + prop: Key, + tagged_value: (Value<'a>, OpId), + conflict: bool, + }, + Insert { + elem_id: ElemId, + tagged_value: (Value<'a>, OpId), + }, +} + +impl<'a> Action for SimpleAction<'a> { + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ) { + let encoding = match typ { + ObjType::Text => ListEncoding::Text(doc.text_encoding()), + _ => ListEncoding::List, + }; + match self { + Self::Put { + prop, + tagged_value, + conflict, + } => { + let tagged_value = (tagged_value.0, doc.id_to_exid(tagged_value.1)); + let prop = doc.ops().export_key(*obj, prop, encoding).unwrap(); + observer.put(doc, exid.clone(), prop, tagged_value, conflict); + } + Self::Insert { + elem_id, + tagged_value: (value, opid), + } => { + let index = doc + .ops() + .search(obj, crate::query::ElemIdPos::new(elem_id, encoding)) + .index() + .unwrap(); + let tagged_value = (value, doc.id_to_exid(opid)); + observer.insert(doc, doc.id_to_exid(obj.0), index, tagged_value); + } + } + } + + fn made_object(&self) -> Option { + match self { + Self::Put { + tagged_value: (Value::Object(_), id), + .. + } => Some((*id).into()), + Self::Insert { + tagged_value: (Value::Object(_), id), + .. + } => Some((*id).into()), + _ => None, + } + } +} + +/// An `Action` which splices for text values +enum TextAction<'a> { + Action(SimpleAction<'a>), + Splice { start: ElemId, chars: String }, +} + +impl<'a> Action for TextAction<'a> { + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ) { + match self { + Self::Action(action) => action.notify_observer(doc, exid, obj, typ, observer), + Self::Splice { start, chars } => { + let index = doc + .ops() + .search( + obj, + crate::query::ElemIdPos::new( + start, + ListEncoding::Text(doc.text_encoding()), + ), + ) + .index() + .unwrap(); + observer.splice_text(doc, doc.id_to_exid(obj.0), index, chars.as_str()); + } + } + } + + fn made_object(&self) -> Option { + match self { + Self::Action(action) => action.made_object(), + _ => None, + } + } +} + +fn list_actions<'a, I: Iterator>>( + actions: I, +) -> impl Iterator> { + actions.map(|a| match a { + SimpleAction::Put { + prop: Key::Seq(elem_id), + tagged_value, + .. + } => SimpleAction::Insert { + elem_id, + tagged_value, + }, + a => a, + }) +} + +/// Condense consecutive `SimpleAction::Insert` actions into one `TextAction::Splice` +fn text_actions<'a, I>(actions: I) -> impl Iterator> +where + I: Iterator>, +{ + TextActions { + ops: actions.peekable(), + } +} + +struct TextActions<'a, I: Iterator>> { + ops: Peekable, +} + +impl<'a, I: Iterator>> Iterator for TextActions<'a, I> { + type Item = TextAction<'a>; + + fn next(&mut self) -> Option { + if let Some(SimpleAction::Insert { .. }) = self.ops.peek() { + let (start, value) = match self.ops.next() { + Some(SimpleAction::Insert { + tagged_value: (value, opid), + .. + }) => (opid, value), + _ => unreachable!(), + }; + let mut chars = match value { + Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => s.to_string(), + _ => "\u{fffc}".to_string(), + }; + while let Some(SimpleAction::Insert { .. }) = self.ops.peek() { + if let Some(SimpleAction::Insert { + tagged_value: (value, _), + .. + }) = self.ops.next() + { + match value { + Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => chars.push_str(s), + _ => chars.push('\u{fffc}'), + } + } + } + Some(TextAction::Splice { + start: ElemId(start), + chars, + }) + } else { + self.ops.next().map(TextAction::Action) + } + } +} + +#[cfg(test)] +mod tests { + use std::borrow::Cow; + + use crate::{transaction::Transactable, ObjType, OpObserver, Prop, ReadDoc, Value}; + + // Observer ops often carry a "tagged value", which is a value and the OpID of the op which + // created that value. For a lot of values (i.e. any scalar value) we don't care about the + // opid. This type implements `PartialEq` for the `Untagged` variant by ignoring the tag, which + // allows us to express tests which don't care about the tag. + #[derive(Clone, Debug)] + enum ObservedValue { + Tagged(crate::Value<'static>, crate::ObjId), + Untagged(crate::Value<'static>), + } + + impl<'a> From<(Value<'a>, crate::ObjId)> for ObservedValue { + fn from(value: (Value<'a>, crate::ObjId)) -> Self { + Self::Tagged(value.0.into_owned(), value.1) + } + } + + impl PartialEq for ObservedValue { + fn eq(&self, other: &ObservedValue) -> bool { + match (self, other) { + (Self::Tagged(v1, o1), Self::Tagged(v2, o2)) => equal_vals(v1, v2) && o1 == o2, + (Self::Untagged(v1), Self::Untagged(v2)) => equal_vals(v1, v2), + (Self::Tagged(v1, _), Self::Untagged(v2)) => equal_vals(v1, v2), + (Self::Untagged(v1), Self::Tagged(v2, _)) => equal_vals(v1, v2), + } + } + } + + /// Consider counters equal if they have the same current value + fn equal_vals(v1: &Value<'_>, v2: &Value<'_>) -> bool { + match (v1, v2) { + (Value::Scalar(v1), Value::Scalar(v2)) => match (v1.as_ref(), v2.as_ref()) { + (crate::ScalarValue::Counter(c1), crate::ScalarValue::Counter(c2)) => { + c1.current == c2.current + } + _ => v1 == v2, + }, + _ => v1 == v2, + } + } + + #[derive(Debug, Clone, PartialEq)] + enum ObserverCall { + Put { + obj: crate::ObjId, + prop: Prop, + value: ObservedValue, + conflict: bool, + }, + Insert { + obj: crate::ObjId, + index: usize, + value: ObservedValue, + }, + SpliceText { + obj: crate::ObjId, + index: usize, + chars: String, + }, + } + + // A Vec is pretty hard to look at in a test failure. This wrapper prints the + // calls out in a nice table so it's easier to see what's different + #[derive(Clone, PartialEq)] + struct Calls(Vec); + + impl std::fmt::Debug for Calls { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut table = prettytable::Table::new(); + table.set_format(*prettytable::format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(prettytable::row![ + "Op", "Object", "Property", "Value", "Conflict" + ]); + for call in &self.0 { + match call { + ObserverCall::Put { + obj, + prop, + value, + conflict, + } => { + table.add_row(prettytable::row![ + "Put", + format!("{}", obj), + prop, + match value { + ObservedValue::Tagged(v, o) => format!("{} ({})", v, o), + ObservedValue::Untagged(v) => format!("{}", v), + }, + conflict + ]); + } + ObserverCall::Insert { obj, index, value } => { + table.add_row(prettytable::row![ + "Insert", + format!("{}", obj), + index, + match value { + ObservedValue::Tagged(v, o) => format!("{} ({})", v, o), + ObservedValue::Untagged(v) => format!("{}", v), + }, + "" + ]); + } + ObserverCall::SpliceText { obj, index, chars } => { + table.add_row(prettytable::row![ + "SpliceText", + format!("{}", obj), + index, + chars, + "" + ]); + } + } + } + let mut out = Vec::new(); + table.print(&mut out).unwrap(); + write!(f, "\n{}\n", String::from_utf8(out).unwrap()) + } + } + + struct ObserverStub { + ops: Vec, + text_as_seq: bool, + } + + impl ObserverStub { + fn new() -> Self { + Self { + ops: Vec::new(), + text_as_seq: true, + } + } + + fn new_text_v2() -> Self { + Self { + ops: Vec::new(), + text_as_seq: false, + } + } + } + + impl OpObserver for ObserverStub { + fn insert( + &mut self, + _doc: &R, + objid: crate::ObjId, + index: usize, + tagged_value: (crate::Value<'_>, crate::ObjId), + ) { + self.ops.push(ObserverCall::Insert { + obj: objid, + index, + value: tagged_value.into(), + }); + } + + fn splice_text( + &mut self, + _doc: &R, + objid: crate::ObjId, + index: usize, + value: &str, + ) { + self.ops.push(ObserverCall::SpliceText { + obj: objid, + index, + chars: value.to_string(), + }); + } + + fn put( + &mut self, + _doc: &R, + objid: crate::ObjId, + prop: crate::Prop, + tagged_value: (crate::Value<'_>, crate::ObjId), + conflict: bool, + ) { + self.ops.push(ObserverCall::Put { + obj: objid, + prop, + value: tagged_value.into(), + conflict, + }); + } + + fn expose( + &mut self, + _doc: &R, + _objid: crate::ObjId, + _prop: crate::Prop, + _tagged_value: (crate::Value<'_>, crate::ObjId), + _conflict: bool, + ) { + panic!("expose not expected"); + } + + fn increment( + &mut self, + _doc: &R, + _objid: crate::ObjId, + _prop: crate::Prop, + _tagged_value: (i64, crate::ObjId), + ) { + panic!("increment not expected"); + } + + fn delete_map(&mut self, _doc: &R, _objid: crate::ObjId, _key: &str) { + panic!("delete not expected"); + } + + fn delete_seq( + &mut self, + _doc: &R, + _objid: crate::ObjId, + _index: usize, + _num: usize, + ) { + panic!("delete not expected"); + } + + fn text_as_seq(&self) -> bool { + self.text_as_seq + } + } + + #[test] + fn basic_test() { + let mut doc = crate::AutoCommit::new(); + doc.put(crate::ROOT, "key", "value").unwrap(); + let map = doc.put_object(crate::ROOT, "map", ObjType::Map).unwrap(); + doc.put(&map, "nested_key", "value").unwrap(); + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + doc.insert(&list, 0, "value").unwrap(); + let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); + doc.insert(&text, 0, "a").unwrap(); + + let mut obs = ObserverStub::new(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "key".into(), + value: ObservedValue::Untagged("value".into()), + conflict: false, + }, + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Put { + obj: crate::ROOT, + prop: "map".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), + conflict: false, + }, + ObserverCall::Put { + obj: crate::ROOT, + prop: "text".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), + conflict: false, + }, + ObserverCall::Put { + obj: map.clone(), + prop: "nested_key".into(), + value: ObservedValue::Untagged("value".into()), + conflict: false, + }, + ObserverCall::Insert { + obj: list, + index: 0, + value: ObservedValue::Untagged("value".into()), + }, + ObserverCall::Insert { + obj: text, + index: 0, + value: ObservedValue::Untagged("a".into()), + }, + ]) + ); + } + + #[test] + fn test_deleted_ops_omitted() { + let mut doc = crate::AutoCommit::new(); + doc.put(crate::ROOT, "key", "value").unwrap(); + doc.delete(crate::ROOT, "key").unwrap(); + let map = doc.put_object(crate::ROOT, "map", ObjType::Map).unwrap(); + doc.put(&map, "nested_key", "value").unwrap(); + doc.delete(&map, "nested_key").unwrap(); + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + doc.insert(&list, 0, "value").unwrap(); + doc.delete(&list, 0).unwrap(); + let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); + doc.insert(&text, 0, "a").unwrap(); + doc.delete(&text, 0).unwrap(); + + doc.put_object(crate::ROOT, "deleted_map", ObjType::Map) + .unwrap(); + doc.delete(crate::ROOT, "deleted_map").unwrap(); + doc.put_object(crate::ROOT, "deleted_list", ObjType::List) + .unwrap(); + doc.delete(crate::ROOT, "deleted_list").unwrap(); + doc.put_object(crate::ROOT, "deleted_text", ObjType::Text) + .unwrap(); + doc.delete(crate::ROOT, "deleted_text").unwrap(); + + let mut obs = ObserverStub::new(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Put { + obj: crate::ROOT, + prop: "map".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), + conflict: false, + }, + ObserverCall::Put { + obj: crate::ROOT, + prop: "text".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), + conflict: false, + }, + ]) + ); + } + + #[test] + fn test_text_spliced() { + let mut doc = crate::AutoCommit::new(); + let text = doc.put_object(crate::ROOT, "text", ObjType::Text).unwrap(); + doc.insert(&text, 0, "a").unwrap(); + doc.splice_text(&text, 1, 0, "bcdef").unwrap(); + doc.splice_text(&text, 2, 2, "g").unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "text".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::Text), text.clone()), + conflict: false, + }, + ObserverCall::SpliceText { + obj: text, + index: 0, + chars: "abgef".to_string() + } + ]) + ); + } + + #[test] + fn test_counters() { + let actor1 = crate::ActorId::from("aa".as_bytes()); + let actor2 = crate::ActorId::from("bb".as_bytes()); + let mut doc = crate::AutoCommit::new().with_actor(actor2); + + let mut doc2 = doc.fork().with_actor(actor1); + doc2.put(crate::ROOT, "key", "someval").unwrap(); + + doc.put(crate::ROOT, "key", crate::ScalarValue::Counter(1.into())) + .unwrap(); + doc.increment(crate::ROOT, "key", 2).unwrap(); + doc.increment(crate::ROOT, "key", 3).unwrap(); + + doc.merge(&mut doc2).unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ObserverCall::Put { + obj: crate::ROOT, + prop: "key".into(), + value: ObservedValue::Untagged(Value::Scalar(Cow::Owned( + crate::ScalarValue::Counter(6.into()) + ))), + conflict: true, + },]) + ); + } + + #[test] + fn test_multiple_list_insertions() { + let mut doc = crate::AutoCommit::new(); + + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + doc.insert(&list, 0, 1).unwrap(); + doc.insert(&list, 1, 2).unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Insert { + obj: list.clone(), + index: 0, + value: ObservedValue::Untagged(1.into()), + }, + ObserverCall::Insert { + obj: list, + index: 1, + value: ObservedValue::Untagged(2.into()), + }, + ]) + ); + } + + #[test] + fn test_concurrent_insertions_at_same_index() { + let mut doc = crate::AutoCommit::new().with_actor(crate::ActorId::from("aa".as_bytes())); + + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + + let mut doc2 = doc.fork().with_actor(crate::ActorId::from("bb".as_bytes())); + + doc.insert(&list, 0, 1).unwrap(); + doc2.insert(&list, 0, 2).unwrap(); + doc.merge(&mut doc2).unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Insert { + obj: list.clone(), + index: 0, + value: ObservedValue::Untagged(2.into()), + }, + ObserverCall::Insert { + obj: list, + index: 1, + value: ObservedValue::Untagged(1.into()), + }, + ]) + ); + } + + #[test] + fn test_insert_objects() { + let mut doc = crate::AutoCommit::new().with_actor(crate::ActorId::from("aa".as_bytes())); + + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + + let map = doc.insert_object(&list, 0, ObjType::Map).unwrap(); + doc.put(&map, "key", "value").unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Insert { + obj: list.clone(), + index: 0, + value: ObservedValue::Tagged(Value::Object(ObjType::Map), map.clone()), + }, + ObserverCall::Put { + obj: map, + prop: "key".into(), + value: ObservedValue::Untagged("value".into()), + conflict: false + }, + ]) + ); + } + + #[test] + fn test_insert_and_update() { + let mut doc = crate::AutoCommit::new(); + + let list = doc.put_object(crate::ROOT, "list", ObjType::List).unwrap(); + + doc.insert(&list, 0, "one").unwrap(); + doc.insert(&list, 1, "two").unwrap(); + doc.put(&list, 0, "three").unwrap(); + doc.put(&list, 1, "four").unwrap(); + + let mut obs = ObserverStub::new_text_v2(); + super::observe_current_state(doc.document(), &mut obs); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ + ObserverCall::Put { + obj: crate::ROOT, + prop: "list".into(), + value: ObservedValue::Tagged(Value::Object(ObjType::List), list.clone()), + conflict: false, + }, + ObserverCall::Insert { + obj: list.clone(), + index: 0, + value: ObservedValue::Untagged("three".into()), + }, + ObserverCall::Insert { + obj: list.clone(), + index: 1, + value: ObservedValue::Untagged("four".into()), + }, + ]) + ); + } +} diff --git a/rust/automerge/src/op_set.rs b/rust/automerge/src/op_set.rs index 5b50d2b0..aab8ce74 100644 --- a/rust/automerge/src/op_set.rs +++ b/rust/automerge/src/op_set.rs @@ -5,7 +5,7 @@ use crate::op_tree::{self, OpTree}; use crate::parents::Parents; use crate::query::{self, OpIdVisSearch, TreeQuery}; use crate::types::{self, ActorId, Key, ListEncoding, ObjId, Op, OpId, OpIds, OpType, Prop}; -use crate::{ObjType, OpObserver}; +use crate::ObjType; use fxhash::FxBuildHasher; use std::borrow::Borrow; use std::cmp::Ordering; @@ -13,7 +13,7 @@ use std::collections::HashMap; use std::ops::RangeBounds; mod load; -pub(crate) use load::{ObservedOpSetBuilder, OpSetBuilder}; +pub(crate) use load::OpSetBuilder; pub(crate) type OpSet = OpSetInternal; @@ -32,12 +32,6 @@ impl OpSetInternal { OpSetBuilder::new() } - /// Create a builder which passes each operation to `observer`. This will be significantly - /// slower than `OpSetBuilder` - pub(crate) fn observed_builder(observer: &mut O) -> ObservedOpSetBuilder<'_, O> { - ObservedOpSetBuilder::new(observer) - } - pub(crate) fn new() -> Self { let mut trees: HashMap<_, _, _> = Default::default(); trees.insert(ObjId::root(), OpTree::new()); @@ -64,7 +58,7 @@ impl OpSetInternal { } pub(crate) fn iter(&self) -> Iter<'_> { - let mut objs: Vec<_> = self.trees.iter().collect(); + let mut objs: Vec<_> = self.trees.iter().map(|t| (t.0, t.1.objtype, t.1)).collect(); objs.sort_by(|a, b| self.m.lamport_cmp((a.0).0, (b.0).0)); Iter { opset: self, @@ -73,6 +67,17 @@ impl OpSetInternal { } } + /// Iterate over objects in the opset in causal order + pub(crate) fn iter_objs( + &self, + ) -> impl Iterator)> + '_ { + let mut objs: Vec<_> = self.trees.iter().map(|t| (t.0, t.1.objtype, t.1)).collect(); + objs.sort_by(|a, b| self.m.lamport_cmp((a.0).0, (b.0).0)); + IterObjs { + trees: objs.into_iter(), + } + } + pub(crate) fn parents(&self, obj: ObjId) -> Parents<'_> { Parents { obj, ops: self } } @@ -286,7 +291,7 @@ impl Default for OpSetInternal { } impl<'a> IntoIterator for &'a OpSetInternal { - type Item = (&'a ObjId, &'a Op); + type Item = (&'a ObjId, ObjType, &'a Op); type IntoIter = Iter<'a>; @@ -295,27 +300,41 @@ impl<'a> IntoIterator for &'a OpSetInternal { } } +pub(crate) struct IterObjs<'a> { + trees: std::vec::IntoIter<(&'a ObjId, ObjType, &'a op_tree::OpTree)>, +} + +impl<'a> Iterator for IterObjs<'a> { + type Item = (&'a ObjId, ObjType, op_tree::OpTreeIter<'a>); + + fn next(&mut self) -> Option { + self.trees + .next() + .map(|(id, typ, tree)| (id, typ, tree.iter())) + } +} + #[derive(Clone)] pub(crate) struct Iter<'a> { opset: &'a OpSet, - trees: std::vec::IntoIter<(&'a ObjId, &'a op_tree::OpTree)>, - current: Option<(&'a ObjId, op_tree::OpTreeIter<'a>)>, + trees: std::vec::IntoIter<(&'a ObjId, ObjType, &'a op_tree::OpTree)>, + current: Option<(&'a ObjId, ObjType, op_tree::OpTreeIter<'a>)>, } impl<'a> Iterator for Iter<'a> { - type Item = (&'a ObjId, &'a Op); + type Item = (&'a ObjId, ObjType, &'a Op); fn next(&mut self) -> Option { - if let Some((id, tree)) = &mut self.current { + if let Some((id, typ, tree)) = &mut self.current { if let Some(next) = tree.next() { - return Some((id, next)); + return Some((id, *typ, next)); } } loop { - self.current = self.trees.next().map(|o| (o.0, o.1.iter())); - if let Some((obj, tree)) = &mut self.current { + self.current = self.trees.next().map(|o| (o.0, o.1, o.2.iter())); + if let Some((obj, typ, tree)) = &mut self.current { if let Some(next) = tree.next() { - return Some((obj, next)); + return Some((obj, *typ, next)); } } else { return None; diff --git a/rust/automerge/src/op_set/load.rs b/rust/automerge/src/op_set/load.rs index 0df7f6ef..e14f46b7 100644 --- a/rust/automerge/src/op_set/load.rs +++ b/rust/automerge/src/op_set/load.rs @@ -6,8 +6,7 @@ use super::{OpSet, OpTree}; use crate::{ op_tree::OpTreeInternal, storage::load::{DocObserver, LoadedObject}, - types::{ObjId, Op}, - Automerge, OpObserver, + types::ObjId, }; /// An opset builder which creates an optree for each object as it finishes loading, inserting the @@ -51,38 +50,3 @@ impl DocObserver for OpSetBuilder { } } } - -/// A DocObserver which just accumulates ops until the document has finished reconstructing and -/// then inserts all of the ops using `OpSet::insert_op_with_observer` -pub(crate) struct ObservedOpSetBuilder<'a, O: OpObserver> { - observer: &'a mut O, - ops: Vec<(ObjId, Op)>, -} - -impl<'a, O: OpObserver> ObservedOpSetBuilder<'a, O> { - pub(crate) fn new(observer: &'a mut O) -> Self { - Self { - observer, - ops: Vec::new(), - } - } -} - -impl<'a, O: OpObserver> DocObserver for ObservedOpSetBuilder<'a, O> { - type Output = OpSet; - - fn object_loaded(&mut self, object: LoadedObject) { - self.ops.reserve(object.ops.len()); - for op in object.ops { - self.ops.push((object.id, op)); - } - } - - fn finish(self, _metadata: super::OpSetMetadata) -> Self::Output { - let mut doc = Automerge::new(); - for (obj, op) in self.ops { - doc.insert_op_with_observer(&obj, op, self.observer); - } - doc.into_ops() - } -} diff --git a/rust/automerge/src/storage/chunk.rs b/rust/automerge/src/storage/chunk.rs index 06e31973..d0048528 100644 --- a/rust/automerge/src/storage/chunk.rs +++ b/rust/automerge/src/storage/chunk.rs @@ -286,7 +286,7 @@ impl Header { fn hash(typ: ChunkType, data: &[u8]) -> ChangeHash { let mut out = vec![u8::from(typ)]; leb128::write::unsigned(&mut out, data.len() as u64).unwrap(); - out.extend(data.as_ref()); + out.extend(data); let hash_result = Sha256::digest(out); let array: [u8; 32] = hash_result.into(); ChangeHash(array) diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index 5d71d989..d3b6b3fa 100644 --- a/rust/automerge/src/sync.rs +++ b/rust/automerge/src/sync.rs @@ -524,7 +524,7 @@ impl Message { encode_many(&mut buf, self.changes.iter_mut(), |buf, change| { leb128::write::unsigned(buf, change.raw_bytes().len() as u64).unwrap(); - buf.extend(change.raw_bytes().as_ref()) + buf.extend::<&[u8]>(change.raw_bytes().as_ref()) }); buf diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index 95f922f3..0fe735d5 100644 --- a/rust/automerge/src/transaction/inner.rs +++ b/rust/automerge/src/transaction/inner.rs @@ -1,6 +1,5 @@ use std::num::NonZeroU64; -use crate::automerge::Actor; use crate::exid::ExId; use crate::query::{self, OpIdSearch}; use crate::storage::Change as StoredChange; diff --git a/rust/deny.toml b/rust/deny.toml index 12a562ce..473cdae8 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -110,6 +110,9 @@ exceptions = [ # should be revied more fully before release { allow = ["MPL-2.0"], name = "cbindgen" }, { allow = ["BSD-3-Clause"], name = "instant" }, + + # we only use prettytable in tests + { allow = ["BSD-3-Clause"], name = "prettytable" }, ] # Some crates don't have (easily) machine readable licensing information, From 1e33c9d9e0eb33e32dfffe5dd4045aac85822e6a Mon Sep 17 00:00:00 2001 From: Alex Good Date: Wed, 1 Feb 2023 18:08:22 +0000 Subject: [PATCH 28/45] Use Automerge::load instead of load_incremental if empty Problem: when running the sync protocol for a new document the API requires that the user create an empty document and then call `receive_sync_message` on that document. This results in the OpObserver for the new document being called with every single op in the document history. For documents with a large history this can be extremely time consuming, but the OpObserver doesn't need to know about all the hidden states. Solution: Modify `Automerge::load_with` and `Automerge::apply_changes_with` to check if the document is empty before applying changes. If the document _is_ empty then we don't call the observer for every change, but instead use `automerge::observe_current_state` to notify the observer of the new state once all the changes have been applied. --- javascript/test/legacy_tests.ts | 3 +- rust/automerge/src/automerge.rs | 71 +++++++++++++++++++++++++-- rust/automerge/src/automerge/tests.rs | 5 ++ rust/automerge/src/lib.rs | 2 +- 4 files changed, 73 insertions(+), 8 deletions(-) diff --git a/javascript/test/legacy_tests.ts b/javascript/test/legacy_tests.ts index 90c731d9..8c2e552e 100644 --- a/javascript/test/legacy_tests.ts +++ b/javascript/test/legacy_tests.ts @@ -1849,9 +1849,8 @@ describe("Automerge", () => { }) assert.deepStrictEqual(patches, [ { action: "put", path: ["birds"], value: [] }, - { action: "insert", path: ["birds", 0], values: [""] }, + { action: "insert", path: ["birds", 0], values: ["", ""] }, { action: "splice", path: ["birds", 0, 0], value: "Goldfinch" }, - { action: "insert", path: ["birds", 1], values: [""] }, { action: "splice", path: ["birds", 1, 0], value: "Chaffinch" }, ]) }) diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index e0db8b5a..a7223c7c 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -37,6 +37,15 @@ pub(crate) enum Actor { Cached(usize), } +/// What to do when loading a document partially succeeds +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OnPartialLoad { + /// Ignore the error and return the loaded changes + Ignore, + /// Fail the entire load + Error, +} + /// An automerge document which does not manage transactions for you. /// /// ## Creating, loading, merging and forking documents @@ -121,6 +130,18 @@ impl Automerge { &self.ops } + /// Whether this document has any operations + pub fn is_empty(&self) -> bool { + self.history.is_empty() && self.queue.is_empty() + } + + pub(crate) fn actor_id(&self) -> ActorId { + match &self.actor { + Actor::Unused(id) => id.clone(), + Actor::Cached(idx) => self.ops.m.actors[*idx].clone(), + } + } + /// Remove the current actor from the opset if it has no ops /// /// If the current actor ID has no ops in the opset then remove it from the cache of actor IDs. @@ -410,20 +431,26 @@ impl Automerge { /// Load a document. pub fn load(data: &[u8]) -> Result { - Self::load_with::<()>(data, VerificationMode::Check, None) + Self::load_with::<()>(data, OnPartialLoad::Error, VerificationMode::Check, None) } /// Load a document without verifying the head hashes /// /// This is useful for debugging as it allows you to examine a corrupted document. pub fn load_unverified_heads(data: &[u8]) -> Result { - Self::load_with::<()>(data, VerificationMode::DontCheck, None) + Self::load_with::<()>( + data, + OnPartialLoad::Error, + VerificationMode::DontCheck, + None, + ) } /// Load a document with an observer #[tracing::instrument(skip(data, observer), err)] pub fn load_with( data: &[u8], + on_error: OnPartialLoad, mode: VerificationMode, mut observer: Option<&mut Obs>, ) -> Result { @@ -501,7 +528,11 @@ impl Automerge { am.apply_change(change, &mut observer); } } - load::LoadedChanges::Partial { error, .. } => return Err(error.into()), + load::LoadedChanges::Partial { error, .. } => { + if on_error == OnPartialLoad::Error { + return Err(error.into()); + } + } } if let Some(observer) = &mut observer { current_state::observe_current_state(&am, *observer); @@ -526,6 +557,18 @@ impl Automerge { data: &[u8], op_observer: Option<&mut Obs>, ) -> Result { + if self.is_empty() { + let mut doc = + Self::load_with::<()>(data, OnPartialLoad::Ignore, VerificationMode::Check, None)?; + doc = doc + .with_encoding(self.text_encoding) + .with_actor(self.actor_id()); + if let Some(obs) = op_observer { + current_state::observe_current_state(&doc, obs); + } + *self = doc; + return Ok(self.ops.len()); + } let changes = match load::load_changes(storage::parse::Input::new(data)) { load::LoadedChanges::Complete(c) => c, load::LoadedChanges::Partial { error, loaded, .. } => { @@ -566,6 +609,11 @@ impl Automerge { changes: I, mut op_observer: Option<&mut Obs>, ) -> Result<(), AutomergeError> { + // Record this so we can avoid observing each individual change and instead just observe + // the final state after all the changes have been applied. We can only do this for an + // empty document right now, once we have logic to produce the diffs between arbitrary + // states of the OpSet we can make this cleaner. + let empty_at_start = self.is_empty(); for c in changes { if !self.history_index.contains_key(&c.hash()) { if self.duplicate_seq(&c) { @@ -575,7 +623,11 @@ impl Automerge { )); } if self.is_causally_ready(&c) { - self.apply_change(c, &mut op_observer); + if empty_at_start { + self.apply_change::<()>(c, &mut None); + } else { + self.apply_change(c, &mut op_observer); + } } else { self.queue.push(c); } @@ -583,7 +635,16 @@ impl Automerge { } while let Some(c) = self.pop_next_causally_ready_change() { if !self.history_index.contains_key(&c.hash()) { - self.apply_change(c, &mut op_observer); + if empty_at_start { + self.apply_change::<()>(c, &mut None); + } else { + self.apply_change(c, &mut op_observer); + } + } + } + if empty_at_start { + if let Some(observer) = &mut op_observer { + current_state::observe_current_state(self, *observer); } } Ok(()) diff --git a/rust/automerge/src/automerge/tests.rs b/rust/automerge/src/automerge/tests.rs index 8d533fed..3511c4ed 100644 --- a/rust/automerge/src/automerge/tests.rs +++ b/rust/automerge/src/automerge/tests.rs @@ -1507,6 +1507,11 @@ fn observe_counter_change_application() { let changes = doc.get_changes(&[]).unwrap().into_iter().cloned(); let mut new_doc = AutoCommit::new().with_observer(VecOpObserver::default()); + // make a new change to the doc to stop the empty doc logic from skipping the intermediate + // patches. The is probably not really necessary, we could update this test to just test that + // the correct final state is emitted. For now though, we leave it as is. + new_doc.put(ROOT, "foo", "bar").unwrap(); + new_doc.observer().take_patches(); new_doc.apply_changes(changes).unwrap(); assert_eq!( new_doc.observer().take_patches(), diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index bafd8983..0b4cd743 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -274,7 +274,7 @@ mod values; #[cfg(feature = "optree-visualisation")] mod visualisation; -pub use crate::automerge::Automerge; +pub use crate::automerge::{Automerge, OnPartialLoad}; pub use autocommit::{AutoCommit, AutoCommitWithObs}; pub use autoserde::AutoSerde; pub use change::{Change, LoadError as LoadChangeError}; From 13a775ed9adc04c55067e3dc2eaa294fc862cb09 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 2 Feb 2023 13:28:22 +0000 Subject: [PATCH 29/45] Speed up loading by generating clocks on demand Context: currently we store a mapping from ChangeHash -> Clock, where `Clock` is the set of (ActorId, (Sequence number, max Op)) pairs derived from the given change and it's dependencies. This clock is used to determine what operations are visible at a given set of heads. Problem: populating this mapping for documents with large histories containing many actors can be very slow as for each change we have to allocate and merge a bunch of hashmaps. Solution: instead of creating the clocks on load, create an adjacency list based representation of the change graph and then derive the clock from this graph when it is needed. Traversing even large graphs is still almost as fast as looking up the clock in a hashmap. --- rust/automerge/src/automerge.rs | 135 ++++------- rust/automerge/src/change_graph.rs | 344 +++++++++++++++++++++++++++++ rust/automerge/src/clock.rs | 6 - rust/automerge/src/clocks.rs | 44 ---- rust/automerge/src/error.rs | 2 +- rust/automerge/src/lib.rs | 2 +- 6 files changed, 392 insertions(+), 141 deletions(-) create mode 100644 rust/automerge/src/change_graph.rs delete mode 100644 rust/automerge/src/clocks.rs diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index a7223c7c..128d4418 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -4,8 +4,7 @@ use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::RangeBounds; -use crate::clock::ClockData; -use crate::clocks::Clocks; +use crate::change_graph::ChangeGraph; use crate::columnar::Key as EncodedKey; use crate::exid::ExId; use crate::keys::Keys; @@ -87,8 +86,8 @@ pub struct Automerge { history: Vec, /// Mapping from change hash to index into the history list. history_index: HashMap, - /// Mapping from change hash to vector clock at this state. - clocks: HashMap, + /// Graph of changes + change_graph: ChangeGraph, /// Mapping from actor index to list of seqs seen for them. states: HashMap>, /// Current dependencies of this document (heads hashes). @@ -111,7 +110,7 @@ impl Automerge { queue: vec![], history: vec![], history_index: HashMap::new(), - clocks: HashMap::new(), + change_graph: ChangeGraph::new(), states: HashMap::new(), ops: Default::default(), deps: Default::default(), @@ -477,14 +476,14 @@ impl Automerge { .map_err(|e| load::Error::InflateDocument(Box::new(e)))?; let mut hashes_by_index = HashMap::new(); let mut actor_to_history: HashMap> = HashMap::new(); - let mut clocks = Clocks::new(); + let mut change_graph = ChangeGraph::new(); for (index, change) in changes.iter().enumerate() { // SAFETY: This should be fine because we just constructed an opset containing // all the changes let actor_index = op_set.m.actors.lookup(change.actor_id()).unwrap(); actor_to_history.entry(actor_index).or_default().push(index); hashes_by_index.insert(index, change.hash()); - clocks.add_change(change, actor_index)?; + change_graph.add_change(change, actor_index)?; } let history_index = hashes_by_index.into_iter().map(|(k, v)| (v, k)).collect(); Self { @@ -492,7 +491,7 @@ impl Automerge { history: changes, history_index, states: actor_to_history, - clocks: clocks.into(), + change_graph, ops: op_set, deps: heads.into_iter().collect(), saved: Default::default(), @@ -824,16 +823,8 @@ impl Automerge { .filter(|hash| self.history_index.contains_key(hash)) .copied() .collect::>(); - let heads_clock = self.clock_at(&heads)?; - // keep the hashes that are concurrent or after the heads - changes.retain(|hash| { - self.clocks - .get(hash) - .unwrap() - .partial_cmp(&heads_clock) - .map_or(true, |o| o == Ordering::Greater) - }); + self.change_graph.remove_ancestors(changes, &heads); Ok(()) } @@ -841,7 +832,7 @@ impl Automerge { /// Get the changes since `have_deps` in this document using a clock internally. fn get_changes_clock(&self, have_deps: &[ChangeHash]) -> Result, AutomergeError> { // get the clock for the given deps - let clock = self.clock_at(have_deps)?; + let clock = self.clock_at(have_deps); // get the documents current clock @@ -875,26 +866,8 @@ impl Automerge { .find(|c| c.actor_id() == self.get_actor()); } - fn clock_at(&self, heads: &[ChangeHash]) -> Result { - if let Some(first_hash) = heads.first() { - let mut clock = self - .clocks - .get(first_hash) - .ok_or(AutomergeError::MissingHash(*first_hash))? - .clone(); - - for hash in &heads[1..] { - let c = self - .clocks - .get(hash) - .ok_or(AutomergeError::MissingHash(*hash))?; - clock.merge(c); - } - - Ok(clock) - } else { - Ok(Clock::new()) - } + fn clock_at(&self, heads: &[ChangeHash]) -> Clock { + self.change_graph.clock_for_heads(heads) } fn get_hash(&self, actor: usize, seq: u64) -> Result { @@ -920,22 +893,9 @@ impl Automerge { .push(history_index); self.history_index.insert(change.hash(), history_index); - let mut clock = Clock::new(); - for hash in change.deps() { - let c = self - .clocks - .get(hash) - .expect("Change's deps should already be in the document"); - clock.merge(c); - } - clock.include( - actor_index, - ClockData { - max_op: change.max_op(), - seq: change.seq(), - }, - ); - self.clocks.insert(change.hash(), clock); + self.change_graph + .add_change(&change, actor_index) + .expect("Change's deps should already be in the document"); self.history_index.insert(change.hash(), history_index); self.history.push(change); @@ -1197,9 +1157,8 @@ impl ReadDoc for Automerge { fn keys_at>(&self, obj: O, heads: &[ChangeHash]) -> KeysAt<'_, '_> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return KeysAt::new(self, self.ops.keys_at(obj, clock)); - } + let clock = self.clock_at(heads); + return KeysAt::new(self, self.ops.keys_at(obj, clock)); } KeysAt::new(self, None) } @@ -1223,10 +1182,9 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> MapRangeAt<'_, R> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - let iter_range = self.ops.map_range_at(obj, range, clock); - return MapRangeAt::new(self, iter_range); - } + let clock = self.clock_at(heads); + let iter_range = self.ops.map_range_at(obj, range, clock); + return MapRangeAt::new(self, iter_range); } MapRangeAt::new(self, None) } @@ -1250,10 +1208,9 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> ListRangeAt<'_, R> { if let Ok((obj, _)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - let iter_range = self.ops.list_range_at(obj, range, clock); - return ListRangeAt::new(self, iter_range); - } + let clock = self.clock_at(heads); + let iter_range = self.ops.list_range_at(obj, range, clock); + return ListRangeAt::new(self, iter_range); } ListRangeAt::new(self, None) } @@ -1272,20 +1229,20 @@ impl ReadDoc for Automerge { fn values_at>(&self, obj: O, heads: &[ChangeHash]) -> Values<'_> { if let Ok((obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return match obj_type { - ObjType::Map | ObjType::Table => { - let iter_range = self.ops.map_range_at(obj, .., clock); - Values::new(self, iter_range) - } - ObjType::List | ObjType::Text => { - let iter_range = self.ops.list_range_at(obj, .., clock); - Values::new(self, iter_range) - } - }; + let clock = self.clock_at(heads); + match obj_type { + ObjType::Map | ObjType::Table => { + let iter_range = self.ops.map_range_at(obj, .., clock); + Values::new(self, iter_range) + } + ObjType::List | ObjType::Text => { + let iter_range = self.ops.list_range_at(obj, .., clock); + Values::new(self, iter_range) + } } + } else { + Values::empty(self) } - Values::empty(self) } fn length>(&self, obj: O) -> usize { @@ -1303,18 +1260,18 @@ impl ReadDoc for Automerge { fn length_at>(&self, obj: O, heads: &[ChangeHash]) -> usize { if let Ok((inner_obj, obj_type)) = self.exid_to_obj(obj.as_ref()) { - if let Ok(clock) = self.clock_at(heads) { - return if obj_type == ObjType::Map || obj_type == ObjType::Table { - self.keys_at(obj, heads).count() - } else { - let encoding = ListEncoding::new(obj_type, self.text_encoding); - self.ops - .search(&inner_obj, query::LenAt::new(clock, encoding)) - .len - }; + let clock = self.clock_at(heads); + if obj_type == ObjType::Map || obj_type == ObjType::Table { + self.keys_at(obj, heads).count() + } else { + let encoding = ListEncoding::new(obj_type, self.text_encoding); + self.ops + .search(&inner_obj, query::LenAt::new(clock, encoding)) + .len } + } else { + 0 } - 0 } fn object_type>(&self, obj: O) -> Result { @@ -1338,7 +1295,7 @@ impl ReadDoc for Automerge { heads: &[ChangeHash], ) -> Result { let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads)?; + let clock = self.clock_at(heads); let query = self.ops.search(&obj, query::ListValsAt::new(clock)); let mut buffer = String::new(); for q in &query.ops { @@ -1413,7 +1370,7 @@ impl ReadDoc for Automerge { ) -> Result, ExId)>, AutomergeError> { let prop = prop.into(); let obj = self.exid_to_obj(obj.as_ref())?.0; - let clock = self.clock_at(heads)?; + let clock = self.clock_at(heads); let result = match prop { Prop::Map(p) => { let prop = self.ops.m.props.lookup(&p); diff --git a/rust/automerge/src/change_graph.rs b/rust/automerge/src/change_graph.rs new file mode 100644 index 00000000..01d269d8 --- /dev/null +++ b/rust/automerge/src/change_graph.rs @@ -0,0 +1,344 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{ + clock::{Clock, ClockData}, + Change, ChangeHash, +}; + +/// The graph of changes +/// +/// This is a sort of adjacency list based representation, except that instead of using linked +/// lists, we keep all the edges and nodes in two vecs and reference them by index which plays nice +/// with the cache +#[derive(Debug, Clone)] +pub(crate) struct ChangeGraph { + nodes: Vec, + edges: Vec, + hashes: Vec, + nodes_by_hash: BTreeMap, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct NodeIdx(u32); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct EdgeIdx(u32); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct HashIdx(u32); + +#[derive(Debug, Clone)] +struct Edge { + // Edges are always child -> parent so we only store the target, the child is implicit + // as you get the edge from the child + target: NodeIdx, + next: Option, +} + +#[derive(Debug, Clone)] +struct ChangeNode { + hash_idx: HashIdx, + actor_index: usize, + seq: u64, + max_op: u64, + parents: Option, +} + +impl ChangeGraph { + pub(crate) fn new() -> Self { + Self { + nodes: Vec::new(), + edges: Vec::new(), + nodes_by_hash: BTreeMap::new(), + hashes: Vec::new(), + } + } + + pub(crate) fn add_change( + &mut self, + change: &Change, + actor_idx: usize, + ) -> Result<(), MissingDep> { + let hash = change.hash(); + if self.nodes_by_hash.contains_key(&hash) { + return Ok(()); + } + let parent_indices = change + .deps() + .iter() + .map(|h| self.nodes_by_hash.get(h).copied().ok_or(MissingDep(*h))) + .collect::, _>>()?; + let node_idx = self.add_node(actor_idx, change); + self.nodes_by_hash.insert(hash, node_idx); + for parent_idx in parent_indices { + self.add_parent(node_idx, parent_idx); + } + Ok(()) + } + + fn add_node(&mut self, actor_index: usize, change: &Change) -> NodeIdx { + let idx = NodeIdx(self.nodes.len() as u32); + let hash_idx = self.add_hash(change.hash()); + self.nodes.push(ChangeNode { + hash_idx, + actor_index, + seq: change.seq(), + max_op: change.max_op(), + parents: None, + }); + idx + } + + fn add_hash(&mut self, hash: ChangeHash) -> HashIdx { + let idx = HashIdx(self.hashes.len() as u32); + self.hashes.push(hash); + idx + } + + fn add_parent(&mut self, child_idx: NodeIdx, parent_idx: NodeIdx) { + let new_edge_idx = EdgeIdx(self.edges.len() as u32); + let new_edge = Edge { + target: parent_idx, + next: None, + }; + self.edges.push(new_edge); + + let child = &mut self.nodes[child_idx.0 as usize]; + if let Some(edge_idx) = child.parents { + let mut edge = &mut self.edges[edge_idx.0 as usize]; + while let Some(next) = edge.next { + edge = &mut self.edges[next.0 as usize]; + } + edge.next = Some(new_edge_idx); + } else { + child.parents = Some(new_edge_idx); + } + } + + fn parents(&self, node_idx: NodeIdx) -> impl Iterator + '_ { + let mut edge_idx = self.nodes[node_idx.0 as usize].parents; + std::iter::from_fn(move || { + let this_edge_idx = edge_idx?; + let edge = &self.edges[this_edge_idx.0 as usize]; + edge_idx = edge.next; + Some(edge.target) + }) + } + + pub(crate) fn clock_for_heads(&self, heads: &[ChangeHash]) -> Clock { + let mut clock = Clock::new(); + + self.traverse_ancestors(heads, |node, _hash| { + clock.include( + node.actor_index, + ClockData { + max_op: node.max_op, + seq: node.seq, + }, + ); + }); + + clock + } + + pub(crate) fn remove_ancestors( + &self, + changes: &mut BTreeSet, + heads: &[ChangeHash], + ) { + self.traverse_ancestors(heads, |_node, hash| { + changes.remove(hash); + }); + } + + /// Call `f` for each (node, hash) in the graph, starting from the given heads + /// + /// No guarantees are made about the order of traversal but each node will only be visited + /// once. + fn traverse_ancestors( + &self, + heads: &[ChangeHash], + mut f: F, + ) { + let mut to_visit = heads + .iter() + .filter_map(|h| self.nodes_by_hash.get(h)) + .copied() + .collect::>(); + + let mut visited = BTreeSet::new(); + + while let Some(idx) = to_visit.pop() { + if visited.contains(&idx) { + continue; + } else { + visited.insert(idx); + } + let node = &self.nodes[idx.0 as usize]; + let hash = &self.hashes[node.hash_idx.0 as usize]; + f(node, hash); + to_visit.extend(self.parents(idx)); + } + } +} + +#[derive(Debug, thiserror::Error)] +#[error("attempted to derive a clock for a change with dependencies we don't have")] +pub struct MissingDep(ChangeHash); + +#[cfg(test)] +mod tests { + use std::{ + num::NonZeroU64, + time::{SystemTime, UNIX_EPOCH}, + }; + + use crate::{ + clock::ClockData, + op_tree::OpSetMetadata, + storage::{change::ChangeBuilder, convert::op_as_actor_id}, + types::{Key, ObjId, Op, OpId, OpIds}, + ActorId, + }; + + use super::*; + + #[test] + fn clock_by_heads() { + let mut builder = TestGraphBuilder::new(); + let actor1 = builder.actor(); + let actor2 = builder.actor(); + let actor3 = builder.actor(); + let change1 = builder.change(&actor1, 10, &[]); + let change2 = builder.change(&actor2, 20, &[change1]); + let change3 = builder.change(&actor3, 30, &[change1]); + let change4 = builder.change(&actor1, 10, &[change2, change3]); + let graph = builder.build(); + + let mut expected_clock = Clock::new(); + expected_clock.include(builder.index(&actor1), ClockData { max_op: 50, seq: 2 }); + expected_clock.include(builder.index(&actor2), ClockData { max_op: 30, seq: 1 }); + expected_clock.include(builder.index(&actor3), ClockData { max_op: 40, seq: 1 }); + + let clock = graph.clock_for_heads(&[change4]); + assert_eq!(clock, expected_clock); + } + + #[test] + fn remove_ancestors() { + let mut builder = TestGraphBuilder::new(); + let actor1 = builder.actor(); + let actor2 = builder.actor(); + let actor3 = builder.actor(); + let change1 = builder.change(&actor1, 10, &[]); + let change2 = builder.change(&actor2, 20, &[change1]); + let change3 = builder.change(&actor3, 30, &[change1]); + let change4 = builder.change(&actor1, 10, &[change2, change3]); + let graph = builder.build(); + + let mut changes = vec![change1, change2, change3, change4] + .into_iter() + .collect::>(); + let heads = vec![change2]; + graph.remove_ancestors(&mut changes, &heads); + + let expected_changes = vec![change3, change4].into_iter().collect::>(); + + assert_eq!(changes, expected_changes); + } + + struct TestGraphBuilder { + actors: Vec, + changes: Vec, + seqs_by_actor: BTreeMap, + } + + impl TestGraphBuilder { + fn new() -> Self { + TestGraphBuilder { + actors: Vec::new(), + changes: Vec::new(), + seqs_by_actor: BTreeMap::new(), + } + } + + fn actor(&mut self) -> ActorId { + let actor = ActorId::random(); + self.actors.push(actor.clone()); + actor + } + + fn index(&self, actor: &ActorId) -> usize { + self.actors.iter().position(|a| a == actor).unwrap() + } + + /// Create a change with `num_new_ops` and `parents` for `actor` + /// + /// The `start_op` and `seq` of the change will be computed from the + /// previous changes for the same actor. + fn change( + &mut self, + actor: &ActorId, + num_new_ops: usize, + parents: &[ChangeHash], + ) -> ChangeHash { + let mut meta = OpSetMetadata::from_actors(self.actors.clone()); + let key = meta.props.cache("key".to_string()); + + let start_op = parents + .iter() + .map(|c| { + self.changes + .iter() + .find(|change| change.hash() == *c) + .unwrap() + .max_op() + }) + .max() + .unwrap_or(0) + + 1; + + let actor_idx = self.index(actor); + let ops = (0..num_new_ops) + .map(|opnum| Op { + id: OpId::new(start_op + opnum as u64, actor_idx), + action: crate::OpType::Put("value".into()), + key: Key::Map(key), + succ: OpIds::empty(), + pred: OpIds::empty(), + insert: false, + }) + .collect::>(); + + let root = ObjId::root(); + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + let seq = self.seqs_by_actor.entry(actor.clone()).or_insert(1); + let change = Change::new( + ChangeBuilder::new() + .with_dependencies(parents.to_vec()) + .with_start_op(NonZeroU64::new(start_op).unwrap()) + .with_actor(actor.clone()) + .with_seq(*seq) + .with_timestamp(timestamp) + .build(ops.iter().map(|op| op_as_actor_id(&root, op, &meta))) + .unwrap(), + ); + *seq = seq.checked_add(1).unwrap(); + let hash = change.hash(); + self.changes.push(change); + hash + } + + fn build(&self) -> ChangeGraph { + let mut graph = ChangeGraph::new(); + for change in &self.changes { + let actor_idx = self.index(change.actor_id()); + graph.add_change(change, actor_idx).unwrap(); + } + graph + } + } +} diff --git a/rust/automerge/src/clock.rs b/rust/automerge/src/clock.rs index 79125323..64d00fcf 100644 --- a/rust/automerge/src/clock.rs +++ b/rust/automerge/src/clock.rs @@ -71,12 +71,6 @@ impl Clock { self.0.get(actor_index) } - pub(crate) fn merge(&mut self, other: &Self) { - for (actor, data) in &other.0 { - self.include(*actor, *data); - } - } - fn is_greater(&self, other: &Self) -> bool { let mut has_greater = false; diff --git a/rust/automerge/src/clocks.rs b/rust/automerge/src/clocks.rs deleted file mode 100644 index 60fc5c71..00000000 --- a/rust/automerge/src/clocks.rs +++ /dev/null @@ -1,44 +0,0 @@ -use crate::{ - clock::{Clock, ClockData}, - Change, ChangeHash, -}; -use std::collections::HashMap; - -pub(crate) struct Clocks(HashMap); - -#[derive(Debug, thiserror::Error)] -#[error("attempted to derive a clock for a change with dependencies we don't have")] -pub struct MissingDep(ChangeHash); - -impl Clocks { - pub(crate) fn new() -> Self { - Self(HashMap::new()) - } - - pub(crate) fn add_change( - &mut self, - change: &Change, - actor_index: usize, - ) -> Result<(), MissingDep> { - let mut clock = Clock::new(); - for hash in change.deps() { - let c = self.0.get(hash).ok_or(MissingDep(*hash))?; - clock.merge(c); - } - clock.include( - actor_index, - ClockData { - max_op: change.max_op(), - seq: change.seq(), - }, - ); - self.0.insert(change.hash(), clock); - Ok(()) - } -} - -impl From for HashMap { - fn from(c: Clocks) -> Self { - c.0 - } -} diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 0f024d86..57a87167 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -7,7 +7,7 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum AutomergeError { #[error(transparent)] - Clocks(#[from] crate::clocks::MissingDep), + ChangeGraph(#[from] crate::change_graph::MissingDep), #[error("failed to load compressed data: {0}")] Deflate(#[source] std::io::Error), #[error("duplicate seq {0} found for actor {1}")] diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index 0b4cd743..fb8a3793 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -244,8 +244,8 @@ mod autocommit; mod automerge; mod autoserde; mod change; +mod change_graph; mod clock; -mod clocks; mod columnar; mod convert; mod error; From c5fde2802f8dfeaadd2394942d1deebbb7a590d7 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Fri, 3 Feb 2023 15:53:09 +0000 Subject: [PATCH 30/45] @automerge/automerge-wasm@0.1.24 and @automerge/automerge@2.0.2-alpha.1 --- javascript/package.json | 4 ++-- rust/automerge-wasm/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/package.json b/javascript/package.json index 017c5a54..8712920c 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.1", + "version": "2.0.2-alpha.1", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -47,7 +47,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.23", + "@automerge/automerge-wasm": "0.1.24", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index cce3199f..57354ce1 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.23", + "version": "0.1.24", "license": "MIT", "files": [ "README.md", From a24d536d16f2adeea7bbdf094402665a80f400ab Mon Sep 17 00:00:00 2001 From: Alex Good Date: Sat, 4 Feb 2023 14:05:10 +0000 Subject: [PATCH 31/45] Move automerge::SequenceTree to automerge_wasm::SequenceTree The `SequenceTree` is only ever used in `automerge_wasm` so move it there. --- rust/automerge-wasm/Cargo.toml | 1 + rust/automerge-wasm/src/lib.rs | 1 + rust/automerge-wasm/src/observer.rs | 4 +- .../src/sequence_tree.rs | 81 +++---------------- rust/automerge/src/lib.rs | 3 - 5 files changed, 14 insertions(+), 76 deletions(-) rename rust/{automerge => automerge-wasm}/src/sequence_tree.rs (87%) diff --git a/rust/automerge-wasm/Cargo.toml b/rust/automerge-wasm/Cargo.toml index 3d2fafe4..b6055a7d 100644 --- a/rust/automerge-wasm/Cargo.toml +++ b/rust/automerge-wasm/Cargo.toml @@ -57,5 +57,6 @@ features = ["console"] [dev-dependencies] futures = "^0.1" +proptest = { version = "^1.0.0", default-features = false, features = ["std"] } wasm-bindgen-futures = "^0.4" wasm-bindgen-test = "^0.3" diff --git a/rust/automerge-wasm/src/lib.rs b/rust/automerge-wasm/src/lib.rs index b53bf3b9..09072ca7 100644 --- a/rust/automerge-wasm/src/lib.rs +++ b/rust/automerge-wasm/src/lib.rs @@ -41,6 +41,7 @@ use wasm_bindgen::JsCast; mod interop; mod observer; +mod sequence_tree; mod sync; mod value; diff --git a/rust/automerge-wasm/src/observer.rs b/rust/automerge-wasm/src/observer.rs index c0b462a6..2351c762 100644 --- a/rust/automerge-wasm/src/observer.rs +++ b/rust/automerge-wasm/src/observer.rs @@ -6,10 +6,12 @@ use crate::{ interop::{self, alloc, js_set}, TextRepresentation, }; -use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, SequenceTree, Value}; +use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, Value}; use js_sys::{Array, Object}; use wasm_bindgen::prelude::*; +use crate::sequence_tree::SequenceTree; + #[derive(Debug, Clone, Default)] pub(crate) struct Observer { enabled: bool, diff --git a/rust/automerge/src/sequence_tree.rs b/rust/automerge-wasm/src/sequence_tree.rs similarity index 87% rename from rust/automerge/src/sequence_tree.rs rename to rust/automerge-wasm/src/sequence_tree.rs index f95ceab3..91b183a2 100644 --- a/rust/automerge/src/sequence_tree.rs +++ b/rust/automerge-wasm/src/sequence_tree.rs @@ -5,10 +5,10 @@ use std::{ }; pub(crate) const B: usize = 16; -pub type SequenceTree = SequenceTreeInternal; +pub(crate) type SequenceTree = SequenceTreeInternal; #[derive(Clone, Debug)] -pub struct SequenceTreeInternal { +pub(crate) struct SequenceTreeInternal { root_node: Option>, } @@ -24,22 +24,17 @@ where T: Clone + Debug, { /// Construct a new, empty, sequence. - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { root_node: None } } /// Get the length of the sequence. - pub fn len(&self) -> usize { + pub(crate) fn len(&self) -> usize { self.root_node.as_ref().map_or(0, |n| n.len()) } - /// Check if the sequence is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - /// Create an iterator through the sequence. - pub fn iter(&self) -> Iter<'_, T> { + pub(crate) fn iter(&self) -> Iter<'_, T> { Iter { inner: self, index: 0, @@ -51,7 +46,7 @@ where /// # Panics /// /// Panics if `index > len`. - pub fn insert(&mut self, index: usize, element: T) { + pub(crate) fn insert(&mut self, index: usize, element: T) { let old_len = self.len(); if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] @@ -94,27 +89,22 @@ where } /// Push the `element` onto the back of the sequence. - pub fn push(&mut self, element: T) { + pub(crate) fn push(&mut self, element: T) { let l = self.len(); self.insert(l, element) } /// Get the `element` at `index` in the sequence. - pub fn get(&self, index: usize) -> Option<&T> { + pub(crate) fn get(&self, index: usize) -> Option<&T> { self.root_node.as_ref().and_then(|n| n.get(index)) } - /// Get the `element` at `index` in the sequence. - pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { - self.root_node.as_mut().and_then(|n| n.get_mut(index)) - } - /// Removes the element at `index` from the sequence. /// /// # Panics /// /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { + pub(crate) fn remove(&mut self, index: usize) -> T { if let Some(root) = self.root_node.as_mut() { #[cfg(debug_assertions)] let len = root.check(); @@ -135,15 +125,6 @@ where panic!("remove from empty tree") } } - - /// Update the `element` at `index` in the sequence, returning the old value. - /// - /// # Panics - /// - /// Panics if `index > len` - pub fn set(&mut self, index: usize, element: T) -> T { - self.root_node.as_mut().unwrap().set(index, element) - } } impl SequenceTreeNode @@ -432,30 +413,6 @@ where assert!(self.is_full()); } - pub(crate) fn set(&mut self, index: usize, element: T) -> T { - if self.is_leaf() { - let old_element = self.elements.get_mut(index).unwrap(); - mem::replace(old_element, element) - } else { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter_mut().enumerate() { - match (cumulative_len + child.len()).cmp(&index) { - Ordering::Less => { - cumulative_len += child.len() + 1; - } - Ordering::Equal => { - let old_element = self.elements.get_mut(child_index).unwrap(); - return mem::replace(old_element, element); - } - Ordering::Greater => { - return child.set(index - cumulative_len, element); - } - } - } - panic!("Invalid index to set: {} but len was {}", index, self.len()) - } - } - pub(crate) fn get(&self, index: usize) -> Option<&T> { if self.is_leaf() { return self.elements.get(index); @@ -475,26 +432,6 @@ where } None } - - pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> { - if self.is_leaf() { - return self.elements.get_mut(index); - } else { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter_mut().enumerate() { - match (cumulative_len + child.len()).cmp(&index) { - Ordering::Less => { - cumulative_len += child.len() + 1; - } - Ordering::Equal => return self.elements.get_mut(child_index), - Ordering::Greater => { - return child.get_mut(index - cumulative_len); - } - } - } - } - None - } } impl Default for SequenceTreeInternal diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index fb8a3793..cbb535af 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -264,7 +264,6 @@ mod op_tree; mod parents; mod query; mod read; -mod sequence_tree; mod storage; pub mod sync; pub mod transaction; @@ -294,8 +293,6 @@ pub use op_observer::Patch; pub use op_observer::VecOpObserver; pub use parents::{Parent, Parents}; pub use read::ReadDoc; -#[doc(hidden)] -pub use sequence_tree::SequenceTree; pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding}; pub use value::{ScalarValue, Value}; pub use values::Values; From 11f063cbfe71bb81d849baca89f5eba8d441d594 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Thu, 9 Feb 2023 11:06:08 +0000 Subject: [PATCH 32/45] Remove nightly from CI --- .github/workflows/ci.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c2d469d5..bfa31bd5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -137,8 +137,6 @@ jobs: matrix: toolchain: - 1.66.0 - - nightly - continue-on-error: ${{ matrix.toolchain == 'nightly' }} steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 From 2cd7427f35e3b9b4a6b4d22d21dd083872015b57 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Jan 2023 14:51:02 -0700 Subject: [PATCH 33/45] Use our leb128 parser for values This ensures that values in automerge documents are encoded correctly, and that no extra data is smuggled in any LEB fields. --- .../src/columnar/column_range/value.rs | 62 +++++++++--------- rust/automerge/src/columnar/encoding.rs | 2 + ...counter_value_has_incorrect_meta.automerge | Bin 0 -> 63 bytes .../fixtures/counter_value_is_ok.automerge | Bin 0 -> 63 bytes .../counter_value_is_overlong.automerge | Bin 0 -> 63 bytes rust/automerge/tests/test.rs | 14 ++++ 6 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge create mode 100644 rust/automerge/tests/fixtures/counter_value_is_ok.automerge create mode 100644 rust/automerge/tests/fixtures/counter_value_is_overlong.automerge diff --git a/rust/automerge/src/columnar/column_range/value.rs b/rust/automerge/src/columnar/column_range/value.rs index 43f63437..03a5aa60 100644 --- a/rust/automerge/src/columnar/column_range/value.rs +++ b/rust/automerge/src/columnar/column_range/value.rs @@ -4,10 +4,15 @@ use crate::{ columnar::{ encoding::{ leb128::{lebsize, ulebsize}, - raw, DecodeColumnError, RawBytes, RawDecoder, RawEncoder, RleDecoder, RleEncoder, Sink, + raw, DecodeColumnError, DecodeError, RawBytes, RawDecoder, RawEncoder, RleDecoder, + RleEncoder, Sink, }, SpliceError, }, + storage::parse::{ + leb128::{leb128_i64, leb128_u64}, + Input, ParseResult, + }, ScalarValue, }; @@ -217,18 +222,8 @@ impl<'a> Iterator for ValueIter<'a> { ValueType::Null => Some(Ok(ScalarValue::Null)), ValueType::True => Some(Ok(ScalarValue::Boolean(true))), ValueType::False => Some(Ok(ScalarValue::Boolean(false))), - ValueType::Uleb => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::unsigned(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Uint(val)) - }), - ValueType::Leb => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Int(val)) - }), + ValueType::Uleb => self.parse_input(val_meta, leb128_u64), + ValueType::Leb => self.parse_input(val_meta, leb128_i64), ValueType::String => self.parse_raw(val_meta, |bytes| { let val = std::str::from_utf8(bytes) .map_err(|e| DecodeColumnError::invalid_value("value", e.to_string()))? @@ -250,17 +245,11 @@ impl<'a> Iterator for ValueIter<'a> { let val = f64::from_le_bytes(raw); Ok(ScalarValue::F64(val)) }), - ValueType::Counter => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Counter(val.into())) + ValueType::Counter => self.parse_input(val_meta, |input| { + leb128_i64(input).map(|(i, n)| (i, ScalarValue::Counter(n.into()))) }), - ValueType::Timestamp => self.parse_raw(val_meta, |mut bytes| { - let val = leb128::read::signed(&mut bytes).map_err(|e| { - DecodeColumnError::invalid_value("value", e.to_string()) - })?; - Ok(ScalarValue::Timestamp(val)) + ValueType::Timestamp => self.parse_input(val_meta, |input| { + leb128_i64(input).map(|(i, n)| (i, ScalarValue::Timestamp(n))) }), ValueType::Unknown(code) => self.parse_raw(val_meta, |bytes| { Ok(ScalarValue::Unknown { @@ -284,8 +273,8 @@ impl<'a> Iterator for ValueIter<'a> { } impl<'a> ValueIter<'a> { - fn parse_raw Result>( - &mut self, + fn parse_raw<'b, R, F: Fn(&'b [u8]) -> Result>( + &'b mut self, meta: ValueMeta, f: F, ) -> Option> { @@ -298,11 +287,24 @@ impl<'a> ValueIter<'a> { } Ok(bytes) => bytes, }; - let val = match f(raw) { - Ok(v) => v, - Err(e) => return Some(Err(e)), - }; - Some(Ok(val)) + Some(f(raw)) + } + + fn parse_input<'b, R, F: Fn(Input<'b>) -> ParseResult<'b, R, DecodeError>>( + &'b mut self, + meta: ValueMeta, + f: F, + ) -> Option> + where + R: Into, + { + self.parse_raw(meta, |raw| match f(Input::new(raw)) { + Err(e) => Err(DecodeColumnError::invalid_value("value", e.to_string())), + Ok((i, _)) if !i.is_empty() => { + Err(DecodeColumnError::invalid_value("value", "extra bytes")) + } + Ok((_, v)) => Ok(v.into()), + }) } pub(crate) fn done(&self) -> bool { diff --git a/rust/automerge/src/columnar/encoding.rs b/rust/automerge/src/columnar/encoding.rs index bbdb34a8..c9435448 100644 --- a/rust/automerge/src/columnar/encoding.rs +++ b/rust/automerge/src/columnar/encoding.rs @@ -46,6 +46,8 @@ pub(crate) enum DecodeError { FromInt(#[from] std::num::TryFromIntError), #[error("bad leb128")] BadLeb(#[from] ::leb128::read::Error), + #[error(transparent)] + BadLeb128(#[from] crate::storage::parse::leb128::Error), #[error("attempted to allocate {attempted} which is larger than the maximum of {maximum}")] OverlargeAllocation { attempted: usize, maximum: usize }, #[error("invalid string encoding")] diff --git a/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge b/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2290b446ca661f302f6591c522a6653ba0be54a6 GIT binary patch literal 63 zcmZq8_iDCFPJPB`${^6qmb+L*-z{NbN`A*m!H-iI8Mkb^bm5T!0|T2Vvk9XUQy5b? TQvp*wVH2@I&u}A*O5KaD{l&S)MXnSh`0lxRq(Bd!v00tEUGyy^a VRsvT7Z~}h;VF7;ue<;uoe*j$F7aafq literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge b/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge new file mode 100644 index 0000000000000000000000000000000000000000..831346f7f4109e2f292e502e13b326ca2485b351 GIT binary patch literal 63 zcmZq8_iD~Rd#9GsltG}IEqAeszFWe=l>CmBf*+?aGH%&+>B1ue1_m}!W)nsyrZA>( TrUIsV#ze+?#(Iql_4Nz@=B*VY literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index ca6c64c0..191ce2f9 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1412,6 +1412,20 @@ fn fuzz_crashers() { } } +fn fixture(name: &str) -> Vec { + fs::read("./tests/fixtures/".to_owned() + name).unwrap() +} + +#[test] +fn overlong_leb() { + // the value metadata says "2", but the LEB is only 1-byte long and there's an extra 0 + assert!(Automerge::load(&fixture("counter_value_has_incorrect_meta.automerge")).is_err()); + // the LEB is overlong (using 2 bytes where one would have sufficed) + assert!(Automerge::load(&fixture("counter_value_is_overlong.automerge")).is_err()); + // the LEB is correct + assert!(Automerge::load(&fixture("counter_value_is_ok.automerge")).is_ok()); +} + #[test] fn negative_64() { let mut doc = Automerge::new(); From 5e82dbc3c83c2336ca675ba8f167db5dba9b17cb Mon Sep 17 00:00:00 2001 From: Orion Henry Date: Mon, 13 Feb 2023 21:17:27 -0600 Subject: [PATCH 34/45] rework how skip works to push the logic into node --- javascript/test/basic_test.ts | 16 +++++ rust/automerge/src/op_tree/node.rs | 68 +++++++++++-------- rust/automerge/src/query/prop.rs | 47 ++----------- rust/automerge/src/query/seek_op.rs | 39 ++--------- .../automerge/src/query/seek_op_with_patch.rs | 38 +---------- 5 files changed, 67 insertions(+), 141 deletions(-) diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 5aa1ac34..0e30dc7c 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -58,6 +58,22 @@ describe("Automerge", () => { }) }) + it("should be able to insert and delete a large number of properties", () => { + let doc = Automerge.init() + + doc = Automerge.change(doc, doc => { + doc['k1'] = true; + }); + + for (let idx = 1; idx <= 200; idx++) { + doc = Automerge.change(doc, doc => { + delete doc['k' + idx]; + doc['k' + (idx + 1)] = true; + assert(Object.keys(doc).length == 1) + }); + } + }) + it("can detect an automerge doc with isAutomerge()", () => { const doc1 = Automerge.from({ sub: { object: true } }) assert(Automerge.isAutomerge(doc1)) diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index ea7fbf48..8f2de662 100644 --- a/rust/automerge/src/op_tree/node.rs +++ b/rust/automerge/src/op_tree/node.rs @@ -27,50 +27,67 @@ impl OpTreeNode { } } + fn search_element<'a, 'b: 'a, Q>( + &'b self, + query: &mut Q, + m: &OpSetMetadata, + ops: &'a [Op], + index: usize, + ) -> bool + where + Q: TreeQuery<'a>, + { + if let Some(e) = self.elements.get(index) { + if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { + return true; + } + } + false + } + pub(crate) fn search<'a, 'b: 'a, Q>( &'b self, query: &mut Q, m: &OpSetMetadata, ops: &'a [Op], - skip: Option, + mut skip: Option, ) -> bool where Q: TreeQuery<'a>, { if self.is_leaf() { - let skip = skip.unwrap_or(0); - for e in self.elements.iter().skip(skip) { + for e in self.elements.iter().skip(skip.unwrap_or(0)) { if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish { return true; } } false } else { - let mut skip = skip.unwrap_or(0); for (child_index, child) in self.children.iter().enumerate() { - match skip.cmp(&child.len()) { - Ordering::Greater => { - // not in this child at all - // take off the number of elements in the child as well as the next element - skip -= child.len() + 1; + match skip { + Some(n) if n > child.len() => { + skip = Some(n - child.len() - 1); } - Ordering::Equal => { - // just try the element - skip -= child.len(); - if let Some(e) = self.elements.get(child_index) { - if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish - { - return true; - } + Some(n) if n == child.len() => { + skip = None; + if self.search_element(query, m, ops, child_index) { + return true; } } - Ordering::Less => { + Some(n) => { + if child.search(query, m, ops, Some(n)) { + return true; + } + skip = Some(0); // important to not be None so we never call query_node again + if self.search_element(query, m, ops, child_index) { + return true; + } + } + None => { // descend and try find it match query.query_node_with_metadata(child, m, ops) { QueryResult::Descend => { - // search in the child node, passing in the number of items left to - // skip - if child.search(query, m, ops, Some(skip)) { + if child.search(query, m, ops, None) { return true; } } @@ -78,14 +95,9 @@ impl OpTreeNode { QueryResult::Next => (), QueryResult::Skip(_) => panic!("had skip from non-root node"), } - if let Some(e) = self.elements.get(child_index) { - if query.query_element_with_metadata(&ops[*e], m) == QueryResult::Finish - { - return true; - } + if self.search_element(query, m, ops, child_index) { + return true; } - // reset the skip to zero so we continue iterating normally - skip = 0; } } } diff --git a/rust/automerge/src/query/prop.rs b/rust/automerge/src/query/prop.rs index f6062ec6..d2a11361 100644 --- a/rust/automerge/src/query/prop.rs +++ b/rust/automerge/src/query/prop.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, ListEncoding, Op}; +use crate::types::{Key, Op}; use std::fmt::Debug; #[derive(Debug, Clone, PartialEq)] @@ -9,15 +9,6 @@ pub(crate) struct Prop<'a> { pub(crate) ops: Vec<&'a Op>, pub(crate) ops_pos: Vec, pub(crate) pos: usize, - start: Option, -} - -#[derive(Debug, Clone, PartialEq)] -struct Start { - /// The index to start searching for in the optree - idx: usize, - /// The total length of the optree - optree_len: usize, } impl<'a> Prop<'a> { @@ -27,7 +18,6 @@ impl<'a> Prop<'a> { ops: vec![], ops_pos: vec![], pos: 0, - start: None, } } } @@ -39,38 +29,9 @@ impl<'a> TreeQuery<'a> for Prop<'a> { m: &OpSetMetadata, ops: &[Op], ) -> QueryResult { - if let Some(Start { - idx: start, - optree_len, - }) = self.start - { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(ListEncoding::default()) == 0 { - if self.pos + child.len() >= optree_len { - self.pos = optree_len; - QueryResult::Finish - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); - self.start = Some(Start { - idx: start, - optree_len: child.len(), - }); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.key)); + self.pos = start; + QueryResult::Skip(start) } fn query_element(&mut self, op: &'a Op) -> QueryResult { diff --git a/rust/automerge/src/query/seek_op.rs b/rust/automerge/src/query/seek_op.rs index 22d1f58d..2ed875d2 100644 --- a/rust/automerge/src/query/seek_op.rs +++ b/rust/automerge/src/query/seek_op.rs @@ -1,6 +1,6 @@ use crate::op_tree::{OpSetMetadata, OpTreeNode}; use crate::query::{binary_search_by, QueryResult, TreeQuery}; -use crate::types::{Key, ListEncoding, Op, HEAD}; +use crate::types::{Key, Op, HEAD}; use std::cmp::Ordering; use std::fmt::Debug; @@ -14,8 +14,6 @@ pub(crate) struct SeekOp<'a> { pub(crate) succ: Vec, /// whether a position has been found found: bool, - /// The found start position of the key if there is one yet (for map objects). - start: Option, } impl<'a> SeekOp<'a> { @@ -25,7 +23,6 @@ impl<'a> SeekOp<'a> { succ: vec![], pos: 0, found: false, - start: None, } } @@ -72,37 +69,9 @@ impl<'a> TreeQuery<'a> for SeekOp<'a> { } } Key::Map(_) => { - if let Some(start) = self.start { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(ListEncoding::List) == 0 { - let child_contains_key = - child.elements.iter().any(|e| ops[*e].key == self.op.key); - if !child_contains_key { - // If we are in a node which has no visible ops, but none of the - // elements of the node match the key of the op, then we must have - // finished processing and so we can just return. - // See https://github.com/automerge/automerge-rs/pull/480 - QueryResult::Finish - } else { - // Otherwise, we need to proceed to the next node - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.start = Some(start); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.pos = start; + QueryResult::Skip(start) } } } diff --git a/rust/automerge/src/query/seek_op_with_patch.rs b/rust/automerge/src/query/seek_op_with_patch.rs index 7cacb032..cd30f5bb 100644 --- a/rust/automerge/src/query/seek_op_with_patch.rs +++ b/rust/automerge/src/query/seek_op_with_patch.rs @@ -16,8 +16,6 @@ pub(crate) struct SeekOpWithPatch<'a> { last_seen: Option, pub(crate) values: Vec<&'a Op>, pub(crate) had_value_before: bool, - /// The found start position of the key if there is one yet (for map objects). - start: Option, } impl<'a> SeekOpWithPatch<'a> { @@ -33,7 +31,6 @@ impl<'a> SeekOpWithPatch<'a> { last_seen: None, values: vec![], had_value_before: false, - start: None, } } @@ -132,38 +129,9 @@ impl<'a> TreeQuery<'a> for SeekOpWithPatch<'a> { // Updating a map: operations appear in sorted order by key Key::Map(_) => { - if let Some(start) = self.start { - if self.pos + child.len() >= start { - // skip empty nodes - if child.index.visible_len(self.encoding) == 0 { - let child_contains_key = - child.elements.iter().any(|e| ops[*e].key == self.op.key); - if !child_contains_key { - // If we are in a node which has no visible ops, but none of the - // elements of the node match the key of the op, then we must have - // finished processing and so we can just return. - // See https://github.com/automerge/automerge-rs/pull/480 - QueryResult::Finish - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - QueryResult::Descend - } - } else { - self.pos += child.len(); - QueryResult::Next - } - } else { - // in the root node find the first op position for the key - // Search for the place where we need to insert the new operation. First find the - // first op with a key >= the key we're updating - let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); - self.start = Some(start); - self.pos = start; - QueryResult::Skip(start) - } + let start = binary_search_by(child, ops, |op| m.key_cmp(&op.key, &self.op.key)); + self.pos = start; + QueryResult::Skip(start) } } } From 9271b20cf5442369f21dec43ebeed097e8092da8 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 16:24:25 +0000 Subject: [PATCH 35/45] Correct logic when skip = B and fix formatting A few tests were failing which exposed the fact that if skip is `B` (the out factor of the OpTree) then we set `skip = None` and this causes us to attempt to return `Skip` in a non root node. I ported the failing test from JS to Rust and fixed the problem. I also fixed the formatting issues. --- javascript/test/basic_test.ts | 10 +++---- rust/automerge-wasm/test/test.ts | 2 +- rust/automerge/src/op_tree/node.rs | 4 +-- rust/automerge/src/sync.rs | 45 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 0e30dc7c..e34484c4 100644 --- a/javascript/test/basic_test.ts +++ b/javascript/test/basic_test.ts @@ -62,15 +62,15 @@ describe("Automerge", () => { let doc = Automerge.init() doc = Automerge.change(doc, doc => { - doc['k1'] = true; - }); + doc["k1"] = true + }) for (let idx = 1; idx <= 200; idx++) { doc = Automerge.change(doc, doc => { - delete doc['k' + idx]; - doc['k' + (idx + 1)] = true; + delete doc["k" + idx] + doc["k" + (idx + 1)] = true assert(Object.keys(doc).length == 1) - }); + }) } }) diff --git a/rust/automerge-wasm/test/test.ts b/rust/automerge-wasm/test/test.ts index 56aaae74..bb4f71e3 100644 --- a/rust/automerge-wasm/test/test.ts +++ b/rust/automerge-wasm/test/test.ts @@ -1447,7 +1447,7 @@ describe('Automerge', () => { sync(n1, n2, s1, s2) // Having n3's last change concurrent to the last sync heads forces us into the slower code path - const change3 = n2.getLastLocalChange() + const change3 = n3.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") n2.applyChanges([change3]) n1.put("_root", "n1", "final"); n1.commit("", 0) diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index 8f2de662..ed1b7646 100644 --- a/rust/automerge/src/op_tree/node.rs +++ b/rust/automerge/src/op_tree/node.rs @@ -69,7 +69,7 @@ impl OpTreeNode { skip = Some(n - child.len() - 1); } Some(n) if n == child.len() => { - skip = None; + skip = Some(0); // important to not be None so we never call query_node again if self.search_element(query, m, ops, child_index) { return true; } @@ -78,7 +78,7 @@ impl OpTreeNode { if child.search(query, m, ops, Some(n)) { return true; } - skip = Some(0); // important to not be None so we never call query_node again + skip = Some(0); // important to not be None so we never call query_node again if self.search_element(query, m, ops, child_index) { return true; } diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index d3b6b3fa..d6dc2580 100644 --- a/rust/automerge/src/sync.rs +++ b/rust/automerge/src/sync.rs @@ -887,6 +887,51 @@ mod tests { assert_eq!(doc2.get_heads(), all_heads); } + #[test] + fn should_handle_lots_of_branching_and_merging() { + let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("01234567").unwrap()); + let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("89abcdef").unwrap()); + let mut doc3 = crate::AutoCommit::new().with_actor(ActorId::try_from("fedcba98").unwrap()); + let mut s1 = State::new(); + let mut s2 = State::new(); + + doc1.put(crate::ROOT, "x", 0).unwrap(); + let change1 = doc1.get_last_local_change().unwrap().clone(); + + doc2.apply_changes([change1.clone()]).unwrap(); + doc3.apply_changes([change1]).unwrap(); + + doc3.put(crate::ROOT, "x", 1).unwrap(); + + //// - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 + //// / \/ \/ \/ + //// / /\ /\ /\ + //// c0 <---- n2c1 <------ n2c2 <------ n2c3 <-- etc. <-- n2c20 <------ n2c21 + //// \ / + //// ---------------------------------------------- n3c1 <----- + for i in 1..20 { + doc1.put(crate::ROOT, "n1", i).unwrap(); + doc2.put(crate::ROOT, "n2", i).unwrap(); + let change1 = doc1.get_last_local_change().unwrap().clone(); + let change2 = doc2.get_last_local_change().unwrap().clone(); + doc1.apply_changes([change2.clone()]).unwrap(); + doc2.apply_changes([change1]).unwrap(); + } + + sync(&mut doc1, &mut doc2, &mut s1, &mut s2); + + //// Having n3's last change concurrent to the last sync heads forces us into the slower code path + let change3 = doc3.get_last_local_change().unwrap().clone(); + doc2.apply_changes([change3]).unwrap(); + + doc1.put(crate::ROOT, "n1", "final").unwrap(); + doc2.put(crate::ROOT, "n1", "final").unwrap(); + + sync(&mut doc1, &mut doc2, &mut s1, &mut s2); + + assert_eq!(doc1.get_heads(), doc2.get_heads()); + } + fn sync( a: &mut crate::AutoCommit, b: &mut crate::AutoCommit, From c92d042c87eb724e4878a4df0f8d31177c410c01 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 17:25:25 +0000 Subject: [PATCH 36/45] @automerge/automerge-wasm@0.1.24 and @automerge/automerge@2.0.2-alpha.2 --- javascript/package.json | 4 ++-- rust/automerge-wasm/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/package.json b/javascript/package.json index 8712920c..e39f398a 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.2-alpha.1", + "version": "2.0.2-alpha.2", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", @@ -47,7 +47,7 @@ "typescript": "^4.9.4" }, "dependencies": { - "@automerge/automerge-wasm": "0.1.24", + "@automerge/automerge-wasm": "0.1.25", "uuid": "^9.0.0" } } diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 57354ce1..80b39fd4 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -8,7 +8,7 @@ "description": "wasm-bindgen bindings to the automerge rust implementation", "homepage": "https://github.com/automerge/automerge-rs/tree/main/automerge-wasm", "repository": "github:automerge/automerge-rs", - "version": "0.1.24", + "version": "0.1.25", "license": "MIT", "files": [ "README.md", From 1425af43cdcd61295e0e65bf47fbce0076353682 Mon Sep 17 00:00:00 2001 From: Alex Good Date: Tue, 14 Feb 2023 19:47:53 +0000 Subject: [PATCH 37/45] @automerge/automerge@2.0.2 --- javascript/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/package.json b/javascript/package.json index e39f398a..79309907 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -4,7 +4,7 @@ "Orion Henry ", "Martin Kleppmann" ], - "version": "2.0.2-alpha.2", + "version": "2.0.2", "description": "Javascript implementation of automerge, backed by @automerge/automerge-wasm", "homepage": "https://github.com/automerge/automerge-rs/tree/main/wrappers/javascript", "repository": "github:automerge/automerge-rs", From 407faefa6e838abe0bd8526716c98eab592aa123 Mon Sep 17 00:00:00 2001 From: Philip Schatz <253202+philschatz@users.noreply.github.com> Date: Wed, 15 Feb 2023 03:23:02 -0600 Subject: [PATCH 38/45] A few setup fixes (#529) * include deno in dependencies * install javascript dependencies * remove redundant operation --- README.md | 3 +++ flake.nix | 1 + rust/automerge/src/automerge.rs | 1 - 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 94e1bbb8..76d48ddd 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,9 @@ brew install cmake node cmocka # install yarn npm install --global yarn +# install javascript dependencies +yarn --cwd ./javascript + # install rust dependencies cargo install wasm-bindgen-cli wasm-opt cargo-deny diff --git a/flake.nix b/flake.nix index 4f9ba1fe..37835738 100644 --- a/flake.nix +++ b/flake.nix @@ -54,6 +54,7 @@ nodejs yarn + deno # c deps cmake diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 128d4418..09c3cc9d 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -897,7 +897,6 @@ impl Automerge { .add_change(&change, actor_index) .expect("Change's deps should already be in the document"); - self.history_index.insert(change.hash(), history_index); self.history.push(change); history_index From 8de2fa9bd49e1bf04f2a864b3a57f911419a86ba Mon Sep 17 00:00:00 2001 From: Jason Kankiewicz Date: Sat, 25 Feb 2023 10:47:00 -0800 Subject: [PATCH 39/45] C API 2 (#530) 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. --- rust/automerge-c/.clang-format | 250 +++ rust/automerge-c/.gitignore | 8 +- rust/automerge-c/CMakeLists.txt | 344 ++- rust/automerge-c/Cargo.toml | 4 +- rust/automerge-c/README.md | 197 +- rust/automerge-c/cbindgen.toml | 20 +- rust/automerge-c/cmake/Cargo.toml.in | 22 + rust/automerge-c/cmake/cbindgen.toml.in | 48 + rust/automerge-c/cmake/config.h.in | 31 +- .../cmake/enum-string-functions-gen.cmake | 183 ++ ...replace.cmake => file-regex-replace.cmake} | 4 +- .../{file_touch.cmake => file-touch.cmake} | 4 +- rust/automerge-c/docs/CMakeLists.txt | 35 + rust/automerge-c/{ => docs}/img/brandmark.png | Bin rust/automerge-c/examples/CMakeLists.txt | 20 +- rust/automerge-c/examples/README.md | 2 +- rust/automerge-c/examples/quickstart.c | 195 +- .../include/automerge-c/utils/result.h | 30 + .../include/automerge-c/utils/stack.h | 130 ++ .../automerge-c/utils/stack_callback_data.h | 53 + .../include/automerge-c/utils/string.h | 29 + rust/automerge-c/src/CMakeLists.txt | 250 --- rust/automerge-c/src/actor_id.rs | 84 +- rust/automerge-c/src/byte_span.rs | 146 +- rust/automerge-c/src/change.rs | 148 +- rust/automerge-c/src/change_hashes.rs | 400 ---- rust/automerge-c/src/changes.rs | 399 ---- rust/automerge-c/src/doc.rs | 607 +++-- rust/automerge-c/src/doc/list.rs | 555 ++--- rust/automerge-c/src/doc/list/item.rs | 97 - rust/automerge-c/src/doc/list/items.rs | 348 --- rust/automerge-c/src/doc/map.rs | 324 +-- rust/automerge-c/src/doc/map/item.rs | 98 - rust/automerge-c/src/doc/map/items.rs | 340 --- rust/automerge-c/src/doc/utils.rs | 27 +- rust/automerge-c/src/index.rs | 84 + rust/automerge-c/src/item.rs | 1963 ++++++++++++++++ rust/automerge-c/src/items.rs | 401 ++++ rust/automerge-c/src/lib.rs | 9 +- rust/automerge-c/src/obj.rs | 86 +- rust/automerge-c/src/obj/item.rs | 73 - rust/automerge-c/src/obj/items.rs | 341 --- rust/automerge-c/src/result.rs | 1039 ++++----- rust/automerge-c/src/result_stack.rs | 156 -- rust/automerge-c/src/strs.rs | 359 --- rust/automerge-c/src/sync.rs | 2 +- rust/automerge-c/src/sync/have.rs | 25 +- rust/automerge-c/src/sync/haves.rs | 378 ---- rust/automerge-c/src/sync/message.rs | 114 +- rust/automerge-c/src/sync/state.rs | 149 +- rust/automerge-c/src/utils/result.c | 33 + rust/automerge-c/src/utils/stack.c | 106 + .../src/utils/stack_callback_data.c | 9 + rust/automerge-c/src/utils/string.c | 46 + rust/automerge-c/test/CMakeLists.txt | 44 +- rust/automerge-c/test/actor_id_tests.c | 145 +- rust/automerge-c/test/base_state.c | 17 + rust/automerge-c/test/base_state.h | 39 + rust/automerge-c/test/byte_span_tests.c | 118 + rust/automerge-c/test/cmocka_utils.c | 88 + rust/automerge-c/test/cmocka_utils.h | 42 +- rust/automerge-c/test/doc_state.c | 27 + rust/automerge-c/test/doc_state.h | 17 + rust/automerge-c/test/doc_tests.c | 351 ++- rust/automerge-c/test/enum_string_tests.c | 148 ++ rust/automerge-c/test/group_state.c | 27 - rust/automerge-c/test/group_state.h | 16 - rust/automerge-c/test/item_tests.c | 94 + rust/automerge-c/test/list_tests.c | 720 +++--- rust/automerge-c/test/macro_utils.c | 47 +- rust/automerge-c/test/macro_utils.h | 29 +- rust/automerge-c/test/main.c | 17 +- rust/automerge-c/test/map_tests.c | 1754 ++++++++------- .../test/ported_wasm/basic_tests.c | 1986 ++++++++--------- rust/automerge-c/test/ported_wasm/suite.c | 7 +- .../automerge-c/test/ported_wasm/sync_tests.c | 1276 +++++------ rust/automerge-c/test/stack_utils.c | 31 - rust/automerge-c/test/stack_utils.h | 38 - rust/automerge-c/test/str_utils.c | 2 +- rust/automerge-c/test/str_utils.h | 19 +- rust/automerge/src/error.rs | 5 + scripts/ci/cmake-build | 2 +- 82 files changed, 9304 insertions(+), 8607 deletions(-) create mode 100644 rust/automerge-c/.clang-format create mode 100644 rust/automerge-c/cmake/Cargo.toml.in create mode 100644 rust/automerge-c/cmake/cbindgen.toml.in create mode 100644 rust/automerge-c/cmake/enum-string-functions-gen.cmake rename rust/automerge-c/cmake/{file_regex_replace.cmake => file-regex-replace.cmake} (87%) rename rust/automerge-c/cmake/{file_touch.cmake => file-touch.cmake} (82%) create mode 100644 rust/automerge-c/docs/CMakeLists.txt rename rust/automerge-c/{ => docs}/img/brandmark.png (100%) create mode 100644 rust/automerge-c/include/automerge-c/utils/result.h create mode 100644 rust/automerge-c/include/automerge-c/utils/stack.h create mode 100644 rust/automerge-c/include/automerge-c/utils/stack_callback_data.h create mode 100644 rust/automerge-c/include/automerge-c/utils/string.h delete mode 100644 rust/automerge-c/src/CMakeLists.txt delete mode 100644 rust/automerge-c/src/change_hashes.rs delete mode 100644 rust/automerge-c/src/changes.rs delete mode 100644 rust/automerge-c/src/doc/list/item.rs delete mode 100644 rust/automerge-c/src/doc/list/items.rs delete mode 100644 rust/automerge-c/src/doc/map/item.rs delete mode 100644 rust/automerge-c/src/doc/map/items.rs create mode 100644 rust/automerge-c/src/index.rs create mode 100644 rust/automerge-c/src/item.rs create mode 100644 rust/automerge-c/src/items.rs delete mode 100644 rust/automerge-c/src/obj/item.rs delete mode 100644 rust/automerge-c/src/obj/items.rs delete mode 100644 rust/automerge-c/src/result_stack.rs delete mode 100644 rust/automerge-c/src/strs.rs delete mode 100644 rust/automerge-c/src/sync/haves.rs create mode 100644 rust/automerge-c/src/utils/result.c create mode 100644 rust/automerge-c/src/utils/stack.c create mode 100644 rust/automerge-c/src/utils/stack_callback_data.c create mode 100644 rust/automerge-c/src/utils/string.c create mode 100644 rust/automerge-c/test/base_state.c create mode 100644 rust/automerge-c/test/base_state.h create mode 100644 rust/automerge-c/test/byte_span_tests.c create mode 100644 rust/automerge-c/test/cmocka_utils.c create mode 100644 rust/automerge-c/test/doc_state.c create mode 100644 rust/automerge-c/test/doc_state.h create mode 100644 rust/automerge-c/test/enum_string_tests.c delete mode 100644 rust/automerge-c/test/group_state.c delete mode 100644 rust/automerge-c/test/group_state.h create mode 100644 rust/automerge-c/test/item_tests.c delete mode 100644 rust/automerge-c/test/stack_utils.c delete mode 100644 rust/automerge-c/test/stack_utils.h diff --git a/rust/automerge-c/.clang-format b/rust/automerge-c/.clang-format new file mode 100644 index 00000000..dbf16c21 --- /dev/null +++ b/rust/automerge-c/.clang-format @@ -0,0 +1,250 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: NextLine +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '([-_](test|unittest))?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: true +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequiresClause: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Left +PPIndentWidth: -1 +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RequiresClausePosition: OwnLine +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/rust/automerge-c/.gitignore b/rust/automerge-c/.gitignore index f04de582..14d74973 100644 --- a/rust/automerge-c/.gitignore +++ b/rust/automerge-c/.gitignore @@ -1,10 +1,10 @@ automerge automerge.h automerge.o -*.cmake +build/ +CMakeCache.txt CMakeFiles +CMakePresets.json Makefile DartConfiguration.tcl -config.h -CMakeCache.txt -Cargo +out/ diff --git a/rust/automerge-c/CMakeLists.txt b/rust/automerge-c/CMakeLists.txt index 1b68669a..056d111b 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -1,97 +1,279 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +project(automerge-c VERSION 0.1.0 + LANGUAGES C + DESCRIPTION "C bindings for the Automerge Rust library.") -# Parse the library name, project name and project version out of Cargo's TOML file. -set(CARGO_LIB_SECTION OFF) +set(LIBRARY_NAME "automerge") -set(LIBRARY_NAME "") - -set(CARGO_PKG_SECTION OFF) - -set(CARGO_PKG_NAME "") - -set(CARGO_PKG_VERSION "") - -file(READ Cargo.toml TOML_STRING) - -string(REPLACE ";" "\\\\;" TOML_STRING "${TOML_STRING}") - -string(REPLACE "\n" ";" TOML_LINES "${TOML_STRING}") - -foreach(TOML_LINE IN ITEMS ${TOML_LINES}) - string(REGEX MATCH "^\\[(lib|package)\\]$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 STREQUAL "lib") - set(CARGO_LIB_SECTION ON) - - set(CARGO_PKG_SECTION OFF) - elseif(CMAKE_MATCH_1 STREQUAL "package") - set(CARGO_LIB_SECTION OFF) - - set(CARGO_PKG_SECTION ON) - endif() - - string(REGEX MATCH "^name += +\"([^\"]+)\"$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 AND (CARGO_LIB_SECTION AND NOT CARGO_PKG_SECTION)) - set(LIBRARY_NAME "${CMAKE_MATCH_1}") - elseif(CMAKE_MATCH_1 AND (NOT CARGO_LIB_SECTION AND CARGO_PKG_SECTION)) - set(CARGO_PKG_NAME "${CMAKE_MATCH_1}") - endif() - - string(REGEX MATCH "^version += +\"([^\"]+)\"$" _ ${TOML_LINE}) - - if(CMAKE_MATCH_1 AND CARGO_PKG_SECTION) - set(CARGO_PKG_VERSION "${CMAKE_MATCH_1}") - endif() - - if(LIBRARY_NAME AND (CARGO_PKG_NAME AND CARGO_PKG_VERSION)) - break() - endif() -endforeach() - -project(${CARGO_PKG_NAME} VERSION 0.0.1 LANGUAGES C DESCRIPTION "C bindings for the Automerge Rust backend.") - -include(CTest) +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) option(BUILD_SHARED_LIBS "Enable the choice of a shared or static library.") +include(CTest) + include(CMakePackageConfigHelpers) include(GNUInstallDirs) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + string(MAKE_C_IDENTIFIER ${PROJECT_NAME} SYMBOL_PREFIX) string(TOUPPER ${SYMBOL_PREFIX} SYMBOL_PREFIX) -set(CARGO_TARGET_DIR "${CMAKE_CURRENT_BINARY_DIR}/Cargo/target") +set(CARGO_TARGET_DIR "${CMAKE_BINARY_DIR}/Cargo/target") -set(CBINDGEN_INCLUDEDIR "${CARGO_TARGET_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") +set(CBINDGEN_INCLUDEDIR "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_INCLUDEDIR}") set(CBINDGEN_TARGET_DIR "${CBINDGEN_INCLUDEDIR}/${PROJECT_NAME}") -add_subdirectory(src) +find_program ( + CARGO_CMD + "cargo" + PATHS "$ENV{CARGO_HOME}/bin" + DOC "The Cargo command" +) -# Generate and install the configuration header. +if(NOT CARGO_CMD) + message(FATAL_ERROR "Cargo (Rust package manager) not found! " + "Please install it and/or set the CARGO_HOME " + "environment variable to its path.") +endif() + +string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) + +if(BUILD_TYPE_LOWER STREQUAL debug) + set(CARGO_BUILD_TYPE "debug") + + set(CARGO_FLAG "") +else() + set(CARGO_BUILD_TYPE "release") + + set(CARGO_FLAG "--release") +endif() + +set(CARGO_FEATURES "") + +set(CARGO_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") + +set(BINDINGS_NAME "${LIBRARY_NAME}_core") + +configure_file( + ${CMAKE_MODULE_PATH}/Cargo.toml.in + ${CMAKE_SOURCE_DIR}/Cargo.toml + @ONLY + NEWLINE_STYLE LF +) + +set(INCLUDE_GUARD_PREFIX "${SYMBOL_PREFIX}") + +configure_file( + ${CMAKE_MODULE_PATH}/cbindgen.toml.in + ${CMAKE_SOURCE_DIR}/cbindgen.toml + @ONLY + NEWLINE_STYLE LF +) + +set(CARGO_OUTPUT + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + ${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX} +) + +# \note cbindgen's naming behavior isn't fully configurable and it ignores +# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252). +add_custom_command( + OUTPUT + ${CARGO_OUTPUT} + COMMAND + # \note cbindgen won't regenerate its output header file after it's been removed but it will after its + # configuration file has been updated. + ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file-touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml + COMMAND + ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} + COMMAND + # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". + ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + COMMAND + # Compensate for cbindgen ignoring `std:mem::size_of()` calls. + ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file-regex-replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + MAIN_DEPENDENCY + src/lib.rs + DEPENDS + src/actor_id.rs + src/byte_span.rs + src/change.rs + src/doc.rs + src/doc/list.rs + src/doc/map.rs + src/doc/utils.rs + src/index.rs + src/item.rs + src/items.rs + src/obj.rs + src/result.rs + src/sync.rs + src/sync/have.rs + src/sync/message.rs + src/sync/state.rs + ${CMAKE_SOURCE_DIR}/build.rs + ${CMAKE_MODULE_PATH}/Cargo.toml.in + ${CMAKE_MODULE_PATH}/cbindgen.toml.in + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} + COMMENT + "Producing the bindings' artifacts with Cargo..." + VERBATIM +) + +add_custom_target(${BINDINGS_NAME}_artifacts ALL + DEPENDS ${CARGO_OUTPUT} +) + +add_library(${BINDINGS_NAME} STATIC IMPORTED GLOBAL) + +target_include_directories(${BINDINGS_NAME} INTERFACE "${CBINDGEN_INCLUDEDIR}") + +set_target_properties( + ${BINDINGS_NAME} + PROPERTIES + # \note Cargo writes a debug build into a nested directory instead of + # decorating its name. + DEBUG_POSTFIX "" + DEFINE_SYMBOL "" + IMPORTED_IMPLIB "" + IMPORTED_LOCATION "${CARGO_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${BINDINGS_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}" + IMPORTED_NO_SONAME "TRUE" + IMPORTED_SONAME "" + LINKER_LANGUAGE C + PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + SOVERSION "${PROJECT_VERSION_MAJOR}" + VERSION "${PROJECT_VERSION}" + # \note Cargo exports all of the symbols automatically. + WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" +) + +target_compile_definitions(${BINDINGS_NAME} INTERFACE $) + +set(UTILS_SUBDIR "utils") + +add_custom_command( + OUTPUT + ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + COMMAND + ${CMAKE_COMMAND} -DPROJECT_NAME=${PROJECT_NAME} -DLIBRARY_NAME=${LIBRARY_NAME} -DSUBDIR=${UTILS_SUBDIR} -P ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + MAIN_DEPENDENCY + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + DEPENDS + ${CMAKE_SOURCE_DIR}/cmake/enum-string-functions-gen.cmake + WORKING_DIRECTORY + ${CMAKE_SOURCE_DIR} + COMMENT + "Generating the enum string functions with CMake..." + VERBATIM +) + +add_custom_target(${LIBRARY_NAME}_utilities + DEPENDS ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c +) + +add_library(${LIBRARY_NAME}) + +target_compile_features(${LIBRARY_NAME} PRIVATE c_std_99) + +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + +set(THREADS_PREFER_PTHREAD_FLAG TRUE) + +find_package(Threads REQUIRED) + +set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) + +if(WIN32) + list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) +else() + list(APPEND LIBRARY_DEPENDENCIES m) +endif() + +target_link_libraries(${LIBRARY_NAME} + PUBLIC ${BINDINGS_NAME} + ${LIBRARY_DEPENDENCIES} +) + +# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't +# contain a non-existent path so its build-time include directory +# must be specified for all of its dependent targets instead. +target_include_directories(${LIBRARY_NAME} + PUBLIC "$" + "$" +) + +add_dependencies(${LIBRARY_NAME} ${BINDINGS_NAME}_artifacts) + +# Generate the configuration header. math(EXPR INTEGER_PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR} * 100000") math(EXPR INTEGER_PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR} * 100") math(EXPR INTEGER_PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}") -math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + ${INTEGER_PROJECT_VERSION_MINOR} + ${INTEGER_PROJECT_VERSION_PATCH}") +math(EXPR INTEGER_PROJECT_VERSION "${INTEGER_PROJECT_VERSION_MAJOR} + \ + ${INTEGER_PROJECT_VERSION_MINOR} + \ + ${INTEGER_PROJECT_VERSION_PATCH}") configure_file( ${CMAKE_MODULE_PATH}/config.h.in - config.h + ${CBINDGEN_TARGET_DIR}/config.h @ONLY NEWLINE_STYLE LF ) +target_sources(${LIBRARY_NAME} + PRIVATE + src/${UTILS_SUBDIR}/result.c + src/${UTILS_SUBDIR}/stack_callback_data.c + src/${UTILS_SUBDIR}/stack.c + src/${UTILS_SUBDIR}/string.c + ${CMAKE_BINARY_DIR}/src/${UTILS_SUBDIR}/enum_string.c + PUBLIC + FILE_SET api TYPE HEADERS + BASE_DIRS + ${CBINDGEN_INCLUDEDIR} + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR} + FILES + ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h + ${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h + ${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h + INTERFACE + FILE_SET config TYPE HEADERS + BASE_DIRS + ${CBINDGEN_INCLUDEDIR} + FILES + ${CBINDGEN_TARGET_DIR}/config.h +) + install( - FILES ${CMAKE_BINARY_DIR}/config.h - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} + TARGETS ${LIBRARY_NAME} + EXPORT ${PROJECT_NAME}-config + FILE_SET api + FILE_SET config +) + +# \note Install the Cargo-built core bindings to enable direct linkage. +install( + FILES $ + DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(EXPORT ${PROJECT_NAME}-config + FILE ${PROJECT_NAME}-config.cmake + NAMESPACE "${PROJECT_NAME}::" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${LIB} ) if(BUILD_TESTING) @@ -100,42 +282,6 @@ if(BUILD_TESTING) enable_testing() endif() +add_subdirectory(docs) + add_subdirectory(examples EXCLUDE_FROM_ALL) - -# Generate and install .cmake files -set(PROJECT_CONFIG_NAME "${PROJECT_NAME}-config") - -set(PROJECT_CONFIG_VERSION_NAME "${PROJECT_CONFIG_NAME}-version") - -write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake - VERSION ${PROJECT_VERSION} - COMPATIBILITY ExactVersion -) - -# The namespace label starts with the title-cased library name. -string(SUBSTRING ${LIBRARY_NAME} 0 1 NS_FIRST) - -string(SUBSTRING ${LIBRARY_NAME} 1 -1 NS_REST) - -string(TOUPPER ${NS_FIRST} NS_FIRST) - -string(TOLOWER ${NS_REST} NS_REST) - -string(CONCAT NAMESPACE ${NS_FIRST} ${NS_REST} "::") - -# \note CMake doesn't automate the exporting of an imported library's targets -# so the package configuration script must do it. -configure_package_config_file( - ${CMAKE_MODULE_PATH}/${PROJECT_CONFIG_NAME}.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} -) - -install( - FILES - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_NAME}.cmake - ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_CONFIG_VERSION_NAME}.cmake - DESTINATION - ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} -) diff --git a/rust/automerge-c/Cargo.toml b/rust/automerge-c/Cargo.toml index d039e460..95a3a29c 100644 --- a/rust/automerge-c/Cargo.toml +++ b/rust/automerge-c/Cargo.toml @@ -7,8 +7,8 @@ license = "MIT" rust-version = "1.57.0" [lib] -name = "automerge" -crate-type = ["cdylib", "staticlib"] +name = "automerge_core" +crate-type = ["staticlib"] bench = false doc = false diff --git a/rust/automerge-c/README.md b/rust/automerge-c/README.md index a9f097e2..1fbca3df 100644 --- a/rust/automerge-c/README.md +++ b/rust/automerge-c/README.md @@ -1,22 +1,29 @@ -automerge-c exposes an API to C that can either be used directly or as a basis -for other language bindings that have good support for calling into C functions. +# Overview -# Building +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. -See the main README for instructions on getting your environment set up, then -you can use `./scripts/ci/cmake-build Release static` to build automerge-c. +# Installing -It will output two files: +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. -- ./build/Cargo/target/include/automerge-c/automerge.h -- ./build/Cargo/target/release/libautomerge.a - -To use these in your application you must arrange for your C compiler to find -these files, either by moving them to the right location on your computer, or -by configuring the compiler to reference these directories. - -- `export LDFLAGS=-L./build/Cargo/target/release -lautomerge` -- `export CFLAGS=-I./build/Cargo/target/include` +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: @@ -25,134 +32,176 @@ using [cross](https://github.com/cross-rs/cross). For example: 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: +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, the header file is currently 32/64-bit dependant. You can re-use it -for all 64-bit architectures, but you must generate a specific header for 32-bit -targets. +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 -For full reference, read through `automerge.h`, or to get started quickly look -at the +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 AMdoc struct which you can get -from `AMcreate()` or `AMload()`. Operations on a given doc are not thread safe -so you must use a mutex or similar to avoid calling more than one function with -the same AMdoc pointer concurrently. +Almost all operations in automerge-c act on an Automerge document +(`AMdoc` struct) which is structurally similar to a JSON document. -As with all functions that either allocate memory, or could fail if given -invalid input, `AMcreate()` returns an `AMresult`. The `AMresult` contains the -returned doc (or error message), and must be freed with `AMfree()` after you are -done to avoid leaking memory. +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 #include +#include +#include +#include int main(int argc, char** argv) { AMresult *docResult = AMcreate(NULL); if (AMresultStatus(docResult) != AM_STATUS_OK) { - printf("failed to create doc: %s", AMerrorMessage(docResult).src); + char* const err_msg = AMstrdup(AMresultError(docResult), NULL); + printf("failed to create doc: %s", err_msg); + free(err_msg); goto cleanup; } - AMdoc *doc = AMresultValue(docResult).doc; + AMdoc *doc; + AMitemToDoc(AMresultItem(docResult), &doc); // useful code goes here! cleanup: - AMfree(docResult); + AMresultFree(docResult); } ``` -If you are writing code in C directly, you can use the `AMpush()` helper -function to reduce the boilerplate of error handling and freeing for you (see -examples/quickstart.c). +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 `AMfree` within a finalizer to ensure that memory -is reclaimed when it is no longer needed. +garbage collector, you can call the `AMresultFree()` function within a finalizer +to ensure that memory is reclaimed when it is no longer needed. -An AMdoc wraps an automerge document which are very similar to JSON documents. -Automerge documents consist of a mutable root, which is always a map from string -keys to values. Values can have the following types: +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 / nul -- An immutable utf-8 string (AMbyteSpan) -- An immutable array of arbitrary bytes (AMbyteSpan) -- A mutable map from string keys to values (AMmap) -- A mutable list of values (AMlist) -- A mutable string (AMtext) +- 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 a value with -`.tag == AM_VALUE_VOID` will be returned, but you cannot write such a value explicitly. +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 mutable objects by the internal object id, -and `AM_ROOT` is always the object id of the root value. +Under the hood, automerge references a mutable object by its object identifier +where `AM_ROOT` signifies a document's root map object. -There is a function to put each type of value into either a map or a list, and a -function to read the current value from a list. As (in general) collaborators +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 `AMvalue` union that you can inspect to -determine its type. +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 null bytes. -As a convenience you can use `AMstr()` to get the representation of a -null-terminated C string as an `AMbyteSpan`. +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 #include +#include +#include +#include int main(int argc, char** argv) { // ...previous example... - AMdoc *doc = AMresultValue(docResult).doc; + AMdoc *doc; + AMitemToDoc(AMresultItem(docResult), &doc); AMresult *putResult = AMmapPutStr(doc, AM_ROOT, AMstr("key"), AMstr("value")); if (AMresultStatus(putResult) != AM_STATUS_OK) { - printf("failed to put: %s", AMerrorMessage(putResult).src); + 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) { - printf("failed to get: %s", AMerrorMessage(getResult).src); + char* const err_msg = AMstrdup(AMresultError(putResult), NULL); + printf("failed to get: %s", err_msg); + free(err_msg); goto cleanup; } - AMvalue got = AMresultValue(getResult); - if (got.tag != AM_VALUE_STR) { + 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; } - printf("Got %zu-character string `%s`", got.str.count, got.str.src); cleanup: - AMfree(getResult); - AMfree(putResult); - AMfree(docResult); + AMresultFree(getResult); + AMresultFree(putResult); + AMresultFree(docResult); } ``` -Functions that do not return an `AMresult` (for example `AMmapItemValue()`) do -not allocate memory, but continue to reference memory that was previously -allocated. It's thus important to keep the original `AMresult` alive (in this -case the one returned by `AMmapRange()`) until after you are done with the return -values of these functions. +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! diff --git a/rust/automerge-c/cbindgen.toml b/rust/automerge-c/cbindgen.toml index ada7f48d..21eaaadd 100644 --- a/rust/automerge-c/cbindgen.toml +++ b/rust/automerge-c/cbindgen.toml @@ -1,7 +1,7 @@ after_includes = """\n /** * \\defgroup enumerations Public Enumerations - Symbolic names for integer constants. + * Symbolic names for integer constants. */ /** @@ -12,21 +12,23 @@ after_includes = """\n #define AM_ROOT NULL /** - * \\memberof AMchangeHash + * \\memberof AMdoc * \\def AM_CHANGE_HASH_SIZE * \\brief The count of bytes in a change hash. */ #define AM_CHANGE_HASH_SIZE 32 """ -autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" +autogen_warning = """ +/** + * \\file + * \\brief All constants, functions and types in the core Automerge C API. + * + * \\warning This file is auto-generated by cbindgen. + */ +""" documentation = true documentation_style = "doxy" -header = """ -/** \\file - * All constants, functions and types in the Automerge library's C API. - */ - """ -include_guard = "AUTOMERGE_H" +include_guard = "AUTOMERGE_C_H" includes = [] language = "C" line_length = 140 diff --git a/rust/automerge-c/cmake/Cargo.toml.in b/rust/automerge-c/cmake/Cargo.toml.in new file mode 100644 index 00000000..781e2fef --- /dev/null +++ b/rust/automerge-c/cmake/Cargo.toml.in @@ -0,0 +1,22 @@ +[package] +name = "@PROJECT_NAME@" +version = "@PROJECT_VERSION@" +authors = ["Orion Henry ", "Jason Kankiewicz "] +edition = "2021" +license = "MIT" +rust-version = "1.57.0" + +[lib] +name = "@BINDINGS_NAME@" +crate-type = ["staticlib"] +bench = false +doc = false + +[dependencies] +@LIBRARY_NAME@ = { path = "../@LIBRARY_NAME@" } +hex = "^0.4.3" +libc = "^0.2" +smol_str = "^0.1.21" + +[build-dependencies] +cbindgen = "^0.24" diff --git a/rust/automerge-c/cmake/cbindgen.toml.in b/rust/automerge-c/cmake/cbindgen.toml.in new file mode 100644 index 00000000..5122b75c --- /dev/null +++ b/rust/automerge-c/cmake/cbindgen.toml.in @@ -0,0 +1,48 @@ +after_includes = """\n +/** + * \\defgroup enumerations Public Enumerations + * Symbolic names for integer constants. + */ + +/** + * \\memberof AMdoc + * \\def AM_ROOT + * \\brief The root object of a document. + */ +#define AM_ROOT NULL + +/** + * \\memberof AMdoc + * \\def AM_CHANGE_HASH_SIZE + * \\brief The count of bytes in a change hash. + */ +#define AM_CHANGE_HASH_SIZE 32 +""" +autogen_warning = """ +/** + * \\file + * \\brief All constants, functions and types in the core Automerge C API. + * + * \\warning This file is auto-generated by cbindgen. + */ +""" +documentation = true +documentation_style = "doxy" +include_guard = "@INCLUDE_GUARD_PREFIX@_H" +includes = [] +language = "C" +line_length = 140 +no_includes = true +style = "both" +sys_includes = ["stdbool.h", "stddef.h", "stdint.h", "time.h"] +usize_is_size_t = true + +[enum] +derive_const_casts = true +enum_class = true +must_use = "MUST_USE_ENUM" +prefix_with_name = true +rename_variants = "ScreamingSnakeCase" + +[export] +item_types = ["constants", "enums", "functions", "opaque", "structs", "typedefs"] diff --git a/rust/automerge-c/cmake/config.h.in b/rust/automerge-c/cmake/config.h.in index 44ba5213..40482cb9 100644 --- a/rust/automerge-c/cmake/config.h.in +++ b/rust/automerge-c/cmake/config.h.in @@ -1,14 +1,35 @@ -#ifndef @SYMBOL_PREFIX@_CONFIG_H -#define @SYMBOL_PREFIX@_CONFIG_H - -/* This header is auto-generated by CMake. */ +#ifndef @INCLUDE_GUARD_PREFIX@_CONFIG_H +#define @INCLUDE_GUARD_PREFIX@_CONFIG_H +/** + * \file + * \brief Configuration pararameters defined by the build system. + * + * \warning This file is auto-generated by CMake. + */ +/** + * \def @SYMBOL_PREFIX@_VERSION + * \brief Denotes a semantic version of the form {MAJOR}{MINOR}{PATCH} as three, + * two-digit decimal numbers without leading zeros (e.g. 100 is 0.1.0). + */ #define @SYMBOL_PREFIX@_VERSION @INTEGER_PROJECT_VERSION@ +/** + * \def @SYMBOL_PREFIX@_MAJOR_VERSION + * \brief Denotes a semantic major version as a decimal number. + */ #define @SYMBOL_PREFIX@_MAJOR_VERSION (@SYMBOL_PREFIX@_VERSION / 100000) +/** + * \def @SYMBOL_PREFIX@_MINOR_VERSION + * \brief Denotes a semantic minor version as a decimal number. + */ #define @SYMBOL_PREFIX@_MINOR_VERSION ((@SYMBOL_PREFIX@_VERSION / 100) % 1000) +/** + * \def @SYMBOL_PREFIX@_PATCH_VERSION + * \brief Denotes a semantic patch version as a decimal number. + */ #define @SYMBOL_PREFIX@_PATCH_VERSION (@SYMBOL_PREFIX@_VERSION % 100) -#endif /* @SYMBOL_PREFIX@_CONFIG_H */ +#endif /* @INCLUDE_GUARD_PREFIX@_CONFIG_H */ diff --git a/rust/automerge-c/cmake/enum-string-functions-gen.cmake b/rust/automerge-c/cmake/enum-string-functions-gen.cmake new file mode 100644 index 00000000..77080e8d --- /dev/null +++ b/rust/automerge-c/cmake/enum-string-functions-gen.cmake @@ -0,0 +1,183 @@ +# This CMake script is used to generate a header and a source file for utility +# functions that convert the tags of generated enum types into strings and +# strings into the tags of generated enum types. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) + +# Seeks the starting line of the source enum's declaration. +macro(seek_enum_mode) + if (line MATCHES "^(typedef[ \t]+)?enum ") + string(REGEX REPLACE "^enum ([0-9a-zA-Z_]+).*$" "\\1" enum_name "${line}") + set(mode "read_tags") + endif() +endmacro() + +# Scans the input for the current enum's tags. +macro(read_tags_mode) + if(line MATCHES "^}") + set(mode "generate") + elseif(line MATCHES "^[A-Z0-9_]+.*$") + string(REGEX REPLACE "^([A-Za-z0-9_]+).*$" "\\1" tmp "${line}") + list(APPEND enum_tags "${tmp}") + endif() +endmacro() + +macro(write_header_file) + # Generate a to-string function declaration. + list(APPEND header_body + "/**\n" + " * \\ingroup enumerations\n" + " * \\brief Gets the string representation of an `${enum_name}` enum tag.\n" + " *\n" + " * \\param[in] tag An `${enum_name}` enum tag.\n" + " * \\return A null-terminated byte string.\n" + " */\n" + "char const* ${enum_name}ToString(${enum_name} const tag)\;\n" + "\n") + # Generate a from-string function declaration. + list(APPEND header_body + "/**\n" + " * \\ingroup enumerations\n" + " * \\brief Gets an `${enum_name}` enum tag from its string representation.\n" + " *\n" + " * \\param[out] dest An `${enum_name}` enum tag pointer.\n" + " * \\param[in] src A null-terminated byte string.\n" + " * \\return `true` if \\p src matches the string representation of an\n" + " * `${enum_name}` enum tag, `false` otherwise.\n" + " */\n" + "bool ${enum_name}FromString(${enum_name}* dest, char const* const src)\;\n" + "\n") +endmacro() + +macro(write_source_file) + # Generate a to-string function implementation. + list(APPEND source_body + "char const* ${enum_name}ToString(${enum_name} const tag) {\n" + " switch (tag) {\n" + " default:\n" + " return \"???\"\;\n") + foreach(label IN LISTS enum_tags) + list(APPEND source_body + " case ${label}:\n" + " return \"${label}\"\;\n") + endforeach() + list(APPEND source_body + " }\n" + "}\n" + "\n") + # Generate a from-string function implementation. + list(APPEND source_body + "bool ${enum_name}FromString(${enum_name}* dest, char const* const src) {\n") + foreach(label IN LISTS enum_tags) + list(APPEND source_body + " if (!strcmp(src, \"${label}\")) {\n" + " *dest = ${label}\;\n" + " return true\;\n" + " }\n") + endforeach() + list(APPEND source_body + " return false\;\n" + "}\n" + "\n") +endmacro() + +function(main) + set(header_body "") + # File header and includes. + list(APPEND header_body + "#ifndef ${include_guard}\n" + "#define ${include_guard}\n" + "/**\n" + " * \\file\n" + " * \\brief Utility functions for converting enum tags into null-terminated\n" + " * byte strings and vice versa.\n" + " *\n" + " * \\warning This file is auto-generated by CMake.\n" + " */\n" + "\n" + "#include \n" + "\n" + "#include <${library_include}>\n" + "\n") + set(source_body "") + # File includes. + list(APPEND source_body + "/** \\warning This file is auto-generated by CMake. */\n" + "\n" + "#include \"stdio.h\"\n" + "#include \"string.h\"\n" + "\n" + "#include <${header_include}>\n" + "\n") + set(enum_name "") + set(enum_tags "") + set(mode "seek_enum") + file(STRINGS "${input_path}" lines) + foreach(line IN LISTS lines) + string(REGEX REPLACE "^(.+)(//.*)?" "\\1" line "${line}") + string(STRIP "${line}" line) + if(mode STREQUAL "seek_enum") + seek_enum_mode() + elseif(mode STREQUAL "read_tags") + read_tags_mode() + else() + # The end of the enum declaration was reached. + if(NOT enum_name) + # The end of the file was reached. + return() + endif() + if(NOT enum_tags) + message(FATAL_ERROR "No tags found for `${enum_name}`.") + endif() + string(TOLOWER "${enum_name}" output_stem_prefix) + string(CONCAT output_stem "${output_stem_prefix}" "_string") + cmake_path(REPLACE_EXTENSION output_stem "h" OUTPUT_VARIABLE output_header_basename) + write_header_file() + write_source_file() + set(enum_name "") + set(enum_tags "") + set(mode "seek_enum") + endif() + endforeach() + # File footer. + list(APPEND header_body + "#endif /* ${include_guard} */\n") + message(STATUS "Generating header file \"${output_header_path}\"...") + file(WRITE "${output_header_path}" ${header_body}) + message(STATUS "Generating source file \"${output_source_path}\"...") + file(WRITE "${output_source_path}" ${source_body}) +endfunction() + +if(NOT DEFINED PROJECT_NAME) + message(FATAL_ERROR "Variable PROJECT_NAME is not defined.") +elseif(NOT DEFINED LIBRARY_NAME) + message(FATAL_ERROR "Variable LIBRARY_NAME is not defined.") +elseif(NOT DEFINED SUBDIR) + message(FATAL_ERROR "Variable SUBDIR is not defined.") +elseif(${CMAKE_ARGC} LESS 9) + message(FATAL_ERROR "Too few arguments.") +elseif(${CMAKE_ARGC} GREATER 10) + message(FATAL_ERROR "Too many arguments.") +elseif(NOT EXISTS ${CMAKE_ARGV5}) + message(FATAL_ERROR "Input header \"${CMAKE_ARGV7}\" not found.") +endif() +cmake_path(CONVERT "${CMAKE_ARGV7}" TO_CMAKE_PATH_LIST input_path NORMALIZE) +cmake_path(CONVERT "${CMAKE_ARGV8}" TO_CMAKE_PATH_LIST output_header_path NORMALIZE) +cmake_path(CONVERT "${CMAKE_ARGV9}" TO_CMAKE_PATH_LIST output_source_path NORMALIZE) +string(TOLOWER "${PROJECT_NAME}" project_root) +cmake_path(CONVERT "${SUBDIR}" TO_CMAKE_PATH_LIST project_subdir NORMALIZE) +string(TOLOWER "${project_subdir}" project_subdir) +string(TOLOWER "${LIBRARY_NAME}" library_stem) +cmake_path(REPLACE_EXTENSION library_stem "h" OUTPUT_VARIABLE library_basename) +string(JOIN "/" library_include "${project_root}" "${library_basename}") +string(TOUPPER "${PROJECT_NAME}" project_name_upper) +string(TOUPPER "${project_subdir}" include_guard_infix) +string(REGEX REPLACE "/" "_" include_guard_infix "${include_guard_infix}") +string(REGEX REPLACE "-" "_" include_guard_prefix "${project_name_upper}") +string(JOIN "_" include_guard_prefix "${include_guard_prefix}" "${include_guard_infix}") +string(JOIN "/" output_header_prefix "${project_root}" "${project_subdir}") +cmake_path(GET output_header_path STEM output_header_stem) +string(TOUPPER "${output_header_stem}" include_guard_stem) +string(JOIN "_" include_guard "${include_guard_prefix}" "${include_guard_stem}" "H") +cmake_path(GET output_header_path FILENAME output_header_basename) +string(JOIN "/" header_include "${output_header_prefix}" "${output_header_basename}") +main() diff --git a/rust/automerge-c/cmake/file_regex_replace.cmake b/rust/automerge-c/cmake/file-regex-replace.cmake similarity index 87% rename from rust/automerge-c/cmake/file_regex_replace.cmake rename to rust/automerge-c/cmake/file-regex-replace.cmake index 27306458..09005bc2 100644 --- a/rust/automerge-c/cmake/file_regex_replace.cmake +++ b/rust/automerge-c/cmake/file-regex-replace.cmake @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +# This CMake script is used to perform string substitutions within a generated +# file. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) if(NOT DEFINED MATCH_REGEX) message(FATAL_ERROR "Variable \"MATCH_REGEX\" is not defined.") diff --git a/rust/automerge-c/cmake/file_touch.cmake b/rust/automerge-c/cmake/file-touch.cmake similarity index 82% rename from rust/automerge-c/cmake/file_touch.cmake rename to rust/automerge-c/cmake/file-touch.cmake index 087d59b6..2c196755 100644 --- a/rust/automerge-c/cmake/file_touch.cmake +++ b/rust/automerge-c/cmake/file-touch.cmake @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) +# This CMake script is used to force Cargo to regenerate the header file for the +# core bindings after the out-of-source build directory has been cleaned. +cmake_minimum_required(VERSION 3.23 FATAL_ERROR) if(NOT DEFINED CONDITION) message(FATAL_ERROR "Variable \"CONDITION\" is not defined.") diff --git a/rust/automerge-c/docs/CMakeLists.txt b/rust/automerge-c/docs/CMakeLists.txt new file mode 100644 index 00000000..1d94c872 --- /dev/null +++ b/rust/automerge-c/docs/CMakeLists.txt @@ -0,0 +1,35 @@ +find_package(Doxygen OPTIONAL_COMPONENTS dot) + +if(DOXYGEN_FOUND) + set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>") + + set(DOXYGEN_GENERATE_LATEX YES) + + set(DOXYGEN_PDF_HYPERLINKS YES) + + set(DOXYGEN_PROJECT_LOGO "${CMAKE_CURRENT_SOURCE_DIR}/img/brandmark.png") + + set(DOXYGEN_SORT_BRIEF_DOCS YES) + + set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") + + doxygen_add_docs( + ${LIBRARY_NAME}_docs + "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" + "${CBINDGEN_TARGET_DIR}/config.h" + "${CBINDGEN_TARGET_DIR}/${UTILS_SUBDIR}/enum_string.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/result.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack_callback_data.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/stack.h" + "${CMAKE_SOURCE_DIR}/${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/${UTILS_SUBDIR}/string.h" + "${CMAKE_SOURCE_DIR}/README.md" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Producing documentation with Doxygen..." + ) + + # \note A Doxygen input file isn't a file-level dependency so the Doxygen + # command must instead depend upon a target that either outputs the + # file or depends upon it also or it will just output an error message + # when it can't be found. + add_dependencies(${LIBRARY_NAME}_docs ${BINDINGS_NAME}_artifacts ${LIBRARY_NAME}_utilities) +endif() diff --git a/rust/automerge-c/img/brandmark.png b/rust/automerge-c/docs/img/brandmark.png similarity index 100% rename from rust/automerge-c/img/brandmark.png rename to rust/automerge-c/docs/img/brandmark.png diff --git a/rust/automerge-c/examples/CMakeLists.txt b/rust/automerge-c/examples/CMakeLists.txt index 3395124c..f080237b 100644 --- a/rust/automerge-c/examples/CMakeLists.txt +++ b/rust/automerge-c/examples/CMakeLists.txt @@ -1,41 +1,39 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - add_executable( - example_quickstart + ${LIBRARY_NAME}_quickstart quickstart.c ) -set_target_properties(example_quickstart PROPERTIES LINKER_LANGUAGE C) +set_target_properties(${LIBRARY_NAME}_quickstart PROPERTIES LINKER_LANGUAGE C) # \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't # contain a non-existent path so its build-time include directory # must be specified for all of its dependent targets instead. target_include_directories( - example_quickstart + ${LIBRARY_NAME}_quickstart PRIVATE "$" ) -target_link_libraries(example_quickstart PRIVATE ${LIBRARY_NAME}) +target_link_libraries(${LIBRARY_NAME}_quickstart PRIVATE ${LIBRARY_NAME}) -add_dependencies(example_quickstart ${LIBRARY_NAME}_artifacts) +add_dependencies(${LIBRARY_NAME}_quickstart ${BINDINGS_NAME}_artifacts) if(BUILD_SHARED_LIBS AND WIN32) add_custom_command( - TARGET example_quickstart + TARGET ${LIBRARY_NAME}_quickstart POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} COMMENT "Copying the DLL built by Cargo into the examples directory..." VERBATIM ) endif() add_custom_command( - TARGET example_quickstart + TARGET ${LIBRARY_NAME}_quickstart POST_BUILD COMMAND - example_quickstart + ${LIBRARY_NAME}_quickstart COMMENT "Running the example quickstart..." VERBATIM diff --git a/rust/automerge-c/examples/README.md b/rust/automerge-c/examples/README.md index 17aa2227..17e69412 100644 --- a/rust/automerge-c/examples/README.md +++ b/rust/automerge-c/examples/README.md @@ -5,5 +5,5 @@ ```shell cmake -E make_directory automerge-c/build cmake -S automerge-c -B automerge-c/build -cmake --build automerge-c/build --target example_quickstart +cmake --build automerge-c/build --target automerge_quickstart ``` diff --git a/rust/automerge-c/examples/quickstart.c b/rust/automerge-c/examples/quickstart.c index bc418511..ab6769ef 100644 --- a/rust/automerge-c/examples/quickstart.c +++ b/rust/automerge-c/examples/quickstart.c @@ -3,152 +3,127 @@ #include #include +#include +#include +#include +#include -static void abort_cb(AMresultStack**, uint8_t); +static bool abort_cb(AMstack**, void*); /** * \brief Based on https://automerge.github.io/docs/quickstart */ int main(int argc, char** argv) { - AMresultStack* stack = NULL; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; - AMobjId const* const cards = AMpush(&stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMobjId const* const card1 = AMpush(&stack, - AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMfree(AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure"))); - AMfree(AMmapPutBool(doc1, card1, AMstr("done"), false)); - AMobjId const* const card2 = AMpush(&stack, - AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - abort_cb).obj_id; - AMfree(AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell"))); - AMfree(AMmapPutBool(doc1, card2, AMstr("done"), false)); - AMfree(AMcommit(doc1, AMstr("Add card"), NULL)); + AMstack* stack = NULL; + AMdoc* doc1; + AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1); + AMobjId const* const cards = + AMitemObjId(AMstackItem(&stack, AMmapPutObject(doc1, AM_ROOT, AMstr("cards"), AM_OBJ_TYPE_LIST), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMobjId const* const card1 = + AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc1, card1, AMstr("title"), AMstr("Rewrite everything in Clojure")), abort_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const card2 = + AMitemObjId(AMstackItem(&stack, AMlistPutObject(doc1, cards, SIZE_MAX, true, AM_OBJ_TYPE_MAP), abort_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc1, card2, AMstr("title"), AMstr("Rewrite everything in Haskell")), abort_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutBool(doc1, card2, AMstr("done"), false), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr("Add card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMdoc* doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, abort_cb).doc; - AMfree(AMmerge(doc2, doc1)); + AMdoc* doc2; + AMitemToDoc(AMstackItem(&stack, AMcreate(NULL), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); + AMstackItem(NULL, AMmerge(doc2, doc1), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMbyteSpan const binary = AMpush(&stack, AMsave(doc1), AM_VALUE_BYTES, abort_cb).bytes; - doc2 = AMpush(&stack, AMload(binary.src, binary.count), AM_VALUE_DOC, abort_cb).doc; + AMbyteSpan binary; + AMitemToBytes(AMstackItem(&stack, AMsave(doc1), abort_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary); + AMitemToDoc(AMstackItem(&stack, AMload(binary.src, binary.count), abort_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2); - AMfree(AMmapPutBool(doc1, card1, AMstr("done"), true)); - AMfree(AMcommit(doc1, AMstr("Mark card as done"), NULL)); + AMstackItem(NULL, AMmapPutBool(doc1, card1, AMstr("done"), true), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr("Mark card as done"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMlistDelete(doc2, cards, 0)); - AMfree(AMcommit(doc2, AMstr("Delete card"), NULL)); + AMstackItem(NULL, AMlistDelete(doc2, cards, 0), abort_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr("Delete card"), NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchanges changes = AMpush(&stack, AMgetChanges(doc1, NULL), AM_VALUE_CHANGES, abort_cb).changes; - AMchange const* change = NULL; - while ((change = AMchangesNext(&changes, 1)) != NULL) { - AMbyteSpan const change_hash = AMchangeHash(change); - AMchangeHashes const heads = AMpush(&stack, - AMchangeHashesInit(&change_hash, 1), - AM_VALUE_CHANGE_HASHES, - abort_cb).change_hashes; - AMbyteSpan const msg = AMchangeMessage(change); - char* const c_msg = calloc(1, msg.count + 1); - strncpy(c_msg, msg.src, msg.count); - printf("%s %ld\n", c_msg, AMobjSize(doc1, cards, &heads)); + AMitems changes = AMstackItems(&stack, AMgetChanges(doc1, NULL), abort_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMitem* item = NULL; + while ((item = AMitemsNext(&changes, 1)) != NULL) { + AMchange const* change; + AMitemToChange(item, &change); + AMitems const heads = AMstackItems(&stack, AMitemFromChangeHash(AMchangeHash(change)), abort_cb, + AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + char* const c_msg = AMstrdup(AMchangeMessage(change), NULL); + printf("%s %zu\n", c_msg, AMobjSize(doc1, cards, &heads)); free(c_msg); } - AMfreeStack(&stack); + AMstackFree(&stack); } -static char const* discriminant_suffix(AMvalueVariant const); - /** - * \brief Prints an error message to `stderr`, deallocates all results in the - * given stack and exits. + * \brief Examines the result at the top of the given stack and, if it's + * invalid, prints an error message to `stderr`, deallocates all results + * in the stack and exits. * - * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \pre \p stack` != NULL`. - * \post `*stack == NULL`. + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to an owned `AMstackCallbackData` struct or `NULL`. + * \return `true` if the top `AMresult` in \p stack is valid, `false` otherwise. + * \pre \p stack `!= NULL`. */ -static void abort_cb(AMresultStack** stack, uint8_t discriminant) { +static bool abort_cb(AMstack** stack, void* data) { static char buffer[512] = {0}; char const* suffix = NULL; if (!stack) { suffix = "Stack*"; - } - else if (!*stack) { + } else if (!*stack) { suffix = "Stack"; - } - else if (!(*stack)->result) { + } else if (!(*stack)->result) { suffix = ""; } if (suffix) { - fprintf(stderr, "Null `AMresult%s*`.", suffix); - AMfreeStack(stack); + fprintf(stderr, "Null `AMresult%s*`.\n", suffix); + AMstackFree(stack); exit(EXIT_FAILURE); - return; + return false; } AMstatus const status = AMresultStatus((*stack)->result); switch (status) { - case AM_STATUS_ERROR: strcpy(buffer, "Error"); break; - case AM_STATUS_INVALID_RESULT: strcpy(buffer, "Invalid result"); break; - case AM_STATUS_OK: break; - default: sprintf(buffer, "Unknown `AMstatus` tag %d", status); + case AM_STATUS_ERROR: + strcpy(buffer, "Error"); + break; + case AM_STATUS_INVALID_RESULT: + strcpy(buffer, "Invalid result"); + break; + case AM_STATUS_OK: + break; + default: + sprintf(buffer, "Unknown `AMstatus` tag %d", status); } if (buffer[0]) { - AMbyteSpan const msg = AMerrorMessage((*stack)->result); - char* const c_msg = calloc(1, msg.count + 1); - strncpy(c_msg, msg.src, msg.count); - fprintf(stderr, "%s; %s.", buffer, c_msg); + char* const c_msg = AMstrdup(AMresultError((*stack)->result), NULL); + fprintf(stderr, "%s; %s.\n", buffer, c_msg); free(c_msg); - AMfreeStack(stack); + AMstackFree(stack); exit(EXIT_FAILURE); - return; + return false; } - AMvalue const value = AMresultValue((*stack)->result); - fprintf(stderr, "Unexpected tag `AM_VALUE_%s` (%d); expected `AM_VALUE_%s`.", - discriminant_suffix(value.tag), - value.tag, - discriminant_suffix(discriminant)); - AMfreeStack(stack); - exit(EXIT_FAILURE); -} - -/** - * \brief Gets the suffix for a discriminant's corresponding string - * representation. - * - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \return A UTF-8 string. - */ -static char const* discriminant_suffix(AMvalueVariant const discriminant) { - char const* suffix = NULL; - switch (discriminant) { - case AM_VALUE_ACTOR_ID: suffix = "ACTOR_ID"; break; - case AM_VALUE_BOOLEAN: suffix = "BOOLEAN"; break; - case AM_VALUE_BYTES: suffix = "BYTES"; break; - case AM_VALUE_CHANGE_HASHES: suffix = "CHANGE_HASHES"; break; - case AM_VALUE_CHANGES: suffix = "CHANGES"; break; - case AM_VALUE_COUNTER: suffix = "COUNTER"; break; - case AM_VALUE_DOC: suffix = "DOC"; break; - case AM_VALUE_F64: suffix = "F64"; break; - case AM_VALUE_INT: suffix = "INT"; break; - case AM_VALUE_LIST_ITEMS: suffix = "LIST_ITEMS"; break; - case AM_VALUE_MAP_ITEMS: suffix = "MAP_ITEMS"; break; - case AM_VALUE_NULL: suffix = "NULL"; break; - case AM_VALUE_OBJ_ID: suffix = "OBJ_ID"; break; - case AM_VALUE_OBJ_ITEMS: suffix = "OBJ_ITEMS"; break; - case AM_VALUE_STR: suffix = "STR"; break; - case AM_VALUE_STRS: suffix = "STRINGS"; break; - case AM_VALUE_SYNC_MESSAGE: suffix = "SYNC_MESSAGE"; break; - case AM_VALUE_SYNC_STATE: suffix = "SYNC_STATE"; break; - case AM_VALUE_TIMESTAMP: suffix = "TIMESTAMP"; break; - case AM_VALUE_UINT: suffix = "UINT"; break; - case AM_VALUE_VOID: suffix = "VOID"; break; - default: suffix = "..."; + if (data) { + AMstackCallbackData* sc_data = (AMstackCallbackData*)data; + AMvalType const tag = AMitemValType(AMresultItem((*stack)->result)); + if (tag != sc_data->bitmask) { + fprintf(stderr, "Unexpected tag `%s` (%d) instead of `%s` at %s:%d.\n", AMvalTypeToString(tag), tag, + AMvalTypeToString(sc_data->bitmask), sc_data->file, sc_data->line); + free(sc_data); + AMstackFree(stack); + exit(EXIT_FAILURE); + return false; + } } - return suffix; + free(data); + return true; } diff --git a/rust/automerge-c/include/automerge-c/utils/result.h b/rust/automerge-c/include/automerge-c/utils/result.h new file mode 100644 index 00000000..ab8a2f93 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/result.h @@ -0,0 +1,30 @@ +#ifndef AUTOMERGE_C_UTILS_RESULT_H +#define AUTOMERGE_C_UTILS_RESULT_H +/** + * \file + * \brief Utility functions for use with `AMresult` structs. + */ + +#include + +#include + +/** + * \brief Transfers the items within an arbitrary list of results into a + * new result in their order of specification. + * \param[in] count The count of subsequent arguments. + * \param[in] ... A \p count list of arguments, each of which is a pointer to + * an `AMresult` struct whose items will be transferred out of it + * and which is subsequently freed. + * \return A pointer to an `AMresult` struct or `NULL`. + * \pre `∀𝑥 ∈` \p ... `, AMresultStatus(𝑥) == AM_STATUS_OK` + * \post `(∃𝑥 ∈` \p ... `, AMresultStatus(𝑥) != AM_STATUS_OK) -> NULL` + * \attention All `AMresult` struct pointer arguments are passed to + * `AMresultFree()` regardless of success; use `AMresultCat()` + * instead if you wish to pass them to `AMresultFree()` yourself. + * \warning The returned `AMresult` struct pointer must be passed to + * `AMresultFree()` in order to avoid a memory leak. + */ +AMresult* AMresultFrom(int count, ...); + +#endif /* AUTOMERGE_C_UTILS_RESULT_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack.h b/rust/automerge-c/include/automerge-c/utils/stack.h new file mode 100644 index 00000000..a8e9fd08 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/stack.h @@ -0,0 +1,130 @@ +#ifndef AUTOMERGE_C_UTILS_STACK_H +#define AUTOMERGE_C_UTILS_STACK_H +/** + * \file + * \brief Utility data structures and functions for hiding `AMresult` structs, + * managing their lifetimes, and automatically applying custom + * validation logic to the `AMitem` structs that they contain. + * + * \note The `AMstack` struct and its related functions drastically reduce the + * need for boilerplate code and/or `goto` statement usage within a C + * application but a higher-level programming language offers even better + * ways to do the same things. + */ + +#include + +/** + * \struct AMstack + * \brief A node in a singly-linked list of result pointers. + */ +typedef struct AMstack { + /** A result to be deallocated. */ + AMresult* result; + /** The previous node in the singly-linked list or `NULL`. */ + struct AMstack* prev; +} AMstack; + +/** + * \memberof AMstack + * \brief The prototype of a function that examines the result at the top of + * the given stack in terms of some arbitrary data. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to arbitrary data or `NULL`. + * \return `true` if the top `AMresult` struct in \p stack is valid, `false` + * otherwise. + * \pre \p stack `!= NULL`. + */ +typedef bool (*AMstackCallback)(AMstack** stack, void* data); + +/** + * \memberof AMstack + * \brief Deallocates the storage for a stack of results. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \pre \p stack `!= NULL` + * \post `*stack == NULL` + */ +void AMstackFree(AMstack** stack); + +/** + * \memberof AMstack + * \brief Gets a result from the stack after removing it. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to the `AMresult` to be popped or `NULL` to + * select the top result in \p stack. + * \return A pointer to an `AMresult` struct or `NULL`. + * \pre \p stack `!= NULL` + * \warning The returned `AMresult` struct pointer must be passed to + * `AMresultFree()` in order to avoid a memory leak. + */ +AMresult* AMstackPop(AMstack** stack, AMresult const* result); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets the + * result if it's valid or gets `NULL` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return \p result or `NULL`. + * \warning If \p stack `== NULL` then \p result is deallocated in order to + * avoid a memory leak. + */ +AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets the + * first item in the sequence of items within that result if it's valid + * or gets `NULL` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return A pointer to an `AMitem` struct or `NULL`. + * \warning If \p stack `== NULL` then \p result is deallocated in order to + * avoid a memory leak. + */ +AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Pushes the given result onto the given stack, calls the given + * callback with the given data to validate it and then either gets an + * `AMitems` struct over the sequence of items within that result if it's + * valid or gets an empty `AMitems` instead. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] result A pointer to an `AMresult` struct. + * \param[in] callback A pointer to a function with the same signature as + * `AMstackCallback()` or `NULL`. + * \param[in] data A pointer to arbitrary data or `NULL` which is passed to + * \p callback. + * \return An `AMitems` struct. + * \warning If \p stack `== NULL` then \p result is deallocated immediately + * in order to avoid a memory leak. + */ +AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data); + +/** + * \memberof AMstack + * \brief Gets the count of results that have been pushed onto the stack. + * + * \param[in,out] stack A pointer to an `AMstack` struct. + * \return A 64-bit unsigned integer. + */ +size_t AMstackSize(AMstack const* const stack); + +#endif /* AUTOMERGE_C_UTILS_STACK_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h new file mode 100644 index 00000000..6f9f1edb --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/stack_callback_data.h @@ -0,0 +1,53 @@ +#ifndef AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H +#define AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H +/** + * \file + * \brief Utility data structures, functions and macros for supplying + * parameters to the custom validation logic applied to `AMitem` + * structs. + */ + +#include + +/** + * \struct AMstackCallbackData + * \brief A data structure for passing the parameters of an item value test + * to an implementation of the `AMstackCallback` function prototype. + */ +typedef struct { + /** A bitmask of `AMvalType` tags. */ + AMvalType bitmask; + /** A null-terminated file path string. */ + char const* file; + /** The ordinal number of a line within a file. */ + int line; +} AMstackCallbackData; + +/** + * \memberof AMstackCallbackData + * \brief Allocates a new `AMstackCallbackData` struct and initializes its + * members from their corresponding arguments. + * + * \param[in] bitmask A bitmask of `AMvalType` tags. + * \param[in] file A null-terminated file path string. + * \param[in] line The ordinal number of a line within a file. + * \return A pointer to a disowned `AMstackCallbackData` struct. + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line); + +/** + * \memberof AMstackCallbackData + * \def AMexpect + * \brief Allocates a new `AMstackCallbackData` struct and initializes it from + * an `AMvalueType` bitmask. + * + * \param[in] bitmask A bitmask of `AMvalType` tags. + * \return A pointer to a disowned `AMstackCallbackData` struct. + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +#define AMexpect(bitmask) AMstackCallbackDataInit(bitmask, __FILE__, __LINE__) + +#endif /* AUTOMERGE_C_UTILS_PUSH_CALLBACK_DATA_H */ diff --git a/rust/automerge-c/include/automerge-c/utils/string.h b/rust/automerge-c/include/automerge-c/utils/string.h new file mode 100644 index 00000000..4d61c2e9 --- /dev/null +++ b/rust/automerge-c/include/automerge-c/utils/string.h @@ -0,0 +1,29 @@ +#ifndef AUTOMERGE_C_UTILS_STRING_H +#define AUTOMERGE_C_UTILS_STRING_H +/** + * \file + * \brief Utility functions for use with `AMbyteSpan` structs that provide + * UTF-8 string views. + */ + +#include + +/** + * \memberof AMbyteSpan + * \brief Returns a pointer to a null-terminated byte string which is a + * duplicate of the given UTF-8 string view except for the substitution + * of its NUL (0) characters with the specified null-terminated byte + * string. + * + * \param[in] str A UTF-8 string view as an `AMbyteSpan` struct. + * \param[in] nul A null-terminated byte string to substitute for NUL characters + * or `NULL` to substitute `"\\0"` for NUL characters. + * \return A disowned null-terminated byte string. + * \pre \p str.src `!= NULL` + * \pre \p str.count `<= sizeof(`\p str.src `)` + * \warning The returned pointer must be passed to `free()` to avoid a memory + * leak. + */ +char* AMstrdup(AMbyteSpan const str, char const* nul); + +#endif /* AUTOMERGE_C_UTILS_STRING_H */ diff --git a/rust/automerge-c/src/CMakeLists.txt b/rust/automerge-c/src/CMakeLists.txt deleted file mode 100644 index e02c0a96..00000000 --- a/rust/automerge-c/src/CMakeLists.txt +++ /dev/null @@ -1,250 +0,0 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - -find_program ( - CARGO_CMD - "cargo" - PATHS "$ENV{CARGO_HOME}/bin" - DOC "The Cargo command" -) - -if(NOT CARGO_CMD) - message(FATAL_ERROR "Cargo (Rust package manager) not found! Install it and/or set the CARGO_HOME environment variable.") -endif() - -string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE_LOWER) - -if(BUILD_TYPE_LOWER STREQUAL debug) - set(CARGO_BUILD_TYPE "debug") - - set(CARGO_FLAG "") -else() - set(CARGO_BUILD_TYPE "release") - - set(CARGO_FLAG "--release") -endif() - -set(CARGO_FEATURES "") - -set(CARGO_CURRENT_BINARY_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}") - -set( - CARGO_OUTPUT - ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX} -) - -if(WIN32) - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - list(APPEND CARGO_OUTPUT ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}) -endif() - -add_custom_command( - OUTPUT - ${CARGO_OUTPUT} - COMMAND - # \note cbindgen won't regenerate its output header file after it's - # been removed but it will after its configuration file has been - # updated. - ${CMAKE_COMMAND} -DCONDITION=NOT_EXISTS -P ${CMAKE_SOURCE_DIR}/cmake/file_touch.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h ${CMAKE_SOURCE_DIR}/cbindgen.toml - COMMAND - ${CMAKE_COMMAND} -E env CARGO_TARGET_DIR=${CARGO_TARGET_DIR} CBINDGEN_TARGET_DIR=${CBINDGEN_TARGET_DIR} ${CARGO_CMD} build ${CARGO_FLAG} ${CARGO_FEATURES} - MAIN_DEPENDENCY - lib.rs - DEPENDS - actor_id.rs - byte_span.rs - change_hashes.rs - change.rs - changes.rs - doc.rs - doc/list.rs - doc/list/item.rs - doc/list/items.rs - doc/map.rs - doc/map/item.rs - doc/map/items.rs - doc/utils.rs - obj.rs - obj/item.rs - obj/items.rs - result.rs - result_stack.rs - strs.rs - sync.rs - sync/have.rs - sync/haves.rs - sync/message.rs - sync/state.rs - ${CMAKE_SOURCE_DIR}/build.rs - ${CMAKE_SOURCE_DIR}/Cargo.toml - ${CMAKE_SOURCE_DIR}/cbindgen.toml - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Producing the library artifacts with Cargo..." - VERBATIM -) - -add_custom_target( - ${LIBRARY_NAME}_artifacts ALL - DEPENDS ${CARGO_OUTPUT} -) - -# \note cbindgen's naming behavior isn't fully configurable and it ignores -# `const fn` calls (https://github.com/eqrion/cbindgen/issues/252). -add_custom_command( - TARGET ${LIBRARY_NAME}_artifacts - POST_BUILD - COMMAND - # Compensate for cbindgen's variant struct naming. - ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+_[^_]+\)_Body -DREPLACE_EXPR=AM\\1 -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen's union tag enum type naming. - ${CMAKE_COMMAND} -DMATCH_REGEX=AM\([^_]+\)_Tag -DREPLACE_EXPR=AM\\1Variant -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen's translation of consecutive uppercase letters to "ScreamingSnakeCase". - ${CMAKE_COMMAND} -DMATCH_REGEX=A_M\([^_]+\)_ -DREPLACE_EXPR=AM_\\1_ -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - COMMAND - # Compensate for cbindgen ignoring `std:mem::size_of()` calls. - ${CMAKE_COMMAND} -DMATCH_REGEX=USIZE_ -DREPLACE_EXPR=\+${CMAKE_SIZEOF_VOID_P} -P ${CMAKE_SOURCE_DIR}/cmake/file_regex_replace.cmake -- ${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h - WORKING_DIRECTORY - ${CMAKE_SOURCE_DIR} - COMMENT - "Compensating for cbindgen deficits..." - VERBATIM -) - -if(BUILD_SHARED_LIBS) - if(WIN32) - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_BINDIR}") - else() - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") - endif() - - set(LIBRARY_DEFINE_SYMBOL "${SYMBOL_PREFIX}_EXPORTS") - - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - set(LIBRARY_IMPLIB "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") - - set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}") - - set(LIBRARY_NO_SONAME "${WIN32}") - - set(LIBRARY_SONAME "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}") - - set(LIBRARY_TYPE "SHARED") -else() - set(LIBRARY_DEFINE_SYMBOL "") - - set(LIBRARY_DESTINATION "${CMAKE_INSTALL_LIBDIR}") - - set(LIBRARY_IMPLIB "") - - set(LIBRARY_LOCATION "${CARGO_CURRENT_BINARY_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX}") - - set(LIBRARY_NO_SONAME "TRUE") - - set(LIBRARY_SONAME "") - - set(LIBRARY_TYPE "STATIC") -endif() - -add_library(${LIBRARY_NAME} ${LIBRARY_TYPE} IMPORTED GLOBAL) - -set_target_properties( - ${LIBRARY_NAME} - PROPERTIES - # \note Cargo writes a debug build into a nested directory instead of - # decorating its name. - DEBUG_POSTFIX "" - DEFINE_SYMBOL "${LIBRARY_DEFINE_SYMBOL}" - IMPORTED_IMPLIB "${LIBRARY_IMPLIB}" - IMPORTED_LOCATION "${LIBRARY_LOCATION}" - IMPORTED_NO_SONAME "${LIBRARY_NO_SONAME}" - IMPORTED_SONAME "${LIBRARY_SONAME}" - LINKER_LANGUAGE C - PUBLIC_HEADER "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" - SOVERSION "${PROJECT_VERSION_MAJOR}" - VERSION "${PROJECT_VERSION}" - # \note Cargo exports all of the symbols automatically. - WINDOWS_EXPORT_ALL_SYMBOLS "TRUE" -) - -target_compile_definitions(${LIBRARY_NAME} INTERFACE $) - -target_include_directories( - ${LIBRARY_NAME} - INTERFACE - "$" -) - -set(CMAKE_THREAD_PREFER_PTHREAD TRUE) - -set(THREADS_PREFER_PTHREAD_FLAG TRUE) - -find_package(Threads REQUIRED) - -set(LIBRARY_DEPENDENCIES Threads::Threads ${CMAKE_DL_LIBS}) - -if(WIN32) - list(APPEND LIBRARY_DEPENDENCIES Bcrypt userenv ws2_32) -else() - list(APPEND LIBRARY_DEPENDENCIES m) -endif() - -target_link_libraries(${LIBRARY_NAME} INTERFACE ${LIBRARY_DEPENDENCIES}) - -install( - FILES $ - TYPE LIB - # \note The basename of an import library output by Cargo is the filename - # of its corresponding shared library. - RENAME "${CMAKE_STATIC_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}" - OPTIONAL -) - -set(LIBRARY_FILE_NAME "${CMAKE_${LIBRARY_TYPE}_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_${LIBRARY_TYPE}_LIBRARY_SUFFIX}") - -install( - FILES $ - RENAME "${LIBRARY_FILE_NAME}" - DESTINATION ${LIBRARY_DESTINATION} -) - -install( - FILES $ - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} -) - -find_package(Doxygen OPTIONAL_COMPONENTS dot) - -if(DOXYGEN_FOUND) - set(DOXYGEN_ALIASES "installed_headerfile=\\headerfile ${LIBRARY_NAME}.h <${PROJECT_NAME}/${LIBRARY_NAME}.h>") - - set(DOXYGEN_GENERATE_LATEX YES) - - set(DOXYGEN_PDF_HYPERLINKS YES) - - set(DOXYGEN_PROJECT_LOGO "${CMAKE_SOURCE_DIR}/img/brandmark.png") - - set(DOXYGEN_SORT_BRIEF_DOCS YES) - - set(DOXYGEN_USE_MDFILE_AS_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md") - - doxygen_add_docs( - ${LIBRARY_NAME}_docs - "${CBINDGEN_TARGET_DIR}/${LIBRARY_NAME}.h" - "${CMAKE_SOURCE_DIR}/README.md" - USE_STAMP_FILE - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMENT "Producing documentation with Doxygen..." - ) - - # \note A Doxygen input file isn't a file-level dependency so the Doxygen - # command must instead depend upon a target that outputs the file or - # it will just output an error message when it can't be found. - add_dependencies(${LIBRARY_NAME}_docs ${LIBRARY_NAME}_artifacts) -endif() diff --git a/rust/automerge-c/src/actor_id.rs b/rust/automerge-c/src/actor_id.rs index bc86d5ef..5a28959e 100644 --- a/rust/automerge-c/src/actor_id.rs +++ b/rust/automerge-c/src/actor_id.rs @@ -1,4 +1,5 @@ use automerge as am; +use libc::c_int; use std::cell::RefCell; use std::cmp::Ordering; use std::str::FromStr; @@ -11,7 +12,7 @@ macro_rules! to_actor_id { let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMactorId pointer").into(), + None => return AMresult::error("Invalid `AMactorId*`").into(), } }}; } @@ -57,11 +58,11 @@ impl AsRef for AMactorId { } /// \memberof AMactorId -/// \brief Gets the value of an actor identifier as a sequence of bytes. +/// \brief Gets the value of an actor identifier as an array of bytes. /// /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \pre \p actor_id `!= NULL`. -/// \return An `AMbyteSpan` struct. +/// \return An `AMbyteSpan` struct for an array of bytes. +/// \pre \p actor_id `!= NULL` /// \internal /// /// # Safety @@ -82,8 +83,8 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa /// \return `-1` if \p actor_id1 `<` \p actor_id2, `0` if /// \p actor_id1 `==` \p actor_id2 and `1` if /// \p actor_id1 `>` \p actor_id2. -/// \pre \p actor_id1 `!= NULL`. -/// \pre \p actor_id2 `!= NULL`. +/// \pre \p actor_id1 `!= NULL` +/// \pre \p actor_id2 `!= NULL` /// \internal /// /// #Safety @@ -93,7 +94,7 @@ pub unsafe extern "C" fn AMactorIdBytes(actor_id: *const AMactorId) -> AMbyteSpa pub unsafe extern "C" fn AMactorIdCmp( actor_id1: *const AMactorId, actor_id2: *const AMactorId, -) -> isize { +) -> c_int { match (actor_id1.as_ref(), actor_id2.as_ref()) { (Some(actor_id1), Some(actor_id2)) => match actor_id1.as_ref().cmp(actor_id2.as_ref()) { Ordering::Less => -1, @@ -101,65 +102,69 @@ pub unsafe extern "C" fn AMactorIdCmp( Ordering::Greater => 1, }, (None, Some(_)) => -1, - (Some(_), None) => 1, (None, None) => 0, + (Some(_), None) => 1, } } /// \memberof AMactorId -/// \brief Allocates a new actor identifier and initializes it with a random -/// UUID. +/// \brief Allocates a new actor identifier and initializes it from a random +/// UUID value. /// -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. #[no_mangle] pub unsafe extern "C" fn AMactorIdInit() -> *mut AMresult { to_result(Ok::(am::ActorId::random())) } /// \memberof AMactorId -/// \brief Allocates a new actor identifier and initializes it from a sequence -/// of bytes. +/// \brief Allocates a new actor identifier and initializes it from an array of +/// bytes value. /// -/// \param[in] src A pointer to a contiguous sequence of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] -pub unsafe extern "C" fn AMactorIdInitBytes(src: *const u8, count: usize) -> *mut AMresult { - let slice = std::slice::from_raw_parts(src, count); - to_result(Ok::(am::ActorId::from( - slice, - ))) +pub unsafe extern "C" fn AMactorIdFromBytes(src: *const u8, count: usize) -> *mut AMresult { + if !src.is_null() { + let value = std::slice::from_raw_parts(src, count); + to_result(Ok::(am::ActorId::from( + value, + ))) + } else { + AMresult::error("Invalid uint8_t*").into() + } } /// \memberof AMactorId /// \brief Allocates a new actor identifier and initializes it from a -/// hexadecimal string. +/// hexadecimal UTF-8 string view value. /// -/// \param[in] hex_str A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// hex_str must be a valid pointer to an AMbyteSpan #[no_mangle] -pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult { +pub unsafe extern "C" fn AMactorIdFromStr(value: AMbyteSpan) -> *mut AMresult { use am::AutomergeError::InvalidActorId; - to_result(match (&hex_str).try_into() { + to_result(match (&value).try_into() { Ok(s) => match am::ActorId::from_str(s) { Ok(actor_id) => Ok(actor_id), Err(_) => Err(InvalidActorId(String::from(s))), @@ -169,11 +174,12 @@ pub unsafe extern "C" fn AMactorIdInitStr(hex_str: AMbyteSpan) -> *mut AMresult } /// \memberof AMactorId -/// \brief Gets the value of an actor identifier as a hexadecimal string. +/// \brief Gets the value of an actor identifier as a UTF-8 hexadecimal string +/// view. /// /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \pre \p actor_id `!= NULL`. /// \return A UTF-8 string view as an `AMbyteSpan` struct. +/// \pre \p actor_id `!= NULL` /// \internal /// /// # Safety diff --git a/rust/automerge-c/src/byte_span.rs b/rust/automerge-c/src/byte_span.rs index fd4c3ca0..5855cfc7 100644 --- a/rust/automerge-c/src/byte_span.rs +++ b/rust/automerge-c/src/byte_span.rs @@ -1,14 +1,17 @@ use automerge as am; -use libc::strlen; +use std::cmp::Ordering; use std::convert::TryFrom; use std::os::raw::c_char; +use libc::{c_int, strlen}; +use smol_str::SmolStr; + macro_rules! to_str { - ($span:expr) => {{ - let result: Result<&str, am::AutomergeError> = (&$span).try_into(); + ($byte_span:expr) => {{ + let result: Result<&str, am::AutomergeError> = (&$byte_span).try_into(); match result { Ok(s) => s, - Err(e) => return AMresult::err(&e.to_string()).into(), + Err(e) => return AMresult::error(&e.to_string()).into(), } }}; } @@ -17,16 +20,17 @@ pub(crate) use to_str; /// \struct AMbyteSpan /// \installed_headerfile -/// \brief A view onto a contiguous sequence of bytes. +/// \brief A view onto an array of bytes. #[repr(C)] pub struct AMbyteSpan { - /// A pointer to an array of bytes. - /// \attention NEVER CALL `free()` ON \p src! - /// \warning \p src is only valid until the `AMfree()` function is called - /// on the `AMresult` struct that stores the array of bytes to - /// which it points. + /// A pointer to the first byte of an array of bytes. + /// \warning \p src is only valid until the array of bytes to which it + /// points is freed. + /// \note If the `AMbyteSpan` came from within an `AMitem` struct then + /// \p src will be freed when the pointer to the `AMresult` struct + /// containing the `AMitem` struct is passed to `AMresultFree()`. pub src: *const u8, - /// The number of bytes in the array. + /// The count of bytes in the array. pub count: usize, } @@ -52,9 +56,7 @@ impl PartialEq for AMbyteSpan { } else if self.src == other.src { return true; } - let slice = unsafe { std::slice::from_raw_parts(self.src, self.count) }; - let other_slice = unsafe { std::slice::from_raw_parts(other.src, other.count) }; - slice == other_slice + <&[u8]>::from(self) == <&[u8]>::from(other) } } @@ -72,10 +74,15 @@ impl From<&am::ActorId> for AMbyteSpan { impl From<&mut am::ActorId> for AMbyteSpan { fn from(actor: &mut am::ActorId) -> Self { - let slice = actor.to_bytes(); + actor.as_ref().into() + } +} + +impl From<&am::ChangeHash> for AMbyteSpan { + fn from(change_hash: &am::ChangeHash) -> Self { Self { - src: slice.as_ptr(), - count: slice.len(), + src: change_hash.0.as_ptr(), + count: change_hash.0.len(), } } } @@ -93,12 +100,9 @@ impl From<*const c_char> for AMbyteSpan { } } -impl From<&am::ChangeHash> for AMbyteSpan { - fn from(change_hash: &am::ChangeHash) -> Self { - Self { - src: change_hash.0.as_ptr(), - count: change_hash.0.len(), - } +impl From<&SmolStr> for AMbyteSpan { + fn from(smol_str: &SmolStr) -> Self { + smol_str.as_bytes().into() } } @@ -111,13 +115,39 @@ impl From<&[u8]> for AMbyteSpan { } } +impl From<&AMbyteSpan> for &[u8] { + fn from(byte_span: &AMbyteSpan) -> Self { + unsafe { std::slice::from_raw_parts(byte_span.src, byte_span.count) } + } +} + +impl From<&AMbyteSpan> for Vec { + fn from(byte_span: &AMbyteSpan) -> Self { + <&[u8]>::from(byte_span).to_vec() + } +} + +impl TryFrom<&AMbyteSpan> for am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(byte_span: &AMbyteSpan) -> Result { + use am::AutomergeError::InvalidChangeHashBytes; + + let slice: &[u8] = byte_span.into(); + match slice.try_into() { + Ok(change_hash) => Ok(change_hash), + Err(e) => Err(InvalidChangeHashBytes(e)), + } + } +} + impl TryFrom<&AMbyteSpan> for &str { type Error = am::AutomergeError; - fn try_from(span: &AMbyteSpan) -> Result { + fn try_from(byte_span: &AMbyteSpan) -> Result { use am::AutomergeError::InvalidCharacter; - let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) }; + let slice = byte_span.into(); match std::str::from_utf8(slice) { Ok(str_) => Ok(str_), Err(e) => Err(InvalidCharacter(e.valid_up_to())), @@ -125,17 +155,69 @@ impl TryFrom<&AMbyteSpan> for &str { } } -/// \brief Creates an AMbyteSpan from a pointer + length +/// \memberof AMbyteSpan +/// \brief Creates a view onto an array of bytes. /// -/// \param[in] src A pointer to a span of bytes -/// \param[in] count The number of bytes in the span -/// \return An `AMbyteSpan` struct +/// \param[in] src A pointer to an array of bytes or `NULL`. +/// \param[in] count The count of bytes to view from the array pointed to by +/// \p src. +/// \return An `AMbyteSpan` struct. +/// \pre \p count `<= sizeof(`\p src `)` +/// \post `(`\p src `== NULL) -> (AMbyteSpan){NULL, 0}` /// \internal /// /// #Safety -/// AMbytes does not retain the underlying storage, so you must discard the -/// return value before freeing the bytes. +/// src must be a byte array of length `>= count` or `std::ptr::null()` #[no_mangle] pub unsafe extern "C" fn AMbytes(src: *const u8, count: usize) -> AMbyteSpan { - AMbyteSpan { src, count } + AMbyteSpan { + src, + count: if src.is_null() { 0 } else { count }, + } +} + +/// \memberof AMbyteSpan +/// \brief Creates a view onto a C string. +/// +/// \param[in] c_str A null-terminated byte string or `NULL`. +/// \return An `AMbyteSpan` struct. +/// \pre Each byte in \p c_str encodes one UTF-8 character. +/// \internal +/// +/// #Safety +/// c_str must be a null-terminated array of `std::os::raw::c_char` or `std::ptr::null()`. +#[no_mangle] +pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan { + c_str.into() +} + +/// \memberof AMbyteSpan +/// \brief Compares two UTF-8 string views lexicographically. +/// +/// \param[in] lhs A UTF-8 string view as an `AMbyteSpan` struct. +/// \param[in] rhs A UTF-8 string view as an `AMbyteSpan` struct. +/// \return Negative value if \p lhs appears before \p rhs in lexicographical order. +/// Zero if \p lhs and \p rhs compare equal. +/// Positive value if \p lhs appears after \p rhs in lexicographical order. +/// \pre \p lhs.src `!= NULL` +/// \pre \p lhs.count `<= sizeof(`\p lhs.src `)` +/// \pre \p rhs.src `!= NULL` +/// \pre \p rhs.count `<= sizeof(`\p rhs.src `)` +/// \internal +/// +/// #Safety +/// lhs.src must be a byte array of length >= lhs.count +/// rhs.src must be a a byte array of length >= rhs.count +#[no_mangle] +pub unsafe extern "C" fn AMstrCmp(lhs: AMbyteSpan, rhs: AMbyteSpan) -> c_int { + match (<&str>::try_from(&lhs), <&str>::try_from(&rhs)) { + (Ok(lhs), Ok(rhs)) => match lhs.cmp(rhs) { + Ordering::Less => -1, + Ordering::Equal => 0, + Ordering::Greater => 1, + }, + (Err(_), Ok(_)) => -1, + (Err(_), Err(_)) => 0, + (Ok(_), Err(_)) => 1, + } } diff --git a/rust/automerge-c/src/change.rs b/rust/automerge-c/src/change.rs index d64a2635..8529ed94 100644 --- a/rust/automerge-c/src/change.rs +++ b/rust/automerge-c/src/change.rs @@ -2,7 +2,6 @@ use automerge as am; use std::cell::RefCell; use crate::byte_span::AMbyteSpan; -use crate::change_hashes::AMchangeHashes; use crate::result::{to_result, AMresult}; macro_rules! to_change { @@ -10,7 +9,7 @@ macro_rules! to_change { let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMchange pointer").into(), + None => return AMresult::error("Invalid `AMchange*`").into(), } }}; } @@ -21,14 +20,14 @@ macro_rules! to_change { #[derive(Eq, PartialEq)] pub struct AMchange { body: *mut am::Change, - changehash: RefCell>, + change_hash: RefCell>, } impl AMchange { pub fn new(change: &mut am::Change) -> Self { Self { body: change, - changehash: Default::default(), + change_hash: Default::default(), } } @@ -40,12 +39,12 @@ impl AMchange { } pub fn hash(&self) -> AMbyteSpan { - let mut changehash = self.changehash.borrow_mut(); - if let Some(changehash) = changehash.as_ref() { - changehash.into() + let mut change_hash = self.change_hash.borrow_mut(); + if let Some(change_hash) = change_hash.as_ref() { + change_hash.into() } else { let hash = unsafe { (*self.body).hash() }; - let ptr = changehash.insert(hash); + let ptr = change_hash.insert(hash); AMbyteSpan { src: ptr.0.as_ptr(), count: hash.as_ref().len(), @@ -70,11 +69,10 @@ impl AsRef for AMchange { /// \brief Gets the first referenced actor identifier in a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \pre \p change `!= NULL`. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p change `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -90,8 +88,8 @@ pub unsafe extern "C" fn AMchangeActorId(change: *const AMchange) -> *mut AMresu /// \memberof AMchange /// \brief Compresses the raw bytes of a change. /// -/// \param[in,out] change A pointer to an `AMchange` struct. -/// \pre \p change `!= NULL`. +/// \param[in] change A pointer to an `AMchange` struct. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -107,18 +105,20 @@ pub unsafe extern "C" fn AMchangeCompress(change: *mut AMchange) { /// \brief Gets the dependencies of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A pointer to an `AMchangeHashes` struct or `NULL`. -/// \pre \p change `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p change `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// change must be a valid pointer to an AMchange #[no_mangle] -pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes { - match change.as_ref() { - Some(change) => AMchangeHashes::new(change.as_ref().deps()), +pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> *mut AMresult { + to_result(match change.as_ref() { + Some(change) => change.as_ref().deps(), None => Default::default(), - } + }) } /// \memberof AMchange @@ -126,7 +126,7 @@ pub unsafe extern "C" fn AMchangeDeps(change: *const AMchange) -> AMchangeHashes /// /// \param[in] change A pointer to an `AMchange` struct. /// \return An `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -141,32 +141,33 @@ pub unsafe extern "C" fn AMchangeExtraBytes(change: *const AMchange) -> AMbyteSp } /// \memberof AMchange -/// \brief Loads a sequence of bytes into a change. +/// \brief Allocates a new change and initializes it from an array of bytes value. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing an `AMchange` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMchangeFromBytes(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::Change::from_bytes(data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::Change::from_bytes(data.to_vec())) } /// \memberof AMchange /// \brief Gets the hash of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A change hash as an `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for a change hash. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -183,8 +184,8 @@ pub unsafe extern "C" fn AMchangeHash(change: *const AMchange) -> AMbyteSpan { /// \brief Tests the emptiness of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A boolean. -/// \pre \p change `!= NULL`. +/// \return `true` if \p change is empty, `false` otherwise. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -198,12 +199,37 @@ pub unsafe extern "C" fn AMchangeIsEmpty(change: *const AMchange) -> bool { } } +/// \memberof AMchange +/// \brief Loads a document into a sequence of changes. +/// +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// src must be a byte array of length `>= count` +#[no_mangle] +pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult { + let data = std::slice::from_raw_parts(src, count); + to_result::, _>>( + am::Automerge::load(data) + .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), + ) +} + /// \memberof AMchange /// \brief Gets the maximum operation index of a change. /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -221,8 +247,8 @@ pub unsafe extern "C" fn AMchangeMaxOp(change: *const AMchange) -> u64 { /// \brief Gets the message of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for a UTF-8 string. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -240,7 +266,7 @@ pub unsafe extern "C" fn AMchangeMessage(change: *const AMchange) -> AMbyteSpan /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -259,7 +285,7 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -267,10 +293,9 @@ pub unsafe extern "C" fn AMchangeSeq(change: *const AMchange) -> u64 { #[no_mangle] pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { if let Some(change) = change.as_ref() { - change.as_ref().len() - } else { - 0 + return change.as_ref().len(); } + 0 } /// \memberof AMchange @@ -278,7 +303,7 @@ pub unsafe extern "C" fn AMchangeSize(change: *const AMchange) -> usize { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit unsigned integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -297,7 +322,7 @@ pub unsafe extern "C" fn AMchangeStartOp(change: *const AMchange) -> u64 { /// /// \param[in] change A pointer to an `AMchange` struct. /// \return A 64-bit signed integer. -/// \pre \p change `!= NULL`. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -315,8 +340,8 @@ pub unsafe extern "C" fn AMchangeTime(change: *const AMchange) -> i64 { /// \brief Gets the raw bytes of a change. /// /// \param[in] change A pointer to an `AMchange` struct. -/// \return An `AMbyteSpan` struct. -/// \pre \p change `!= NULL`. +/// \return An `AMbyteSpan` struct for an array of bytes. +/// \pre \p change `!= NULL` /// \internal /// /// # Safety @@ -329,28 +354,3 @@ pub unsafe extern "C" fn AMchangeRawBytes(change: *const AMchange) -> AMbyteSpan Default::default() } } - -/// \memberof AMchange -/// \brief Loads a document into a sequence of changes. -/// -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing a sequence of -/// `AMchange` structs. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be a byte array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangeLoadDocument(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result::, _>>( - am::Automerge::load(&data) - .and_then(|d| d.get_changes(&[]).map(|c| c.into_iter().cloned().collect())), - ) -} diff --git a/rust/automerge-c/src/change_hashes.rs b/rust/automerge-c/src/change_hashes.rs deleted file mode 100644 index 029612e9..00000000 --- a/rust/automerge-c/src/change_hashes.rs +++ /dev/null @@ -1,400 +0,0 @@ -use automerge as am; -use std::cmp::Ordering; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::byte_span::AMbyteSpan; -use crate::result::{to_result, AMresult}; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(change_hashes: &[am::ChangeHash], offset: isize) -> Self { - Self { - len: change_hashes.len(), - offset, - ptr: change_hashes.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { - if self.is_stopped() { - return None; - } - let slice: &[am::ChangeHash] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[am::ChangeHash] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::ChangeHash, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMchangeHashes -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of change hashes. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMchangeHashes { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMchangeHashes { - pub fn new(change_hashes: &[am::ChangeHash]) -> Self { - Self { - detail: Detail::new(change_hashes, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&am::ChangeHash> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&am::ChangeHash> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::ChangeHash]> for AMchangeHashes { - fn as_ref(&self) -> &[am::ChangeHash] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::ChangeHash, detail.len) } - } -} - -impl Default for AMchangeHashes { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMchangeHashes -/// \brief Advances an iterator over a sequence of change hashes by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesAdvance(change_hashes: *mut AMchangeHashes, n: isize) { - if let Some(change_hashes) = change_hashes.as_mut() { - change_hashes.advance(n); - }; -} - -/// \memberof AMchangeHashes -/// \brief Compares the sequences of change hashes underlying a pair of -/// iterators. -/// -/// \param[in] change_hashes1 A pointer to an `AMchangeHashes` struct. -/// \param[in] change_hashes2 A pointer to an `AMchangeHashes` struct. -/// \return `-1` if \p change_hashes1 `<` \p change_hashes2, `0` if -/// \p change_hashes1 `==` \p change_hashes2 and `1` if -/// \p change_hashes1 `>` \p change_hashes2. -/// \pre \p change_hashes1 `!= NULL`. -/// \pre \p change_hashes2 `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes1 must be a valid pointer to an AMchangeHashes -/// change_hashes2 must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesCmp( - change_hashes1: *const AMchangeHashes, - change_hashes2: *const AMchangeHashes, -) -> isize { - match (change_hashes1.as_ref(), change_hashes2.as_ref()) { - (Some(change_hashes1), Some(change_hashes2)) => { - match change_hashes1.as_ref().cmp(change_hashes2.as_ref()) { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - } - } - (None, Some(_)) => -1, - (Some(_), None) => 1, - (None, None) => 0, - } -} - -/// \memberof AMchangeHashes -/// \brief Allocates an iterator over a sequence of change hashes and -/// initializes it from a sequence of byte spans. -/// -/// \param[in] src A pointer to an array of `AMbyteSpan` structs. -/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be an AMbyteSpan array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { - let mut change_hashes = Vec::::new(); - for n in 0..count { - let byte_span = &*src.add(n); - let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); - match slice.try_into() { - Ok(change_hash) => { - change_hashes.push(change_hash); - } - Err(e) => { - return to_result(Err(e)); - } - } - } - to_result(Ok::, am::InvalidChangeHashSlice>( - change_hashes, - )) -} - -/// \memberof AMchangeHashes -/// \brief Gets the change hash at the current position of an iterator over a -/// sequence of change hashes and then advances it by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes -/// was previously advanced past its forward/reverse limit. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesNext( - change_hashes: *mut AMchangeHashes, - n: isize, -) -> AMbyteSpan { - if let Some(change_hashes) = change_hashes.as_mut() { - if let Some(change_hash) = change_hashes.next(n) { - return change_hash.into(); - } - } - Default::default() -} - -/// \memberof AMchangeHashes -/// \brief Advances an iterator over a sequence of change hashes by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the change hash at its new -/// position. -/// -/// \param[in,out] change_hashes A pointer to an `AMchangeHashes` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return An `AMbyteSpan` struct with `.src == NULL` when \p change_hashes is -/// presently advanced past its forward/reverse limit. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesPrev( - change_hashes: *mut AMchangeHashes, - n: isize, -) -> AMbyteSpan { - if let Some(change_hashes) = change_hashes.as_mut() { - if let Some(change_hash) = change_hashes.prev(n) { - return change_hash.into(); - } - } - Default::default() -} - -/// \memberof AMchangeHashes -/// \brief Gets the size of the sequence of change hashes underlying an -/// iterator. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return The count of values in \p change_hashes. -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesSize(change_hashes: *const AMchangeHashes) -> usize { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.len() - } else { - 0 - } -} - -/// \memberof AMchangeHashes -/// \brief Creates an iterator over the same sequence of change hashes as the -/// given one but with the opposite position and direction. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return An `AMchangeHashes` struct -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesReversed( - change_hashes: *const AMchangeHashes, -) -> AMchangeHashes { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.reversed() - } else { - Default::default() - } -} - -/// \memberof AMchangeHashes -/// \brief Creates an iterator at the starting position over the same sequence -/// of change hashes as the given one. -/// -/// \param[in] change_hashes A pointer to an `AMchangeHashes` struct. -/// \return An `AMchangeHashes` struct -/// \pre \p change_hashes `!= NULL`. -/// \internal -/// -/// #Safety -/// change_hashes must be a valid pointer to an AMchangeHashes -#[no_mangle] -pub unsafe extern "C" fn AMchangeHashesRewound( - change_hashes: *const AMchangeHashes, -) -> AMchangeHashes { - if let Some(change_hashes) = change_hashes.as_ref() { - change_hashes.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/changes.rs b/rust/automerge-c/src/changes.rs deleted file mode 100644 index 1bff35c8..00000000 --- a/rust/automerge-c/src/changes.rs +++ /dev/null @@ -1,399 +0,0 @@ -use automerge as am; -use std::collections::BTreeMap; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::byte_span::AMbyteSpan; -use crate::change::AMchange; -use crate::result::{to_result, AMresult}; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, - storage: *mut c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(changes: &[am::Change], offset: isize, storage: &mut BTreeMap) -> Self { - let storage: *mut BTreeMap = storage; - Self { - len: changes.len(), - offset, - ptr: changes.as_ptr() as *const c_void, - storage: storage as *mut c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<*const AMchange> { - if self.is_stopped() { - return None; - } - let slice: &mut [am::Change] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - let value = match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMchange::new(&mut slice[index])); - storage.get_mut(&index).unwrap() - } - }; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &mut [am::Change] = - unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut am::Change, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - Some(match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMchange::new(&mut slice[index])); - storage.get_mut(&index).unwrap() - } - }) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - storage: self.storage, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - storage: self.storage, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts( - (&detail as *const Detail) as *const u8, - USIZE_USIZE_USIZE_USIZE_, - ) - .try_into() - .unwrap() - } - } -} - -/// \struct AMchanges -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of changes. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMchanges { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_USIZE_], -} - -impl AMchanges { - pub fn new(changes: &[am::Change], storage: &mut BTreeMap) -> Self { - Self { - detail: Detail::new(changes, 0, &mut *storage).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<*const AMchange> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMchange> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::Change]> for AMchanges { - fn as_ref(&self) -> &[am::Change] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::Change, detail.len) } - } -} - -impl Default for AMchanges { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMchanges -/// \brief Advances an iterator over a sequence of changes by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesAdvance(changes: *mut AMchanges, n: isize) { - if let Some(changes) = changes.as_mut() { - changes.advance(n); - }; -} - -/// \memberof AMchanges -/// \brief Tests the equality of two sequences of changes underlying a pair of -/// iterators. -/// -/// \param[in] changes1 A pointer to an `AMchanges` struct. -/// \param[in] changes2 A pointer to an `AMchanges` struct. -/// \return `true` if \p changes1 `==` \p changes2 and `false` otherwise. -/// \pre \p changes1 `!= NULL`. -/// \pre \p changes2 `!= NULL`. -/// \internal -/// -/// #Safety -/// changes1 must be a valid pointer to an AMchanges -/// changes2 must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesEqual( - changes1: *const AMchanges, - changes2: *const AMchanges, -) -> bool { - match (changes1.as_ref(), changes2.as_ref()) { - (Some(changes1), Some(changes2)) => changes1.as_ref() == changes2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMchanges -/// \brief Allocates an iterator over a sequence of changes and initializes it -/// from a sequence of byte spans. -/// -/// \param[in] src A pointer to an array of `AMbyteSpan` structs. -/// \param[in] count The number of `AMbyteSpan` structs to copy from \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`) / sizeof(AMbyteSpan)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. -/// \internal -/// -/// # Safety -/// src must be an AMbyteSpan array of size `>= count` -#[no_mangle] -pub unsafe extern "C" fn AMchangesInit(src: *const AMbyteSpan, count: usize) -> *mut AMresult { - let mut changes = Vec::::new(); - for n in 0..count { - let byte_span = &*src.add(n); - let slice = std::slice::from_raw_parts(byte_span.src, byte_span.count); - match slice.try_into() { - Ok(change) => { - changes.push(change); - } - Err(e) => { - return to_result(Err::, am::LoadChangeError>(e)); - } - } - } - to_result(Ok::, am::LoadChangeError>(changes)) -} - -/// \memberof AMchanges -/// \brief Gets the change at the current position of an iterator over a -/// sequence of changes and then advances it by at most \p |n| positions -/// where the sign of \p n is relative to the iterator's direction. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes was -/// previously advanced past its forward/reverse limit. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesNext(changes: *mut AMchanges, n: isize) -> *const AMchange { - if let Some(changes) = changes.as_mut() { - if let Some(change) = changes.next(n) { - return change; - } - } - std::ptr::null() -} - -/// \memberof AMchanges -/// \brief Advances an iterator over a sequence of changes by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction and then gets the change at its new position. -/// -/// \param[in,out] changes A pointer to an `AMchanges` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMchange` struct that's `NULL` when \p changes is -/// presently advanced past its forward/reverse limit. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesPrev(changes: *mut AMchanges, n: isize) -> *const AMchange { - if let Some(changes) = changes.as_mut() { - if let Some(change) = changes.prev(n) { - return change; - } - } - std::ptr::null() -} - -/// \memberof AMchanges -/// \brief Gets the size of the sequence of changes underlying an iterator. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return The count of values in \p changes. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesSize(changes: *const AMchanges) -> usize { - if let Some(changes) = changes.as_ref() { - changes.len() - } else { - 0 - } -} - -/// \memberof AMchanges -/// \brief Creates an iterator over the same sequence of changes as the given -/// one but with the opposite position and direction. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return An `AMchanges` struct. -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesReversed(changes: *const AMchanges) -> AMchanges { - if let Some(changes) = changes.as_ref() { - changes.reversed() - } else { - Default::default() - } -} - -/// \memberof AMchanges -/// \brief Creates an iterator at the starting position over the same sequence -/// of changes as the given one. -/// -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \return An `AMchanges` struct -/// \pre \p changes `!= NULL`. -/// \internal -/// -/// #Safety -/// changes must be a valid pointer to an AMchanges -#[no_mangle] -pub unsafe extern "C" fn AMchangesRewound(changes: *const AMchanges) -> AMchanges { - if let Some(changes) = changes.as_ref() { - changes.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc.rs b/rust/automerge-c/src/doc.rs index f02c01bf..82f52bf7 100644 --- a/rust/automerge-c/src/doc.rs +++ b/rust/automerge-c/src/doc.rs @@ -6,43 +6,23 @@ use std::ops::{Deref, DerefMut}; use crate::actor_id::{to_actor_id, AMactorId}; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; +use crate::items::AMitems; use crate::obj::{to_obj_id, AMobjId, AMobjType}; -use crate::result::{to_result, AMresult, AMvalue}; +use crate::result::{to_result, AMresult}; use crate::sync::{to_sync_message, AMsyncMessage, AMsyncState}; pub mod list; pub mod map; pub mod utils; -use crate::changes::AMchanges; -use crate::doc::utils::{to_doc, to_doc_mut}; - -macro_rules! to_changes { - ($handle:expr) => {{ - let handle = $handle.as_ref(); - match handle { - Some(b) => b, - None => return AMresult::err("Invalid AMchanges pointer").into(), - } - }}; -} - -macro_rules! to_index { - ($index:expr, $len:expr, $param_name:expr) => {{ - if $index > $len && $index != usize::MAX { - return AMresult::err(&format!("Invalid {} {}", $param_name, $index)).into(); - } - std::cmp::min($index, $len) - }}; -} +use crate::doc::utils::{clamp, to_doc, to_doc_mut, to_items}; macro_rules! to_sync_state_mut { ($handle:expr) => {{ let handle = $handle.as_mut(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncState pointer").into(), + None => return AMresult::error("Invalid `AMsyncState*`").into(), } }}; } @@ -57,6 +37,10 @@ impl AMdoc { pub fn new(auto_commit: am::AutoCommit) -> Self { Self(auto_commit) } + + pub fn is_equal_to(&mut self, other: &mut Self) -> bool { + self.document().get_heads() == other.document().get_heads() + } } impl AsRef for AMdoc { @@ -82,38 +66,38 @@ impl DerefMut for AMdoc { /// \memberof AMdoc /// \brief Applies a sequence of changes to a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] changes A pointer to an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p changes `!= NULL`. -/// \return A pointer to an `AMresult` struct containing a void. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] items A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE` +/// items. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p items `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// changes must be a valid pointer to an AMchanges. +/// items must be a valid pointer to an AMitems. #[no_mangle] -pub unsafe extern "C" fn AMapplyChanges( - doc: *mut AMdoc, - changes: *const AMchanges, -) -> *mut AMresult { +pub unsafe extern "C" fn AMapplyChanges(doc: *mut AMdoc, items: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let changes = to_changes!(changes); - to_result(doc.apply_changes(changes.as_ref().to_vec())) + let items = to_items!(items); + match Vec::::try_from(items) { + Ok(changes) => to_result(doc.apply_changes(changes)), + Err(e) => AMresult::error(&e.to_string()).into(), + } } /// \memberof AMdoc /// \brief Allocates storage for a document and initializes it by duplicating /// the given document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -129,10 +113,9 @@ pub unsafe extern "C" fn AMclone(doc: *const AMdoc) -> *mut AMresult { /// /// \param[in] actor_id A pointer to an `AMactorId` struct or `NULL` for a /// random one. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -149,15 +132,15 @@ pub unsafe extern "C" fn AMcreate(actor_id: *const AMactorId) -> *mut AMresult { /// \brief Commits the current operations on a document with an optional /// message and/or *nix timestamp (milliseconds). /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// with one element if there were operations to commit, or void if -/// there were no operations to commit. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` +/// item if there were operations to commit or an `AM_VAL_TYPE_VOID` item +/// if there were no operations to commit. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -183,24 +166,24 @@ pub unsafe extern "C" fn AMcommit( /// \brief Creates an empty change with an optional message and/or *nix /// timestamp (milliseconds). /// -/// This is useful if you wish to create a "merge commit" which has as its -/// dependents the current heads of the document but you don't have any -/// operations to add to the document. +/// \details This is useful if you wish to create a "merge commit" which has as +/// its dependents the current heads of the document but you don't have +/// any operations to add to the document. /// /// \note If there are outstanding uncommitted changes to the document -/// then two changes will be created: one for creating the outstanding changes -/// and one for the empty change. The empty change will always be the -/// latest change in the document after this call and the returned hash will be -/// the hash of that empty change. +/// then two changes will be created: one for creating the outstanding +/// changes and one for the empty change. The empty change will always be +/// the latest change in the document after this call and the returned +/// hash will be the hash of that empty change. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] message A UTF-8 string view as an `AMbyteSpan` struct. /// \param[in] timestamp A pointer to a 64-bit integer or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// with one element. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with one `AM_VAL_TYPE_CHANGE_HASH` +/// item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -226,11 +209,11 @@ pub unsafe extern "C" fn AMemptyChange( /// \brief Tests the equality of two documents after closing their respective /// transactions. /// -/// \param[in,out] doc1 An `AMdoc` struct. -/// \param[in,out] doc2 An `AMdoc` struct. +/// \param[in] doc1 A pointer to an `AMdoc` struct. +/// \param[in] doc2 A pointer to an `AMdoc` struct. /// \return `true` if \p doc1 `==` \p doc2 and `false` otherwise. -/// \pre \p doc1 `!= NULL`. -/// \pre \p doc2 `!= NULL`. +/// \pre \p doc1 `!= NULL` +/// \pre \p doc2 `!= NULL` /// \internal /// /// #Safety @@ -239,33 +222,36 @@ pub unsafe extern "C" fn AMemptyChange( #[no_mangle] pub unsafe extern "C" fn AMequal(doc1: *mut AMdoc, doc2: *mut AMdoc) -> bool { match (doc1.as_mut(), doc2.as_mut()) { - (Some(doc1), Some(doc2)) => doc1.document().get_heads() == doc2.document().get_heads(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (Some(doc1), Some(doc2)) => doc1.is_equal_to(doc2), + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMdoc -/// \brief Forks this document at the current or a historical point for use by +/// \brief Forks this document at its current or a historical point for use by /// a different actor. -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// point or `NULL` for the current point. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical point or `NULL` to select its +/// current point. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) -> *mut AMresult { +pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); match heads.as_ref() { None => to_result(doc.fork()), - Some(heads) => to_result(doc.fork_at(heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.fork_at(&heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -273,14 +259,14 @@ pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) - /// \brief Generates a synchronization message for a peer based upon the given /// synchronization state. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. -/// \return A pointer to an `AMresult` struct containing either a pointer to an -/// `AMsyncMessage` struct or a void. -/// \pre \p doc `!= NULL`. -/// \pre \p sync_state `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] sync_state A pointer to an `AMsyncState` struct. +/// \return A pointer to an `AMresult` struct with either an +/// `AM_VAL_TYPE_SYNC_MESSAGE` or `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -300,11 +286,10 @@ pub unsafe extern "C" fn AMgenerateSyncMessage( /// \brief Gets a document's actor identifier. /// /// \param[in] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMactorId` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_ACTOR_ID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -320,20 +305,22 @@ pub unsafe extern "C" fn AMgetActorId(doc: *const AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Gets the change added to a document by its respective hash. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre \p count `>= AM_CHANGE_HASH_SIZE`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE` item. +/// \pre \p doc `!= NULL` +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src') >= AM_CHANGE_HASH_SIZE` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// src must be a byte array of size `>= automerge::types::HASH_SIZE` +/// src must be a byte array of length `>= automerge::types::HASH_SIZE` #[no_mangle] pub unsafe extern "C" fn AMgetChangeByHash( doc: *mut AMdoc, @@ -344,48 +331,48 @@ pub unsafe extern "C" fn AMgetChangeByHash( let slice = std::slice::from_raw_parts(src, count); match slice.try_into() { Ok(change_hash) => to_result(doc.get_change_by_hash(&change_hash)), - Err(e) => AMresult::err(&e.to_string()).into(), + Err(e) => AMresult::error(&e.to_string()).into(), } } /// \memberof AMdoc /// \brief Gets the changes added to a document by their respective hashes. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] have_deps A pointer to an `AMchangeHashes` struct or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] have_deps A pointer to an `AMitems` struct with +/// `AM_VAL_TYPE_CHANGE_HASH` items or `NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] -pub unsafe extern "C" fn AMgetChanges( - doc: *mut AMdoc, - have_deps: *const AMchangeHashes, -) -> *mut AMresult { +pub unsafe extern "C" fn AMgetChanges(doc: *mut AMdoc, have_deps: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let empty_deps = Vec::::new(); let have_deps = match have_deps.as_ref() { - Some(have_deps) => have_deps.as_ref(), - None => &empty_deps, + Some(have_deps) => match Vec::::try_from(have_deps) { + Ok(change_hashes) => change_hashes, + Err(e) => return AMresult::error(&e.to_string()).into(), + }, + None => Vec::::new(), }; - to_result(doc.get_changes(have_deps)) + to_result(doc.get_changes(&have_deps)) } /// \memberof AMdoc /// \brief Gets the changes added to a second document that weren't added to /// a first document. /// -/// \param[in,out] doc1 An `AMdoc` struct. -/// \param[in,out] doc2 An `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchanges` struct. -/// \pre \p doc1 `!= NULL`. -/// \pre \p doc2 `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc1 A pointer to an `AMdoc` struct. +/// \param[in] doc2 A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p doc1 `!= NULL` +/// \pre \p doc2 `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -401,12 +388,11 @@ pub unsafe extern "C" fn AMgetChangesAdded(doc1: *mut AMdoc, doc2: *mut AMdoc) - /// \memberof AMdoc /// \brief Gets the current heads of a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -423,41 +409,42 @@ pub unsafe extern "C" fn AMgetHeads(doc: *mut AMdoc) -> *mut AMresult { /// \brief Gets the hashes of the changes in a document that aren't transitive /// dependencies of the given hashes of changes. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct or `NULL`. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items or `NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMgetMissingDeps( - doc: *mut AMdoc, - heads: *const AMchangeHashes, -) -> *mut AMresult { +pub unsafe extern "C" fn AMgetMissingDeps(doc: *mut AMdoc, heads: *const AMitems) -> *mut AMresult { let doc = to_doc_mut!(doc); - let empty_heads = Vec::::new(); let heads = match heads.as_ref() { - Some(heads) => heads.as_ref(), - None => &empty_heads, + None => Vec::::new(), + Some(heads) => match >::try_from(heads) { + Ok(heads) => heads, + Err(e) => { + return AMresult::error(&e.to_string()).into(); + } + }, }; - to_result(doc.get_missing_deps(heads)) + to_result(doc.get_missing_deps(heads.as_slice())) } /// \memberof AMdoc /// \brief Gets the last change made to a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing either an `AMchange` -/// struct or a void. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct containing either an +/// `AM_VAL_TYPE_CHANGE` or `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -473,29 +460,33 @@ pub unsafe extern "C" fn AMgetLastLocalChange(doc: *mut AMdoc) -> *mut AMresult /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys or `NULL` for current keys. -/// \return A pointer to an `AMresult` struct containing an `AMstrs` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical keys or `NULL` to select current +/// keys. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_STR` items. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMkeys( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.keys(obj_id)), - Some(heads) => to_result(doc.keys_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.keys_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -504,42 +495,43 @@ pub unsafe extern "C" fn AMkeys( /// form of an incremental save. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMdoc` struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_DOC` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMload(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::AutoCommit::load(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::AutoCommit::load(data)) } /// \memberof AMdoc /// \brief Loads the compact form of an incremental save into a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to load. -/// \return A pointer to an `AMresult` struct containing the number of -/// operations loaded from \p src. -/// \pre \p doc `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to load from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_UINT` item. +/// \pre \p doc `!= NULL` +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMloadIncremental( doc: *mut AMdoc, @@ -547,23 +539,21 @@ pub unsafe extern "C" fn AMloadIncremental( count: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(doc.load_incremental(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(doc.load_incremental(data)) } /// \memberof AMdoc /// \brief Applies all of the changes in \p src which are not in \p dest to /// \p dest. /// -/// \param[in,out] dest A pointer to an `AMdoc` struct. -/// \param[in,out] src A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an `AMchangeHashes` -/// struct. -/// \pre \p dest `!= NULL`. -/// \pre \p src `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] dest A pointer to an `AMdoc` struct. +/// \param[in] src A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p dest `!= NULL` +/// \pre \p src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -580,31 +570,37 @@ pub unsafe extern "C" fn AMmerge(dest: *mut AMdoc, src: *mut AMdoc) -> *mut AMre /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// size or `NULL` for current size. -/// \return A 64-bit unsigned integer. -/// \pre \p doc `!= NULL`. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical size or `NULL` to select its +/// current size. +/// \return The count of items in the object identified by \p obj_id. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMobjSize( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> usize { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); match heads.as_ref() { - None => doc.length(obj_id), - Some(heads) => doc.length_at(obj_id, heads.as_ref()), + None => { + return doc.length(obj_id); + } + Some(heads) => { + if let Ok(heads) = >::try_from(heads) { + return doc.length_at(obj_id, &heads); + } + } } - } else { - 0 } + 0 } /// \memberof AMdoc @@ -612,8 +608,9 @@ pub unsafe extern "C" fn AMobjSize( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \return An `AMobjType`. -/// \pre \p doc `!= NULL`. +/// \return An `AMobjType` tag or `0`. +/// \pre \p doc `!= NULL` +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -623,44 +620,45 @@ pub unsafe extern "C" fn AMobjSize( pub unsafe extern "C" fn AMobjObjType(doc: *const AMdoc, obj_id: *const AMobjId) -> AMobjType { if let Some(doc) = doc.as_ref() { let obj_id = to_obj_id!(obj_id); - match doc.object_type(obj_id) { - Err(_) => AMobjType::Void, - Ok(obj_type) => obj_type.into(), + if let Ok(obj_type) = doc.object_type(obj_id) { + return (&obj_type).into(); } - } else { - AMobjType::Void } + Default::default() } /// \memberof AMdoc -/// \brief Gets the current or historical values of an object within its entire -/// range. +/// \brief Gets the current or historical items of an entire object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// items or `NULL` for current items. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select its historical items or `NULL` to select +/// its current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] -pub unsafe extern "C" fn AMobjValues( +pub unsafe extern "C" fn AMobjItems( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.values(obj_id)), - Some(heads) => to_result(doc.values_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.values_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } @@ -670,7 +668,7 @@ pub unsafe extern "C" fn AMobjValues( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc. -/// \pre \p doc `!= NULL`. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety @@ -678,23 +676,22 @@ pub unsafe extern "C" fn AMobjValues( #[no_mangle] pub unsafe extern "C" fn AMpendingOps(doc: *const AMdoc) -> usize { if let Some(doc) = doc.as_ref() { - doc.pending_ops() - } else { - 0 + return doc.pending_ops(); } + 0 } /// \memberof AMdoc /// \brief Receives a synchronization message from a peer based upon a given /// synchronization state. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \param[in,out] sync_state A pointer to an `AMsyncState` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p sync_state `!= NULL`. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p sync_state `!= NULL` +/// \pre \p sync_message `!= NULL` /// \internal /// /// # Safety @@ -720,9 +717,9 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( /// \brief Cancels the pending operations added during a document's current /// transaction and gets the number of cancellations. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc that were cancelled. -/// \pre \p doc `!= NULL`. +/// \pre \p doc `!= NULL` /// \internal /// /// # Safety @@ -730,21 +727,19 @@ pub unsafe extern "C" fn AMreceiveSyncMessage( #[no_mangle] pub unsafe extern "C" fn AMrollback(doc: *mut AMdoc) -> usize { if let Some(doc) = doc.as_mut() { - doc.rollback() - } else { - 0 + return doc.rollback(); } + 0 } /// \memberof AMdoc /// \brief Saves the entirety of a document into a compact form. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -759,12 +754,11 @@ pub unsafe extern "C" fn AMsave(doc: *mut AMdoc) -> *mut AMresult { /// \brief Saves the changes to a document since its last save into a compact /// form. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] doc A pointer to an `AMdoc` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -778,13 +772,13 @@ pub unsafe extern "C" fn AMsaveIncremental(doc: *mut AMdoc) -> *mut AMresult { /// \memberof AMdoc /// \brief Puts the actor identifier of a document. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] actor_id A pointer to an `AMactorId` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p actor_id `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p actor_id `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -805,76 +799,65 @@ pub unsafe extern "C" fn AMsetActorId( /// \brief Splices values into and/or removes values from the identified object /// at a given position within it. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] pos A position in the object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. -/// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate +/// \param[in] del The number of values to delete or `SIZE_MAX` to indicate /// all of them. -/// \param[in] src A pointer to an array of `AMvalue` structs. -/// \param[in] count The number of `AMvalue` structs in \p src to load. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. -/// \pre `(`\p src `!= NULL and 1 <=` \p count `<= sizeof(`\p src`)/ -/// sizeof(AMvalue)) or `\p src `== NULL or `\p count `== 0`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] values A copy of an `AMitems` struct from which values will be +/// spliced starting at its current position; call +/// `AMitemsRewound()` on a used `AMitems` first to ensure +/// that all of its values are spliced in. Pass `(AMitems){0}` +/// when zero values should be spliced in. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be an AMvalue array of size `>= count` or std::ptr::null() +/// values must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMsplice( doc: *mut AMdoc, obj_id: *const AMobjId, pos: usize, del: usize, - src: *const AMvalue, - count: usize, + values: AMitems, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = to_index!(pos, len, "pos"); - let del = to_index!(del, len, "del"); - let mut vals: Vec = vec![]; - if !(src.is_null() || count == 0) { - let c_vals = std::slice::from_raw_parts(src, count); - for c_val in c_vals { - match c_val.try_into() { - Ok(s) => { - vals.push(s); - } - Err(e) => { - return AMresult::err(&e.to_string()).into(); - } - } - } + let pos = clamp!(pos, len, "pos"); + let del = clamp!(del, len, "del"); + match Vec::::try_from(&values) { + Ok(vals) => to_result(doc.splice(obj_id, pos, del, vals)), + Err(e) => AMresult::error(&e.to_string()).into(), } - to_result(doc.splice(obj_id, pos, del, vals)) } /// \memberof AMdoc /// \brief Splices characters into and/or removes characters from the /// identified object at a given position within it. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] pos A position in the text object identified by \p obj_id or /// `SIZE_MAX` to indicate one past its end. /// \param[in] del The number of characters to delete or `SIZE_MAX` to indicate /// all of them. /// \param[in] text A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id`)` or \p pos `== SIZE_MAX`. -/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id`)` or \p del `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre `0 <=` \p del `<= AMobjSize(`\p obj_id `)` or \p del `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -891,8 +874,8 @@ pub unsafe extern "C" fn AMspliceText( let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); let len = doc.length(obj_id); - let pos = to_index!(pos, len, "pos"); - let del = to_index!(del, len, "del"); + let pos = clamp!(pos, len, "pos"); + let del = clamp!(del, len, "del"); to_result(doc.splice_text(obj_id, pos, del, to_str!(text))) } @@ -901,28 +884,32 @@ pub unsafe extern "C" fn AMspliceText( /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys or `NULL` for current keys. -/// \return A pointer to an `AMresult` struct containing a UTF-8 string. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] heads A pointer to an `AMitems` struct containing +/// `AM_VAL_TYPE_CHANGE_HASH` items to select a historical string +/// or `NULL` to select the current string. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMtext( doc: *const AMdoc, obj_id: *const AMobjId, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); match heads.as_ref() { None => to_result(doc.text(obj_id)), - Some(heads) => to_result(doc.text_at(obj_id, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.text_at(obj_id, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } diff --git a/rust/automerge-c/src/doc/list.rs b/rust/automerge-c/src/doc/list.rs index 6bcdeabf..c4503322 100644 --- a/rust/automerge-c/src/doc/list.rs +++ b/rust/automerge-c/src/doc/list.rs @@ -3,47 +3,44 @@ use automerge::transaction::Transactable; use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; -use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc}; -use crate::obj::{to_obj_type, AMobjId, AMobjType}; +use crate::doc::{to_doc, to_doc_mut, AMdoc}; +use crate::items::AMitems; +use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType}; use crate::result::{to_result, AMresult}; -pub mod item; -pub mod items; - macro_rules! adjust { - ($index:expr, $insert:expr, $len:expr) => {{ + ($pos:expr, $insert:expr, $len:expr) => {{ // An empty object can only be inserted into. let insert = $insert || $len == 0; let end = if insert { $len } else { $len - 1 }; - if $index > end && $index != usize::MAX { - return AMresult::err(&format!("Invalid index {}", $index)).into(); + if $pos > end && $pos != usize::MAX { + return AMresult::error(&format!("Invalid pos {}", $pos)).into(); } - (std::cmp::min($index, end), insert) + (std::cmp::min($pos, end), insert) }}; } macro_rules! to_range { ($begin:expr, $end:expr) => {{ if $begin > $end { - return AMresult::err(&format!("Invalid range [{}-{})", $begin, $end)).into(); + return AMresult::error(&format!("Invalid range [{}-{})", $begin, $end)).into(); }; ($begin..$end) }}; } /// \memberof AMdoc -/// \brief Deletes an index in a list object. +/// \brief Deletes an item from a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -53,101 +50,109 @@ macro_rules! to_range { pub unsafe extern "C" fn AMlistDelete( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(doc.delete(obj_id, index)) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + to_result(doc.delete(obj_id, pos)) } /// \memberof AMdoc -/// \brief Gets the current or historical value at an index in a list object. +/// \brief Gets a current or historical item within a list object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// value or `NULL` for the current value. -/// \return A pointer to an `AMresult` struct that doesn't contain a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical item at \p pos or `NULL` +/// to select the current item at \p pos. +/// \return A pointer to an `AMresult` struct with an `AMitem` struct. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistGet( doc: *const AMdoc, obj_id: *const AMobjId, - index: usize, - heads: *const AMchangeHashes, + pos: usize, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(match heads.as_ref() { - None => doc.get(obj_id, index), - Some(heads) => doc.get_at(obj_id, index, heads.as_ref()), - }) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + match heads.as_ref() { + None => to_result(doc.get(obj_id, pos)), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_at(obj_id, pos, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, + } } /// \memberof AMdoc -/// \brief Gets all of the historical values at an index in a list object until -/// its current one or a specific one. +/// \brief Gets all of the historical items at a position within a list object +/// until its current one or a specific one. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// last value or `NULL` for the current last value. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical last item or `NULL` to select +/// the current last item. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistGetAll( doc: *const AMdoc, obj_id: *const AMobjId, - index: usize, - heads: *const AMchangeHashes, + pos: usize, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); match heads.as_ref() { - None => to_result(doc.get_all(obj_id, index)), - Some(heads) => to_result(doc.get_all_at(obj_id, index, heads.as_ref())), + None => to_result(doc.get_all(obj_id, pos)), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_all_at(obj_id, pos, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Increments a counter at an index in a list object by the given -/// value. +/// \brief Increments a counter value in an item within a list object by the +/// given value. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -157,32 +162,33 @@ pub unsafe extern "C" fn AMlistGetAll( pub unsafe extern "C" fn AMlistIncrement( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, _) = adjust!(index, false, doc.length(obj_id)); - to_result(doc.increment(obj_id, index, value)) + let (pos, _) = adjust!(pos, false, doc.length(obj_id)); + to_result(doc.increment(obj_id, pos, value)) } /// \memberof AMdoc -/// \brief Puts a boolean as the value at an index in a list object. +/// \brief Puts a boolean value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A boolean. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -192,84 +198,85 @@ pub unsafe extern "C" fn AMlistIncrement( pub unsafe extern "C" fn AMlistPutBool( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: bool, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Boolean(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a sequence of bytes as the value at an index in a list object. +/// \brief Puts an array of bytes value at a position within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p src before \p index instead of -/// writing \p src over \p index. -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. +/// \param[in] value A view onto the array of bytes to copy from as an +/// `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be a byte array of size `>= count` +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMlistPutBytes( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, - val: AMbyteSpan, + value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); - let mut value = Vec::new(); - value.extend_from_slice(std::slice::from_raw_parts(val.src, val.count)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); + let value: Vec = (&value).into(); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a CRDT counter as the value at an index in a list object. +/// \brief Puts a CRDT counter value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -279,38 +286,39 @@ pub unsafe extern "C" fn AMlistPutBytes( pub unsafe extern "C" fn AMlistPutCounter( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Counter(value.into()); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a float as the value at an index in a list object. +/// \brief Puts a float value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit float. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -320,37 +328,38 @@ pub unsafe extern "C" fn AMlistPutCounter( pub unsafe extern "C" fn AMlistPutF64( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: f64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a signed integer as the value at an index in a list object. +/// \brief Puts a signed integer value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -360,36 +369,37 @@ pub unsafe extern "C" fn AMlistPutF64( pub unsafe extern "C" fn AMlistPutInt( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts null as the value at an index in a list object. +/// \brief Puts a null value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -399,38 +409,37 @@ pub unsafe extern "C" fn AMlistPutInt( pub unsafe extern "C" fn AMlistPutNull( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, ()) + doc.insert(obj_id, pos, ()) } else { - doc.put(obj_id, index, ()) + doc.put(obj_id, pos, ()) }) } /// \memberof AMdoc -/// \brief Puts an empty object as the value at an index in a list object. +/// \brief Puts an empty object value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] obj_type An `AMobjIdType` enum tag. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMobjId` struct. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -440,82 +449,85 @@ pub unsafe extern "C" fn AMlistPutNull( pub unsafe extern "C" fn AMlistPutObject( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, obj_type: AMobjType, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); - let object = to_obj_type!(obj_type); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); + let obj_type = to_obj_type!(obj_type); to_result(if insert { - doc.insert_object(obj_id, index, object) + (doc.insert_object(obj_id, pos, obj_type), obj_type) } else { - doc.put_object(obj_id, index, object) + (doc.put_object(obj_id, pos, obj_type), obj_type) }) } /// \memberof AMdoc -/// \brief Puts a UTF-8 string as the value at an index in a list object. +/// \brief Puts a UTF-8 string value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \pre \p value `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// value must be a null-terminated array of `c_char` +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMlistPutStr( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = to_str!(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts a *nix timestamp (milliseconds) as the value at an index in a +/// \brief Puts a *nix timestamp (milliseconds) value into an item within a /// list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -525,38 +537,39 @@ pub unsafe extern "C" fn AMlistPutStr( pub unsafe extern "C" fn AMlistPutTimestamp( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); let value = am::ScalarValue::Timestamp(value); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Puts an unsigned integer as the value at an index in a list object. +/// \brief Puts an unsigned integer value into an item within a list object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] index An index in the list object identified by \p obj_id or -/// `SIZE_MAX` to indicate its last index if \p insert -/// `== false` or one past its last index if \p insert -/// `== true`. -/// \param[in] insert A flag to insert \p value before \p index instead of -/// writing \p value over \p index. +/// \param[in] pos The position of an item within the list object identified by +/// \p obj_id or `SIZE_MAX` to indicate its last item if +/// \p insert `== false` or one past its last item if +/// \p insert `== true`. +/// \param[in] insert A flag for inserting a new item for \p value before +/// \p pos instead of putting \p value into the item at +/// \p pos. /// \param[in] value A 64-bit unsigned integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre `0 <=` \p index `<= AMobjSize(`\p obj_id`)` or \p index `== SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre `0 <=` \p pos `<= AMobjSize(`\p obj_id `)` or \p pos `== SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -566,56 +579,58 @@ pub unsafe extern "C" fn AMlistPutTimestamp( pub unsafe extern "C" fn AMlistPutUint( doc: *mut AMdoc, obj_id: *const AMobjId, - index: usize, + pos: usize, insert: bool, value: u64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let obj_id = to_obj_id!(obj_id); - let (index, insert) = adjust!(index, insert, doc.length(obj_id)); + let (pos, insert) = adjust!(pos, insert, doc.length(obj_id)); to_result(if insert { - doc.insert(obj_id, index, value) + doc.insert(obj_id, pos, value) } else { - doc.put(obj_id, index, value) + doc.put(obj_id, pos, value) }) } /// \memberof AMdoc -/// \brief Gets the current or historical indices and values of the list object -/// within the given range. +/// \brief Gets the current or historical items in the list object within the +/// given range. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] begin The first index in a range of indices. -/// \param[in] end At least one past the last index in a range of indices. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// indices and values or `NULL` for current indices and -/// values. -/// \return A pointer to an `AMresult` struct containing an `AMlistItems` -/// struct. -/// \pre \p doc `!= NULL`. -/// \pre \p begin `<=` \p end `<= SIZE_MAX`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] begin The first pos in a range of indices. +/// \param[in] end At least one past the last pos in a range of indices. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical items or `NULL` to select +/// current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \pre \p begin `<=` \p end `<= SIZE_MAX` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMlistRange( doc: *const AMdoc, obj_id: *const AMobjId, begin: usize, end: usize, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let range = to_range!(begin, end); match heads.as_ref() { None => to_result(doc.list_range(obj_id, range)), - Some(heads) => to_result(doc.list_range_at(obj_id, range, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.list_range_at(obj_id, range, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } diff --git a/rust/automerge-c/src/doc/list/item.rs b/rust/automerge-c/src/doc/list/item.rs deleted file mode 100644 index 7a3869f3..00000000 --- a/rust/automerge-c/src/doc/list/item.rs +++ /dev/null @@ -1,97 +0,0 @@ -use automerge as am; - -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMlistItem -/// \installed_headerfile -/// \brief An item in a list object. -pub struct AMlistItem { - /// The index of an item in a list object. - index: usize, - /// The object identifier of an item in a list object. - obj_id: AMobjId, - /// The value of an item in a list object. - value: am::Value<'static>, -} - -impl AMlistItem { - pub fn new(index: usize, value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - index, - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMlistItem { - fn eq(&self, other: &Self) -> bool { - self.index == other.index && self.obj_id == other.obj_id && self.value == other.value - } -} - -/* -impl From<&AMlistItem> for (usize, am::Value<'static>, am::ObjId) { - fn from(list_item: &AMlistItem) -> Self { - (list_item.index, list_item.value.0.clone(), list_item.obj_id.as_ref().clone()) - } -} -*/ - -/// \memberof AMlistItem -/// \brief Gets the index of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return A 64-bit unsigned integer. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemIndex(list_item: *const AMlistItem) -> usize { - if let Some(list_item) = list_item.as_ref() { - list_item.index - } else { - usize::MAX - } -} - -/// \memberof AMlistItem -/// \brief Gets the object identifier of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemObjId(list_item: *const AMlistItem) -> *const AMobjId { - if let Some(list_item) = list_item.as_ref() { - &list_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMlistItem -/// \brief Gets the value of an item in a list object. -/// -/// \param[in] list_item A pointer to an `AMlistItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p list_item `!= NULL`. -/// \internal -/// -/// # Safety -/// list_item must be a valid pointer to an AMlistItem -#[no_mangle] -pub unsafe extern "C" fn AMlistItemValue<'a>(list_item: *const AMlistItem) -> AMvalue<'a> { - if let Some(list_item) = list_item.as_ref() { - (&list_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/doc/list/items.rs b/rust/automerge-c/src/doc/list/items.rs deleted file mode 100644 index 5b4a11fd..00000000 --- a/rust/automerge-c/src/doc/list/items.rs +++ /dev/null @@ -1,348 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::doc::list::item::AMlistItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(list_items: &[AMlistItem], offset: isize) -> Self { - Self { - len: list_items.len(), - offset, - ptr: list_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMlistItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMlistItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMlistItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMlistItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of list object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMlistItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMlistItems { - pub fn new(list_items: &[AMlistItem]) -> Self { - Self { - detail: Detail::new(list_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMlistItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMlistItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMlistItem]> for AMlistItems { - fn as_ref(&self) -> &[AMlistItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMlistItem, detail.len) } - } -} - -impl Default for AMlistItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMlistItems -/// \brief Advances an iterator over a sequence of list object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsAdvance(list_items: *mut AMlistItems, n: isize) { - if let Some(list_items) = list_items.as_mut() { - list_items.advance(n); - }; -} - -/// \memberof AMlistItems -/// \brief Tests the equality of two sequences of list object items underlying -/// a pair of iterators. -/// -/// \param[in] list_items1 A pointer to an `AMlistItems` struct. -/// \param[in] list_items2 A pointer to an `AMlistItems` struct. -/// \return `true` if \p list_items1 `==` \p list_items2 and `false` otherwise. -/// \pre \p list_items1 `!= NULL`. -/// \pre \p list_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items1 must be a valid pointer to an AMlistItems -/// list_items2 must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsEqual( - list_items1: *const AMlistItems, - list_items2: *const AMlistItems, -) -> bool { - match (list_items1.as_ref(), list_items2.as_ref()) { - (Some(list_items1), Some(list_items2)) => list_items1.as_ref() == list_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMlistItems -/// \brief Gets the list object item at the current position of an iterator -/// over a sequence of list object items and then advances it by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMlistItem` struct that's `NULL` when -/// \p list_items was previously advanced past its forward/reverse -/// limit. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsNext( - list_items: *mut AMlistItems, - n: isize, -) -> *const AMlistItem { - if let Some(list_items) = list_items.as_mut() { - if let Some(list_item) = list_items.next(n) { - return list_item; - } - } - std::ptr::null() -} - -/// \memberof AMlistItems -/// \brief Advances an iterator over a sequence of list object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the list object item at its new -/// position. -/// -/// \param[in,out] list_items A pointer to an `AMlistItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMlistItem` struct that's `NULL` when -/// \p list_items is presently advanced past its forward/reverse limit. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsPrev( - list_items: *mut AMlistItems, - n: isize, -) -> *const AMlistItem { - if let Some(list_items) = list_items.as_mut() { - if let Some(list_item) = list_items.prev(n) { - return list_item; - } - } - std::ptr::null() -} - -/// \memberof AMlistItems -/// \brief Gets the size of the sequence of list object items underlying an -/// iterator. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return The count of values in \p list_items. -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsSize(list_items: *const AMlistItems) -> usize { - if let Some(list_items) = list_items.as_ref() { - list_items.len() - } else { - 0 - } -} - -/// \memberof AMlistItems -/// \brief Creates an iterator over the same sequence of list object items as -/// the given one but with the opposite position and direction. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return An `AMlistItems` struct -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsReversed(list_items: *const AMlistItems) -> AMlistItems { - if let Some(list_items) = list_items.as_ref() { - list_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMlistItems -/// \brief Creates an iterator at the starting position over the same sequence -/// of list object items as the given one. -/// -/// \param[in] list_items A pointer to an `AMlistItems` struct. -/// \return An `AMlistItems` struct -/// \pre \p list_items `!= NULL`. -/// \internal -/// -/// #Safety -/// list_items must be a valid pointer to an AMlistItems -#[no_mangle] -pub unsafe extern "C" fn AMlistItemsRewound(list_items: *const AMlistItems) -> AMlistItems { - if let Some(list_items) = list_items.as_ref() { - list_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc/map.rs b/rust/automerge-c/src/doc/map.rs index 86c6b4a2..b2f7db02 100644 --- a/rust/automerge-c/src/doc/map.rs +++ b/rust/automerge-c/src/doc/map.rs @@ -3,31 +3,29 @@ use automerge::transaction::Transactable; use automerge::ReadDoc; use crate::byte_span::{to_str, AMbyteSpan}; -use crate::change_hashes::AMchangeHashes; -use crate::doc::{to_doc, to_doc_mut, to_obj_id, AMdoc}; -use crate::obj::{to_obj_type, AMobjId, AMobjType}; +use crate::doc::{to_doc, to_doc_mut, AMdoc}; +use crate::items::AMitems; +use crate::obj::{to_obj_id, to_obj_type, AMobjId, AMobjType}; use crate::result::{to_result, AMresult}; -pub mod item; -pub mod items; - /// \memberof AMdoc -/// \brief Deletes a key in a map object. +/// \brief Deletes an item from a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapDelete( doc: *mut AMdoc, @@ -40,96 +38,107 @@ pub unsafe extern "C" fn AMmapDelete( } /// \memberof AMdoc -/// \brief Gets the current or historical value for a key in a map object. +/// \brief Gets a current or historical item within a map object. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// value or `NULL` for the current value. -/// \return A pointer to an `AMresult` struct that doesn't contain a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical item at \p key or `NULL` +/// to select the current item at \p key. +/// \return A pointer to an `AMresult` struct with an `AMitem` struct. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// key.src must be a byte array of length >= key.count +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapGet( doc: *const AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let key = to_str!(key); match heads.as_ref() { None => to_result(doc.get(obj_id, key)), - Some(heads) => to_result(doc.get_at(obj_id, key, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_at(obj_id, key, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Gets all of the historical values for a key in a map object until +/// \brief Gets all of the historical items at a key within a map object until /// its current one or a specific one. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for a historical -/// last value or `NULL` for the current last value. -/// \return A pointer to an `AMresult` struct containing an `AMobjItems` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select a historical last item or `NULL` to +/// select the current last item. +/// \return A pointer to an `AMresult` struct with an `AMItems` struct. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// key.src must be a byte array of length >= key.count +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapGetAll( doc: *const AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); let key = to_str!(key); match heads.as_ref() { None => to_result(doc.get_all(obj_id, key)), - Some(heads) => to_result(doc.get_all_at(obj_id, key, heads.as_ref())), + Some(heads) => match >::try_from(heads) { + Ok(heads) => to_result(doc.get_all_at(obj_id, key, &heads)), + Err(e) => AMresult::error(&e.to_string()).into(), + }, } } /// \memberof AMdoc -/// \brief Increments a counter for a key in a map object by the given value. +/// \brief Increments a counter at a key in a map object by the given value. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapIncrement( doc: *mut AMdoc, @@ -145,21 +154,22 @@ pub unsafe extern "C" fn AMmapIncrement( /// \memberof AMdoc /// \brief Puts a boolean as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A boolean. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutBool( doc: *mut AMdoc, @@ -173,59 +183,58 @@ pub unsafe extern "C" fn AMmapPutBool( } /// \memberof AMdoc -/// \brief Puts a sequence of bytes as the value of a key in a map object. +/// \brief Puts an array of bytes value at a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. -/// \param[in] key A UTF-8 string view key for the map object identified by -/// \p obj_id as an `AMbyteSpan` struct. -/// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes to copy from \p src. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] key The UTF-8 string view key of an item within the map object +/// identified by \p obj_id as an `AMbyteSpan` struct. +/// \param[in] value A view onto an array of bytes as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// src must be a byte array of size `>= count` +/// key.src must be a byte array of length >= key.count +/// value.src must be a byte array of length >= value.count #[no_mangle] pub unsafe extern "C" fn AMmapPutBytes( doc: *mut AMdoc, obj_id: *const AMobjId, key: AMbyteSpan, - val: AMbyteSpan, + value: AMbyteSpan, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let key = to_str!(key); - let mut vec = Vec::new(); - vec.extend_from_slice(std::slice::from_raw_parts(val.src, val.count)); - to_result(doc.put(to_obj_id!(obj_id), key, vec)) + to_result(doc.put(to_obj_id!(obj_id), key, Vec::::from(&value))) } /// \memberof AMdoc /// \brief Puts a CRDT counter as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutCounter( doc: *mut AMdoc, @@ -245,20 +254,21 @@ pub unsafe extern "C" fn AMmapPutCounter( /// \memberof AMdoc /// \brief Puts null as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutNull( doc: *mut AMdoc, @@ -273,23 +283,22 @@ pub unsafe extern "C" fn AMmapPutNull( /// \memberof AMdoc /// \brief Puts an empty object as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] obj_type An `AMobjIdType` enum tag. -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMobjId` struct. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \pre \p obj_type != `AM_OBJ_TYPE_VOID`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_OBJ_TYPE` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutObject( doc: *mut AMdoc, @@ -299,27 +308,29 @@ pub unsafe extern "C" fn AMmapPutObject( ) -> *mut AMresult { let doc = to_doc_mut!(doc); let key = to_str!(key); - to_result(doc.put_object(to_obj_id!(obj_id), key, to_obj_type!(obj_type))) + let obj_type = to_obj_type!(obj_type); + to_result((doc.put_object(to_obj_id!(obj_id), key, obj_type), obj_type)) } /// \memberof AMdoc /// \brief Puts a float as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit float. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutF64( doc: *mut AMdoc, @@ -335,21 +346,22 @@ pub unsafe extern "C" fn AMmapPutF64( /// \memberof AMdoc /// \brief Puts a signed integer as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutInt( doc: *mut AMdoc, @@ -365,21 +377,22 @@ pub unsafe extern "C" fn AMmapPutInt( /// \memberof AMdoc /// \brief Puts a UTF-8 string as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutStr( doc: *mut AMdoc, @@ -395,21 +408,22 @@ pub unsafe extern "C" fn AMmapPutStr( /// \brief Puts a *nix timestamp (milliseconds) as the value of a key in a map /// object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit signed integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutTimestamp( doc: *mut AMdoc, @@ -425,21 +439,22 @@ pub unsafe extern "C" fn AMmapPutTimestamp( /// \memberof AMdoc /// \brief Puts an unsigned integer as the value of a key in a map object. /// -/// \param[in,out] doc A pointer to an `AMdoc` struct. +/// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] key A UTF-8 string view key for the map object identified by /// \p obj_id as an `AMbyteSpan` struct. /// \param[in] value A 64-bit unsigned integer. -/// \return A pointer to an `AMresult` struct containing a void. -/// \pre \p doc `!= NULL`. -/// \pre \p key `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_VOID` item. +/// \pre \p doc `!= NULL` +/// \pre \p key.src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() +/// key.src must be a byte array of length >= key.count #[no_mangle] pub unsafe extern "C" fn AMmapPutUint( doc: *mut AMdoc, @@ -453,71 +468,82 @@ pub unsafe extern "C" fn AMmapPutUint( } /// \memberof AMdoc -/// \brief Gets the current or historical keys and values of the map object -/// within the given range. +/// \brief Gets the current or historical items of the map object within the +/// given range. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \param[in] obj_id A pointer to an `AMobjId` struct or `AM_ROOT`. /// \param[in] begin The first key in a subrange or `AMstr(NULL)` to indicate the /// absolute first key. -/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)` to -/// indicate one past the absolute last key. -/// \param[in] heads A pointer to an `AMchangeHashes` struct for historical -/// keys and values or `NULL` for current keys and values. -/// \return A pointer to an `AMresult` struct containing an `AMmapItems` -/// struct. -/// \pre \p doc `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] end The key one past the last key in a subrange or `AMstr(NULL)` +/// to indicate one past the absolute last key. +/// \param[in] heads A pointer to an `AMitems` struct with `AM_VAL_TYPE_CHANGE_HASH` +/// items to select historical items or `NULL` to select +/// current items. +/// \return A pointer to an `AMresult` struct with an `AMitems` struct. +/// \pre \p doc `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// obj_id must be a valid pointer to an AMobjId or std::ptr::null() -/// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() +/// begin.src must be a byte array of length >= begin.count or std::ptr::null() +/// end.src must be a byte array of length >= end.count or std::ptr::null() +/// heads must be a valid pointer to an AMitems or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMmapRange( doc: *const AMdoc, obj_id: *const AMobjId, begin: AMbyteSpan, end: AMbyteSpan, - heads: *const AMchangeHashes, + heads: *const AMitems, ) -> *mut AMresult { let doc = to_doc!(doc); let obj_id = to_obj_id!(obj_id); + let heads = match heads.as_ref() { + None => None, + Some(heads) => match >::try_from(heads) { + Ok(heads) => Some(heads), + Err(e) => { + return AMresult::error(&e.to_string()).into(); + } + }, + }; match (begin.is_null(), end.is_null()) { (false, false) => { let (begin, end) = (to_str!(begin).to_string(), to_str!(end).to_string()); if begin > end { - return AMresult::err(&format!("Invalid range [{}-{})", begin, end)).into(); + return AMresult::error(&format!("Invalid range [{}-{})", begin, end)).into(); }; let bounds = begin..end; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (false, true) => { let bounds = to_str!(begin).to_string()..; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (true, false) => { let bounds = ..to_str!(end).to_string(); - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } } (true, true) => { let bounds = ..; - if let Some(heads) = heads.as_ref() { - to_result(doc.map_range_at(obj_id, bounds, heads.as_ref())) + if let Some(heads) = heads { + to_result(doc.map_range_at(obj_id, bounds, &heads)) } else { to_result(doc.map_range(obj_id, bounds)) } diff --git a/rust/automerge-c/src/doc/map/item.rs b/rust/automerge-c/src/doc/map/item.rs deleted file mode 100644 index 7914fdc4..00000000 --- a/rust/automerge-c/src/doc/map/item.rs +++ /dev/null @@ -1,98 +0,0 @@ -use automerge as am; - -use crate::byte_span::AMbyteSpan; -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMmapItem -/// \installed_headerfile -/// \brief An item in a map object. -pub struct AMmapItem { - /// The key of an item in a map object. - key: String, - /// The object identifier of an item in a map object. - obj_id: AMobjId, - /// The value of an item in a map object. - value: am::Value<'static>, -} - -impl AMmapItem { - pub fn new(key: &'static str, value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - key: key.to_string(), - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMmapItem { - fn eq(&self, other: &Self) -> bool { - self.key == other.key && self.obj_id == other.obj_id && self.value == other.value - } -} - -/* -impl From<&AMmapItem> for (String, am::Value<'static>, am::ObjId) { - fn from(map_item: &AMmapItem) -> Self { - (map_item.key.into_string().unwrap(), map_item.value.0.clone(), map_item.obj_id.as_ref().clone()) - } -} -*/ - -/// \memberof AMmapItem -/// \brief Gets the key of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return An `AMbyteSpan` view of a UTF-8 string. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemKey(map_item: *const AMmapItem) -> AMbyteSpan { - if let Some(map_item) = map_item.as_ref() { - map_item.key.as_bytes().into() - } else { - Default::default() - } -} - -/// \memberof AMmapItem -/// \brief Gets the object identifier of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemObjId(map_item: *const AMmapItem) -> *const AMobjId { - if let Some(map_item) = map_item.as_ref() { - &map_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMmapItem -/// \brief Gets the value of an item in a map object. -/// -/// \param[in] map_item A pointer to an `AMmapItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p map_item `!= NULL`. -/// \internal -/// -/// # Safety -/// map_item must be a valid pointer to an AMmapItem -#[no_mangle] -pub unsafe extern "C" fn AMmapItemValue<'a>(map_item: *const AMmapItem) -> AMvalue<'a> { - if let Some(map_item) = map_item.as_ref() { - (&map_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/doc/map/items.rs b/rust/automerge-c/src/doc/map/items.rs deleted file mode 100644 index cd305971..00000000 --- a/rust/automerge-c/src/doc/map/items.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::doc::map::item::AMmapItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(map_items: &[AMmapItem], offset: isize) -> Self { - Self { - len: map_items.len(), - offset, - ptr: map_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMmapItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMmapItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMmapItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMmapItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of map object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMmapItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMmapItems { - pub fn new(map_items: &[AMmapItem]) -> Self { - Self { - detail: Detail::new(map_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMmapItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMmapItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMmapItem]> for AMmapItems { - fn as_ref(&self) -> &[AMmapItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMmapItem, detail.len) } - } -} - -impl Default for AMmapItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMmapItems -/// \brief Advances an iterator over a sequence of map object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsAdvance(map_items: *mut AMmapItems, n: isize) { - if let Some(map_items) = map_items.as_mut() { - map_items.advance(n); - }; -} - -/// \memberof AMmapItems -/// \brief Tests the equality of two sequences of map object items underlying -/// a pair of iterators. -/// -/// \param[in] map_items1 A pointer to an `AMmapItems` struct. -/// \param[in] map_items2 A pointer to an `AMmapItems` struct. -/// \return `true` if \p map_items1 `==` \p map_items2 and `false` otherwise. -/// \pre \p map_items1 `!= NULL`. -/// \pre \p map_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items1 must be a valid pointer to an AMmapItems -/// map_items2 must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsEqual( - map_items1: *const AMmapItems, - map_items2: *const AMmapItems, -) -> bool { - match (map_items1.as_ref(), map_items2.as_ref()) { - (Some(map_items1), Some(map_items2)) => map_items1.as_ref() == map_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMmapItems -/// \brief Gets the map object item at the current position of an iterator -/// over a sequence of map object items and then advances it by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items -/// was previously advanced past its forward/reverse limit. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsNext(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { - if let Some(map_items) = map_items.as_mut() { - if let Some(map_item) = map_items.next(n) { - return map_item; - } - } - std::ptr::null() -} - -/// \memberof AMmapItems -/// \brief Advances an iterator over a sequence of map object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the map object item at its new -/// position. -/// -/// \param[in,out] map_items A pointer to an `AMmapItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMmapItem` struct that's `NULL` when \p map_items -/// is presently advanced past its forward/reverse limit. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsPrev(map_items: *mut AMmapItems, n: isize) -> *const AMmapItem { - if let Some(map_items) = map_items.as_mut() { - if let Some(map_item) = map_items.prev(n) { - return map_item; - } - } - std::ptr::null() -} - -/// \memberof AMmapItems -/// \brief Gets the size of the sequence of map object items underlying an -/// iterator. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return The count of values in \p map_items. -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsSize(map_items: *const AMmapItems) -> usize { - if let Some(map_items) = map_items.as_ref() { - map_items.len() - } else { - 0 - } -} - -/// \memberof AMmapItems -/// \brief Creates an iterator over the same sequence of map object items as -/// the given one but with the opposite position and direction. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return An `AMmapItems` struct -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsReversed(map_items: *const AMmapItems) -> AMmapItems { - if let Some(map_items) = map_items.as_ref() { - map_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMmapItems -/// \brief Creates an iterator at the starting position over the same sequence of map object items as the given one. -/// -/// \param[in] map_items A pointer to an `AMmapItems` struct. -/// \return An `AMmapItems` struct -/// \pre \p map_items `!= NULL`. -/// \internal -/// -/// #Safety -/// map_items must be a valid pointer to an AMmapItems -#[no_mangle] -pub unsafe extern "C" fn AMmapItemsRewound(map_items: *const AMmapItems) -> AMmapItems { - if let Some(map_items) = map_items.as_ref() { - map_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/doc/utils.rs b/rust/automerge-c/src/doc/utils.rs index d98a9a8b..ce465b84 100644 --- a/rust/automerge-c/src/doc/utils.rs +++ b/rust/automerge-c/src/doc/utils.rs @@ -1,9 +1,20 @@ +macro_rules! clamp { + ($index:expr, $len:expr, $param_name:expr) => {{ + if $index > $len && $index != usize::MAX { + return AMresult::error(&format!("Invalid {} {}", $param_name, $index)).into(); + } + std::cmp::min($index, $len) + }}; +} + +pub(crate) use clamp; + macro_rules! to_doc { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMdoc pointer").into(), + None => return AMresult::error("Invalid `AMdoc*`").into(), } }}; } @@ -15,9 +26,21 @@ macro_rules! to_doc_mut { let handle = $handle.as_mut(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMdoc pointer").into(), + None => return AMresult::error("Invalid `AMdoc*`").into(), } }}; } pub(crate) use to_doc_mut; + +macro_rules! to_items { + ($handle:expr) => {{ + let handle = $handle.as_ref(); + match handle { + Some(b) => b, + None => return AMresult::error("Invalid `AMitems*`").into(), + } + }}; +} + +pub(crate) use to_items; diff --git a/rust/automerge-c/src/index.rs b/rust/automerge-c/src/index.rs new file mode 100644 index 00000000..f1ea153b --- /dev/null +++ b/rust/automerge-c/src/index.rs @@ -0,0 +1,84 @@ +use automerge as am; + +use std::any::type_name; + +use smol_str::SmolStr; + +use crate::byte_span::AMbyteSpan; + +/// \struct AMindex +/// \installed_headerfile +/// \brief An item index. +#[derive(PartialEq)] +pub enum AMindex { + /// A UTF-8 string key variant. + Key(SmolStr), + /// A 64-bit unsigned integer position variant. + Pos(usize), +} + +impl TryFrom<&AMindex> for AMbyteSpan { + type Error = am::AutomergeError; + + fn try_from(item: &AMindex) -> Result { + use am::AutomergeError::InvalidValueType; + use AMindex::*; + + if let Key(key) = item { + return Ok(key.into()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&AMindex> for usize { + type Error = am::AutomergeError; + + fn try_from(item: &AMindex) -> Result { + use am::AutomergeError::InvalidValueType; + use AMindex::*; + + if let Pos(pos) = item { + return Ok(*pos); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +/// \ingroup enumerations +/// \enum AMidxType +/// \installed_headerfile +/// \brief The type of an item's index. +#[derive(PartialEq, Eq)] +#[repr(u8)] +pub enum AMidxType { + /// The default tag, not a type signifier. + Default = 0, + /// A UTF-8 string view key. + Key, + /// A 64-bit unsigned integer position. + Pos, +} + +impl Default for AMidxType { + fn default() -> Self { + Self::Default + } +} + +impl From<&AMindex> for AMidxType { + fn from(index: &AMindex) -> Self { + use AMindex::*; + + match index { + Key(_) => Self::Key, + Pos(_) => Self::Pos, + } + } +} diff --git a/rust/automerge-c/src/item.rs b/rust/automerge-c/src/item.rs new file mode 100644 index 00000000..94735464 --- /dev/null +++ b/rust/automerge-c/src/item.rs @@ -0,0 +1,1963 @@ +use automerge as am; + +use std::any::type_name; +use std::borrow::Cow; +use std::cell::{RefCell, UnsafeCell}; +use std::rc::Rc; + +use crate::actor_id::AMactorId; +use crate::byte_span::{to_str, AMbyteSpan}; +use crate::change::AMchange; +use crate::doc::AMdoc; +use crate::index::{AMidxType, AMindex}; +use crate::obj::AMobjId; +use crate::result::{to_result, AMresult}; +use crate::sync::{AMsyncHave, AMsyncMessage, AMsyncState}; + +/// \struct AMunknownValue +/// \installed_headerfile +/// \brief A value (typically for a `set` operation) whose type is unknown. +#[derive(Default, Eq, PartialEq)] +#[repr(C)] +pub struct AMunknownValue { + /// The value's raw bytes. + bytes: AMbyteSpan, + /// The value's encoded type identifier. + type_code: u8, +} + +pub enum Value { + ActorId(am::ActorId, UnsafeCell>), + Change(Box, UnsafeCell>), + ChangeHash(am::ChangeHash), + Doc(RefCell), + SyncHave(AMsyncHave), + SyncMessage(AMsyncMessage), + SyncState(RefCell), + Value(am::Value<'static>), +} + +impl Value { + pub fn try_into_bytes(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Bytes(vector) = scalar.as_ref() { + return Ok(vector.as_slice().into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_change_hash(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Self::ChangeHash(change_hash) = &self { + return Ok(change_hash.into()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_counter(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Counter(counter) = scalar.as_ref() { + return Ok(counter.into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_int(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Int(int) = scalar.as_ref() { + return Ok(*int); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_str(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Str(smol_str) = scalar.as_ref() { + return Ok(smol_str.into()); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } + + pub fn try_into_timestamp(&self) -> Result { + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Self::Value(Scalar(scalar)) = &self { + if let Timestamp(timestamp) = scalar.as_ref() { + return Ok(*timestamp); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl From for Value { + fn from(actor_id: am::ActorId) -> Self { + Self::ActorId(actor_id, Default::default()) + } +} + +impl From for Value { + fn from(auto_commit: am::AutoCommit) -> Self { + Self::Doc(RefCell::new(AMdoc::new(auto_commit))) + } +} + +impl From for Value { + fn from(change: am::Change) -> Self { + Self::Change(Box::new(change), Default::default()) + } +} + +impl From for Value { + fn from(change_hash: am::ChangeHash) -> Self { + Self::ChangeHash(change_hash) + } +} + +impl From for Value { + fn from(have: am::sync::Have) -> Self { + Self::SyncHave(AMsyncHave::new(have)) + } +} + +impl From for Value { + fn from(message: am::sync::Message) -> Self { + Self::SyncMessage(AMsyncMessage::new(message)) + } +} + +impl From for Value { + fn from(state: am::sync::State) -> Self { + Self::SyncState(RefCell::new(AMsyncState::new(state))) + } +} + +impl From> for Value { + fn from(value: am::Value<'static>) -> Self { + Self::Value(value) + } +} + +impl From for Value { + fn from(string: String) -> Self { + Self::Value(am::Value::Scalar(Cow::Owned(am::ScalarValue::Str( + string.into(), + )))) + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Change(change, _) => Ok(change), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + ChangeHash(change_hash) => Ok(change_hash), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + return Ok(scalar.as_ref()); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + ActorId(actor_id, c_actor_id) => unsafe { + Ok((*c_actor_id.get()).get_or_insert(AMactorId::new(actor_id))) + }, + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Change(change, c_change) => unsafe { + Ok((*c_change.get()).get_or_insert(AMchange::new(change))) + }, + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + Doc(doc) => Ok(doc.get_mut()), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncHave(sync_have) => Ok(sync_have), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a Value> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(value: &'a Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncMessage(sync_message) => Ok(sync_message), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl<'a> TryFrom<&'a mut Value> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(value: &'a mut Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + + match value { + SyncState(sync_state) => Ok(sync_state.get_mut()), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), + } + } +} + +impl TryFrom<&Value> for bool { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Boolean(boolean) = scalar.as_ref() { + return Ok(*boolean); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for f64 { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let F64(float) = scalar.as_ref() { + return Ok(*float); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for u64 { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Uint(uint) = scalar.as_ref() { + return Ok(*uint); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl TryFrom<&Value> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(value: &Value) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidValueType; + use am::ScalarValue::*; + use am::Value::*; + + if let Value(Scalar(scalar)) = value { + if let Unknown { bytes, type_code } = scalar.as_ref() { + return Ok(Self { + bytes: bytes.as_slice().into(), + type_code: *type_code, + }); + } + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }) + } +} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + use self::Value::*; + + match (self, other) { + (ActorId(lhs, _), ActorId(rhs, _)) => *lhs == *rhs, + (Change(lhs, _), Change(rhs, _)) => lhs == rhs, + (ChangeHash(lhs), ChangeHash(rhs)) => lhs == rhs, + (Doc(lhs), Doc(rhs)) => lhs.as_ptr() == rhs.as_ptr(), + (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, + (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, + (Value(lhs), Value(rhs)) => lhs == rhs, + _ => false, + } + } +} + +#[derive(Default)] +pub struct Item { + /// The item's index. + index: Option, + /// The item's identifier. + obj_id: Option, + /// The item's value. + value: Option, +} + +impl Item { + pub fn try_into_bytes(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_bytes(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_change_hash(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_change_hash(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_counter(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_counter(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_int(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_int(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_str(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_str(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + + pub fn try_into_timestamp(&self) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &self.value { + return value.try_into_timestamp(); + } + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } +} + +impl From for Item { + fn from(actor_id: am::ActorId) -> Self { + Value::from(actor_id).into() + } +} + +impl From for Item { + fn from(auto_commit: am::AutoCommit) -> Self { + Value::from(auto_commit).into() + } +} + +impl From for Item { + fn from(change: am::Change) -> Self { + Value::from(change).into() + } +} + +impl From for Item { + fn from(change_hash: am::ChangeHash) -> Self { + Value::from(change_hash).into() + } +} + +impl From<(am::ObjId, am::ObjType)> for Item { + fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { + Self { + index: None, + obj_id: Some(AMobjId::new(obj_id)), + value: Some(am::Value::Object(obj_type).into()), + } + } +} + +impl From for Item { + fn from(have: am::sync::Have) -> Self { + Value::from(have).into() + } +} + +impl From for Item { + fn from(message: am::sync::Message) -> Self { + Value::from(message).into() + } +} + +impl From for Item { + fn from(state: am::sync::State) -> Self { + Value::from(state).into() + } +} + +impl From> for Item { + fn from(value: am::Value<'static>) -> Self { + Value::from(value).into() + } +} + +impl From for Item { + fn from(string: String) -> Self { + Value::from(string).into() + } +} + +impl From for Item { + fn from(value: Value) -> Self { + Self { + index: None, + obj_id: None, + value: Some(value), + } + } +} + +impl PartialEq for Item { + fn eq(&self, other: &Self) -> bool { + self.index == other.index && self.obj_id == other.obj_id && self.value == other.value + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl From<&Item> for AMidxType { + fn from(item: &Item) -> Self { + if let Some(index) = &item.index { + return index.into(); + } + Default::default() + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a Item> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(item: &'a Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl<'a> TryFrom<&'a mut Item> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &mut item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for bool { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for f64 { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for u64 { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use am::AutomergeError::InvalidValueType; + + if let Some(value) = &item.value { + value.try_into() + } else { + Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::>().to_string(), + }) + } + } +} + +impl TryFrom<&Item> for (am::Value<'static>, am::ObjId) { + type Error = am::AutomergeError; + + fn try_from(item: &Item) -> Result { + use self::Value::*; + use am::AutomergeError::InvalidObjId; + use am::AutomergeError::InvalidValueType; + + let expected = type_name::().to_string(); + match (&item.obj_id, &item.value) { + (None, None) | (None, Some(_)) => Err(InvalidObjId("".to_string())), + (Some(_), None) => Err(InvalidValueType { + expected, + unexpected: type_name::>().to_string(), + }), + (Some(obj_id), Some(value)) => match value { + ActorId(_, _) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + ChangeHash(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Change(_, _) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Doc(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncHave(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncMessage(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + SyncState(_) => Err(InvalidValueType { + expected, + unexpected: type_name::().to_string(), + }), + Value(v) => Ok((v.clone(), obj_id.as_ref().clone())), + }, + } + } +} + +/// \struct AMitem +/// \installed_headerfile +/// \brief An item within a result. +#[derive(Clone)] +pub struct AMitem(Rc); + +impl AMitem { + pub fn exact(obj_id: am::ObjId, value: Value) -> Self { + Self(Rc::new(Item { + index: None, + obj_id: Some(AMobjId::new(obj_id)), + value: Some(value), + })) + } + + pub fn indexed(index: AMindex, obj_id: am::ObjId, value: Value) -> Self { + Self(Rc::new(Item { + index: Some(index), + obj_id: Some(AMobjId::new(obj_id)), + value: Some(value), + })) + } +} + +impl AsRef for AMitem { + fn as_ref(&self) -> &Item { + self.0.as_ref() + } +} + +impl Default for AMitem { + fn default() -> Self { + Self(Rc::new(Item { + index: None, + obj_id: None, + value: None, + })) + } +} + +impl From for AMitem { + fn from(actor_id: am::ActorId) -> Self { + Value::from(actor_id).into() + } +} + +impl From for AMitem { + fn from(auto_commit: am::AutoCommit) -> Self { + Value::from(auto_commit).into() + } +} + +impl From for AMitem { + fn from(change: am::Change) -> Self { + Value::from(change).into() + } +} + +impl From for AMitem { + fn from(change_hash: am::ChangeHash) -> Self { + Value::from(change_hash).into() + } +} + +impl From<(am::ObjId, am::ObjType)> for AMitem { + fn from((obj_id, obj_type): (am::ObjId, am::ObjType)) -> Self { + Self(Rc::new(Item::from((obj_id, obj_type)))) + } +} + +impl From for AMitem { + fn from(have: am::sync::Have) -> Self { + Value::from(have).into() + } +} + +impl From for AMitem { + fn from(message: am::sync::Message) -> Self { + Value::from(message).into() + } +} + +impl From for AMitem { + fn from(state: am::sync::State) -> Self { + Value::from(state).into() + } +} + +impl From> for AMitem { + fn from(value: am::Value<'static>) -> Self { + Value::from(value).into() + } +} + +impl From for AMitem { + fn from(string: String) -> Self { + Value::from(string).into() + } +} + +impl From for AMitem { + fn from(value: Value) -> Self { + Self(Rc::new(Item::from(value))) + } +} + +impl PartialEq for AMitem { + fn eq(&self, other: &Self) -> bool { + self.as_ref() == other.as_ref() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::Change { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::ChangeHash { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a am::ScalarValue { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMactorId { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMchange { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMdoc { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMsyncHave { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a AMitem> for &'a AMsyncMessage { + type Error = am::AutomergeError; + + fn try_from(item: &'a AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl<'a> TryFrom<&'a mut AMitem> for &'a mut AMsyncState { + type Error = am::AutomergeError; + + fn try_from(item: &'a mut AMitem) -> Result { + if let Some(item) = Rc::get_mut(&mut item.0) { + item.try_into() + } else { + Err(Self::Error::Fail) + } + } +} + +impl TryFrom<&AMitem> for bool { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for f64 { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for u64 { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for AMunknownValue { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +impl TryFrom<&AMitem> for (am::Value<'static>, am::ObjId) { + type Error = am::AutomergeError; + + fn try_from(item: &AMitem) -> Result { + item.as_ref().try_into() + } +} + +/// \ingroup enumerations +/// \enum AMvalType +/// \installed_headerfile +/// \brief The type of an item's value. +#[derive(PartialEq, Eq)] +#[repr(u32)] +pub enum AMvalType { + /// An actor identifier value. + ActorId = 1 << 1, + /// A boolean value. + Bool = 1 << 2, + /// A view onto an array of bytes value. + Bytes = 1 << 3, + /// A change value. + Change = 1 << 4, + /// A change hash value. + ChangeHash = 1 << 5, + /// A CRDT counter value. + Counter = 1 << 6, + /// The default tag, not a type signifier. + Default = 0, + /// A document value. + Doc = 1 << 7, + /// A 64-bit float value. + F64 = 1 << 8, + /// A 64-bit signed integer value. + Int = 1 << 9, + /// A null value. + Null = 1 << 10, + /// An object type value. + ObjType = 1 << 11, + /// A UTF-8 string view value. + Str = 1 << 12, + /// A synchronization have value. + SyncHave = 1 << 13, + /// A synchronization message value. + SyncMessage = 1 << 14, + /// A synchronization state value. + SyncState = 1 << 15, + /// A *nix timestamp (milliseconds) value. + Timestamp = 1 << 16, + /// A 64-bit unsigned integer value. + Uint = 1 << 17, + /// An unknown type of value. + Unknown = 1 << 18, + /// A void. + Void = 1 << 0, +} + +impl Default for AMvalType { + fn default() -> Self { + Self::Default + } +} + +impl From<&am::Value<'static>> for AMvalType { + fn from(value: &am::Value<'static>) -> Self { + use am::ScalarValue::*; + use am::Value::*; + + match value { + Object(_) => Self::ObjType, + Scalar(scalar) => match scalar.as_ref() { + Boolean(_) => Self::Bool, + Bytes(_) => Self::Bytes, + Counter(_) => Self::Counter, + F64(_) => Self::F64, + Int(_) => Self::Int, + Null => Self::Null, + Str(_) => Self::Str, + Timestamp(_) => Self::Timestamp, + Uint(_) => Self::Uint, + Unknown { .. } => Self::Unknown, + }, + } + } +} + +impl From<&Value> for AMvalType { + fn from(value: &Value) -> Self { + use self::Value::*; + + match value { + ActorId(_, _) => Self::ActorId, + Change(_, _) => Self::Change, + ChangeHash(_) => Self::ChangeHash, + Doc(_) => Self::Doc, + SyncHave(_) => Self::SyncHave, + SyncMessage(_) => Self::SyncMessage, + SyncState(_) => Self::SyncState, + Value(v) => v.into(), + } + } +} + +impl From<&Item> for AMvalType { + fn from(item: &Item) -> Self { + if let Some(value) = &item.value { + return value.into(); + } + Self::Void + } +} + +/// \memberof AMitem +/// \brief Tests the equality of two items. +/// +/// \param[in] item1 A pointer to an `AMitem` struct. +/// \param[in] item2 A pointer to an `AMitem` struct. +/// \return `true` if \p item1 `==` \p item2 and `false` otherwise. +/// \pre \p item1 `!= NULL` +/// \pre \p item2 `!= NULL` +/// \post `!(`\p item1 `&&` \p item2 `) -> false` +/// \internal +/// +/// #Safety +/// item1 must be a valid AMitem pointer +/// item2 must be a valid AMitem pointer +#[no_mangle] +pub unsafe extern "C" fn AMitemEqual(item1: *const AMitem, item2: *const AMitem) -> bool { + match (item1.as_ref(), item2.as_ref()) { + (Some(item1), Some(item2)) => *item1 == *item2, + (None, None) | (None, Some(_)) | (Some(_), None) => false, + } +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a boolean value. +/// +/// \param[in] value A boolean. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BOOL` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromBool(value: bool) -> *mut AMresult { + AMresult::item(am::Value::from(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from an array of bytes value. +/// +/// \param[in] src A pointer to an array of bytes. +/// \param[in] count The count of bytes to copy from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromBytes(src: *const u8, count: usize) -> *mut AMresult { + let value = std::slice::from_raw_parts(src, count); + AMresult::item(am::Value::bytes(value.to_vec()).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a change hash value. +/// +/// \param[in] value A change hash as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_CHANGE_HASH` item. +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromChangeHash(value: AMbyteSpan) -> *mut AMresult { + to_result(am::ChangeHash::try_from(&value)) +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a CRDT counter value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_COUNTER` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromCounter(value: i64) -> *mut AMresult { + AMresult::item(am::Value::counter(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a float value. +/// +/// \param[in] value A 64-bit float. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_F64` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromF64(value: f64) -> *mut AMresult { + AMresult::item(am::Value::f64(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a signed integer value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_INT` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromInt(value: i64) -> *mut AMresult { + AMresult::item(am::Value::int(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a null value. +/// +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_NULL` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromNull() -> *mut AMresult { + AMresult::item(am::Value::from(()).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a UTF-8 string value. +/// +/// \param[in] value A UTF-8 string view as an `AMbyteSpan` struct. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_STR` item. +/// \pre \p value.src `!= NULL` +/// \pre `0 <` \p value.count `<= sizeof(`\p value.src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// value.src must be a byte array of length >= value.count +#[no_mangle] +pub unsafe extern "C" fn AMitemFromStr(value: AMbyteSpan) -> *mut AMresult { + AMresult::item(am::Value::str(to_str!(value)).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from a *nix timestamp +/// (milliseconds) value. +/// +/// \param[in] value A 64-bit signed integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_TIMESTAMP` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromTimestamp(value: i64) -> *mut AMresult { + AMresult::item(am::Value::timestamp(value).into()).into() +} + +/// \memberof AMitem +/// \brief Allocates a new item and initializes it from an unsigned integer value. +/// +/// \param[in] value A 64-bit unsigned integer. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_UINT` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +#[no_mangle] +pub unsafe extern "C" fn AMitemFromUint(value: u64) -> *mut AMresult { + AMresult::item(am::Value::uint(value).into()).into() +} + +/// \memberof AMitem +/// \brief Gets the type of an item's index. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return An `AMidxType` enum tag. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemIdxType(item: *const AMitem) -> AMidxType { + if let Some(item) = item.as_ref() { + return item.0.as_ref().into(); + } + Default::default() +} + +/// \memberof AMitem +/// \brief Gets the object identifier of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A pointer to an `AMobjId` struct. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemObjId(item: *const AMitem) -> *const AMobjId { + if let Some(item) = item.as_ref() { + if let Some(obj_id) = &item.as_ref().obj_id { + return obj_id; + } + } + std::ptr::null() +} + +/// \memberof AMitem +/// \brief Gets the UTF-8 string view key index of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. +/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_KEY` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemKey(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Some(index) = &item.as_ref().index { + if let Ok(key) = index.try_into() { + if !value.is_null() { + *value = key; + return true; + } + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unsigned integer position index of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a `size_t`. +/// \return `true` if `AMitemIdxType(`\p item `) == AM_IDX_TYPE_POS` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemPos(item: *const AMitem, value: *mut usize) -> bool { + if let Some(item) = item.as_ref() { + if let Some(index) = &item.as_ref().index { + if let Ok(pos) = index.try_into() { + if !value.is_null() { + *value = pos; + return true; + } + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the reference count of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A 64-bit unsigned integer. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemRefCount(item: *const AMitem) -> usize { + if let Some(item) = item.as_ref() { + return Rc::strong_count(&item.0); + } + 0 +} + +/// \memberof AMitem +/// \brief Gets a new result for an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return A pointer to an `AMresult` struct. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemResult(item: *const AMitem) -> *mut AMresult { + if let Some(item) = item.as_ref() { + return AMresult::item(item.clone()).into(); + } + std::ptr::null_mut() +} + +/// \memberof AMitem +/// \brief Gets the actor identifier value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMactorId` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_ACTOR_ID` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToActorId( + item: *const AMitem, + value: *mut *const AMactorId, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(actor_id) = <&AMactorId>::try_from(item) { + if !value.is_null() { + *value = actor_id; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the boolean value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a boolean. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BOOL` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToBool(item: *const AMitem, value: *mut bool) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(boolean) = item.try_into() { + if !value.is_null() { + *value = boolean; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the array of bytes value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_BYTES` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToBytes(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(bytes) = item.as_ref().try_into_bytes() { + if !value.is_null() { + *value = bytes; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the change value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMchange` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToChange(item: *mut AMitem, value: *mut *mut AMchange) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(change) = <&mut AMchange>::try_from(item) { + if !value.is_null() { + *value = change; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the change hash value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_CHANGE_HASH` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToChangeHash(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(change_hash) = item.as_ref().try_into_change_hash() { + if !value.is_null() { + *value = change_hash; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the CRDT counter value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_COUNTER` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToCounter(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(counter) = item.as_ref().try_into_counter() { + if !value.is_null() { + *value = counter; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the document value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMdoc` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_DOC` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToDoc(item: *mut AMitem, value: *mut *const AMdoc) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(doc) = <&mut AMdoc>::try_from(item) { + if !value.is_null() { + *value = doc; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the float value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a 64-bit float. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_F64` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToF64(item: *const AMitem, value: *mut f64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(float) = item.try_into() { + if !value.is_null() { + *value = float; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the integer value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_INT` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToInt(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(int) = item.as_ref().try_into_int() { + if !value.is_null() { + *value = int; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the UTF-8 string view value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a UTF-8 string view as an `AMbyteSpan` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_STR` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToStr(item: *const AMitem, value: *mut AMbyteSpan) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(str) = item.as_ref().try_into_str() { + if !value.is_null() { + *value = str; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization have value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncHave` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_HAVE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncHave( + item: *const AMitem, + value: *mut *const AMsyncHave, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(sync_have) = <&AMsyncHave>::try_from(item) { + if !value.is_null() { + *value = sync_have; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization message value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncMessage` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_MESSAGE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncMessage( + item: *const AMitem, + value: *mut *const AMsyncMessage, +) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(sync_message) = <&AMsyncMessage>::try_from(item) { + if !value.is_null() { + *value = sync_message; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the synchronization state value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMsyncState` struct pointer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_SYNC_STATE` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToSyncState( + item: *mut AMitem, + value: *mut *mut AMsyncState, +) -> bool { + if let Some(item) = item.as_mut() { + if let Ok(sync_state) = <&mut AMsyncState>::try_from(item) { + if !value.is_null() { + *value = sync_state; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the *nix timestamp (milliseconds) value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a signed 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_TIMESTAMP` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToTimestamp(item: *const AMitem, value: *mut i64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(timestamp) = item.as_ref().try_into_timestamp() { + if !value.is_null() { + *value = timestamp; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unsigned integer value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to a unsigned 64-bit integer. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UINT` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToUint(item: *const AMitem, value: *mut u64) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(uint) = item.try_into() { + if !value.is_null() { + *value = uint; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the unknown type of value of an item. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \param[out] value A pointer to an `AMunknownValue` struct. +/// \return `true` if `AMitemValType(`\p item `) == AM_VAL_TYPE_UNKNOWN` and +/// \p *value has been reassigned, `false` otherwise. +/// \pre \p item `!= NULL` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemToUnknown(item: *const AMitem, value: *mut AMunknownValue) -> bool { + if let Some(item) = item.as_ref() { + if let Ok(unknown) = item.try_into() { + if !value.is_null() { + *value = unknown; + return true; + } + } + } + false +} + +/// \memberof AMitem +/// \brief Gets the type of an item's value. +/// +/// \param[in] item A pointer to an `AMitem` struct. +/// \return An `AMvalType` enum tag. +/// \pre \p item `!= NULL` +/// \post `(`\p item `== NULL) -> 0` +/// \internal +/// +/// # Safety +/// item must be a valid pointer to an AMitem +#[no_mangle] +pub unsafe extern "C" fn AMitemValType(item: *const AMitem) -> AMvalType { + if let Some(item) = item.as_ref() { + return item.0.as_ref().into(); + } + Default::default() +} diff --git a/rust/automerge-c/src/items.rs b/rust/automerge-c/src/items.rs new file mode 100644 index 00000000..361078b3 --- /dev/null +++ b/rust/automerge-c/src/items.rs @@ -0,0 +1,401 @@ +use automerge as am; + +use std::ffi::c_void; +use std::marker::PhantomData; +use std::mem::size_of; + +use crate::item::AMitem; +use crate::result::AMresult; + +#[repr(C)] +struct Detail { + len: usize, + offset: isize, + ptr: *const c_void, +} + +/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call +/// (https://github.com/eqrion/cbindgen/issues/252) but it will +/// propagate the name of a constant initialized from it so if the +/// constant's name is a symbolic representation of the value it can be +/// converted into a number by post-processing the header it generated. +pub const USIZE_USIZE_USIZE_: usize = size_of::(); + +impl Detail { + fn new(items: &[AMitem], offset: isize) -> Self { + Self { + len: items.len(), + offset, + ptr: items.as_ptr() as *mut c_void, + } + } + + pub fn advance(&mut self, n: isize) { + if n == 0 { + return; + } + let len = self.len as isize; + self.offset = if self.offset < 0 { + // It's reversed. + let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); + if unclipped >= 0 { + // Clip it to the forward stop. + len + } else { + std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) + } + } else { + let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); + if unclipped < 0 { + // Clip it to the reverse stop. + -(len + 1) + } else { + std::cmp::max(0, std::cmp::min(unclipped, len)) + } + } + } + + pub fn get_index(&self) -> usize { + (self.offset + + if self.offset < 0 { + self.len as isize + } else { + 0 + }) as usize + } + + pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { + if self.is_stopped() { + return None; + } + let slice: &mut [AMitem] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; + let value = &mut slice[self.get_index()]; + self.advance(n); + Some(value) + } + + pub fn is_stopped(&self) -> bool { + let len = self.len as isize; + self.offset < -len || self.offset == len + } + + pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { + self.advance(-n); + if self.is_stopped() { + return None; + } + let slice: &mut [AMitem] = + unsafe { std::slice::from_raw_parts_mut(self.ptr as *mut AMitem, self.len) }; + Some(&mut slice[self.get_index()]) + } + + pub fn reversed(&self) -> Self { + Self { + len: self.len, + offset: -(self.offset + 1), + ptr: self.ptr, + } + } + + pub fn rewound(&self) -> Self { + Self { + len: self.len, + offset: if self.offset < 0 { -1 } else { 0 }, + ptr: self.ptr, + } + } +} + +impl From for [u8; USIZE_USIZE_USIZE_] { + fn from(detail: Detail) -> Self { + unsafe { + std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) + .try_into() + .unwrap() + } + } +} + +/// \struct AMitems +/// \installed_headerfile +/// \brief A random-access iterator over a sequence of `AMitem` structs. +#[repr(C)] +#[derive(Eq, PartialEq)] +pub struct AMitems<'a> { + /// An implementation detail that is intentionally opaque. + /// \warning Modifying \p detail will cause undefined behavior. + /// \note The actual size of \p detail will vary by platform, this is just + /// the one for the platform this documentation was built on. + detail: [u8; USIZE_USIZE_USIZE_], + phantom: PhantomData<&'a mut AMresult>, +} + +impl<'a> AMitems<'a> { + pub fn new(items: &[AMitem]) -> Self { + Self { + detail: Detail::new(items, 0).into(), + phantom: PhantomData, + } + } + + pub fn advance(&mut self, n: isize) { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.advance(n); + } + + pub fn len(&self) -> usize { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + detail.len + } + + pub fn next(&mut self, n: isize) -> Option<&mut AMitem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.next(n) + } + + pub fn prev(&mut self, n: isize) -> Option<&mut AMitem> { + let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; + detail.prev(n) + } + + pub fn reversed(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.reversed().into(), + phantom: PhantomData, + } + } + + pub fn rewound(&self) -> Self { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + Self { + detail: detail.rewound().into(), + phantom: PhantomData, + } + } +} + +impl<'a> AsRef<[AMitem]> for AMitems<'a> { + fn as_ref(&self) -> &[AMitem] { + let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; + unsafe { std::slice::from_raw_parts(detail.ptr as *const AMitem, detail.len) } + } +} + +impl<'a> Default for AMitems<'a> { + fn default() -> Self { + Self { + detail: [0; USIZE_USIZE_USIZE_], + phantom: PhantomData, + } + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut changes = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::Change>::try_from(item.as_ref()) { + Ok(change) => { + changes.push(change.clone()); + } + Err(e) => { + return Err(e); + } + } + } + Ok(changes) + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut change_hashes = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::ChangeHash>::try_from(item.as_ref()) { + Ok(change_hash) => { + change_hashes.push(*change_hash); + } + Err(e) => { + return Err(e); + } + } + } + Ok(change_hashes) + } +} + +impl TryFrom<&AMitems<'_>> for Vec { + type Error = am::AutomergeError; + + fn try_from(items: &AMitems<'_>) -> Result { + let mut scalars = Vec::::with_capacity(items.len()); + for item in items.as_ref().iter() { + match <&am::ScalarValue>::try_from(item.as_ref()) { + Ok(scalar) => { + scalars.push(scalar.clone()); + } + Err(e) => { + return Err(e); + } + } + } + Ok(scalars) + } +} + +/// \memberof AMitems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsAdvance(items: *mut AMitems, n: isize) { + if let Some(items) = items.as_mut() { + items.advance(n); + }; +} + +/// \memberof AMitems +/// \brief Tests the equality of two sequences of object items underlying a +/// pair of iterators. +/// +/// \param[in] items1 A pointer to an `AMitems` struct. +/// \param[in] items2 A pointer to an `AMitems` struct. +/// \return `true` if \p items1 `==` \p items2 and `false` otherwise. +/// \pre \p items1 `!= NULL` +/// \pre \p items1 `!= NULL` +/// \post `!(`\p items1 `&&` \p items2 `) -> false` +/// \internal +/// +/// #Safety +/// items1 must be a valid pointer to an AMitems +/// items2 must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsEqual(items1: *const AMitems, items2: *const AMitems) -> bool { + match (items1.as_ref(), items2.as_ref()) { + (Some(items1), Some(items2)) => items1.as_ref() == items2.as_ref(), + (None, None) | (None, Some(_)) | (Some(_), None) => false, + } +} + +/// \memberof AMitems +/// \brief Gets the object item at the current position of an iterator over a +/// sequence of object items and then advances it by at most \p |n| +/// positions where the sign of \p n is relative to the iterator's +/// direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMitem` struct that's `NULL` when \p items +/// was previously advanced past its forward/reverse limit. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsNext(items: *mut AMitems, n: isize) -> *mut AMitem { + if let Some(items) = items.as_mut() { + if let Some(item) = items.next(n) { + return item; + } + } + std::ptr::null_mut() +} + +/// \memberof AMitems +/// \brief Advances an iterator over a sequence of object items by at most +/// \p |n| positions where the sign of \p n is relative to the +/// iterator's direction and then gets the object item at its new +/// position. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum +/// number of positions to advance. +/// \return A pointer to an `AMitem` struct that's `NULL` when \p items +/// is presently advanced past its forward/reverse limit. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsPrev(items: *mut AMitems, n: isize) -> *mut AMitem { + if let Some(items) = items.as_mut() { + if let Some(obj_item) = items.prev(n) { + return obj_item; + } + } + std::ptr::null_mut() +} + +/// \memberof AMitems +/// \brief Gets the size of the sequence underlying an iterator. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return The count of items in \p items. +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsSize(items: *const AMitems) -> usize { + if let Some(items) = items.as_ref() { + return items.len(); + } + 0 +} + +/// \memberof AMitems +/// \brief Creates an iterator over the same sequence of items as the +/// given one but with the opposite position and direction. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return An `AMitems` struct +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsReversed(items: *const AMitems) -> AMitems { + if let Some(items) = items.as_ref() { + return items.reversed(); + } + Default::default() +} + +/// \memberof AMitems +/// \brief Creates an iterator at the starting position over the same sequence +/// of items as the given one. +/// +/// \param[in] items A pointer to an `AMitems` struct. +/// \return An `AMitems` struct +/// \pre \p items `!= NULL` +/// \internal +/// +/// #Safety +/// items must be a valid pointer to an AMitems +#[no_mangle] +pub unsafe extern "C" fn AMitemsRewound(items: *const AMitems) -> AMitems { + if let Some(items) = items.as_ref() { + return items.rewound(); + } + Default::default() +} diff --git a/rust/automerge-c/src/lib.rs b/rust/automerge-c/src/lib.rs index 6418bd33..1ee1a85d 100644 --- a/rust/automerge-c/src/lib.rs +++ b/rust/automerge-c/src/lib.rs @@ -1,11 +1,12 @@ mod actor_id; mod byte_span; mod change; -mod change_hashes; -mod changes; mod doc; +mod index; +mod item; +mod items; mod obj; mod result; -mod result_stack; -mod strs; mod sync; + +// include!(concat!(env!("OUT_DIR"), "/enum_string_functions.rs")); diff --git a/rust/automerge-c/src/obj.rs b/rust/automerge-c/src/obj.rs index 46ff617b..3d52286c 100644 --- a/rust/automerge-c/src/obj.rs +++ b/rust/automerge-c/src/obj.rs @@ -1,12 +1,10 @@ use automerge as am; +use std::any::type_name; use std::cell::RefCell; use std::ops::Deref; use crate::actor_id::AMactorId; -pub mod item; -pub mod items; - macro_rules! to_obj_id { ($handle:expr) => {{ match $handle.as_ref() { @@ -19,12 +17,11 @@ macro_rules! to_obj_id { pub(crate) use to_obj_id; macro_rules! to_obj_type { - ($am_obj_type:expr) => {{ - match $am_obj_type { - AMobjType::Map => am::ObjType::Map, - AMobjType::List => am::ObjType::List, - AMobjType::Text => am::ObjType::Text, - AMobjType::Void => return AMresult::err("Invalid AMobjType value").into(), + ($c_obj_type:expr) => {{ + let result: Result = (&$c_obj_type).try_into(); + match result { + Ok(obj_type) => obj_type, + Err(e) => return AMresult::error(&e.to_string()).into(), } }}; } @@ -79,11 +76,11 @@ impl Deref for AMobjId { } /// \memberof AMobjId -/// \brief Gets the actor identifier of an object identifier. +/// \brief Gets the actor identifier component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A pointer to an `AMactorId` struct or `NULL`. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -97,11 +94,11 @@ pub unsafe extern "C" fn AMobjIdActorId(obj_id: *const AMobjId) -> *const AMacto } /// \memberof AMobjId -/// \brief Gets the counter of an object identifier. +/// \brief Gets the counter component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A 64-bit unsigned integer. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety @@ -124,8 +121,9 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 { /// \param[in] obj_id1 A pointer to an `AMobjId` struct. /// \param[in] obj_id2 A pointer to an `AMobjId` struct. /// \return `true` if \p obj_id1 `==` \p obj_id2 and `false` otherwise. -/// \pre \p obj_id1 `!= NULL`. -/// \pre \p obj_id2 `!= NULL`. +/// \pre \p obj_id1 `!= NULL` +/// \pre \p obj_id1 `!= NULL` +/// \post `!(`\p obj_id1 `&&` \p obj_id2 `) -> false` /// \internal /// /// #Safety @@ -135,26 +133,28 @@ pub unsafe extern "C" fn AMobjIdCounter(obj_id: *const AMobjId) -> u64 { pub unsafe extern "C" fn AMobjIdEqual(obj_id1: *const AMobjId, obj_id2: *const AMobjId) -> bool { match (obj_id1.as_ref(), obj_id2.as_ref()) { (Some(obj_id1), Some(obj_id2)) => obj_id1 == obj_id2, - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMobjId -/// \brief Gets the index of an object identifier. +/// \brief Gets the index component of an object identifier. /// /// \param[in] obj_id A pointer to an `AMobjId` struct. /// \return A 64-bit unsigned integer. -/// \pre \p obj_id `!= NULL`. +/// \pre \p obj_id `!= NULL` /// \internal /// /// # Safety /// obj_id must be a valid pointer to an AMobjId #[no_mangle] pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize { + use am::ObjId::*; + if let Some(obj_id) = obj_id.as_ref() { match obj_id.as_ref() { - am::ObjId::Id(_, _, index) => *index, - am::ObjId::Root => 0, + Id(_, _, index) => *index, + Root => 0, } } else { usize::MAX @@ -163,26 +163,54 @@ pub unsafe extern "C" fn AMobjIdIndex(obj_id: *const AMobjId) -> usize { /// \ingroup enumerations /// \enum AMobjType +/// \installed_headerfile /// \brief The type of an object value. +#[derive(PartialEq, Eq)] #[repr(u8)] pub enum AMobjType { - /// A void. - /// \note This tag is unalphabetized to evaluate as false. - Void = 0, + /// The default tag, not a type signifier. + Default = 0, /// A list. - List, + List = 1, /// A key-value map. Map, /// A list of Unicode graphemes. Text, } -impl From for AMobjType { - fn from(o: am::ObjType) -> Self { +impl Default for AMobjType { + fn default() -> Self { + Self::Default + } +} + +impl From<&am::ObjType> for AMobjType { + fn from(o: &am::ObjType) -> Self { + use am::ObjType::*; + match o { - am::ObjType::Map | am::ObjType::Table => AMobjType::Map, - am::ObjType::List => AMobjType::List, - am::ObjType::Text => AMobjType::Text, + List => Self::List, + Map | Table => Self::Map, + Text => Self::Text, + } + } +} + +impl TryFrom<&AMobjType> for am::ObjType { + type Error = am::AutomergeError; + + fn try_from(c_obj_type: &AMobjType) -> Result { + use am::AutomergeError::InvalidValueType; + use AMobjType::*; + + match c_obj_type { + List => Ok(Self::List), + Map => Ok(Self::Map), + Text => Ok(Self::Text), + _ => Err(InvalidValueType { + expected: type_name::().to_string(), + unexpected: type_name::().to_string(), + }), } } } diff --git a/rust/automerge-c/src/obj/item.rs b/rust/automerge-c/src/obj/item.rs deleted file mode 100644 index a2e99d06..00000000 --- a/rust/automerge-c/src/obj/item.rs +++ /dev/null @@ -1,73 +0,0 @@ -use automerge as am; - -use crate::obj::AMobjId; -use crate::result::AMvalue; - -/// \struct AMobjItem -/// \installed_headerfile -/// \brief An item in an object. -pub struct AMobjItem { - /// The object identifier of an item in an object. - obj_id: AMobjId, - /// The value of an item in an object. - value: am::Value<'static>, -} - -impl AMobjItem { - pub fn new(value: am::Value<'static>, obj_id: am::ObjId) -> Self { - Self { - obj_id: AMobjId::new(obj_id), - value, - } - } -} - -impl PartialEq for AMobjItem { - fn eq(&self, other: &Self) -> bool { - self.obj_id == other.obj_id && self.value == other.value - } -} - -impl From<&AMobjItem> for (am::Value<'static>, am::ObjId) { - fn from(obj_item: &AMobjItem) -> Self { - (obj_item.value.clone(), obj_item.obj_id.as_ref().clone()) - } -} - -/// \memberof AMobjItem -/// \brief Gets the object identifier of an item in an object. -/// -/// \param[in] obj_item A pointer to an `AMobjItem` struct. -/// \return A pointer to an `AMobjId` struct. -/// \pre \p obj_item `!= NULL`. -/// \internal -/// -/// # Safety -/// obj_item must be a valid pointer to an AMobjItem -#[no_mangle] -pub unsafe extern "C" fn AMobjItemObjId(obj_item: *const AMobjItem) -> *const AMobjId { - if let Some(obj_item) = obj_item.as_ref() { - &obj_item.obj_id - } else { - std::ptr::null() - } -} - -/// \memberof AMobjItem -/// \brief Gets the value of an item in an object. -/// -/// \param[in] obj_item A pointer to an `AMobjItem` struct. -/// \return An `AMvalue` struct. -/// \pre \p obj_item `!= NULL`. -/// \internal -/// -/// # Safety -/// obj_item must be a valid pointer to an AMobjItem -#[no_mangle] -pub unsafe extern "C" fn AMobjItemValue<'a>(obj_item: *const AMobjItem) -> AMvalue<'a> { - if let Some(obj_item) = obj_item.as_ref() { - (&obj_item.value).into() - } else { - AMvalue::Void - } -} diff --git a/rust/automerge-c/src/obj/items.rs b/rust/automerge-c/src/obj/items.rs deleted file mode 100644 index d6b847cf..00000000 --- a/rust/automerge-c/src/obj/items.rs +++ /dev/null @@ -1,341 +0,0 @@ -use std::ffi::c_void; -use std::mem::size_of; - -use crate::obj::item::AMobjItem; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(obj_items: &[AMobjItem], offset: isize) -> Self { - Self { - len: obj_items.len(), - offset, - ptr: obj_items.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { - if self.is_stopped() { - return None; - } - let slice: &[AMobjItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; - let value = &slice[self.get_index()]; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[AMobjItem] = - unsafe { std::slice::from_raw_parts(self.ptr as *const AMobjItem, self.len) }; - Some(&slice[self.get_index()]) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMobjItems -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of object items. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMobjItems { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMobjItems { - pub fn new(obj_items: &[AMobjItem]) -> Self { - Self { - detail: Detail::new(obj_items, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<&AMobjItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<&AMobjItem> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[AMobjItem]> for AMobjItems { - fn as_ref(&self) -> &[AMobjItem] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const AMobjItem, detail.len) } - } -} - -impl Default for AMobjItems { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMobjItems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsAdvance(obj_items: *mut AMobjItems, n: isize) { - if let Some(obj_items) = obj_items.as_mut() { - obj_items.advance(n); - }; -} - -/// \memberof AMobjItems -/// \brief Tests the equality of two sequences of object items underlying a -/// pair of iterators. -/// -/// \param[in] obj_items1 A pointer to an `AMobjItems` struct. -/// \param[in] obj_items2 A pointer to an `AMobjItems` struct. -/// \return `true` if \p obj_items1 `==` \p obj_items2 and `false` otherwise. -/// \pre \p obj_items1 `!= NULL`. -/// \pre \p obj_items2 `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items1 must be a valid pointer to an AMobjItems -/// obj_items2 must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsEqual( - obj_items1: *const AMobjItems, - obj_items2: *const AMobjItems, -) -> bool { - match (obj_items1.as_ref(), obj_items2.as_ref()) { - (Some(obj_items1), Some(obj_items2)) => obj_items1.as_ref() == obj_items2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMobjItems -/// \brief Gets the object item at the current position of an iterator over a -/// sequence of object items and then advances it by at most \p |n| -/// positions where the sign of \p n is relative to the iterator's -/// direction. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items -/// was previously advanced past its forward/reverse limit. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsNext(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { - if let Some(obj_items) = obj_items.as_mut() { - if let Some(obj_item) = obj_items.next(n) { - return obj_item; - } - } - std::ptr::null() -} - -/// \memberof AMobjItems -/// \brief Advances an iterator over a sequence of object items by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the object item at its new -/// position. -/// -/// \param[in,out] obj_items A pointer to an `AMobjItems` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMobjItem` struct that's `NULL` when \p obj_items -/// is presently advanced past its forward/reverse limit. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsPrev(obj_items: *mut AMobjItems, n: isize) -> *const AMobjItem { - if let Some(obj_items) = obj_items.as_mut() { - if let Some(obj_item) = obj_items.prev(n) { - return obj_item; - } - } - std::ptr::null() -} - -/// \memberof AMobjItems -/// \brief Gets the size of the sequence of object items underlying an -/// iterator. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return The count of values in \p obj_items. -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsSize(obj_items: *const AMobjItems) -> usize { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.len() - } else { - 0 - } -} - -/// \memberof AMobjItems -/// \brief Creates an iterator over the same sequence of object items as the -/// given one but with the opposite position and direction. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return An `AMobjItems` struct -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsReversed(obj_items: *const AMobjItems) -> AMobjItems { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.reversed() - } else { - Default::default() - } -} - -/// \memberof AMobjItems -/// \brief Creates an iterator at the starting position over the same sequence -/// of object items as the given one. -/// -/// \param[in] obj_items A pointer to an `AMobjItems` struct. -/// \return An `AMobjItems` struct -/// \pre \p obj_items `!= NULL`. -/// \internal -/// -/// #Safety -/// obj_items must be a valid pointer to an AMobjItems -#[no_mangle] -pub unsafe extern "C" fn AMobjItemsRewound(obj_items: *const AMobjItems) -> AMobjItems { - if let Some(obj_items) = obj_items.as_ref() { - obj_items.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/result.rs b/rust/automerge-c/src/result.rs index 599ada96..2975f38b 100644 --- a/rust/automerge-c/src/result.rs +++ b/rust/automerge-c/src/result.rs @@ -1,513 +1,85 @@ use automerge as am; -use smol_str::SmolStr; -use std::any::type_name; -use std::collections::BTreeMap; use std::ops::{Range, RangeFrom, RangeFull, RangeTo}; -use crate::actor_id::AMactorId; use crate::byte_span::AMbyteSpan; -use crate::change::AMchange; -use crate::change_hashes::AMchangeHashes; -use crate::changes::AMchanges; -use crate::doc::list::{item::AMlistItem, items::AMlistItems}; -use crate::doc::map::{item::AMmapItem, items::AMmapItems}; -use crate::doc::AMdoc; -use crate::obj::item::AMobjItem; -use crate::obj::items::AMobjItems; -use crate::obj::AMobjId; -use crate::strs::AMstrs; -use crate::sync::{AMsyncMessage, AMsyncState}; - -/// \struct AMvalue -/// \installed_headerfile -/// \brief A discriminated union of value type variants for a result. -/// -/// \enum AMvalueVariant -/// \brief A value type discriminant. -/// -/// \var AMvalue::actor_id -/// An actor identifier as a pointer to an `AMactorId` struct. -/// -/// \var AMvalue::boolean -/// A boolean. -/// -/// \var AMvalue::bytes -/// A sequence of bytes as an `AMbyteSpan` struct. -/// -/// \var AMvalue::change_hashes -/// A sequence of change hashes as an `AMchangeHashes` struct. -/// -/// \var AMvalue::changes -/// A sequence of changes as an `AMchanges` struct. -/// -/// \var AMvalue::counter -/// A CRDT counter. -/// -/// \var AMvalue::doc -/// A document as a pointer to an `AMdoc` struct. -/// -/// \var AMvalue::f64 -/// A 64-bit float. -/// -/// \var AMvalue::int_ -/// A 64-bit signed integer. -/// -/// \var AMvalue::list_items -/// A sequence of list object items as an `AMlistItems` struct. -/// -/// \var AMvalue::map_items -/// A sequence of map object items as an `AMmapItems` struct. -/// -/// \var AMvalue::obj_id -/// An object identifier as a pointer to an `AMobjId` struct. -/// -/// \var AMvalue::obj_items -/// A sequence of object items as an `AMobjItems` struct. -/// -/// \var AMvalue::str -/// A UTF-8 string view as an `AMbyteSpan` struct. -/// -/// \var AMvalue::strs -/// A sequence of UTF-8 strings as an `AMstrs` struct. -/// -/// \var AMvalue::sync_message -/// A synchronization message as a pointer to an `AMsyncMessage` struct. -/// -/// \var AMvalue::sync_state -/// A synchronization state as a pointer to an `AMsyncState` struct. -/// -/// \var AMvalue::tag -/// The variant discriminator. -/// -/// \var AMvalue::timestamp -/// A *nix timestamp (milliseconds). -/// -/// \var AMvalue::uint -/// A 64-bit unsigned integer. -/// -/// \var AMvalue::unknown -/// A value of unknown type as an `AMunknownValue` struct. -#[repr(u8)] -pub enum AMvalue<'a> { - /// A void variant. - /// \note This tag is unalphabetized so that a zeroed struct will have it. - Void, - /// An actor identifier variant. - ActorId(&'a AMactorId), - /// A boolean variant. - Boolean(bool), - /// A byte array variant. - Bytes(AMbyteSpan), - /// A change hashes variant. - ChangeHashes(AMchangeHashes), - /// A changes variant. - Changes(AMchanges), - /// A CRDT counter variant. - Counter(i64), - /// A document variant. - Doc(*mut AMdoc), - /// A 64-bit float variant. - F64(f64), - /// A 64-bit signed integer variant. - Int(i64), - /// A list items variant. - ListItems(AMlistItems), - /// A map items variant. - MapItems(AMmapItems), - /// A null variant. - Null, - /// An object identifier variant. - ObjId(&'a AMobjId), - /// An object items variant. - ObjItems(AMobjItems), - /// A UTF-8 string view variant. - Str(AMbyteSpan), - /// A UTF-8 string views variant. - Strs(AMstrs), - /// A synchronization message variant. - SyncMessage(&'a AMsyncMessage), - /// A synchronization state variant. - SyncState(&'a mut AMsyncState), - /// A *nix timestamp (milliseconds) variant. - Timestamp(i64), - /// A 64-bit unsigned integer variant. - Uint(u64), - /// An unknown type of scalar value variant. - Unknown(AMunknownValue), -} - -impl<'a> PartialEq for AMvalue<'a> { - fn eq(&self, other: &Self) -> bool { - use AMvalue::*; - - match (self, other) { - (ActorId(lhs), ActorId(rhs)) => *lhs == *rhs, - (Boolean(lhs), Boolean(rhs)) => lhs == rhs, - (Bytes(lhs), Bytes(rhs)) => lhs == rhs, - (ChangeHashes(lhs), ChangeHashes(rhs)) => lhs == rhs, - (Changes(lhs), Changes(rhs)) => lhs == rhs, - (Counter(lhs), Counter(rhs)) => lhs == rhs, - (Doc(lhs), Doc(rhs)) => *lhs == *rhs, - (F64(lhs), F64(rhs)) => lhs == rhs, - (Int(lhs), Int(rhs)) => lhs == rhs, - (ListItems(lhs), ListItems(rhs)) => lhs == rhs, - (MapItems(lhs), MapItems(rhs)) => lhs == rhs, - (ObjId(lhs), ObjId(rhs)) => *lhs == *rhs, - (ObjItems(lhs), ObjItems(rhs)) => lhs == rhs, - (Str(lhs), Str(rhs)) => lhs == rhs, - (Strs(lhs), Strs(rhs)) => lhs == rhs, - (SyncMessage(lhs), SyncMessage(rhs)) => *lhs == *rhs, - (SyncState(lhs), SyncState(rhs)) => *lhs == *rhs, - (Timestamp(lhs), Timestamp(rhs)) => lhs == rhs, - (Uint(lhs), Uint(rhs)) => lhs == rhs, - (Unknown(lhs), Unknown(rhs)) => lhs == rhs, - (Null, Null) | (Void, Void) => true, - _ => false, - } - } -} - -impl From<&am::Value<'_>> for AMvalue<'_> { - fn from(value: &am::Value<'_>) -> Self { - match value { - am::Value::Scalar(scalar) => match scalar.as_ref() { - am::ScalarValue::Boolean(flag) => AMvalue::Boolean(*flag), - am::ScalarValue::Bytes(bytes) => AMvalue::Bytes(bytes.as_slice().into()), - am::ScalarValue::Counter(counter) => AMvalue::Counter(counter.into()), - am::ScalarValue::F64(float) => AMvalue::F64(*float), - am::ScalarValue::Int(int) => AMvalue::Int(*int), - am::ScalarValue::Null => AMvalue::Null, - am::ScalarValue::Str(smol_str) => AMvalue::Str(smol_str.as_bytes().into()), - am::ScalarValue::Timestamp(timestamp) => AMvalue::Timestamp(*timestamp), - am::ScalarValue::Uint(uint) => AMvalue::Uint(*uint), - am::ScalarValue::Unknown { bytes, type_code } => AMvalue::Unknown(AMunknownValue { - bytes: bytes.as_slice().into(), - type_code: *type_code, - }), - }, - // \todo Confirm that an object variant should be ignored - // when there's no object ID variant. - am::Value::Object(_) => AMvalue::Void, - } - } -} - -impl From<&AMvalue<'_>> for u8 { - fn from(value: &AMvalue) -> Self { - use AMvalue::*; - - // \warning These numbers must correspond to the order in which the - // variants of an AMvalue are declared within it. - match value { - ActorId(_) => 1, - Boolean(_) => 2, - Bytes(_) => 3, - ChangeHashes(_) => 4, - Changes(_) => 5, - Counter(_) => 6, - Doc(_) => 7, - F64(_) => 8, - Int(_) => 9, - ListItems(_) => 10, - MapItems(_) => 11, - Null => 12, - ObjId(_) => 13, - ObjItems(_) => 14, - Str(_) => 15, - Strs(_) => 16, - SyncMessage(_) => 17, - SyncState(_) => 18, - Timestamp(_) => 19, - Uint(_) => 20, - Unknown(..) => 21, - Void => 0, - } - } -} - -impl TryFrom<&AMvalue<'_>> for am::ScalarValue { - type Error = am::AutomergeError; - - fn try_from(c_value: &AMvalue) -> Result { - use am::AutomergeError::InvalidValueType; - use AMvalue::*; - - let expected = type_name::().to_string(); - match c_value { - Boolean(b) => Ok(am::ScalarValue::Boolean(*b)), - Bytes(span) => { - let slice = unsafe { std::slice::from_raw_parts(span.src, span.count) }; - Ok(am::ScalarValue::Bytes(slice.to_vec())) - } - Counter(c) => Ok(am::ScalarValue::Counter(c.into())), - F64(f) => Ok(am::ScalarValue::F64(*f)), - Int(i) => Ok(am::ScalarValue::Int(*i)), - Str(span) => { - let result: Result<&str, am::AutomergeError> = span.try_into(); - match result { - Ok(str_) => Ok(am::ScalarValue::Str(SmolStr::new(str_))), - Err(e) => Err(e), - } - } - Timestamp(t) => Ok(am::ScalarValue::Timestamp(*t)), - Uint(u) => Ok(am::ScalarValue::Uint(*u)), - Null => Ok(am::ScalarValue::Null), - Unknown(AMunknownValue { bytes, type_code }) => { - let slice = unsafe { std::slice::from_raw_parts(bytes.src, bytes.count) }; - Ok(am::ScalarValue::Unknown { - bytes: slice.to_vec(), - type_code: *type_code, - }) - } - ActorId(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ChangeHashes(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Changes(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Doc(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ListItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - MapItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ObjId(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - ObjItems(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Strs(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncMessage(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - SyncState(_) => Err(InvalidValueType { - expected, - unexpected: type_name::().to_string(), - }), - Void => Err(InvalidValueType { - expected, - unexpected: type_name::<()>().to_string(), - }), - } - } -} - -/// \memberof AMvalue -/// \brief Tests the equality of two values. -/// -/// \param[in] value1 A pointer to an `AMvalue` struct. -/// \param[in] value2 A pointer to an `AMvalue` struct. -/// \return `true` if \p value1 `==` \p value2 and `false` otherwise. -/// \pre \p value1 `!= NULL`. -/// \pre \p value2 `!= NULL`. -/// \internal -/// -/// #Safety -/// value1 must be a valid AMvalue pointer -/// value2 must be a valid AMvalue pointer -#[no_mangle] -pub unsafe extern "C" fn AMvalueEqual(value1: *const AMvalue, value2: *const AMvalue) -> bool { - match (value1.as_ref(), value2.as_ref()) { - (Some(value1), Some(value2)) => *value1 == *value2, - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} +use crate::index::AMindex; +use crate::item::AMitem; +use crate::items::AMitems; /// \struct AMresult /// \installed_headerfile /// \brief A discriminated union of result variants. pub enum AMresult { - ActorId(am::ActorId, Option), - ChangeHashes(Vec), - Changes(Vec, Option>), - Doc(Box), + Items(Vec), Error(String), - ListItems(Vec), - MapItems(Vec), - ObjId(AMobjId), - ObjItems(Vec), - String(String), - Strings(Vec), - SyncMessage(AMsyncMessage), - SyncState(Box), - Value(am::Value<'static>), - Void, } impl AMresult { - pub(crate) fn err(s: &str) -> Self { - AMresult::Error(s.to_string()) + pub(crate) fn error(s: &str) -> Self { + Self::Error(s.to_string()) + } + + pub(crate) fn item(item: AMitem) -> Self { + Self::Items(vec![item]) + } + + pub(crate) fn items(items: Vec) -> Self { + Self::Items(items) + } +} + +impl Default for AMresult { + fn default() -> Self { + Self::Items(vec![]) } } impl From for AMresult { fn from(auto_commit: am::AutoCommit) -> Self { - AMresult::Doc(Box::new(AMdoc::new(auto_commit))) + Self::item(AMitem::exact(am::ROOT, auto_commit.into())) + } +} + +impl From for AMresult { + fn from(change: am::Change) -> Self { + Self::item(change.into()) } } impl From for AMresult { fn from(change_hash: am::ChangeHash) -> Self { - AMresult::ChangeHashes(vec![change_hash]) + Self::item(change_hash.into()) } } impl From> for AMresult { - fn from(c: Option) -> Self { - match c { - Some(c) => c.into(), - None => AMresult::Void, + fn from(maybe: Option) -> Self { + match maybe { + Some(change_hash) => change_hash.into(), + None => Self::item(Default::default()), } } } -impl From> for AMresult { - fn from(keys: am::Keys<'_, '_>) -> Self { - AMresult::Strings(keys.collect()) - } -} - -impl From> for AMresult { - fn from(keys: am::KeysAt<'_, '_>) -> Self { - AMresult::Strings(keys.collect()) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRange<'static, Range>) -> Self { - AMresult::ListItems( - list_range - .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { - AMresult::ListItems( - list_range - .map(|(i, v, o)| AMlistItem::new(i, v.clone(), o)) - .collect(), - ) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, Range>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) - } -} - -impl From>> for AMresult { - fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { - let map_items: Vec = map_range - .map(|(k, v, o): (&'_ str, am::Value<'_>, am::ObjId)| AMmapItem::new(k, v.clone(), o)) - .collect(); - AMresult::MapItems(map_items) +impl From> for AMresult { + fn from(maybe: Result) -> Self { + match maybe { + Ok(change_hash) => change_hash.into(), + Err(e) => Self::error(&e.to_string()), + } } } impl From for AMresult { fn from(state: am::sync::State) -> Self { - AMresult::SyncState(Box::new(AMsyncState::new(state))) + Self::item(state.into()) } } impl From> for AMresult { fn from(pairs: am::Values<'static>) -> Self { - AMresult::ObjItems(pairs.map(|(v, o)| AMobjItem::new(v.clone(), o)).collect()) - } -} - -impl From, am::ObjId)>, am::AutomergeError>> for AMresult { - fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { - match maybe { - Ok(pairs) => AMresult::ObjItems( - pairs - .into_iter() - .map(|(v, o)| AMobjItem::new(v, o)) - .collect(), - ), - Err(e) => AMresult::err(&e.to_string()), - } + Self::items(pairs.map(|(v, o)| AMitem::exact(o, v.into())).collect()) } } @@ -517,37 +89,150 @@ impl From for *mut AMresult { } } +impl From> for AMresult { + fn from(keys: am::Keys<'_, '_>) -> Self { + Self::items(keys.map(|s| s.into()).collect()) + } +} + +impl From> for AMresult { + fn from(keys: am::KeysAt<'_, '_>) -> Self { + Self::items(keys.map(|s| s.into()).collect()) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRange<'static, Range>) -> Self { + Self::items( + list_range + .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(list_range: am::ListRangeAt<'static, Range>) -> Self { + Self::items( + list_range + .map(|(i, v, o)| AMitem::indexed(AMindex::Pos(i), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, Range>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, Range>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFrom>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFrom>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRange<'static, RangeFull>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeFull>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRange<'static, RangeTo>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + +impl From>> for AMresult { + fn from(map_range: am::MapRangeAt<'static, RangeTo>) -> Self { + Self::items( + map_range + .map(|(k, v, o)| AMitem::indexed(AMindex::Key(k.into()), o, v.into())) + .collect(), + ) + } +} + impl From> for AMresult { fn from(maybe: Option<&am::Change>) -> Self { - match maybe { - Some(change) => AMresult::Changes(vec![change.clone()], None), - None => AMresult::Void, - } + Self::item(match maybe { + Some(change) => change.clone().into(), + None => Default::default(), + }) } } impl From> for AMresult { fn from(maybe: Option) -> Self { - match maybe { - Some(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), - None => AMresult::Void, - } + Self::item(match maybe { + Some(message) => message.into(), + None => Default::default(), + }) } } impl From> for AMresult { fn from(maybe: Result<(), am::AutomergeError>) -> Self { match maybe { - Ok(()) => AMresult::Void, - Err(e) => AMresult::err(&e.to_string()), + Ok(()) => Self::item(Default::default()), + Err(e) => Self::error(&e.to_string()), } } } + impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => AMresult::ActorId(actor_id, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(actor_id) => Self::item(actor_id.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -555,8 +240,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(actor_id) => AMresult::ActorId(actor_id, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(actor_id) => Self::item(actor_id.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -564,8 +249,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(auto_commit) => AMresult::Doc(Box::new(AMdoc::new(auto_commit))), - Err(e) => AMresult::err(&e.to_string()), + Ok(auto_commit) => Self::item(auto_commit.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -573,17 +258,17 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(change) => AMresult::Changes(vec![change], None), - Err(e) => AMresult::err(&e.to_string()), + Ok(change) => Self::item(change.into()), + Err(e) => Self::error(&e.to_string()), } } } -impl From> for AMresult { - fn from(maybe: Result) -> Self { - match maybe { - Ok(obj_id) => AMresult::ObjId(AMobjId::new(obj_id)), - Err(e) => AMresult::err(&e.to_string()), +impl From<(Result, am::ObjType)> for AMresult { + fn from(tuple: (Result, am::ObjType)) -> Self { + match tuple { + (Ok(obj_id), obj_type) => Self::item((obj_id, obj_type).into()), + (Err(e), _) => Self::error(&e.to_string()), } } } @@ -591,8 +276,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(message) => AMresult::SyncMessage(AMsyncMessage::new(message)), - Err(e) => AMresult::err(&e.to_string()), + Ok(message) => Self::item(message.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -600,8 +285,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(state) => AMresult::SyncState(Box::new(AMsyncState::new(state))), - Err(e) => AMresult::err(&e.to_string()), + Ok(state) => Self::item(state.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -609,8 +294,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(value) => AMresult::Value(value), - Err(e) => AMresult::err(&e.to_string()), + Ok(value) => Self::item(value.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -618,12 +303,9 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::ObjId)>, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { match maybe { - Ok(Some((value, obj_id))) => match value { - am::Value::Object(_) => AMresult::ObjId(AMobjId::new(obj_id)), - _ => AMresult::Value(value), - }, - Ok(None) => AMresult::Void, - Err(e) => AMresult::err(&e.to_string()), + Ok(Some((value, obj_id))) => Self::item(AMitem::exact(obj_id, value.into())), + Ok(None) => Self::item(Default::default()), + Err(e) => Self::error(&e.to_string()), } } } @@ -631,8 +313,8 @@ impl From, am::ObjId)>, am::AutomergeError>> f impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(string) => AMresult::String(string), - Err(e) => AMresult::err(&e.to_string()), + Ok(string) => Self::item(string.into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -640,8 +322,8 @@ impl From> for AMresult { impl From> for AMresult { fn from(maybe: Result) -> Self { match maybe { - Ok(size) => AMresult::Value(am::Value::uint(size as u64)), - Err(e) => AMresult::err(&e.to_string()), + Ok(size) => Self::item(am::Value::uint(size as u64).into()), + Err(e) => Self::error(&e.to_string()), } } } @@ -649,17 +331,8 @@ impl From> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(changes) => AMresult::Changes(changes, None), - Err(e) => AMresult::err(&e.to_string()), - } - } -} - -impl From, am::LoadChangeError>> for AMresult { - fn from(maybe: Result, am::LoadChangeError>) -> Self { - match maybe { - Ok(changes) => AMresult::Changes(changes, None), - Err(e) => AMresult::err(&e.to_string()), + Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), + Err(e) => Self::error(&e.to_string()), } } } @@ -667,12 +340,22 @@ impl From, am::LoadChangeError>> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(changes) => { - let changes: Vec = - changes.iter().map(|&change| change.clone()).collect(); - AMresult::Changes(changes, None) - } - Err(e) => AMresult::err(&e.to_string()), + Ok(changes) => Self::items( + changes + .into_iter() + .map(|change| change.clone().into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), + } + } +} + +impl From, am::LoadChangeError>> for AMresult { + fn from(maybe: Result, am::LoadChangeError>) -> Self { + match maybe { + Ok(changes) => Self::items(changes.into_iter().map(|change| change.into()).collect()), + Err(e) => Self::error(&e.to_string()), } } } @@ -680,8 +363,13 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), - Err(e) => AMresult::err(&e.to_string()), + Ok(change_hashes) => Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), } } } @@ -689,8 +377,27 @@ impl From, am::AutomergeError>> for AMresult { impl From, am::InvalidChangeHashSlice>> for AMresult { fn from(maybe: Result, am::InvalidChangeHashSlice>) -> Self { match maybe { - Ok(change_hashes) => AMresult::ChangeHashes(change_hashes), - Err(e) => AMresult::err(&e.to_string()), + Ok(change_hashes) => Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), + } + } +} + +impl From, am::ObjId)>, am::AutomergeError>> for AMresult { + fn from(maybe: Result, am::ObjId)>, am::AutomergeError>) -> Self { + match maybe { + Ok(pairs) => Self::items( + pairs + .into_iter() + .map(|(v, o)| AMitem::exact(o, v.into())) + .collect(), + ), + Err(e) => Self::error(&e.to_string()), } } } @@ -698,28 +405,66 @@ impl From, am::InvalidChangeHashSlice>> for AMresult impl From, am::AutomergeError>> for AMresult { fn from(maybe: Result, am::AutomergeError>) -> Self { match maybe { - Ok(bytes) => AMresult::Value(am::Value::bytes(bytes)), - Err(e) => AMresult::err(&e.to_string()), + Ok(bytes) => Self::item(am::Value::bytes(bytes).into()), + Err(e) => Self::error(&e.to_string()), } } } +impl From<&[am::Change]> for AMresult { + fn from(changes: &[am::Change]) -> Self { + Self::items(changes.iter().map(|change| change.clone().into()).collect()) + } +} + impl From> for AMresult { fn from(changes: Vec<&am::Change>) -> Self { - let changes: Vec = changes.iter().map(|&change| change.clone()).collect(); - AMresult::Changes(changes, None) + Self::items( + changes + .into_iter() + .map(|change| change.clone().into()) + .collect(), + ) + } +} + +impl From<&[am::ChangeHash]> for AMresult { + fn from(change_hashes: &[am::ChangeHash]) -> Self { + Self::items( + change_hashes + .iter() + .map(|change_hash| (*change_hash).into()) + .collect(), + ) + } +} + +impl From<&[am::sync::Have]> for AMresult { + fn from(haves: &[am::sync::Have]) -> Self { + Self::items(haves.iter().map(|have| have.clone().into()).collect()) } } impl From> for AMresult { fn from(change_hashes: Vec) -> Self { - AMresult::ChangeHashes(change_hashes) + Self::items( + change_hashes + .into_iter() + .map(|change_hash| change_hash.into()) + .collect(), + ) + } +} + +impl From> for AMresult { + fn from(haves: Vec) -> Self { + Self::items(haves.into_iter().map(|have| have.into()).collect()) } } impl From> for AMresult { fn from(bytes: Vec) -> Self { - AMresult::Value(am::Value::bytes(bytes)) + Self::item(am::Value::bytes(bytes).into()) } } @@ -729,8 +474,9 @@ pub fn to_result>(r: R) -> *mut AMresult { /// \ingroup enumerations /// \enum AMstatus +/// \installed_headerfile /// \brief The status of an API call. -#[derive(Debug)] +#[derive(PartialEq, Eq)] #[repr(u8)] pub enum AMstatus { /// Success. @@ -742,35 +488,80 @@ pub enum AMstatus { InvalidResult, } +/// \memberof AMresult +/// \brief Concatenates the items from two results. +/// +/// \param[in] dest A pointer to an `AMresult` struct. +/// \param[in] src A pointer to an `AMresult` struct. +/// \return A pointer to an `AMresult` struct with the items from \p dest in +/// their original order followed by the items from \p src in their +/// original order. +/// \pre \p dest `!= NULL` +/// \pre \p src `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +/// \internal +/// +/// # Safety +/// dest must be a valid pointer to an AMresult +/// src must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultCat(dest: *const AMresult, src: *const AMresult) -> *mut AMresult { + use AMresult::*; + + match (dest.as_ref(), src.as_ref()) { + (Some(dest), Some(src)) => match (dest, src) { + (Items(dest_items), Items(src_items)) => { + return AMresult::items( + dest_items + .iter() + .cloned() + .chain(src_items.iter().cloned()) + .collect(), + ) + .into(); + } + (Error(_), Error(_)) | (Error(_), Items(_)) | (Items(_), Error(_)) => { + AMresult::error("Invalid `AMresult`").into() + } + }, + (None, None) | (None, Some(_)) | (Some(_), None) => { + AMresult::error("Invalid `AMresult*`").into() + } + } +} + /// \memberof AMresult /// \brief Gets a result's error message string. /// /// \param[in] result A pointer to an `AMresult` struct. /// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \pre \p result `!= NULL`. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMerrorMessage(result: *const AMresult) -> AMbyteSpan { - match result.as_ref() { - Some(AMresult::Error(s)) => s.as_bytes().into(), - _ => Default::default(), +pub unsafe extern "C" fn AMresultError(result: *const AMresult) -> AMbyteSpan { + use AMresult::*; + + if let Some(Error(message)) = result.as_ref() { + return message.as_bytes().into(); } + Default::default() } /// \memberof AMresult /// \brief Deallocates the storage for a result. /// -/// \param[in,out] result A pointer to an `AMresult` struct. -/// \pre \p result `!= NULL`. +/// \param[in] result A pointer to an `AMresult` struct. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] -pub unsafe extern "C" fn AMfree(result: *mut AMresult) { +pub unsafe extern "C" fn AMresultFree(result: *mut AMresult) { if !result.is_null() { let result: AMresult = *Box::from_raw(result); drop(result) @@ -778,39 +569,67 @@ pub unsafe extern "C" fn AMfree(result: *mut AMresult) { } /// \memberof AMresult -/// \brief Gets the size of a result's value. +/// \brief Gets a result's first item. /// /// \param[in] result A pointer to an `AMresult` struct. -/// \return The count of values in \p result. -/// \pre \p result `!= NULL`. +/// \return A pointer to an `AMitem` struct. +/// \pre \p result `!= NULL` +/// \internal +/// +/// # Safety +/// result must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultItem(result: *mut AMresult) -> *mut AMitem { + use AMresult::*; + + if let Some(Items(items)) = result.as_mut() { + if !items.is_empty() { + return &mut items[0]; + } + } + std::ptr::null_mut() +} + +/// \memberof AMresult +/// \brief Gets a result's items. +/// +/// \param[in] result A pointer to an `AMresult` struct. +/// \return An `AMitems` struct. +/// \pre \p result `!= NULL` +/// \internal +/// +/// # Safety +/// result must be a valid pointer to an AMresult +#[no_mangle] +pub unsafe extern "C" fn AMresultItems<'a>(result: *mut AMresult) -> AMitems<'a> { + use AMresult::*; + + if let Some(Items(items)) = result.as_mut() { + if !items.is_empty() { + return AMitems::new(items); + } + } + Default::default() +} + +/// \memberof AMresult +/// \brief Gets the size of a result. +/// +/// \param[in] result A pointer to an `AMresult` struct. +/// \return The count of items within \p result. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { - if let Some(result) = result.as_ref() { - use AMresult::*; + use self::AMresult::*; - match result { - Error(_) | Void => 0, - ActorId(_, _) - | Doc(_) - | ObjId(_) - | String(_) - | SyncMessage(_) - | SyncState(_) - | Value(_) => 1, - ChangeHashes(change_hashes) => change_hashes.len(), - Changes(changes, _) => changes.len(), - ListItems(list_items) => list_items.len(), - MapItems(map_items) => map_items.len(), - ObjItems(obj_items) => obj_items.len(), - Strings(cstrings) => cstrings.len(), - } - } else { - 0 + if let Some(Items(items)) = result.as_ref() { + return items.len(); } + 0 } /// \memberof AMresult @@ -818,94 +637,24 @@ pub unsafe extern "C" fn AMresultSize(result: *const AMresult) -> usize { /// /// \param[in] result A pointer to an `AMresult` struct. /// \return An `AMstatus` enum tag. -/// \pre \p result `!= NULL`. +/// \pre \p result `!= NULL` /// \internal /// /// # Safety /// result must be a valid pointer to an AMresult #[no_mangle] pub unsafe extern "C" fn AMresultStatus(result: *const AMresult) -> AMstatus { - match result.as_ref() { - Some(AMresult::Error(_)) => AMstatus::Error, - None => AMstatus::InvalidResult, - _ => AMstatus::Ok, - } -} + use AMresult::*; -/// \memberof AMresult -/// \brief Gets a result's value. -/// -/// \param[in] result A pointer to an `AMresult` struct. -/// \return An `AMvalue` struct. -/// \pre \p result `!= NULL`. -/// \internal -/// -/// # Safety -/// result must be a valid pointer to an AMresult -#[no_mangle] -pub unsafe extern "C" fn AMresultValue<'a>(result: *mut AMresult) -> AMvalue<'a> { - let mut content = AMvalue::Void; - if let Some(result) = result.as_mut() { + if let Some(result) = result.as_ref() { match result { - AMresult::ActorId(actor_id, c_actor_id) => match c_actor_id { - None => { - content = AMvalue::ActorId(&*c_actor_id.insert(AMactorId::new(&*actor_id))); - } - Some(c_actor_id) => { - content = AMvalue::ActorId(&*c_actor_id); - } - }, - AMresult::ChangeHashes(change_hashes) => { - content = AMvalue::ChangeHashes(AMchangeHashes::new(change_hashes)); + Error(_) => { + return AMstatus::Error; } - AMresult::Changes(changes, storage) => { - content = AMvalue::Changes(AMchanges::new( - changes, - storage.get_or_insert(BTreeMap::new()), - )); + _ => { + return AMstatus::Ok; } - AMresult::Doc(doc) => content = AMvalue::Doc(&mut **doc), - AMresult::Error(_) => {} - AMresult::ListItems(list_items) => { - content = AMvalue::ListItems(AMlistItems::new(list_items)); - } - AMresult::MapItems(map_items) => { - content = AMvalue::MapItems(AMmapItems::new(map_items)); - } - AMresult::ObjId(obj_id) => { - content = AMvalue::ObjId(obj_id); - } - AMresult::ObjItems(obj_items) => { - content = AMvalue::ObjItems(AMobjItems::new(obj_items)); - } - AMresult::String(string) => content = AMvalue::Str(string.as_bytes().into()), - AMresult::Strings(strings) => { - content = AMvalue::Strs(AMstrs::new(strings)); - } - AMresult::SyncMessage(sync_message) => { - content = AMvalue::SyncMessage(sync_message); - } - AMresult::SyncState(sync_state) => { - content = AMvalue::SyncState(&mut *sync_state); - } - AMresult::Value(value) => { - content = (&*value).into(); - } - AMresult::Void => {} } - }; - content -} - -/// \struct AMunknownValue -/// \installed_headerfile -/// \brief A value (typically for a `set` operation) whose type is unknown. -/// -#[derive(Eq, PartialEq)] -#[repr(C)] -pub struct AMunknownValue { - /// The value's raw bytes. - bytes: AMbyteSpan, - /// The value's encoded type identifier. - type_code: u8, + } + AMstatus::InvalidResult } diff --git a/rust/automerge-c/src/result_stack.rs b/rust/automerge-c/src/result_stack.rs deleted file mode 100644 index cfb9c7d2..00000000 --- a/rust/automerge-c/src/result_stack.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::result::{AMfree, AMresult, AMresultStatus, AMresultValue, AMstatus, AMvalue}; - -/// \struct AMresultStack -/// \installed_headerfile -/// \brief A node in a singly-linked list of result pointers. -/// -/// \note Using this data structure is purely optional because its only purpose -/// is to make memory management tolerable for direct usage of this API -/// in C, C++ and Objective-C. -#[repr(C)] -pub struct AMresultStack { - /// A result to be deallocated. - pub result: *mut AMresult, - /// The next node in the singly-linked list or `NULL`. - pub next: *mut AMresultStack, -} - -impl AMresultStack { - pub fn new(result: *mut AMresult, next: *mut AMresultStack) -> Self { - Self { result, next } - } -} - -/// \memberof AMresultStack -/// \brief Deallocates the storage for a stack of results. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \return The number of `AMresult` structs freed. -/// \pre \p stack `!= NULL`. -/// \post `*stack == NULL`. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -#[no_mangle] -pub unsafe extern "C" fn AMfreeStack(stack: *mut *mut AMresultStack) -> usize { - if stack.is_null() { - return 0; - } - let mut count: usize = 0; - while !(*stack).is_null() { - AMfree(AMpop(stack)); - count += 1; - } - count -} - -/// \memberof AMresultStack -/// \brief Gets the topmost result from the stack after removing it. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \return A pointer to an `AMresult` struct or `NULL`. -/// \pre \p stack `!= NULL`. -/// \post `*stack == NULL`. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -#[no_mangle] -pub unsafe extern "C" fn AMpop(stack: *mut *mut AMresultStack) -> *mut AMresult { - if stack.is_null() || (*stack).is_null() { - return std::ptr::null_mut(); - } - let top = Box::from_raw(*stack); - *stack = top.next; - let result = top.result; - drop(top); - result -} - -/// \memberof AMresultStack -/// \brief The prototype of a function to be called when a value matching the -/// given discriminant cannot be extracted from the result at the top of -/// the given stack. -/// -/// \note Implementing this function is purely optional because its only purpose -/// is to make memory management tolerable for direct usage of this API -/// in C, C++ and Objective-C. -pub type AMpushCallback = - Option ()>; - -/// \memberof AMresultStack -/// \brief Pushes the given result onto the given stack and then either extracts -/// a value matching the given discriminant from that result or, -/// failing that, calls the given function and gets a void value instead. -/// -/// \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. -/// \param[in] result A pointer to an `AMresult` struct. -/// \param[in] discriminant An `AMvalue` variant's corresponding enum tag. -/// \param[in] callback A pointer to a function with the same signature as -/// `AMpushCallback()` or `NULL`. -/// \return An `AMvalue` struct. -/// \pre \p stack `!= NULL`. -/// \pre \p result `!= NULL`. -/// \warning If \p stack `== NULL` then \p result is deallocated in order to -/// prevent a memory leak. -/// \note Calling this function is purely optional because its only purpose is -/// to make memory management tolerable for direct usage of this API in -/// C, C++ and Objective-C. -/// \internal -/// -/// # Safety -/// stack must be a valid AMresultStack pointer pointer -/// result must be a valid AMresult pointer -#[no_mangle] -pub unsafe extern "C" fn AMpush<'a>( - stack: *mut *mut AMresultStack, - result: *mut AMresult, - discriminant: u8, - callback: AMpushCallback, -) -> AMvalue<'a> { - if stack.is_null() { - // There's no stack to push the result onto so it has to be freed in - // order to prevent a memory leak. - AMfree(result); - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } else if result.is_null() { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - // Always push the result onto the stack, even if it's wrong, so that the - // given callback can retrieve it. - let node = Box::new(AMresultStack::new(result, *stack)); - let top = Box::into_raw(node); - *stack = top; - // Test that the result contains a value. - match AMresultStatus(result) { - AMstatus::Ok => {} - _ => { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - } - // Test that the result's value matches the given discriminant. - let value = AMresultValue(result); - if discriminant != u8::from(&value) { - if let Some(callback) = callback { - callback(stack, discriminant); - } - return AMvalue::Void; - } - value -} diff --git a/rust/automerge-c/src/strs.rs b/rust/automerge-c/src/strs.rs deleted file mode 100644 index a36861b7..00000000 --- a/rust/automerge-c/src/strs.rs +++ /dev/null @@ -1,359 +0,0 @@ -use std::cmp::Ordering; -use std::ffi::c_void; -use std::mem::size_of; -use std::os::raw::c_char; - -use crate::byte_span::AMbyteSpan; - -/// \brief Creates a string view from a C string. -/// -/// \param[in] c_str A UTF-8 C string. -/// \return A UTF-8 string view as an `AMbyteSpan` struct. -/// \internal -/// -/// #Safety -/// c_str must be a null-terminated array of `c_char` -#[no_mangle] -pub unsafe extern "C" fn AMstr(c_str: *const c_char) -> AMbyteSpan { - c_str.into() -} - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new(strings: &[String], offset: isize) -> Self { - Self { - len: strings.len(), - offset, - ptr: strings.as_ptr() as *const c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option { - if self.is_stopped() { - return None; - } - let slice: &[String] = - unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; - let value = slice[self.get_index()].as_bytes().into(); - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[String] = - unsafe { std::slice::from_raw_parts(self.ptr as *const String, self.len) }; - Some(slice[self.get_index()].as_bytes().into()) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts((&detail as *const Detail) as *const u8, USIZE_USIZE_USIZE_) - .try_into() - .unwrap() - } - } -} - -/// \struct AMstrs -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of UTF-8 strings. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMstrs { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_], -} - -impl AMstrs { - pub fn new(strings: &[String]) -> Self { - Self { - detail: Detail::new(strings, 0).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[String]> for AMstrs { - fn as_ref(&self) -> &[String] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const String, detail.len) } - } -} - -impl Default for AMstrs { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMstrs -/// \brief Advances an iterator over a sequence of UTF-8 strings by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsAdvance(strs: *mut AMstrs, n: isize) { - if let Some(strs) = strs.as_mut() { - strs.advance(n); - }; -} - -/// \memberof AMstrs -/// \brief Compares the sequences of UTF-8 strings underlying a pair of -/// iterators. -/// -/// \param[in] strs1 A pointer to an `AMstrs` struct. -/// \param[in] strs2 A pointer to an `AMstrs` struct. -/// \return `-1` if \p strs1 `<` \p strs2, `0` if -/// \p strs1 `==` \p strs2 and `1` if -/// \p strs1 `>` \p strs2. -/// \pre \p strs1 `!= NULL`. -/// \pre \p strs2 `!= NULL`. -/// \internal -/// -/// #Safety -/// strs1 must be a valid pointer to an AMstrs -/// strs2 must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsCmp(strs1: *const AMstrs, strs2: *const AMstrs) -> isize { - match (strs1.as_ref(), strs2.as_ref()) { - (Some(strs1), Some(strs2)) => match strs1.as_ref().cmp(strs2.as_ref()) { - Ordering::Less => -1, - Ordering::Equal => 0, - Ordering::Greater => 1, - }, - (None, Some(_)) => -1, - (Some(_), None) => 1, - (None, None) => 0, - } -} - -/// \memberof AMstrs -/// \brief Gets the key at the current position of an iterator over a sequence -/// of UTF-8 strings and then advances it by at most \p |n| positions -/// where the sign of \p n is relative to the iterator's direction. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` -/// when \p strs was previously advanced past its forward/reverse limit. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsNext(strs: *mut AMstrs, n: isize) -> AMbyteSpan { - if let Some(strs) = strs.as_mut() { - if let Some(key) = strs.next(n) { - return key; - } - } - Default::default() -} - -/// \memberof AMstrs -/// \brief Advances an iterator over a sequence of UTF-8 strings by at most -/// \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the key at its new position. -/// -/// \param[in,out] strs A pointer to an `AMstrs` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A UTF-8 string view as an `AMbyteSpan` struct that's `AMstr(NULL)` -/// when \p strs is presently advanced past its forward/reverse limit. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsPrev(strs: *mut AMstrs, n: isize) -> AMbyteSpan { - if let Some(strs) = strs.as_mut() { - if let Some(key) = strs.prev(n) { - return key; - } - } - Default::default() -} - -/// \memberof AMstrs -/// \brief Gets the size of the sequence of UTF-8 strings underlying an -/// iterator. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return The count of values in \p strs. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsSize(strs: *const AMstrs) -> usize { - if let Some(strs) = strs.as_ref() { - strs.len() - } else { - 0 - } -} - -/// \memberof AMstrs -/// \brief Creates an iterator over the same sequence of UTF-8 strings as the -/// given one but with the opposite position and direction. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return An `AMstrs` struct. -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsReversed(strs: *const AMstrs) -> AMstrs { - if let Some(strs) = strs.as_ref() { - strs.reversed() - } else { - AMstrs::default() - } -} - -/// \memberof AMstrs -/// \brief Creates an iterator at the starting position over the same sequence -/// of UTF-8 strings as the given one. -/// -/// \param[in] strs A pointer to an `AMstrs` struct. -/// \return An `AMstrs` struct -/// \pre \p strs `!= NULL`. -/// \internal -/// -/// #Safety -/// strs must be a valid pointer to an AMstrs -#[no_mangle] -pub unsafe extern "C" fn AMstrsRewound(strs: *const AMstrs) -> AMstrs { - if let Some(strs) = strs.as_ref() { - strs.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/sync.rs b/rust/automerge-c/src/sync.rs index cfed1af5..fe0332a1 100644 --- a/rust/automerge-c/src/sync.rs +++ b/rust/automerge-c/src/sync.rs @@ -1,7 +1,7 @@ mod have; -mod haves; mod message; mod state; +pub(crate) use have::AMsyncHave; pub(crate) use message::{to_sync_message, AMsyncMessage}; pub(crate) use state::AMsyncState; diff --git a/rust/automerge-c/src/sync/have.rs b/rust/automerge-c/src/sync/have.rs index 312151e7..37d2031f 100644 --- a/rust/automerge-c/src/sync/have.rs +++ b/rust/automerge-c/src/sync/have.rs @@ -1,23 +1,23 @@ use automerge as am; -use crate::change_hashes::AMchangeHashes; +use crate::result::{to_result, AMresult}; /// \struct AMsyncHave /// \installed_headerfile /// \brief A summary of the changes that the sender of a synchronization /// message already has. #[derive(Clone, Eq, PartialEq)] -pub struct AMsyncHave(*const am::sync::Have); +pub struct AMsyncHave(am::sync::Have); impl AMsyncHave { - pub fn new(have: &am::sync::Have) -> Self { + pub fn new(have: am::sync::Have) -> Self { Self(have) } } impl AsRef for AMsyncHave { fn as_ref(&self) -> &am::sync::Have { - unsafe { &*self.0 } + &self.0 } } @@ -25,17 +25,18 @@ impl AsRef for AMsyncHave { /// \brief Gets the heads of the sender. /// /// \param[in] sync_have A pointer to an `AMsyncHave` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_have `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_have `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_have must be a valid pointer to an AMsyncHave #[no_mangle] -pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> AMchangeHashes { - if let Some(sync_have) = sync_have.as_ref() { - AMchangeHashes::new(&sync_have.as_ref().last_sync) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncHaveLastSync(sync_have: *const AMsyncHave) -> *mut AMresult { + to_result(match sync_have.as_ref() { + Some(sync_have) => sync_have.as_ref().last_sync.as_slice(), + None => Default::default(), + }) } diff --git a/rust/automerge-c/src/sync/haves.rs b/rust/automerge-c/src/sync/haves.rs deleted file mode 100644 index c74b8e96..00000000 --- a/rust/automerge-c/src/sync/haves.rs +++ /dev/null @@ -1,378 +0,0 @@ -use automerge as am; -use std::collections::BTreeMap; -use std::ffi::c_void; -use std::mem::size_of; - -use crate::sync::have::AMsyncHave; - -#[repr(C)] -struct Detail { - len: usize, - offset: isize, - ptr: *const c_void, - storage: *mut c_void, -} - -/// \note cbindgen won't propagate the value of a `std::mem::size_of()` call -/// (https://github.com/eqrion/cbindgen/issues/252) but it will -/// propagate the name of a constant initialized from it so if the -/// constant's name is a symbolic representation of the value it can be -/// converted into a number by post-processing the header it generated. -pub const USIZE_USIZE_USIZE_USIZE_: usize = size_of::(); - -impl Detail { - fn new( - haves: &[am::sync::Have], - offset: isize, - storage: &mut BTreeMap, - ) -> Self { - let storage: *mut BTreeMap = storage; - Self { - len: haves.len(), - offset, - ptr: haves.as_ptr() as *const c_void, - storage: storage as *mut c_void, - } - } - - pub fn advance(&mut self, n: isize) { - if n == 0 { - return; - } - let len = self.len as isize; - self.offset = if self.offset < 0 { - // It's reversed. - let unclipped = self.offset.checked_sub(n).unwrap_or(isize::MIN); - if unclipped >= 0 { - // Clip it to the forward stop. - len - } else { - std::cmp::min(std::cmp::max(-(len + 1), unclipped), -1) - } - } else { - let unclipped = self.offset.checked_add(n).unwrap_or(isize::MAX); - if unclipped < 0 { - // Clip it to the reverse stop. - -(len + 1) - } else { - std::cmp::max(0, std::cmp::min(unclipped, len)) - } - } - } - - pub fn get_index(&self) -> usize { - (self.offset - + if self.offset < 0 { - self.len as isize - } else { - 0 - }) as usize - } - - pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { - if self.is_stopped() { - return None; - } - let slice: &[am::sync::Have] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - let value = match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMsyncHave::new(&slice[index])); - storage.get_mut(&index).unwrap() - } - }; - self.advance(n); - Some(value) - } - - pub fn is_stopped(&self) -> bool { - let len = self.len as isize; - self.offset < -len || self.offset == len - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { - self.advance(-n); - if self.is_stopped() { - return None; - } - let slice: &[am::sync::Have] = - unsafe { std::slice::from_raw_parts(self.ptr as *const am::sync::Have, self.len) }; - let storage = unsafe { &mut *(self.storage as *mut BTreeMap) }; - let index = self.get_index(); - Some(match storage.get_mut(&index) { - Some(value) => value, - None => { - storage.insert(index, AMsyncHave::new(&slice[index])); - storage.get_mut(&index).unwrap() - } - }) - } - - pub fn reversed(&self) -> Self { - Self { - len: self.len, - offset: -(self.offset + 1), - ptr: self.ptr, - storage: self.storage, - } - } - - pub fn rewound(&self) -> Self { - Self { - len: self.len, - offset: if self.offset < 0 { -1 } else { 0 }, - ptr: self.ptr, - storage: self.storage, - } - } -} - -impl From for [u8; USIZE_USIZE_USIZE_USIZE_] { - fn from(detail: Detail) -> Self { - unsafe { - std::slice::from_raw_parts( - (&detail as *const Detail) as *const u8, - USIZE_USIZE_USIZE_USIZE_, - ) - .try_into() - .unwrap() - } - } -} - -/// \struct AMsyncHaves -/// \installed_headerfile -/// \brief A random-access iterator over a sequence of synchronization haves. -#[repr(C)] -#[derive(Eq, PartialEq)] -pub struct AMsyncHaves { - /// An implementation detail that is intentionally opaque. - /// \warning Modifying \p detail will cause undefined behavior. - /// \note The actual size of \p detail will vary by platform, this is just - /// the one for the platform this documentation was built on. - detail: [u8; USIZE_USIZE_USIZE_USIZE_], -} - -impl AMsyncHaves { - pub fn new(haves: &[am::sync::Have], storage: &mut BTreeMap) -> Self { - Self { - detail: Detail::new(haves, 0, storage).into(), - } - } - - pub fn advance(&mut self, n: isize) { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.advance(n); - } - - pub fn len(&self) -> usize { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - detail.len - } - - pub fn next(&mut self, n: isize) -> Option<*const AMsyncHave> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.next(n) - } - - pub fn prev(&mut self, n: isize) -> Option<*const AMsyncHave> { - let detail = unsafe { &mut *(self.detail.as_mut_ptr() as *mut Detail) }; - detail.prev(n) - } - - pub fn reversed(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.reversed().into(), - } - } - - pub fn rewound(&self) -> Self { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - Self { - detail: detail.rewound().into(), - } - } -} - -impl AsRef<[am::sync::Have]> for AMsyncHaves { - fn as_ref(&self) -> &[am::sync::Have] { - let detail = unsafe { &*(self.detail.as_ptr() as *const Detail) }; - unsafe { std::slice::from_raw_parts(detail.ptr as *const am::sync::Have, detail.len) } - } -} - -impl Default for AMsyncHaves { - fn default() -> Self { - Self { - detail: [0; USIZE_USIZE_USIZE_USIZE_], - } - } -} - -/// \memberof AMsyncHaves -/// \brief Advances an iterator over a sequence of synchronization haves by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesAdvance(sync_haves: *mut AMsyncHaves, n: isize) { - if let Some(sync_haves) = sync_haves.as_mut() { - sync_haves.advance(n); - }; -} - -/// \memberof AMsyncHaves -/// \brief Tests the equality of two sequences of synchronization haves -/// underlying a pair of iterators. -/// -/// \param[in] sync_haves1 A pointer to an `AMsyncHaves` struct. -/// \param[in] sync_haves2 A pointer to an `AMsyncHaves` struct. -/// \return `true` if \p sync_haves1 `==` \p sync_haves2 and `false` otherwise. -/// \pre \p sync_haves1 `!= NULL`. -/// \pre \p sync_haves2 `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves1 must be a valid pointer to an AMsyncHaves -/// sync_haves2 must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesEqual( - sync_haves1: *const AMsyncHaves, - sync_haves2: *const AMsyncHaves, -) -> bool { - match (sync_haves1.as_ref(), sync_haves2.as_ref()) { - (Some(sync_haves1), Some(sync_haves2)) => sync_haves1.as_ref() == sync_haves2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, - } -} - -/// \memberof AMsyncHaves -/// \brief Gets the synchronization have at the current position of an iterator -/// over a sequence of synchronization haves and then advances it by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMsyncHave` struct that's `NULL` when -/// \p sync_haves was previously advanced past its forward/reverse -/// limit. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesNext( - sync_haves: *mut AMsyncHaves, - n: isize, -) -> *const AMsyncHave { - if let Some(sync_haves) = sync_haves.as_mut() { - if let Some(sync_have) = sync_haves.next(n) { - return sync_have; - } - } - std::ptr::null() -} - -/// \memberof AMsyncHaves -/// \brief Advances an iterator over a sequence of synchronization haves by at -/// most \p |n| positions where the sign of \p n is relative to the -/// iterator's direction and then gets the synchronization have at its -/// new position. -/// -/// \param[in,out] sync_haves A pointer to an `AMsyncHaves` struct. -/// \param[in] n The direction (\p -n -> opposite, \p n -> same) and maximum -/// number of positions to advance. -/// \return A pointer to an `AMsyncHave` struct that's `NULL` when -/// \p sync_haves is presently advanced past its forward/reverse limit. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesPrev( - sync_haves: *mut AMsyncHaves, - n: isize, -) -> *const AMsyncHave { - if let Some(sync_haves) = sync_haves.as_mut() { - if let Some(sync_have) = sync_haves.prev(n) { - return sync_have; - } - } - std::ptr::null() -} - -/// \memberof AMsyncHaves -/// \brief Gets the size of the sequence of synchronization haves underlying an -/// iterator. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return The count of values in \p sync_haves. -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesSize(sync_haves: *const AMsyncHaves) -> usize { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.len() - } else { - 0 - } -} - -/// \memberof AMsyncHaves -/// \brief Creates an iterator over the same sequence of synchronization haves -/// as the given one but with the opposite position and direction. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return An `AMsyncHaves` struct -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesReversed(sync_haves: *const AMsyncHaves) -> AMsyncHaves { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.reversed() - } else { - Default::default() - } -} - -/// \memberof AMsyncHaves -/// \brief Creates an iterator at the starting position over the same sequence -/// of synchronization haves as the given one. -/// -/// \param[in] sync_haves A pointer to an `AMsyncHaves` struct. -/// \return An `AMsyncHaves` struct -/// \pre \p sync_haves `!= NULL`. -/// \internal -/// -/// #Safety -/// sync_haves must be a valid pointer to an AMsyncHaves -#[no_mangle] -pub unsafe extern "C" fn AMsyncHavesRewound(sync_haves: *const AMsyncHaves) -> AMsyncHaves { - if let Some(sync_haves) = sync_haves.as_ref() { - sync_haves.rewound() - } else { - Default::default() - } -} diff --git a/rust/automerge-c/src/sync/message.rs b/rust/automerge-c/src/sync/message.rs index 46a6d29a..bdb1db34 100644 --- a/rust/automerge-c/src/sync/message.rs +++ b/rust/automerge-c/src/sync/message.rs @@ -3,18 +3,15 @@ use std::cell::RefCell; use std::collections::BTreeMap; use crate::change::AMchange; -use crate::change_hashes::AMchangeHashes; -use crate::changes::AMchanges; use crate::result::{to_result, AMresult}; use crate::sync::have::AMsyncHave; -use crate::sync::haves::AMsyncHaves; macro_rules! to_sync_message { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncMessage pointer").into(), + None => return AMresult::error("Invalid `AMsyncMessage*`").into(), } }}; } @@ -51,55 +48,52 @@ impl AsRef for AMsyncMessage { /// \brief Gets the changes for the recipient to apply. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchanges` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> AMchanges { - if let Some(sync_message) = sync_message.as_ref() { - AMchanges::new( - &sync_message.body.changes, - &mut sync_message.changes_storage.borrow_mut(), - ) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageChanges(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.body.changes.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage -/// \brief Decodes a sequence of bytes into a synchronization message. +/// \brief Decodes an array of bytes into a synchronization message. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to decode. -/// \return A pointer to an `AMresult` struct containing an `AMsyncMessage` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to decode from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_MESSAGE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMsyncMessageDecode(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::sync::Message::decode(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::sync::Message::decode(data)) } /// \memberof AMsyncMessage -/// \brief Encodes a synchronization message as a sequence of bytes. +/// \brief Encodes a synchronization message as an array of bytes. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p sync_message `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTES` item. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -114,41 +108,40 @@ pub unsafe extern "C" fn AMsyncMessageEncode(sync_message: *const AMsyncMessage) /// \brief Gets a summary of the changes that the sender already has. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMhaves` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_SYNC_HAVE` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> AMsyncHaves { - if let Some(sync_message) = sync_message.as_ref() { - AMsyncHaves::new( - &sync_message.as_ref().have, - &mut sync_message.haves_storage.borrow_mut(), - ) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageHaves(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().have.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage /// \brief Gets the heads of the sender. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> AMchangeHashes { - if let Some(sync_message) = sync_message.as_ref() { - AMchangeHashes::new(&sync_message.as_ref().heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().heads.as_slice(), + None => Default::default(), + }) } /// \memberof AMsyncMessage @@ -156,17 +149,18 @@ pub unsafe extern "C" fn AMsyncMessageHeads(sync_message: *const AMsyncMessage) /// by the recipient. /// /// \param[in] sync_message A pointer to an `AMsyncMessage` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_message `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_message `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] -pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> AMchangeHashes { - if let Some(sync_message) = sync_message.as_ref() { - AMchangeHashes::new(&sync_message.as_ref().need) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncMessageNeeds(sync_message: *const AMsyncMessage) -> *mut AMresult { + to_result(match sync_message.as_ref() { + Some(sync_message) => sync_message.as_ref().need.as_slice(), + None => Default::default(), + }) } diff --git a/rust/automerge-c/src/sync/state.rs b/rust/automerge-c/src/sync/state.rs index 1c1d316f..1d85ed98 100644 --- a/rust/automerge-c/src/sync/state.rs +++ b/rust/automerge-c/src/sync/state.rs @@ -2,17 +2,15 @@ use automerge as am; use std::cell::RefCell; use std::collections::BTreeMap; -use crate::change_hashes::AMchangeHashes; use crate::result::{to_result, AMresult}; use crate::sync::have::AMsyncHave; -use crate::sync::haves::AMsyncHaves; macro_rules! to_sync_state { ($handle:expr) => {{ let handle = $handle.as_ref(); match handle { Some(b) => b, - None => return AMresult::err("Invalid AMsyncState pointer").into(), + None => return AMresult::error("Invalid `AMsyncState*`").into(), } }}; } @@ -56,36 +54,35 @@ impl From for *mut AMsyncState { } /// \memberof AMsyncState -/// \brief Decodes a sequence of bytes into a synchronization state. +/// \brief Decodes an array of bytes into a synchronization state. /// /// \param[in] src A pointer to an array of bytes. -/// \param[in] count The number of bytes in \p src to decode. -/// \return A pointer to an `AMresult` struct containing an `AMsyncState` -/// struct. -/// \pre \p src `!= NULL`. -/// \pre `0 <` \p count `<= sizeof(`\p src`)`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \param[in] count The count of bytes to decode from the array pointed to by +/// \p src. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item. +/// \pre \p src `!= NULL` +/// \pre `sizeof(`\p src `) > 0` +/// \pre \p count `<= sizeof(`\p src `)` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety -/// src must be a byte array of size `>= count` +/// src must be a byte array of length `>= count` #[no_mangle] pub unsafe extern "C" fn AMsyncStateDecode(src: *const u8, count: usize) -> *mut AMresult { - let mut data = Vec::new(); - data.extend_from_slice(std::slice::from_raw_parts(src, count)); - to_result(am::sync::State::decode(&data)) + let data = std::slice::from_raw_parts(src, count); + to_result(am::sync::State::decode(data)) } /// \memberof AMsyncState -/// \brief Encodes a synchronizaton state as a sequence of bytes. +/// \brief Encodes a synchronization state as an array of bytes. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return A pointer to an `AMresult` struct containing an array of bytes as -/// an `AMbyteSpan` struct. -/// \pre \p sync_state `!= NULL`. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_BYTE_SPAN` item. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety @@ -102,8 +99,9 @@ pub unsafe extern "C" fn AMsyncStateEncode(sync_state: *const AMsyncState) -> *m /// \param[in] sync_state1 A pointer to an `AMsyncState` struct. /// \param[in] sync_state2 A pointer to an `AMsyncState` struct. /// \return `true` if \p sync_state1 `==` \p sync_state2 and `false` otherwise. -/// \pre \p sync_state1 `!= NULL`. -/// \pre \p sync_state2 `!= NULL`. +/// \pre \p sync_state1 `!= NULL` +/// \pre \p sync_state2 `!= NULL` +/// \post `!(`\p sync_state1 `&&` \p sync_state2 `) -> false` /// \internal /// /// #Safety @@ -116,18 +114,17 @@ pub unsafe extern "C" fn AMsyncStateEqual( ) -> bool { match (sync_state1.as_ref(), sync_state2.as_ref()) { (Some(sync_state1), Some(sync_state2)) => sync_state1.as_ref() == sync_state2.as_ref(), - (None, Some(_)) | (Some(_), None) | (None, None) => false, + (None, None) | (None, Some(_)) | (Some(_), None) => false, } } /// \memberof AMsyncState -/// \brief Allocates a new synchronization state and initializes it with -/// defaults. +/// \brief Allocates a new synchronization state and initializes it from +/// default values. /// -/// \return A pointer to an `AMresult` struct containing a pointer to an -/// `AMsyncState` struct. -/// \warning The returned `AMresult` struct must be deallocated with `AMfree()` -/// in order to prevent a memory leak. +/// \return A pointer to an `AMresult` struct with an `AM_VAL_TYPE_SYNC_STATE` item. +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. #[no_mangle] pub extern "C" fn AMsyncStateInit() -> *mut AMresult { to_result(am::sync::State::new()) @@ -137,40 +134,36 @@ pub extern "C" fn AMsyncStateInit() -> *mut AMresult { /// \brief Gets the heads that are shared by both peers. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState #[no_mangle] -pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> AMchangeHashes { - if let Some(sync_state) = sync_state.as_ref() { - AMchangeHashes::new(&sync_state.as_ref().shared_heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncStateSharedHeads(sync_state: *const AMsyncState) -> *mut AMresult { + let sync_state = to_sync_state!(sync_state); + to_result(sync_state.as_ref().shared_heads.as_slice()) } /// \memberof AMsyncState /// \brief Gets the heads that were last sent by this peer. /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState #[no_mangle] -pub unsafe extern "C" fn AMsyncStateLastSentHeads( - sync_state: *const AMsyncState, -) -> AMchangeHashes { - if let Some(sync_state) = sync_state.as_ref() { - AMchangeHashes::new(&sync_state.as_ref().last_sent_heads) - } else { - Default::default() - } +pub unsafe extern "C" fn AMsyncStateLastSentHeads(sync_state: *const AMsyncState) -> *mut AMresult { + let sync_state = to_sync_state!(sync_state); + to_result(sync_state.as_ref().last_sent_heads.as_slice()) } /// \memberof AMsyncState @@ -178,11 +171,13 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMhaves` struct is relevant, `false` otherwise. -/// \return An `AMhaves` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. -/// \internal +/// the returned `AMitems` struct is relevant, `false` otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_SYNC_HAVE` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. +//// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState @@ -191,15 +186,15 @@ pub unsafe extern "C" fn AMsyncStateLastSentHeads( pub unsafe extern "C" fn AMsyncStateTheirHaves( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMsyncHaves { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(haves) = &sync_state.as_ref().their_have { *has_value = true; - return AMsyncHaves::new(haves, &mut sync_state.their_haves_storage.borrow_mut()); - }; + return to_result(haves.as_slice()); + } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } /// \memberof AMsyncState @@ -207,29 +202,31 @@ pub unsafe extern "C" fn AMsyncStateTheirHaves( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMchangeHashes` struct is relevant, `false` -/// otherwise. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. +/// the returned `AMitems` struct is relevant, `false` +/// otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool. +/// has_value must be a valid pointer to a bool #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirHeads( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMchangeHashes { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_heads { *has_value = true; - return AMchangeHashes::new(change_hashes); + return to_result(change_hashes.as_slice()); } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } /// \memberof AMsyncState @@ -237,27 +234,29 @@ pub unsafe extern "C" fn AMsyncStateTheirHeads( /// /// \param[in] sync_state A pointer to an `AMsyncState` struct. /// \param[out] has_value A pointer to a boolean flag that is set to `true` if -/// the returned `AMchangeHashes` struct is relevant, `false` -/// otherwise. -/// \return An `AMchangeHashes` struct. -/// \pre \p sync_state `!= NULL`. -/// \pre \p has_value `!= NULL`. +/// the returned `AMitems` struct is relevant, `false` +/// otherwise. +/// \return A pointer to an `AMresult` struct with `AM_VAL_TYPE_CHANGE_HASH` items. +/// \pre \p sync_state `!= NULL` +/// \pre \p has_value `!= NULL` +/// \warning The returned `AMresult` struct pointer must be passed to +/// `AMresultFree()` in order to avoid a memory leak. /// \internal /// /// # Safety /// sync_state must be a valid pointer to an AMsyncState -/// has_value must be a valid pointer to a bool. +/// has_value must be a valid pointer to a bool #[no_mangle] pub unsafe extern "C" fn AMsyncStateTheirNeeds( sync_state: *const AMsyncState, has_value: *mut bool, -) -> AMchangeHashes { +) -> *mut AMresult { if let Some(sync_state) = sync_state.as_ref() { if let Some(change_hashes) = &sync_state.as_ref().their_need { *has_value = true; - return AMchangeHashes::new(change_hashes); + return to_result(change_hashes.as_slice()); } }; *has_value = false; - Default::default() + to_result(Vec::::new()) } diff --git a/rust/automerge-c/src/utils/result.c b/rust/automerge-c/src/utils/result.c new file mode 100644 index 00000000..f922ca31 --- /dev/null +++ b/rust/automerge-c/src/utils/result.c @@ -0,0 +1,33 @@ +#include + +#include + +AMresult* AMresultFrom(int count, ...) { + AMresult* result = NULL; + bool is_ok = true; + va_list args; + va_start(args, count); + for (int i = 0; i != count; ++i) { + AMresult* src = va_arg(args, AMresult*); + AMresult* dest = result; + is_ok = (AMresultStatus(src) == AM_STATUS_OK); + if (is_ok) { + if (dest) { + result = AMresultCat(dest, src); + is_ok = (AMresultStatus(result) == AM_STATUS_OK); + AMresultFree(dest); + AMresultFree(src); + } else { + result = src; + } + } else { + AMresultFree(src); + } + } + va_end(args); + if (!is_ok) { + AMresultFree(result); + result = NULL; + } + return result; +} diff --git a/rust/automerge-c/src/utils/stack.c b/rust/automerge-c/src/utils/stack.c new file mode 100644 index 00000000..2cad7c5c --- /dev/null +++ b/rust/automerge-c/src/utils/stack.c @@ -0,0 +1,106 @@ +#include +#include + +#include +#include + +void AMstackFree(AMstack** stack) { + if (stack) { + while (*stack) { + AMresultFree(AMstackPop(stack, NULL)); + } + } +} + +AMresult* AMstackPop(AMstack** stack, const AMresult* result) { + if (!stack) { + return NULL; + } + AMstack** prev = stack; + if (result) { + while (*prev && ((*prev)->result != result)) { + *prev = (*prev)->prev; + } + } + if (!*prev) { + return NULL; + } + AMstack* target = *prev; + *prev = target->prev; + AMresult* popped = target->result; + free(target); + return popped; +} + +AMresult* AMstackResult(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + if (!stack) { + if (callback) { + /* Create a local stack so that the callback can still examine the + * result. */ + AMstack node = {.result = result, .prev = NULL}; + AMstack* stack = &node; + callback(&stack, data); + } else { + /* \note There is no reason to call this function when both the + * stack and the callback are null. */ + fprintf(stderr, "ERROR: NULL AMstackCallback!\n"); + } + /* \note Nothing can be returned without a stack regardless of + * whether or not the callback validated the result. */ + AMresultFree(result); + return NULL; + } + /* Always push the result onto the stack, even if it's null, so that the + * callback can examine it. */ + AMstack* next = calloc(1, sizeof(AMstack)); + *next = (AMstack){.result = result, .prev = *stack}; + AMstack* top = next; + *stack = top; + if (callback) { + if (!callback(stack, data)) { + /* The result didn't pass the callback's examination. */ + return NULL; + } + } else { + /* Report an obvious error. */ + if (result) { + AMbyteSpan const err_msg = AMresultError(result); + if (err_msg.src && err_msg.count) { + /* \note The callback may be null because the result is supposed + * to be examined externally so return it despite an + * error. */ + char* const cstr = AMstrdup(err_msg, NULL); + fprintf(stderr, "WARNING: %s.\n", cstr); + free(cstr); + } + } else { + /* \note There's no reason to call this function when both the + * result and the callback are null. */ + fprintf(stderr, "ERROR: NULL AMresult*!\n"); + return NULL; + } + } + return result; +} + +AMitem* AMstackItem(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + AMitems items = AMstackItems(stack, result, callback, data); + return AMitemsNext(&items, 1); +} + +AMitems AMstackItems(AMstack** stack, AMresult* result, AMstackCallback callback, void* data) { + return (AMstackResult(stack, result, callback, data)) ? AMresultItems(result) : (AMitems){0}; +} + +size_t AMstackSize(AMstack const* const stack) { + if (!stack) { + return 0; + } + size_t count = 0; + AMstack const* prev = stack; + while (prev) { + ++count; + prev = prev->prev; + } + return count; +} \ No newline at end of file diff --git a/rust/automerge-c/src/utils/stack_callback_data.c b/rust/automerge-c/src/utils/stack_callback_data.c new file mode 100644 index 00000000..f1e988d8 --- /dev/null +++ b/rust/automerge-c/src/utils/stack_callback_data.c @@ -0,0 +1,9 @@ +#include + +#include + +AMstackCallbackData* AMstackCallbackDataInit(AMvalType const bitmask, char const* const file, int const line) { + AMstackCallbackData* data = malloc(sizeof(AMstackCallbackData)); + *data = (AMstackCallbackData){.bitmask = bitmask, .file = file, .line = line}; + return data; +} diff --git a/rust/automerge-c/src/utils/string.c b/rust/automerge-c/src/utils/string.c new file mode 100644 index 00000000..a0d1ebe3 --- /dev/null +++ b/rust/automerge-c/src/utils/string.c @@ -0,0 +1,46 @@ +#include +#include + +#include + +char* AMstrdup(AMbyteSpan const str, char const* nul) { + if (!str.src) { + return NULL; + } else if (!str.count) { + return strdup(""); + } + nul = (nul) ? nul : "\\0"; + size_t const nul_len = strlen(nul); + char* dup = NULL; + size_t dup_len = 0; + char const* begin = str.src; + char const* end = begin; + for (size_t i = 0; i != str.count; ++i, ++end) { + if (!*end) { + size_t const len = end - begin; + size_t const alloc_len = dup_len + len + nul_len; + if (dup) { + dup = realloc(dup, alloc_len + 1); + } else { + dup = malloc(alloc_len + 1); + } + memcpy(dup + dup_len, begin, len); + memcpy(dup + dup_len + len, nul, nul_len); + dup[alloc_len] = '\0'; + begin = end + 1; + dup_len = alloc_len; + } + } + if (begin != end) { + size_t const len = end - begin; + size_t const alloc_len = dup_len + len; + if (dup) { + dup = realloc(dup, alloc_len + 1); + } else { + dup = malloc(alloc_len + 1); + } + memcpy(dup + dup_len, begin, len); + dup[alloc_len] = '\0'; + } + return dup; +} diff --git a/rust/automerge-c/test/CMakeLists.txt b/rust/automerge-c/test/CMakeLists.txt index 704a27da..1759f140 100644 --- a/rust/automerge-c/test/CMakeLists.txt +++ b/rust/automerge-c/test/CMakeLists.txt @@ -1,53 +1,51 @@ -cmake_minimum_required(VERSION 3.18 FATAL_ERROR) - -find_package(cmocka REQUIRED) +find_package(cmocka CONFIG REQUIRED) add_executable( - test_${LIBRARY_NAME} + ${LIBRARY_NAME}_test actor_id_tests.c + base_state.c + byte_span_tests.c + cmocka_utils.c + enum_string_tests.c + doc_state.c doc_tests.c - group_state.c + item_tests.c list_tests.c macro_utils.c main.c map_tests.c - stack_utils.c str_utils.c ported_wasm/basic_tests.c ported_wasm/suite.c ported_wasm/sync_tests.c ) -set_target_properties(test_${LIBRARY_NAME} PROPERTIES LINKER_LANGUAGE C) +set_target_properties(${LIBRARY_NAME}_test PROPERTIES LINKER_LANGUAGE C) -# \note An imported library's INTERFACE_INCLUDE_DIRECTORIES property can't -# contain a non-existent path so its build-time include directory -# must be specified for all of its dependent targets instead. -target_include_directories( - test_${LIBRARY_NAME} - PRIVATE "$" -) +if(WIN32) + set(CMOCKA "cmocka::cmocka") +else() + set(CMOCKA "cmocka") +endif() -target_link_libraries(test_${LIBRARY_NAME} PRIVATE cmocka ${LIBRARY_NAME}) +target_link_libraries(${LIBRARY_NAME}_test PRIVATE ${CMOCKA} ${LIBRARY_NAME}) -add_dependencies(test_${LIBRARY_NAME} ${LIBRARY_NAME}_artifacts) +add_dependencies(${LIBRARY_NAME}_test ${BINDINGS_NAME}_artifacts) if(BUILD_SHARED_LIBS AND WIN32) add_custom_command( - TARGET test_${LIBRARY_NAME} + TARGET ${LIBRARY_NAME}_test POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CARGO_CURRENT_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_${CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} - ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Copying the DLL built by Cargo into the test directory..." + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMENT "Copying the DLL into the tests directory..." VERBATIM ) endif() -add_test(NAME test_${LIBRARY_NAME} COMMAND test_${LIBRARY_NAME}) +add_test(NAME ${LIBRARY_NAME}_test COMMAND ${LIBRARY_NAME}_test) add_custom_command( - TARGET test_${LIBRARY_NAME} + TARGET ${LIBRARY_NAME}_test POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} --config $ --output-on-failure diff --git a/rust/automerge-c/test/actor_id_tests.c b/rust/automerge-c/test/actor_id_tests.c index c98f2554..918d6213 100644 --- a/rust/automerge-c/test/actor_id_tests.c +++ b/rust/automerge-c/test/actor_id_tests.c @@ -14,99 +14,126 @@ #include "cmocka_utils.h" #include "str_utils.h" +/** + * \brief State for a group of cmocka test cases. + */ typedef struct { + /** An actor ID as an array of bytes. */ uint8_t* src; - AMbyteSpan str; + /** The count of bytes in \p src. */ size_t count; -} GroupState; + /** A stack of results. */ + AMstack* stack; + /** An actor ID as a hexadecimal string. */ + AMbyteSpan str; +} DocState; static int group_setup(void** state) { - GroupState* group_state = test_calloc(1, sizeof(GroupState)); - group_state->str.src = "000102030405060708090a0b0c0d0e0f"; - group_state->str.count = strlen(group_state->str.src); - group_state->count = group_state->str.count / 2; - group_state->src = test_malloc(group_state->count); - hex_to_bytes(group_state->str.src, group_state->src, group_state->count); - *state = group_state; + DocState* doc_state = test_calloc(1, sizeof(DocState)); + doc_state->str = AMstr("000102030405060708090a0b0c0d0e0f"); + doc_state->count = doc_state->str.count / 2; + doc_state->src = test_calloc(doc_state->count, sizeof(uint8_t)); + hex_to_bytes(doc_state->str.src, doc_state->src, doc_state->count); + *state = doc_state; return 0; } static int group_teardown(void** state) { - GroupState* group_state = *state; - test_free(group_state->src); - test_free(group_state); + DocState* doc_state = *state; + test_free(doc_state->src); + AMstackFree(&doc_state->stack); + test_free(doc_state); return 0; } -static void test_AMactorIdInit() { +static void test_AMactorIdFromBytes(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; + /* Non-empty string. */ + AMresult* result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, doc_state->count), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + assert_int_equal(AMresultSize(result), 1); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_int_equal(bytes.count, doc_state->count); + assert_memory_equal(bytes.src, doc_state->src, bytes.count); + /* Empty array. */ + /** \todo Find out if this is intentionally allowed. */ + result = AMstackResult(stack_ptr, AMactorIdFromBytes(doc_state->src, 0), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + /* NULL array. */ + result = AMstackResult(stack_ptr, AMactorIdFromBytes(NULL, doc_state->count), NULL, NULL); + if (AMresultStatus(result) == AM_STATUS_OK) { + fail_msg("AMactorId from NULL."); + } +} + +static void test_AMactorIdFromStr(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; + AMresult* result = AMstackResult(stack_ptr, AMactorIdFromStr(doc_state->str), NULL, NULL); + if (AMresultStatus(result) != AM_STATUS_OK) { + fail_msg_view("%s", AMresultError(result)); + } + assert_int_equal(AMresultSize(result), 1); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + /* The hexadecimal string should've been decoded as identical bytes. */ + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_int_equal(bytes.count, doc_state->count); + assert_memory_equal(bytes.src, doc_state->src, bytes.count); + /* The bytes should've been encoded as an identical hexadecimal string. */ + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const str = AMactorIdStr(actor_id); + assert_int_equal(str.count, doc_state->str.count); + assert_memory_equal(str.src, doc_state->str.src, str.count); +} + +static void test_AMactorIdInit(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->stack; AMresult* prior_result = NULL; AMbyteSpan prior_bytes = {NULL, 0}; AMbyteSpan prior_str = {NULL, 0}; - AMresult* result = NULL; for (size_t i = 0; i != 11; ++i) { - result = AMactorIdInit(); + AMresult* result = AMstackResult(stack_ptr, AMactorIdInit(), NULL, NULL); if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); + fail_msg_view("%s", AMresultError(result)); } assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - AMbyteSpan const str = AMactorIdStr(value.actor_id); + AMitem* const item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_ACTOR_ID); + AMactorId const* actor_id; + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const bytes = AMactorIdBytes(actor_id); + assert_true(AMitemToActorId(item, &actor_id)); + AMbyteSpan const str = AMactorIdStr(actor_id); if (prior_result) { size_t const max_byte_count = fmax(bytes.count, prior_bytes.count); assert_memory_not_equal(bytes.src, prior_bytes.src, max_byte_count); size_t const max_char_count = fmax(str.count, prior_str.count); assert_memory_not_equal(str.src, prior_str.src, max_char_count); - AMfree(prior_result); } prior_result = result; prior_bytes = bytes; prior_str = str; } - AMfree(result); -} - -static void test_AMactorIdInitBytes(void **state) { - GroupState* group_state = *state; - AMresult* const result = AMactorIdInitBytes(group_state->src, group_state->count); - if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); - } - assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - assert_int_equal(bytes.count, group_state->count); - assert_memory_equal(bytes.src, group_state->src, bytes.count); - AMfree(result); -} - -static void test_AMactorIdInitStr(void **state) { - GroupState* group_state = *state; - AMresult* const result = AMactorIdInitStr(group_state->str); - if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); - } - assert_int_equal(AMresultSize(result), 1); - AMvalue const value = AMresultValue(result); - assert_int_equal(value.tag, AM_VALUE_ACTOR_ID); - /* The hexadecimal string should've been decoded as identical bytes. */ - AMbyteSpan const bytes = AMactorIdBytes(value.actor_id); - assert_int_equal(bytes.count, group_state->count); - assert_memory_equal(bytes.src, group_state->src, bytes.count); - /* The bytes should've been encoded as an identical hexadecimal string. */ - AMbyteSpan const str = AMactorIdStr(value.actor_id); - assert_int_equal(str.count, group_state->str.count); - assert_memory_equal(str.src, group_state->str.src, str.count); - AMfree(result); } int run_actor_id_tests(void) { const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMactorIdFromBytes), + cmocka_unit_test(test_AMactorIdFromStr), cmocka_unit_test(test_AMactorIdInit), - cmocka_unit_test(test_AMactorIdInitBytes), - cmocka_unit_test(test_AMactorIdInitStr), }; return cmocka_run_group_tests(tests, group_setup, group_teardown); diff --git a/rust/automerge-c/test/base_state.c b/rust/automerge-c/test/base_state.c new file mode 100644 index 00000000..53325a99 --- /dev/null +++ b/rust/automerge-c/test/base_state.c @@ -0,0 +1,17 @@ +#include + +/* local */ +#include "base_state.h" + +int setup_base(void** state) { + BaseState* base_state = calloc(1, sizeof(BaseState)); + *state = base_state; + return 0; +} + +int teardown_base(void** state) { + BaseState* base_state = *state; + AMstackFree(&base_state->stack); + free(base_state); + return 0; +} diff --git a/rust/automerge-c/test/base_state.h b/rust/automerge-c/test/base_state.h new file mode 100644 index 00000000..3c4ff01b --- /dev/null +++ b/rust/automerge-c/test/base_state.h @@ -0,0 +1,39 @@ +#ifndef TESTS_BASE_STATE_H +#define TESTS_BASE_STATE_H + +#include + +/* local */ +#include +#include + +/** + * \struct BaseState + * \brief The shared state for one or more cmocka test cases. + */ +typedef struct { + /** A stack of results. */ + AMstack* stack; +} BaseState; + +/** + * \memberof BaseState + * \brief Sets up the shared state for one or more cmocka test cases. + * + * \param[in,out] state A pointer to a pointer to a `BaseState` struct. + * \pre \p state `!= NULL`. + * \warning The `BaseState` struct returned through \p state must be + * passed to `teardown_base()` in order to avoid a memory leak. + */ +int setup_base(void** state); + +/** + * \memberof BaseState + * \brief Tears down the shared state for one or more cmocka test cases. + * + * \param[in] state A pointer to a pointer to a `BaseState` struct. + * \pre \p state `!= NULL`. + */ +int teardown_base(void** state); + +#endif /* TESTS_BASE_STATE_H */ diff --git a/rust/automerge-c/test/byte_span_tests.c b/rust/automerge-c/test/byte_span_tests.c new file mode 100644 index 00000000..43856f3b --- /dev/null +++ b/rust/automerge-c/test/byte_span_tests.c @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include + +static void test_AMbytes(void** state) { + static char const DATA[] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}; + + AMbyteSpan bytes = AMbytes(DATA, sizeof(DATA)); + assert_int_equal(bytes.count, sizeof(DATA)); + assert_memory_equal(bytes.src, DATA, bytes.count); + assert_ptr_equal(bytes.src, DATA); + /* Empty view */ + bytes = AMbytes(DATA, 0); + assert_int_equal(bytes.count, 0); + assert_ptr_equal(bytes.src, DATA); + /* Invalid array */ + bytes = AMbytes(NULL, SIZE_MAX); + assert_int_not_equal(bytes.count, SIZE_MAX); + assert_int_equal(bytes.count, 0); + assert_ptr_equal(bytes.src, NULL); +} + +static void test_AMstr(void** state) { + AMbyteSpan str = AMstr("abcdefghijkl"); + assert_int_equal(str.count, strlen("abcdefghijkl")); + assert_memory_equal(str.src, "abcdefghijkl", str.count); + /* Empty string */ + static char const* const EMPTY = ""; + + str = AMstr(EMPTY); + assert_int_equal(str.count, 0); + assert_ptr_equal(str.src, EMPTY); + /* Invalid string */ + str = AMstr(NULL); + assert_int_equal(str.count, 0); + assert_ptr_equal(str.src, NULL); +} + +static void test_AMstrCmp(void** state) { + /* Length ordering */ + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdefghijkl")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("abcdef")), 1); + /* Lexicographical ordering */ + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("ghijkl"), AMstr("abcdef")), 1); + /* Case ordering */ + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("ABCDEFGHIJKL")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("ABCDEFGHIJKL")), 1); + assert_int_equal(AMstrCmp(AMstr("ABCDEFGHIJKL"), AMstr("abcdef")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("ABCDEFGHIJKL")), 1); + assert_int_equal(AMstrCmp(AMstr("GHIJKL"), AMstr("abcdef")), -1); + assert_int_equal(AMstrCmp(AMstr("abcdef"), AMstr("GHIJKL")), 1); + /* NUL character inclusion */ + static char const SRC[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0', 'g', 'h', 'i', 'j', 'k', 'l'}; + static AMbyteSpan const NUL_STR = {.src = SRC, .count = 13}; + + assert_int_equal(AMstrCmp(AMstr("abcdef"), NUL_STR), -1); + assert_int_equal(AMstrCmp(NUL_STR, NUL_STR), 0); + assert_int_equal(AMstrCmp(NUL_STR, AMstr("abcdef")), 1); + /* Empty string */ + assert_int_equal(AMstrCmp(AMstr(""), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr(""), AMstr("")), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr("")), 1); + /* Invalid string */ + assert_int_equal(AMstrCmp(AMstr(NULL), AMstr("abcdefghijkl")), -1); + assert_int_equal(AMstrCmp(AMstr(NULL), AMstr(NULL)), 0); + assert_int_equal(AMstrCmp(AMstr("abcdefghijkl"), AMstr(NULL)), 1); +} + +static void test_AMstrdup(void** state) { + static char const SRC[] = {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0', 'g', 'h', 'i', '\0', 'j', 'k', 'l'}; + static AMbyteSpan const NUL_STR = {.src = SRC, .count = 15}; + + /* Default substitution ("\\0") for NUL */ + char* dup = AMstrdup(NUL_STR, NULL); + assert_int_equal(strlen(dup), 18); + assert_string_equal(dup, "abc\\0def\\0ghi\\0jkl"); + free(dup); + /* Arbitrary substitution for NUL */ + dup = AMstrdup(NUL_STR, ":-O"); + assert_int_equal(strlen(dup), 21); + assert_string_equal(dup, "abc:-Odef:-Oghi:-Ojkl"); + free(dup); + /* Empty substitution for NUL */ + dup = AMstrdup(NUL_STR, ""); + assert_int_equal(strlen(dup), 12); + assert_string_equal(dup, "abcdefghijkl"); + free(dup); + /* Empty string */ + dup = AMstrdup(AMstr(""), NULL); + assert_int_equal(strlen(dup), 0); + assert_string_equal(dup, ""); + free(dup); + /* Invalid string */ + assert_null(AMstrdup(AMstr(NULL), NULL)); +} + +int run_byte_span_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMbytes), + cmocka_unit_test(test_AMstr), + cmocka_unit_test(test_AMstrCmp), + cmocka_unit_test(test_AMstrdup), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/rust/automerge-c/test/cmocka_utils.c b/rust/automerge-c/test/cmocka_utils.c new file mode 100644 index 00000000..37c57fb1 --- /dev/null +++ b/rust/automerge-c/test/cmocka_utils.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include + +/* third-party */ +#include +#include +#include +#include + +/* local */ +#include "cmocka_utils.h" + +/** + * \brief Assert that the given expression is true and report failure in terms + * of a line number within a file. + * + * \param[in] c An expression. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define assert_true_where(c, file, line) _assert_true(cast_ptr_to_largest_integral_type(c), #c, file, line) + +/** + * \brief Assert that the given pointer is non-NULL and report failure in terms + * of a line number within a file. + * + * \param[in] c An expression. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define assert_non_null_where(c, file, line) assert_true_where(c, file, line) + +/** + * \brief Forces the test to fail immediately and quit, printing the reason in + * terms of a line number within a file. + * + * \param[in] msg A message string into which \p str is interpolated. + * \param[in] str An owned string. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define fail_msg_where(msg, str, file, line) \ + do { \ + print_error("ERROR: " msg "\n", str); \ + _fail(file, line); \ + } while (0) + +/** + * \brief Forces the test to fail immediately and quit, printing the reason in + * terms of a line number within a file. + * + * \param[in] msg A message string into which \p view.src is interpolated. + * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. + * \param[in] file A file's full path string. + * \param[in] line A line number. + */ +#define fail_msg_view_where(msg, view, file, line) \ + do { \ + char* const str = AMstrdup(view, NULL); \ + print_error("ERROR: " msg "\n", str); \ + free(str); \ + _fail(file, line); \ + } while (0) + +bool cmocka_cb(AMstack** stack, void* data) { + assert_non_null(data); + AMstackCallbackData* const sc_data = (AMstackCallbackData*)data; + assert_non_null_where(stack, sc_data->file, sc_data->line); + assert_non_null_where(*stack, sc_data->file, sc_data->line); + assert_non_null_where((*stack)->result, sc_data->file, sc_data->line); + if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { + fail_msg_view_where("%s", AMresultError((*stack)->result), sc_data->file, sc_data->line); + return false; + } + /* Test that the types of all item values are members of the mask. */ + AMitems items = AMresultItems((*stack)->result); + AMitem* item = NULL; + while ((item = AMitemsNext(&items, 1)) != NULL) { + AMvalType const tag = AMitemValType(item); + if (!(tag & sc_data->bitmask)) { + fail_msg_where("Unexpected value type `%s`.", AMvalTypeToString(tag), sc_data->file, sc_data->line); + return false; + } + } + return true; +} diff --git a/rust/automerge-c/test/cmocka_utils.h b/rust/automerge-c/test/cmocka_utils.h index 1b488362..b6611bcc 100644 --- a/rust/automerge-c/test/cmocka_utils.h +++ b/rust/automerge-c/test/cmocka_utils.h @@ -1,22 +1,42 @@ -#ifndef CMOCKA_UTILS_H -#define CMOCKA_UTILS_H +#ifndef TESTS_CMOCKA_UTILS_H +#define TESTS_CMOCKA_UTILS_H +#include #include /* third-party */ +#include #include +/* local */ +#include "base_state.h" + /** * \brief Forces the test to fail immediately and quit, printing the reason. * - * \param[in] view A string view as an `AMbyteSpan` struct. + * \param[in] msg A message string into which \p view.src is interpolated. + * \param[in] view A UTF-8 string view as an `AMbyteSpan` struct. */ -#define fail_msg_view(msg, view) do { \ - char* const c_str = test_calloc(1, view.count + 1); \ - strncpy(c_str, view.src, view.count); \ - print_error(msg, c_str); \ - test_free(c_str); \ - fail(); \ -} while (0) +#define fail_msg_view(msg, view) \ + do { \ + char* const c_str = AMstrdup(view, NULL); \ + print_error("ERROR: " msg "\n", c_str); \ + free(c_str); \ + fail(); \ + } while (0) -#endif /* CMOCKA_UTILS_H */ +/** + * \brief Validates the top result in a stack based upon the parameters + * specified within the given data structure and reports violations + * using cmocka assertions. + * + * \param[in,out] stack A pointer to a pointer to an `AMstack` struct. + * \param[in] data A pointer to an owned `AMpushData` struct. + * \return `true` if the top `AMresult` struct in \p stack is valid, `false` + * otherwise. + * \pre \p stack `!= NULL`. + * \pre \p data `!= NULL`. + */ +bool cmocka_cb(AMstack** stack, void* data); + +#endif /* TESTS_CMOCKA_UTILS_H */ diff --git a/rust/automerge-c/test/doc_state.c b/rust/automerge-c/test/doc_state.c new file mode 100644 index 00000000..3cbece50 --- /dev/null +++ b/rust/automerge-c/test/doc_state.c @@ -0,0 +1,27 @@ +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include "cmocka_utils.h" +#include "doc_state.h" + +int setup_doc(void** state) { + DocState* doc_state = test_calloc(1, sizeof(DocState)); + setup_base((void**)&doc_state->base_state); + AMitemToDoc(AMstackItem(&doc_state->base_state->stack, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), + &doc_state->doc); + *state = doc_state; + return 0; +} + +int teardown_doc(void** state) { + DocState* doc_state = *state; + teardown_base((void**)&doc_state->base_state); + test_free(doc_state); + return 0; +} diff --git a/rust/automerge-c/test/doc_state.h b/rust/automerge-c/test/doc_state.h new file mode 100644 index 00000000..525a49fa --- /dev/null +++ b/rust/automerge-c/test/doc_state.h @@ -0,0 +1,17 @@ +#ifndef TESTS_DOC_STATE_H +#define TESTS_DOC_STATE_H + +/* local */ +#include +#include "base_state.h" + +typedef struct { + BaseState* base_state; + AMdoc* doc; +} DocState; + +int setup_doc(void** state); + +int teardown_doc(void** state); + +#endif /* TESTS_DOC_STATE_H */ diff --git a/rust/automerge-c/test/doc_tests.c b/rust/automerge-c/test/doc_tests.c index 217a4862..c1d21928 100644 --- a/rust/automerge-c/test/doc_tests.c +++ b/rust/automerge-c/test/doc_tests.c @@ -9,12 +9,14 @@ /* local */ #include -#include "group_state.h" -#include "stack_utils.h" +#include +#include "base_state.h" +#include "cmocka_utils.h" +#include "doc_state.h" #include "str_utils.h" typedef struct { - GroupState* group_state; + DocState* doc_state; AMbyteSpan actor_id_str; uint8_t* actor_id_bytes; size_t actor_id_size; @@ -22,7 +24,7 @@ typedef struct { static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); - group_setup((void**)&test_state->group_state); + setup_doc((void**)&test_state->doc_state); test_state->actor_id_str.src = "000102030405060708090a0b0c0d0e0f"; test_state->actor_id_str.count = strlen(test_state->actor_id_str.src); test_state->actor_id_size = test_state->actor_id_str.count / 2; @@ -34,204 +36,195 @@ static int setup(void** state) { static int teardown(void** state) { TestState* test_state = *state; - group_teardown((void**)&test_state->group_state); + teardown_doc((void**)&test_state->doc_state); test_free(test_state->actor_id_bytes); test_free(test_state); return 0; } -static void test_AMkeys_empty() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMstrs forward = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 0); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 0); - assert_null(AMstrsNext(&forward, 1).src); - assert_null(AMstrsPrev(&forward, 1).src); - assert_null(AMstrsNext(&reverse, 1).src); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMkeys_list() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutInt(doc, list, 0, true, 0)); - AMfree(AMlistPutInt(doc, list, 1, true, 0)); - AMfree(AMlistPutInt(doc, list, 2, true, 0)); - AMstrs forward = AMpush(&stack, - AMkeys(doc, list, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 3); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 3); - /* Forward iterator forward. */ - AMbyteSpan str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsNext(&forward, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - assert_null(AMstrsNext(&forward, 1).src); - // /* Forward iterator reverse. */ - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsPrev(&forward, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - assert_null(AMstrsPrev(&forward, 1).src); - /* Reverse iterator forward. */ - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsNext(&reverse, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - assert_null(AMstrsNext(&reverse, 1).src); - /* Reverse iterator reverse. */ - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "2@"), str.src); - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "3@"), str.src); - str = AMstrsPrev(&reverse, 1); - assert_ptr_equal(strstr(str.src, "4@"), str.src); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMkeys_map() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1)); - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2)); - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3)); - AMstrs forward = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&forward), 3); - AMstrs reverse = AMstrsReversed(&forward); - assert_int_equal(AMstrsSize(&reverse), 3); - /* Forward iterator forward. */ - AMbyteSpan str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsNext(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - assert_null(AMstrsNext(&forward, 1).src); - /* Forward iterator reverse. */ - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsPrev(&forward, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - assert_null(AMstrsPrev(&forward, 1).src); - /* Reverse iterator forward. */ - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsNext(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - assert_null(AMstrsNext(&reverse, 1).src); - /* Reverse iterator reverse. */ - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "one", str.count); - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 5); - assert_memory_equal(str.src, "three", str.count); - str = AMstrsPrev(&reverse, 1); - assert_int_equal(str.count, 3); - assert_memory_equal(str.src, "two", str.count); - assert_null(AMstrsPrev(&reverse, 1).src); - AMfreeStack(&stack); -} - -static void test_AMputActor_bytes(void **state) { +static void test_AMkeys_empty(void** state) { TestState* test_state = *state; - AMactorId const* actor_id = AMpush(&test_state->group_state->stack, - AMactorIdInitBytes( - test_state->actor_id_bytes, - test_state->actor_id_size), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(test_state->group_state->doc, actor_id)); - actor_id = AMpush(&test_state->group_state->stack, - AMgetActorId(test_state->group_state->doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_int_equal(AMitemsSize(&forward), 0); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 0); + assert_null(AMitemsNext(&forward, 1)); + assert_null(AMitemsPrev(&forward, 1)); + assert_null(AMitemsNext(&reverse, 1)); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMkeys_list(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutInt(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutInt(doc, list, 1, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutInt(doc, list, 2, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&forward), 3); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 3); + /* Forward iterator forward. */ + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_null(AMitemsNext(&forward, 1)); + // /* Forward iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_null(AMitemsPrev(&forward, 1)); + /* Reverse iterator forward. */ + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_null(AMitemsNext(&reverse, 1)); + /* Reverse iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "2@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "3@"), str.src); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_ptr_equal(strstr(str.src, "4@"), str.src); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMkeys_map(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("one"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("two"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("three"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMitems forward = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&forward), 3); + AMitems reverse = AMitemsReversed(&forward); + assert_int_equal(AMitemsSize(&reverse), 3); + /* Forward iterator forward. */ + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsNext(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_null(AMitemsNext(&forward, 1)); + /* Forward iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsPrev(&forward, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_null(AMitemsPrev(&forward, 1)); + /* Reverse iterator forward. */ + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsNext(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_null(AMitemsNext(&reverse, 1)); + /* Reverse iterator reverse. */ + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "one", str.count); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 5); + assert_memory_equal(str.src, "three", str.count); + assert_true(AMitemToStr(AMitemsPrev(&reverse, 1), &str)); + assert_int_equal(str.count, 3); + assert_memory_equal(str.src, "two", str.count); + assert_null(AMitemsPrev(&reverse, 1)); +} + +static void test_AMputActor_bytes(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes(test_state->actor_id_bytes, test_state->actor_id_size), cmocka_cb, + AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); AMbyteSpan const bytes = AMactorIdBytes(actor_id); assert_int_equal(bytes.count, test_state->actor_id_size); assert_memory_equal(bytes.src, test_state->actor_id_bytes, bytes.count); } -static void test_AMputActor_str(void **state) { +static void test_AMputActor_str(void** state) { TestState* test_state = *state; - AMactorId const* actor_id = AMpush(&test_state->group_state->stack, - AMactorIdInitStr(test_state->actor_id_str), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(test_state->group_state->doc, actor_id)); - actor_id = AMpush(&test_state->group_state->stack, - AMgetActorId(test_state->group_state->doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(test_state->actor_id_str), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->doc_state->doc, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMgetActorId(test_state->doc_state->doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); AMbyteSpan const str = AMactorIdStr(actor_id); assert_int_equal(str.count, test_state->actor_id_str.count); assert_memory_equal(str.src, test_state->actor_id_str.src, str.count); } -static void test_AMspliceText() { - AMresultStack* stack = NULL; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const text = AMpush(&stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, text, 0, 0, AMstr("one + "))); - AMfree(AMspliceText(doc, text, 4, 2, AMstr("two = "))); - AMfree(AMspliceText(doc, text, 8, 2, AMstr("three"))); - AMbyteSpan const str = AMpush(&stack, - AMtext(doc, text, NULL), - AM_VALUE_STR, - cmocka_cb).str; - static char const* const STR_VALUE = "one two three"; - assert_int_equal(str.count, strlen(STR_VALUE)); - assert_memory_equal(str.src, STR_VALUE, str.count); - AMfreeStack(&stack); +static void test_AMspliceText(void** state) { + TestState* test_state = *state; + AMstack** stack_ptr = &test_state->doc_state->base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("one + ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMspliceText(doc, text, 4, 2, AMstr("two = ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMspliceText(doc, text, 8, 2, AMstr("three")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); + assert_int_equal(str.count, strlen("one two three")); + assert_memory_equal(str.src, "one two three", str.count); } int run_doc_tests(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test(test_AMkeys_empty), - cmocka_unit_test(test_AMkeys_list), - cmocka_unit_test(test_AMkeys_map), + cmocka_unit_test_setup_teardown(test_AMkeys_empty, setup, teardown), + cmocka_unit_test_setup_teardown(test_AMkeys_list, setup, teardown), + cmocka_unit_test_setup_teardown(test_AMkeys_map, setup, teardown), cmocka_unit_test_setup_teardown(test_AMputActor_bytes, setup, teardown), cmocka_unit_test_setup_teardown(test_AMputActor_str, setup, teardown), - cmocka_unit_test(test_AMspliceText), + cmocka_unit_test_setup_teardown(test_AMspliceText, setup, teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/rust/automerge-c/test/enum_string_tests.c b/rust/automerge-c/test/enum_string_tests.c new file mode 100644 index 00000000..11131e43 --- /dev/null +++ b/rust/automerge-c/test/enum_string_tests.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include + +#define assert_to_string(function, tag) assert_string_equal(function(tag), #tag) + +#define assert_from_string(function, type, tag) \ + do { \ + type out; \ + assert_true(function(&out, #tag)); \ + assert_int_equal(out, tag); \ + } while (0) + +static void test_AMidxTypeToString(void** state) { + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_DEFAULT); + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_KEY); + assert_to_string(AMidxTypeToString, AM_IDX_TYPE_POS); + /* Zero tag */ + assert_string_equal(AMidxTypeToString(0), "AM_IDX_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMidxTypeToString(-1), "???"); +} + +static void test_AMidxTypeFromString(void** state) { + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_DEFAULT); + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_KEY); + assert_from_string(AMidxTypeFromString, AMidxType, AM_IDX_TYPE_POS); + /* Invalid tag */ + AMidxType out = -1; + assert_false(AMidxTypeFromString(&out, "???")); + assert_int_equal(out, (AMidxType)-1); +} + +static void test_AMobjTypeToString(void** state) { + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_DEFAULT); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_LIST); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_MAP); + assert_to_string(AMobjTypeToString, AM_OBJ_TYPE_TEXT); + /* Zero tag */ + assert_string_equal(AMobjTypeToString(0), "AM_OBJ_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMobjTypeToString(-1), "???"); +} + +static void test_AMobjTypeFromString(void** state) { + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_DEFAULT); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_LIST); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_MAP); + assert_from_string(AMobjTypeFromString, AMobjType, AM_OBJ_TYPE_TEXT); + /* Invalid tag */ + AMobjType out = -1; + assert_false(AMobjTypeFromString(&out, "???")); + assert_int_equal(out, (AMobjType)-1); +} + +static void test_AMstatusToString(void** state) { + assert_to_string(AMstatusToString, AM_STATUS_ERROR); + assert_to_string(AMstatusToString, AM_STATUS_INVALID_RESULT); + assert_to_string(AMstatusToString, AM_STATUS_OK); + /* Zero tag */ + assert_string_equal(AMstatusToString(0), "AM_STATUS_OK"); + /* Invalid tag */ + assert_string_equal(AMstatusToString(-1), "???"); +} + +static void test_AMstatusFromString(void** state) { + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_ERROR); + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_INVALID_RESULT); + assert_from_string(AMstatusFromString, AMstatus, AM_STATUS_OK); + /* Invalid tag */ + AMstatus out = -1; + assert_false(AMstatusFromString(&out, "???")); + assert_int_equal(out, (AMstatus)-1); +} + +static void test_AMvalTypeToString(void** state) { + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_ACTOR_ID); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BOOL); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_BYTES); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_CHANGE_HASH); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_COUNTER); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DEFAULT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_DOC); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_F64); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_INT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_NULL); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_OBJ_TYPE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_STR); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_HAVE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_MESSAGE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_SYNC_STATE); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_TIMESTAMP); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UINT); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_UNKNOWN); + assert_to_string(AMvalTypeToString, AM_VAL_TYPE_VOID); + /* Zero tag */ + assert_string_equal(AMvalTypeToString(0), "AM_VAL_TYPE_DEFAULT"); + /* Invalid tag */ + assert_string_equal(AMvalTypeToString(-1), "???"); +} + +static void test_AMvalTypeFromString(void** state) { + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_ACTOR_ID); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BOOL); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_BYTES); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_CHANGE_HASH); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_COUNTER); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DEFAULT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_DOC); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_F64); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_INT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_NULL); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_OBJ_TYPE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_STR); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_HAVE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_MESSAGE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_SYNC_STATE); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_TIMESTAMP); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UINT); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_UNKNOWN); + assert_from_string(AMvalTypeFromString, AMvalType, AM_VAL_TYPE_VOID); + /* Invalid tag */ + AMvalType out = -1; + assert_false(AMvalTypeFromString(&out, "???")); + assert_int_equal(out, (AMvalType)-1); +} + +int run_enum_string_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMidxTypeToString), cmocka_unit_test(test_AMidxTypeFromString), + cmocka_unit_test(test_AMobjTypeToString), cmocka_unit_test(test_AMobjTypeFromString), + cmocka_unit_test(test_AMstatusToString), cmocka_unit_test(test_AMstatusFromString), + cmocka_unit_test(test_AMvalTypeToString), cmocka_unit_test(test_AMvalTypeFromString), + }; + + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/rust/automerge-c/test/group_state.c b/rust/automerge-c/test/group_state.c deleted file mode 100644 index 0ee14317..00000000 --- a/rust/automerge-c/test/group_state.c +++ /dev/null @@ -1,27 +0,0 @@ -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include "group_state.h" -#include "stack_utils.h" - -int group_setup(void** state) { - GroupState* group_state = test_calloc(1, sizeof(GroupState)); - group_state->doc = AMpush(&group_state->stack, - AMcreate(NULL), - AM_VALUE_DOC, - cmocka_cb).doc; - *state = group_state; - return 0; -} - -int group_teardown(void** state) { - GroupState* group_state = *state; - AMfreeStack(&group_state->stack); - test_free(group_state); - return 0; -} diff --git a/rust/automerge-c/test/group_state.h b/rust/automerge-c/test/group_state.h deleted file mode 100644 index a71d9dc9..00000000 --- a/rust/automerge-c/test/group_state.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef GROUP_STATE_H -#define GROUP_STATE_H - -/* local */ -#include - -typedef struct { - AMresultStack* stack; - AMdoc* doc; -} GroupState; - -int group_setup(void** state); - -int group_teardown(void** state); - -#endif /* GROUP_STATE_H */ diff --git a/rust/automerge-c/test/item_tests.c b/rust/automerge-c/test/item_tests.c new file mode 100644 index 00000000..a30b0556 --- /dev/null +++ b/rust/automerge-c/test/item_tests.c @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include + +/* third-party */ +#include + +/* local */ +#include +#include +#include "cmocka_utils.h" +#include "doc_state.h" + +static void test_AMitemResult(void** state) { + enum { ITEM_COUNT = 1000 }; + + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + /* Append the strings to a list so that they'll be in numerical order. */ + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + for (size_t pos = 0; pos != ITEM_COUNT; ++pos) { + size_t const count = snprintf(NULL, 0, "%zu", pos); + char* const src = test_calloc(count + 1, sizeof(char)); + assert_int_equal(sprintf(src, "%zu", pos), count); + AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, pos, true, AMbytes(src, count)), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + test_free(src); + } + /* Get an item iterator. */ + AMitems items = AMstackItems(stack_ptr, AMlistRange(doc_state->doc, list, 0, SIZE_MAX, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + /* Get the item iterator's result so that it can be freed later. */ + AMresult const* const items_result = (*stack_ptr)->result; + /* Iterate over all of the items and copy their pointers into an array. */ + AMitem* item_ptrs[ITEM_COUNT] = {NULL}; + AMitem* item = NULL; + for (size_t pos = 0; (item = AMitemsNext(&items, 1)) != NULL; ++pos) { + /* The item's reference count should be 1. */ + assert_int_equal(AMitemRefCount(item), 1); + if (pos & 1) { + /* Create a redundant result for an odd item. */ + AMitem* const new_item = AMstackItem(stack_ptr, AMitemResult(item), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + /* The item's old and new pointers will never match. */ + assert_ptr_not_equal(new_item, item); + /* The item's reference count will have been incremented. */ + assert_int_equal(AMitemRefCount(item), 2); + assert_int_equal(AMitemRefCount(new_item), 2); + /* The item's old and new indices should match. */ + assert_int_equal(AMitemIdxType(item), AMitemIdxType(new_item)); + assert_int_equal(AMitemIdxType(item), AM_IDX_TYPE_POS); + size_t pos, new_pos; + assert_true(AMitemPos(item, &pos)); + assert_true(AMitemPos(new_item, &new_pos)); + assert_int_equal(pos, new_pos); + /* The item's old and new object IDs should match. */ + AMobjId const* const obj_id = AMitemObjId(item); + AMobjId const* const new_obj_id = AMitemObjId(new_item); + assert_true(AMobjIdEqual(obj_id, new_obj_id)); + /* The item's old and new value types should match. */ + assert_int_equal(AMitemValType(item), AMitemValType(new_item)); + /* The item's old and new string values should match. */ + AMbyteSpan str; + assert_true(AMitemToStr(item, &str)); + AMbyteSpan new_str; + assert_true(AMitemToStr(new_item, &new_str)); + assert_int_equal(str.count, new_str.count); + assert_memory_equal(str.src, new_str.src, new_str.count); + /* The item's old and new object IDs are one and the same. */ + assert_ptr_equal(obj_id, new_obj_id); + /* The item's old and new string values are one and the same. */ + assert_ptr_equal(str.src, new_str.src); + /* Save the item's new pointer. */ + item_ptrs[pos] = new_item; + } + } + /* Free the item iterator's result. */ + AMresultFree(AMstackPop(stack_ptr, items_result)); + /* An odd item's reference count should be 1 again. */ + for (size_t pos = 1; pos < ITEM_COUNT; pos += 2) { + assert_int_equal(AMitemRefCount(item_ptrs[pos]), 1); + } +} + +int run_item_tests(void) { + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_AMitemResult), + }; + + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); +} diff --git a/rust/automerge-c/test/list_tests.c b/rust/automerge-c/test/list_tests.c index f9bbb340..723dd038 100644 --- a/rust/automerge-c/test/list_tests.c +++ b/rust/automerge-c/test/list_tests.c @@ -11,367 +11,417 @@ /* local */ #include +#include +#include "base_state.h" #include "cmocka_utils.h" -#include "group_state.h" +#include "doc_state.h" #include "macro_utils.h" -#include "stack_utils.h" static void test_AMlistIncrement(void** state) { - GroupState* group_state = *state; - AMobjId const* const list = AMpush( - &group_state->stack, - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutCounter(group_state->doc, list, 0, true, 0)); - assert_int_equal(AMpush(&group_state->stack, - AMlistGet(group_state->doc, list, 0, NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 0); - AMfree(AMpop(&group_state->stack)); - AMfree(AMlistIncrement(group_state->doc, list, 0, 3)); - assert_int_equal(AMpush(&group_state->stack, - AMlistGet(group_state->doc, list, 0, NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 3); - AMfree(AMpop(&group_state->stack)); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutCounter(doc_state->doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + int64_t counter; + assert_true(AMitemToCounter( + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 0); + AMresultFree(AMstackPop(stack_ptr, NULL)); + AMstackItem(NULL, AMlistIncrement(doc_state->doc, list, 0, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToCounter( + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 3); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -#define test_AMlistPut(suffix, mode) test_AMlistPut ## suffix ## _ ## mode +#define test_AMlistPut(suffix, mode) test_AMlistPut##suffix##_##mode -#define static_void_test_AMlistPut(suffix, mode, member, scalar_value) \ -static void test_AMlistPut ## suffix ## _ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPut ## suffix(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - scalar_value)); \ - assert_true(AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AMvalue_discriminant(#suffix), \ - cmocka_cb).member == scalar_value); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPut(suffix, mode, type, scalar_value) \ + static void test_AMlistPut##suffix##_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPut##suffix(doc_state->doc, list, 0, !strcmp(#mode, "insert"), scalar_value), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + type value; \ + assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, \ + AMexpect(suffix_to_val_type(#suffix))), \ + &value)); \ + assert_true(value == scalar_value); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutBytes(mode) test_AMlistPutBytes ## _ ## mode +#define test_AMlistPutBytes(mode) test_AMlistPutBytes##_##mode -#define static_void_test_AMlistPutBytes(mode, bytes_value) \ -static void test_AMlistPutBytes_ ## mode(void **state) { \ - static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ - \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutBytes(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - AMbytes(bytes_value, BYTES_SIZE))); \ - AMbyteSpan const bytes = AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AM_VALUE_BYTES, \ - cmocka_cb).bytes; \ - assert_int_equal(bytes.count, BYTES_SIZE); \ - assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutBytes(mode, bytes_value) \ + static void test_AMlistPutBytes_##mode(void** state) { \ + static size_t const BYTES_SIZE = sizeof(bytes_value) / sizeof(uint8_t); \ + \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem( \ + NULL, AMlistPutBytes(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMbytes(bytes_value, BYTES_SIZE)), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + AMbyteSpan bytes; \ + assert_true(AMitemToBytes( \ + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), \ + &bytes)); \ + assert_int_equal(bytes.count, BYTES_SIZE); \ + assert_memory_equal(bytes.src, bytes_value, BYTES_SIZE); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutNull(mode) test_AMlistPutNull_ ## mode +#define test_AMlistPutNull(mode) test_AMlistPutNull_##mode -#define static_void_test_AMlistPutNull(mode) \ -static void test_AMlistPutNull_ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutNull(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"))); \ - AMresult* const result = AMlistGet(group_state->doc, list, 0, NULL); \ - if (AMresultStatus(result) != AM_STATUS_OK) { \ - fail_msg_view("%s", AMerrorMessage(result)); \ - } \ - assert_int_equal(AMresultSize(result), 1); \ - assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); \ - AMfree(result); \ -} +#define static_void_test_AMlistPutNull(mode) \ + static void test_AMlistPutNull_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPutNull(doc_state->doc, list, 0, !strcmp(#mode, "insert")), cmocka_cb, \ + AMexpect(AM_VAL_TYPE_VOID)); \ + AMresult* result = AMstackResult(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), NULL, NULL); \ + if (AMresultStatus(result) != AM_STATUS_OK) { \ + fail_msg_view("%s", AMresultError(result)); \ + } \ + assert_int_equal(AMresultSize(result), 1); \ + assert_int_equal(AMitemValType(AMresultItem(result)), AM_VAL_TYPE_NULL); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutObject(label, mode) test_AMlistPutObject_ ## label ## _ ## mode +#define test_AMlistPutObject(label, mode) test_AMlistPutObject_##label##_##mode -#define static_void_test_AMlistPutObject(label, mode) \ -static void test_AMlistPutObject_ ## label ## _ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMobjType const obj_type = AMobjType_tag(#label); \ - if (obj_type != AM_OBJ_TYPE_VOID) { \ - AMobjId const* const obj_id = AMpush( \ - &group_state->stack, \ - AMlistPutObject(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - obj_type), \ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ - } \ - else { \ - AMpush(&group_state->stack, \ - AMlistPutObject(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - obj_type), \ - AM_VALUE_VOID, \ - NULL); \ - assert_int_not_equal(AMresultStatus(group_state->stack->result), \ - AM_STATUS_OK); \ - } \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutObject(label, mode) \ + static void test_AMlistPutObject_##label##_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMobjType const obj_type = suffix_to_obj_type(#label); \ + AMobjId const* const obj_id = AMitemObjId( \ + AMstackItem(stack_ptr, AMlistPutObject(doc_state->doc, list, 0, !strcmp(#mode, "insert"), obj_type), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -#define test_AMlistPutStr(mode) test_AMlistPutStr ## _ ## mode +#define test_AMlistPutStr(mode) test_AMlistPutStr##_##mode -#define static_void_test_AMlistPutStr(mode, str_value) \ -static void test_AMlistPutStr_ ## mode(void **state) { \ - GroupState* group_state = *state; \ - AMobjId const* const list = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST),\ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - AMfree(AMlistPutStr(group_state->doc, \ - list, \ - 0, \ - !strcmp(#mode, "insert"), \ - AMstr(str_value))); \ - AMbyteSpan const str = AMpush( \ - &group_state->stack, \ - AMlistGet(group_state->doc, list, 0, NULL), \ - AM_VALUE_STR, \ - cmocka_cb).str; \ - assert_int_equal(str.count, strlen(str_value)); \ - assert_memory_equal(str.src, str_value, str.count); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMlistPutStr(mode, str_value) \ + static void test_AMlistPutStr_##mode(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjId const* const list = AMitemObjId( \ + AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + AMstackItem(NULL, AMlistPutStr(doc_state->doc, list, 0, !strcmp(#mode, "insert"), AMstr(str_value)), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); \ + AMbyteSpan str; \ + assert_true(AMitemToStr( \ + AMstackItem(stack_ptr, AMlistGet(doc_state->doc, list, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), \ + &str)); \ + assert_int_equal(str.count, strlen(str_value)); \ + assert_memory_equal(str.src, str_value, str.count); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static_void_test_AMlistPut(Bool, insert, boolean, true) +static_void_test_AMlistPut(Bool, insert, bool, true); -static_void_test_AMlistPut(Bool, update, boolean, true) +static_void_test_AMlistPut(Bool, update, bool, true); static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; -static_void_test_AMlistPutBytes(insert, BYTES_VALUE) +static_void_test_AMlistPutBytes(insert, BYTES_VALUE); -static_void_test_AMlistPutBytes(update, BYTES_VALUE) +static_void_test_AMlistPutBytes(update, BYTES_VALUE); -static_void_test_AMlistPut(Counter, insert, counter, INT64_MAX) +static_void_test_AMlistPut(Counter, insert, int64_t, INT64_MAX); -static_void_test_AMlistPut(Counter, update, counter, INT64_MAX) +static_void_test_AMlistPut(Counter, update, int64_t, INT64_MAX); -static_void_test_AMlistPut(F64, insert, f64, DBL_MAX) +static_void_test_AMlistPut(F64, insert, double, DBL_MAX); -static_void_test_AMlistPut(F64, update, f64, DBL_MAX) +static_void_test_AMlistPut(F64, update, double, DBL_MAX); -static_void_test_AMlistPut(Int, insert, int_, INT64_MAX) +static_void_test_AMlistPut(Int, insert, int64_t, INT64_MAX); -static_void_test_AMlistPut(Int, update, int_, INT64_MAX) +static_void_test_AMlistPut(Int, update, int64_t, INT64_MAX); -static_void_test_AMlistPutNull(insert) +static_void_test_AMlistPutNull(insert); -static_void_test_AMlistPutNull(update) +static_void_test_AMlistPutNull(update); -static_void_test_AMlistPutObject(List, insert) +static_void_test_AMlistPutObject(List, insert); -static_void_test_AMlistPutObject(List, update) +static_void_test_AMlistPutObject(List, update); -static_void_test_AMlistPutObject(Map, insert) +static_void_test_AMlistPutObject(Map, insert); -static_void_test_AMlistPutObject(Map, update) +static_void_test_AMlistPutObject(Map, update); -static_void_test_AMlistPutObject(Text, insert) +static_void_test_AMlistPutObject(Text, insert); -static_void_test_AMlistPutObject(Text, update) +static_void_test_AMlistPutObject(Text, update); -static_void_test_AMlistPutObject(Void, insert) +static_void_test_AMlistPutStr(insert, + "Hello, " + "world!"); -static_void_test_AMlistPutObject(Void, update) +static_void_test_AMlistPutStr(update, + "Hello," + " world" + "!"); -static_void_test_AMlistPutStr(insert, "Hello, world!") +static_void_test_AMlistPut(Timestamp, insert, int64_t, INT64_MAX); -static_void_test_AMlistPutStr(update, "Hello, world!") +static_void_test_AMlistPut(Timestamp, update, int64_t, INT64_MAX); -static_void_test_AMlistPut(Timestamp, insert, timestamp, INT64_MAX) +static_void_test_AMlistPut(Uint, insert, uint64_t, UINT64_MAX); -static_void_test_AMlistPut(Timestamp, update, timestamp, INT64_MAX) +static_void_test_AMlistPut(Uint, update, uint64_t, UINT64_MAX); -static_void_test_AMlistPut(Uint, insert, uint, UINT64_MAX) - -static_void_test_AMlistPut(Uint, update, uint, UINT64_MAX) - -static void test_get_list_values(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; +static void test_get_range_values(void** state) { + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* Insert elements. */ - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("First"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Second"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Third"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fourth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Fifth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Sixth"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Seventh"))); - AMfree(AMlistPutStr(doc1, list, 0, true, AMstr("Eighth"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("First")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Second")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Third")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fourth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Fifth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Sixth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Seventh")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 0, true, AMstr("Eighth")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchangeHashes const v1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMdoc* const doc2 = AMpush(&stack, - AMfork(doc1, NULL), - AM_VALUE_DOC, - cmocka_cb).doc; + AMitems const v1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(doc1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMfree(AMlistPutStr(doc1, list, 2, false, AMstr("Third V2"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc1, list, 2, false, AMstr("Third V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMlistPutStr(doc2, list, 2, false, AMstr("Third V3"))); - AMfree(AMcommit(doc2, AMstr(NULL), NULL)); + AMstackItem(NULL, AMlistPutStr(doc2, list, 2, false, AMstr("Third V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMlistItems range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 8); + /* Forward vs. reverse: complete current list range. */ + AMitems range = + AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size_t size = AMitemsSize(&range); + assert_int_equal(size, 8); + AMitems range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + size_t pos; + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 0); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 7); - AMlistItem const* list_item = NULL; - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + AMitem *item1, *item_back1; + size_t count, middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 3, 6, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItems range_back = AMlistItemsReversed(&range); - assert_int_equal(AMlistItemsSize(&range), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); + /* Forward vs. reverse: partial current list range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 1, 6, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 5); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 1); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 5); - range = AMlistItemsRewound(&range); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, NULL), NULL, NULL); + /** \note An item returned from an `AMlistGet()` call doesn't include + the index used to retrieve it. */ + assert_int_equal(AMitemIdxType(item2), 0); + assert_int_equal(AMitemIdxType(item_back2), 0); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 8); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + /* Forward vs. reverse: complete historical map range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 8); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 0); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 7); + + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 3, 6, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - range_back = AMlistItemsReversed(&range); - assert_int_equal(AMlistItemsSize(&range), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range, 1)), 3); - assert_int_equal(AMlistItemIndex(AMlistItemsNext(&range_back, 1)), 5); + /* Forward vs. reverse: partial historical map range. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 2, 7, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 5); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemPos(AMitemsNext(&range, 1), &pos)); + assert_int_equal(pos, 2); + assert_true(AMitemPos(AMitemsNext(&range_back, 1), &pos)); + assert_int_equal(pos, 6); - range = AMlistItemsRewound(&range); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMresult* result = AMlistGet(doc1, list, AMlistItemIndex(list_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMlistItemObjId(list_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + size_t pos1, pos_back1; + assert_true(AMitemPos(item1, &pos1)); + assert_true(AMitemPos(item_back1, &pos_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(pos1, pos_back1); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(pos1, pos_back1); + } + AMitem* item2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMlistGet(doc1, list, pos_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMobjItems values = AMpush(&stack, - AMobjValues(doc1, list, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); - AMobjItem const* value = NULL; - while ((list_item = AMlistItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); + /* List range vs. object range: complete current. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + AMitem *item, *obj_item; + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } - range = AMpush(&stack, - AMlistRange(doc1, list, 0, SIZE_MAX, &v1), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - values = AMpush(&stack, - AMobjValues(doc1, list, &v1), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMlistItemsSize(&range), AMobjItemsSize(&values)); - while ((list_item = AMlistItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMlistItemValue(list_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMlistItemObjId(list_item), AMobjItemObjId(value))); + /* List range vs. object range: complete historical. */ + range = AMstackItems(stack_ptr, AMlistRange(doc1, list, 0, SIZE_MAX, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, list, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } } -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * list object's string value which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into a + * list object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -381,60 +431,52 @@ static void test_get_NUL_string_value(void** state) { doc[0] = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, - 255, 181, 76, 79, 129, 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, - 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, 5, 241, 136, 205, - 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, - 6, 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, - 1, 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, - 127, 0, 127, 7, 127, 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, - 0, 112, 115, 127, 0, 0}; + 133, 111, 74, 131, 224, 28, 197, 17, 0, 113, 1, 16, 246, 137, 63, 193, 255, 181, 76, 79, 129, + 213, 133, 29, 214, 158, 164, 15, 1, 207, 184, 14, 57, 1, 194, 79, 247, 82, 160, 134, 227, 144, + 5, 241, 136, 205, 238, 250, 251, 54, 34, 250, 210, 96, 204, 132, 153, 203, 110, 109, 6, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 3, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 1, 48, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMlistGet(doc, AM_ROOT, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, AM_ROOT, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_insert_at_index(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* Insert both at the same index. */ - AMfree(AMlistPutUint(doc, list, 0, true, 0)); - AMfree(AMlistPutUint(doc, list, 0, true, 1)); + AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutUint(doc, list, 0, true, 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); assert_int_equal(AMobjSize(doc, list, NULL), 2); - AMstrs const keys = AMpush(&stack, - AMkeys(doc, list, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 2); - AMlistItems const range = AMpush(&stack, - AMlistRange(doc, list, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemsSize(&range), 2); + AMitems const keys = AMstackItems(stack_ptr, AMkeys(doc, list, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 2); + AMitems const range = + AMstackItems(stack_ptr, AMlistRange(doc, list, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); + assert_int_equal(AMitemsSize(&range), 2); } int run_list_tests(void) { @@ -458,18 +500,16 @@ int run_list_tests(void) { cmocka_unit_test(test_AMlistPutObject(Map, update)), cmocka_unit_test(test_AMlistPutObject(Text, insert)), cmocka_unit_test(test_AMlistPutObject(Text, update)), - cmocka_unit_test(test_AMlistPutObject(Void, insert)), - cmocka_unit_test(test_AMlistPutObject(Void, update)), cmocka_unit_test(test_AMlistPutStr(insert)), cmocka_unit_test(test_AMlistPutStr(update)), cmocka_unit_test(test_AMlistPut(Timestamp, insert)), cmocka_unit_test(test_AMlistPut(Timestamp, update)), cmocka_unit_test(test_AMlistPut(Uint, insert)), cmocka_unit_test(test_AMlistPut(Uint, update)), - cmocka_unit_test_setup_teardown(test_get_list_values, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_insert_at_index, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_insert_at_index, setup_base, teardown_base), }; - return cmocka_run_group_tests(tests, group_setup, group_teardown); + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); } diff --git a/rust/automerge-c/test/macro_utils.c b/rust/automerge-c/test/macro_utils.c index 6d7578b6..3a546eb5 100644 --- a/rust/automerge-c/test/macro_utils.c +++ b/rust/automerge-c/test/macro_utils.c @@ -3,23 +3,36 @@ /* local */ #include "macro_utils.h" -AMvalueVariant AMvalue_discriminant(char const* suffix) { - if (!strcmp(suffix, "Bool")) return AM_VALUE_BOOLEAN; - else if (!strcmp(suffix, "Bytes")) return AM_VALUE_BYTES; - else if (!strcmp(suffix, "Counter")) return AM_VALUE_COUNTER; - else if (!strcmp(suffix, "F64")) return AM_VALUE_F64; - else if (!strcmp(suffix, "Int")) return AM_VALUE_INT; - else if (!strcmp(suffix, "Null")) return AM_VALUE_NULL; - else if (!strcmp(suffix, "Str")) return AM_VALUE_STR; - else if (!strcmp(suffix, "Timestamp")) return AM_VALUE_TIMESTAMP; - else if (!strcmp(suffix, "Uint")) return AM_VALUE_UINT; - else return AM_VALUE_VOID; +AMobjType suffix_to_obj_type(char const* obj_type_label) { + if (!strcmp(obj_type_label, "List")) + return AM_OBJ_TYPE_LIST; + else if (!strcmp(obj_type_label, "Map")) + return AM_OBJ_TYPE_MAP; + else if (!strcmp(obj_type_label, "Text")) + return AM_OBJ_TYPE_TEXT; + else + return AM_OBJ_TYPE_DEFAULT; } -AMobjType AMobjType_tag(char const* obj_type_label) { - if (!strcmp(obj_type_label, "List")) return AM_OBJ_TYPE_LIST; - else if (!strcmp(obj_type_label, "Map")) return AM_OBJ_TYPE_MAP; - else if (!strcmp(obj_type_label, "Text")) return AM_OBJ_TYPE_TEXT; - else if (!strcmp(obj_type_label, "Void")) return AM_OBJ_TYPE_VOID; - else return 0; +AMvalType suffix_to_val_type(char const* suffix) { + if (!strcmp(suffix, "Bool")) + return AM_VAL_TYPE_BOOL; + else if (!strcmp(suffix, "Bytes")) + return AM_VAL_TYPE_BYTES; + else if (!strcmp(suffix, "Counter")) + return AM_VAL_TYPE_COUNTER; + else if (!strcmp(suffix, "F64")) + return AM_VAL_TYPE_F64; + else if (!strcmp(suffix, "Int")) + return AM_VAL_TYPE_INT; + else if (!strcmp(suffix, "Null")) + return AM_VAL_TYPE_NULL; + else if (!strcmp(suffix, "Str")) + return AM_VAL_TYPE_STR; + else if (!strcmp(suffix, "Timestamp")) + return AM_VAL_TYPE_TIMESTAMP; + else if (!strcmp(suffix, "Uint")) + return AM_VAL_TYPE_UINT; + else + return AM_VAL_TYPE_DEFAULT; } diff --git a/rust/automerge-c/test/macro_utils.h b/rust/automerge-c/test/macro_utils.h index 62e262ce..e4c2c5b9 100644 --- a/rust/automerge-c/test/macro_utils.h +++ b/rust/automerge-c/test/macro_utils.h @@ -1,24 +1,23 @@ -#ifndef MACRO_UTILS_H -#define MACRO_UTILS_H +#ifndef TESTS_MACRO_UTILS_H +#define TESTS_MACRO_UTILS_H /* local */ #include /** - * \brief Gets the result value discriminant corresponding to a function name - * suffix. + * \brief Gets the object type tag corresponding to an object type suffix. * - * \param[in] suffix A string. - * \return An `AMvalue` struct discriminant. - */ -AMvalueVariant AMvalue_discriminant(char const* suffix); - -/** - * \brief Gets the object type tag corresponding to an object type label. - * - * \param[in] obj_type_label A string. + * \param[in] suffix An object type suffix string. * \return An `AMobjType` enum tag. */ -AMobjType AMobjType_tag(char const* obj_type_label); +AMobjType suffix_to_obj_type(char const* suffix); -#endif /* MACRO_UTILS_H */ +/** + * \brief Gets the value type tag corresponding to a value type suffix. + * + * \param[in] suffix A value type suffix string. + * \return An `AMvalType` enum tag. + */ +AMvalType suffix_to_val_type(char const* suffix); + +#endif /* TESTS_MACRO_UTILS_H */ diff --git a/rust/automerge-c/test/main.c b/rust/automerge-c/test/main.c index 09b71bd5..2996c9b3 100644 --- a/rust/automerge-c/test/main.c +++ b/rust/automerge-c/test/main.c @@ -1,6 +1,6 @@ +#include #include #include -#include #include /* third-party */ @@ -8,8 +8,14 @@ extern int run_actor_id_tests(void); +extern int run_byte_span_tests(void); + extern int run_doc_tests(void); +extern int run_enum_string_tests(void); + +extern int run_item_tests(void); + extern int run_list_tests(void); extern int run_map_tests(void); @@ -17,11 +23,6 @@ extern int run_map_tests(void); extern int run_ported_wasm_suite(void); int main(void) { - return ( - run_actor_id_tests() + - run_doc_tests() + - run_list_tests() + - run_map_tests() + - run_ported_wasm_suite() - ); + return (run_actor_id_tests() + run_byte_span_tests() + run_doc_tests() + run_enum_string_tests() + + run_item_tests() + run_list_tests() + run_map_tests() + run_ported_wasm_suite()); } diff --git a/rust/automerge-c/test/map_tests.c b/rust/automerge-c/test/map_tests.c index 194da2e8..2ee2e69a 100644 --- a/rust/automerge-c/test/map_tests.c +++ b/rust/automerge-c/test/map_tests.c @@ -11,144 +11,133 @@ /* local */ #include +#include +#include +#include "base_state.h" #include "cmocka_utils.h" -#include "group_state.h" +#include "doc_state.h" #include "macro_utils.h" -#include "stack_utils.h" static void test_AMmapIncrement(void** state) { - GroupState* group_state = *state; - AMfree(AMmapPutCounter(group_state->doc, AM_ROOT, AMstr("Counter"), 0)); - assert_int_equal(AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 0); - AMfree(AMpop(&group_state->stack)); - AMfree(AMmapIncrement(group_state->doc, AM_ROOT, AMstr("Counter"), 3)); - assert_int_equal(AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 3); - AMfree(AMpop(&group_state->stack)); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutCounter(doc_state->doc, AM_ROOT, AMstr("Counter"), 0), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + int64_t counter; + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 0); + AMresultFree(AMstackPop(stack_ptr, NULL)); + AMstackItem(NULL, AMmapIncrement(doc_state->doc, AM_ROOT, AMstr("Counter"), 3), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Counter"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 3); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -#define test_AMmapPut(suffix) test_AMmapPut ## suffix +#define test_AMmapPut(suffix) test_AMmapPut##suffix -#define static_void_test_AMmapPut(suffix, member, scalar_value) \ -static void test_AMmapPut ## suffix(void **state) { \ - GroupState* group_state = *state; \ - AMfree(AMmapPut ## suffix(group_state->doc, \ - AM_ROOT, \ - AMstr(#suffix), \ - scalar_value)); \ - assert_true(AMpush( \ - &group_state->stack, \ - AMmapGet(group_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ - AMvalue_discriminant(#suffix), \ - cmocka_cb).member == scalar_value); \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMmapPut(suffix, type, scalar_value) \ + static void test_AMmapPut##suffix(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMstackItem(NULL, AMmapPut##suffix(doc_state->doc, AM_ROOT, AMstr(#suffix), scalar_value), cmocka_cb, \ + AMexpect(AM_VAL_TYPE_VOID)); \ + type value; \ + assert_true(AMitemTo##suffix(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr(#suffix), NULL), \ + cmocka_cb, AMexpect(suffix_to_val_type(#suffix))), \ + &value)); \ + assert_true(value == scalar_value); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static void test_AMmapPutBytes(void **state) { +static void test_AMmapPutBytes(void** state) { static AMbyteSpan const KEY = {"Bytes", 5}; static uint8_t const BYTES_VALUE[] = {INT8_MIN, INT8_MAX / 2, INT8_MAX}; static size_t const BYTES_SIZE = sizeof(BYTES_VALUE) / sizeof(uint8_t); - GroupState* group_state = *state; - AMfree(AMmapPutBytes(group_state->doc, - AM_ROOT, - KEY, - AMbytes(BYTES_VALUE, BYTES_SIZE))); - AMbyteSpan const bytes = AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, KEY, NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutBytes(doc_state->doc, AM_ROOT, KEY, AMbytes(BYTES_VALUE, BYTES_SIZE)), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan bytes; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &bytes)); assert_int_equal(bytes.count, BYTES_SIZE); assert_memory_equal(bytes.src, BYTES_VALUE, BYTES_SIZE); - AMfree(AMpop(&group_state->stack)); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -static void test_AMmapPutNull(void **state) { +static void test_AMmapPutNull(void** state) { static AMbyteSpan const KEY = {"Null", 4}; - GroupState* group_state = *state; - AMfree(AMmapPutNull(group_state->doc, AM_ROOT, KEY)); - AMresult* const result = AMmapGet(group_state->doc, AM_ROOT, KEY, NULL); + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutNull(doc_state->doc, AM_ROOT, KEY), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMresult* result = AMstackResult(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, KEY, NULL), NULL, NULL); if (AMresultStatus(result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage(result)); + fail_msg_view("%s", AMresultError(result)); } assert_int_equal(AMresultSize(result), 1); - assert_int_equal(AMresultValue(result).tag, AM_VALUE_NULL); - AMfree(result); + AMitem* item = AMresultItem(result); + assert_int_equal(AMitemValType(item), AM_VAL_TYPE_NULL); } -#define test_AMmapPutObject(label) test_AMmapPutObject_ ## label +#define test_AMmapPutObject(label) test_AMmapPutObject_##label -#define static_void_test_AMmapPutObject(label) \ -static void test_AMmapPutObject_ ## label(void **state) { \ - GroupState* group_state = *state; \ - AMobjType const obj_type = AMobjType_tag(#label); \ - if (obj_type != AM_OBJ_TYPE_VOID) { \ - AMobjId const* const obj_id = AMpush( \ - &group_state->stack, \ - AMmapPutObject(group_state->doc, \ - AM_ROOT, \ - AMstr(#label), \ - obj_type), \ - AM_VALUE_OBJ_ID, \ - cmocka_cb).obj_id; \ - assert_non_null(obj_id); \ - assert_int_equal(AMobjObjType(group_state->doc, obj_id), obj_type); \ - assert_int_equal(AMobjSize(group_state->doc, obj_id, NULL), 0); \ - } \ - else { \ - AMpush(&group_state->stack, \ - AMmapPutObject(group_state->doc, \ - AM_ROOT, \ - AMstr(#label), \ - obj_type), \ - AM_VALUE_VOID, \ - NULL); \ - assert_int_not_equal(AMresultStatus(group_state->stack->result), \ - AM_STATUS_OK); \ - } \ - AMfree(AMpop(&group_state->stack)); \ -} +#define static_void_test_AMmapPutObject(label) \ + static void test_AMmapPutObject_##label(void** state) { \ + DocState* doc_state = *state; \ + AMstack** stack_ptr = &doc_state->base_state->stack; \ + AMobjType const obj_type = suffix_to_obj_type(#label); \ + AMobjId const* const obj_id = \ + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc_state->doc, AM_ROOT, AMstr(#label), obj_type), \ + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); \ + assert_non_null(obj_id); \ + assert_int_equal(AMobjObjType(doc_state->doc, obj_id), obj_type); \ + assert_int_equal(AMobjSize(doc_state->doc, obj_id, NULL), 0); \ + AMresultFree(AMstackPop(stack_ptr, NULL)); \ + } -static void test_AMmapPutStr(void **state) { - GroupState* group_state = *state; - AMfree(AMmapPutStr(group_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!"))); - AMbyteSpan const str = AMpush(&group_state->stack, - AMmapGet(group_state->doc, AM_ROOT, AMstr("Str"), NULL), - AM_VALUE_STR, - cmocka_cb).str; +static void test_AMmapPutStr(void** state) { + DocState* doc_state = *state; + AMstack** stack_ptr = &doc_state->base_state->stack; + AMstackItem(NULL, AMmapPutStr(doc_state->doc, AM_ROOT, AMstr("Str"), AMstr("Hello, world!")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMbyteSpan str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMmapGet(doc_state->doc, AM_ROOT, AMstr("Str"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, strlen("Hello, world!")); assert_memory_equal(str.src, "Hello, world!", str.count); - AMfree(AMpop(&group_state->stack)); + AMresultFree(AMstackPop(stack_ptr, NULL)); } -static_void_test_AMmapPut(Bool, boolean, true) +static_void_test_AMmapPut(Bool, bool, true); -static_void_test_AMmapPut(Counter, counter, INT64_MAX) +static_void_test_AMmapPut(Counter, int64_t, INT64_MAX); -static_void_test_AMmapPut(F64, f64, DBL_MAX) +static_void_test_AMmapPut(F64, double, DBL_MAX); -static_void_test_AMmapPut(Int, int_, INT64_MAX) +static_void_test_AMmapPut(Int, int64_t, INT64_MAX); -static_void_test_AMmapPutObject(List) +static_void_test_AMmapPutObject(List); -static_void_test_AMmapPutObject(Map) +static_void_test_AMmapPutObject(Map); -static_void_test_AMmapPutObject(Text) +static_void_test_AMmapPutObject(Text); -static_void_test_AMmapPutObject(Void) +static_void_test_AMmapPut(Timestamp, int64_t, INT64_MAX); -static_void_test_AMmapPut(Timestamp, timestamp, INT64_MAX) +static_void_test_AMmapPut(Uint, int64_t, UINT64_MAX); -static_void_test_AMmapPut(Uint, uint, UINT64_MAX) - -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * map object's key which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into + * a map object's key which will truncate it in a C application. */ static void test_get_NUL_key(void** state) { /* @@ -158,39 +147,37 @@ static void test_get_NUL_key(void** state) { doc['o\0ps'] = 'oops'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_SRC[] = {'o', '\0', 'p', 's'}; static AMbyteSpan const OOPS_KEY = {.src = OOPS_SRC, .count = sizeof(OOPS_SRC) / sizeof(uint8_t)}; static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, - 193, 58, 122, 66, 134, 151, 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, - 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, 150, 44, 201, 136, - 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, - 1, 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, - 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, - 127, 7, 127, 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, - 111, 111, 112, 115, 127, 0, 0 - }; + 133, 111, 74, 131, 233, 150, 60, 244, 0, 116, 1, 16, 223, 253, 146, 193, 58, 122, 66, 134, 151, + 225, 210, 51, 58, 86, 247, 8, 1, 49, 118, 234, 228, 42, 116, 171, 13, 164, 99, 244, 27, 19, + 150, 44, 201, 136, 222, 219, 90, 246, 226, 123, 77, 120, 157, 155, 55, 182, 2, 178, 64, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 4, 111, 0, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 111, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, OOPS_KEY, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_not_equal(OOPS_KEY.count, strlen(OOPS_KEY.src)); assert_int_equal(str.count, strlen("oops")); assert_memory_equal(str.src, "oops", str.count); } -/** \brief A JavaScript application can introduce NUL (`\0`) characters into a - * map object's string value which will truncate it in a C application. +/** + * \brief A JavaScript application can introduce NUL (`\0`) characters into a + * map object's string value which will truncate it in a C application. */ static void test_get_NUL_string_value(void** state) { /* @@ -200,1209 +187,1369 @@ static void test_get_NUL_string_value(void** state) { doc.oops = 'o\0ps'; }); const bytes = Automerge.save(doc); - console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], bytes).join(", ") + "};"); + console.log("static uint8_t const SAVED_DOC[] = {" + Array.apply([], + bytes).join(", ") + "};"); */ static uint8_t const OOPS_VALUE[] = {'o', '\0', 'p', 's'}; static size_t const OOPS_SIZE = sizeof(OOPS_VALUE) / sizeof(uint8_t); static uint8_t const SAVED_DOC[] = { - 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, - 125, 55, 71, 154, 136, 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, - 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, 6, 109, 18, 172, 75, - 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, - 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, - 66, 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, - 0, 127, 7, 127, 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, - 70, 111, 0, 112, 115, 127, 0, 0 - }; + 133, 111, 74, 131, 63, 94, 151, 29, 0, 116, 1, 16, 156, 159, 189, 12, 125, 55, 71, 154, 136, + 104, 237, 186, 45, 224, 32, 22, 1, 36, 163, 164, 222, 81, 42, 1, 247, 231, 156, 54, 222, 76, + 6, 109, 18, 172, 75, 36, 118, 120, 68, 73, 87, 186, 230, 127, 68, 19, 81, 149, 185, 6, 1, + 2, 3, 2, 19, 2, 35, 2, 64, 2, 86, 2, 8, 21, 6, 33, 2, 35, 2, 52, 1, 66, + 2, 86, 2, 87, 4, 128, 1, 2, 127, 0, 127, 1, 127, 1, 127, 0, 127, 0, 127, 7, 127, + 4, 111, 111, 112, 115, 127, 0, 127, 1, 1, 127, 1, 127, 70, 111, 0, 112, 115, 127, 0, 0}; static size_t const SAVED_DOC_SIZE = sizeof(SAVED_DOC) / sizeof(uint8_t); - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, - AMload(SAVED_DOC, SAVED_DOC_SIZE), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(SAVED_DOC, SAVED_DOC_SIZE), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("oops"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_not_equal(str.count, strlen(OOPS_VALUE)); assert_int_equal(str.count, OOPS_SIZE); assert_memory_equal(str.src, OOPS_VALUE, str.count); } static void test_range_iter_map(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8)); - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9)); - AMfree(AMcommit(doc, AMstr(NULL), NULL)); - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMmapItems map_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_int_equal(AMmapItemsSize(&map_items), 4); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("b"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("c"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 7), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("a"), 8), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("d"), 9), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); + assert_int_equal(AMitemsSize(&map_items), 4); /* ["b"-"d") */ - AMmapItems range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr("d"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range, 1); + AMitem* next = AMitemsNext(&range, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + uint64_t uint; + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* ["b"-) */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("b"), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 9); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 9); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* [-"d") */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr("d"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 8); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 8); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); /* ["a"-) */ - range = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + range = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr("a"), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)); /* First */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "a", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 8); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 8); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 6); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "b", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 4); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 4); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "c", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 5); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 5); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fourth */ - next = AMmapItemsNext(&range, 1); + next = AMitemsNext(&range, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "d", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_UINT); - assert_int_equal(next_value.uint, 9); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_UINT); + assert_true(AMitemToUint(next, &uint)); + assert_int_equal(uint, 9); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 7); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Fifth */ - assert_null(AMmapItemsNext(&range, 1)); + assert_null(AMitemsNext(&range, 1)); } static void test_map_range_back_and_forth_single(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "c", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "c", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "a", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_back_and_forth_double(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id1= AMpush(&stack, - AMactorIdInitBytes("\0", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc1, actor_id1)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMactorId const* actor_id1; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); + AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* The second actor should win all conflicts here. */ - AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id2 = AMpush(&stack, - AMactorIdInitBytes("\1", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc2, actor_id2)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMactorId const* actor_id2; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "cc", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "cc", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "aa", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_single(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id = AMpush(&stack, - AMgetActorId(doc), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); + AMactorId const* actor_id; + assert_true(AMitemToActorId(AMstackItem(stack_ptr, AMgetActorId(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "a", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "a", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "b", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "b", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 1); - assert_memory_equal(next_value.str.src, "c", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 1); + assert_memory_equal(str.src, "c", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 0); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "c", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "c", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "b", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "b", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 1); - assert_memory_equal(next_back_value.str.src, "a", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 1); + assert_memory_equal(str_back.src, "a", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 0); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_map_range_at_back_and_forth_double(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id1= AMpush(&stack, - AMactorIdInitBytes("\0", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc1, actor_id1)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMactorId const* actor_id1; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\0", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id1)); + AMstackItem(NULL, AMsetActorId(doc1, actor_id1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("1"), AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("2"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("3"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* The second actor should win all conflicts here. */ - AMdoc* const doc2 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMactorId const* const actor_id2= AMpush(&stack, - AMactorIdInitBytes("\1", 1), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id; - AMfree(AMsetActorId(doc2, actor_id2)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb"))); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc"))); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + AMactorId const* actor_id2; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromBytes("\1", 1), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id2)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("1"), AMstr("aa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("2"), AMstr("bb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("3"), AMstr("cc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); - AMfree(AMmerge(doc1, doc2)); - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* Forward, back, back. */ - AMmapItems range_all = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; + AMitems range_all = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); /* First */ - AMmapItem const* next = AMmapItemsNext(&range_all, 1); + AMitem* next = AMitemsNext(&range_all, 1); assert_non_null(next); - AMbyteSpan key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - AMvalue next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - AMobjId const* next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + AMbyteSpan str; + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + AMobjId const* next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - AMmapItems range_back_all = AMmapItemsReversed(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); - AMmapItem const* next_back = AMmapItemsNext(&range_back_all, 1); + AMitems range_back_all = AMitemsReversed(&range_all); + range_back_all = AMitemsRewound(&range_back_all); + AMitem* next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - AMvalue next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - AMobjId const* next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + AMbyteSpan str_back; + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + AMobjId const* next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Forward, back, forward. */ - range_all = AMmapItemsRewound(&range_all); - range_back_all = AMmapItemsRewound(&range_back_all); + range_all = AMitemsRewound(&range_all); + range_back_all = AMitemsRewound(&range_back_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward, forward, forward. */ - range_all = AMmapItemsRewound(&range_all); + range_all = AMitemsRewound(&range_all); /* First */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "aa", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "aa", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Second */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "bb", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "bb", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Third */ - next = AMmapItemsNext(&range_all, 1); + next = AMitemsNext(&range_all, 1); assert_non_null(next); - key = AMmapItemKey(next); + assert_int_equal(AMitemIdxType(next), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_value = AMmapItemValue(next); - assert_int_equal(next_value.tag, AM_VALUE_STR); - assert_int_equal(next_value.str.count, 2); - assert_memory_equal(next_value.str.src, "cc", next_value.str.count); - next_obj_id = AMmapItemObjId(next); + assert_int_equal(AMitemValType(next), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next, &str)); + assert_int_equal(str.count, 2); + assert_memory_equal(str.src, "cc", str.count); + next_obj_id = AMitemObjId(next); assert_int_equal(AMobjIdCounter(next_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_obj_id), 1); /* Forward stop */ - assert_null(AMmapItemsNext(&range_all, 1)); + assert_null(AMitemsNext(&range_all, 1)); /* Back, back, back. */ - range_back_all = AMmapItemsRewound(&range_back_all); + range_back_all = AMitemsRewound(&range_back_all); /* Third */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "3", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "cc", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "cc", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 3); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Second */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "2", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "bb", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "bb", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 2); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* First */ - next_back = AMmapItemsNext(&range_back_all, 1); + next_back = AMitemsNext(&range_back_all, 1); assert_non_null(next_back); - key = AMmapItemKey(next_back); + assert_int_equal(AMitemIdxType(next_back), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(next_back, &key)); assert_int_equal(key.count, 1); assert_memory_equal(key.src, "1", key.count); - next_back_value = AMmapItemValue(next_back); - assert_int_equal(next_back_value.tag, AM_VALUE_STR); - assert_int_equal(next_back_value.str.count, 2); - assert_memory_equal(next_back_value.str.src, "aa", next_back_value.str.count); - next_back_obj_id = AMmapItemObjId(next_back); + assert_int_equal(AMitemValType(next_back), AM_VAL_TYPE_STR); + assert_true(AMitemToStr(next_back, &str_back)); + assert_int_equal(str_back.count, 2); + assert_memory_equal(str_back.src, "aa", str_back.count); + next_back_obj_id = AMitemObjId(next_back); assert_int_equal(AMobjIdCounter(next_back_obj_id), 1); assert_int_equal(AMactorIdCmp(AMobjIdActorId(next_back_obj_id), actor_id2), 0); assert_int_equal(AMobjIdIndex(next_back_obj_id), 1); /* Back stop */ - assert_null(AMmapItemsNext(&range_back_all, 1)); + assert_null(AMitemsNext(&range_back_all, 1)); } static void test_get_range_values(void** state) { - AMresultStack* stack = *state; - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc"))); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("aa"), AMstr("aaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("bb"), AMstr("bbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("dd"), AMstr("ddd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMchangeHashes const v1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMdoc* const doc2 = AMpush(&stack, AMfork(doc1, NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMitems const v1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(doc1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2"))); - AMfree(AMcommit(doc1, AMstr(NULL), NULL)); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("cc"), AMstr("ccc V2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc1, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc V3"))); - AMfree(AMcommit(doc2, AMstr(NULL), NULL)); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("cc"), AMstr("ccc V3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(doc2, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMfree(AMmerge(doc1, doc2)); + AMstackItem(NULL, AMmerge(doc1, doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); - AMmapItems range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems range_back = AMmapItemsReversed(&range); - assert_int_equal(AMmapItemsSize(&range), 2); + /* Forward vs. reverse: complete current map range. */ + AMitems range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size_t size = AMitemsSize(&range); + assert_int_equal(size, 4); + AMitems range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + AMbyteSpan key; + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - AMmapItem const* map_item = NULL; - while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + AMitem *item1, *item_back1; + size_t count, middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - assert_int_equal(AMmapItemsSize(&range_back), 2); + /* Forward vs. reverse: partial current map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("aa"), AMstr("dd"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 3); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "cc", key.count); - while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), NULL); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, NULL), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, NULL), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr("b"), AMstr("d"), &v1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - range_back = AMmapItemsReversed(&range); - assert_int_equal(AMmapItemsSize(&range), 2); + /* Forward vs. reverse: complete historical map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 4); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "aa", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - while ((map_item = AMmapItemsNext(&range, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - assert_int_equal(AMmapItemsSize(&range_back), 2); + /* Forward vs. reverse: partial historical map range. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr("bb"), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + size = AMitemsSize(&range); + assert_int_equal(size, 3); + range_back = AMitemsReversed(&range); + assert_int_equal(AMitemsSize(&range_back), size); + assert_true(AMitemKey(AMitemsNext(&range, 1), &key)); + assert_memory_equal(key.src, "bb", key.count); + assert_true(AMitemKey(AMitemsNext(&range_back, 1), &key)); + assert_memory_equal(key.src, "dd", key.count); - while ((map_item = AMmapItemsNext(&range_back, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMresult* result = AMmapGet(doc1, AM_ROOT, AMmapItemKey(map_item), &v1); - AMvalue const val2 = AMresultValue(result); - assert_true(AMvalueEqual(&val1, &val2)); - assert_non_null(AMmapItemObjId(map_item)); - AMfree(result); + middle = size / 2; + range = AMitemsRewound(&range); + range_back = AMitemsRewound(&range_back); + for (item1 = NULL, item_back1 = NULL, count = 0; item1 && item_back1; + item1 = AMitemsNext(&range, 1), item_back1 = AMitemsNext(&range_back, 1), ++count) { + AMbyteSpan key1, key_back1; + assert_true(AMitemKey(item1, &key1)); + assert_true(AMitemKey(item_back1, &key_back1)); + if ((count == middle) && (middle & 1)) { + /* The iterators are crossing in the middle. */ + assert_int_equal(AMstrCmp(key1, key_back1), 0); + assert_true(AMitemEqual(item1, item_back1)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item_back1))); + } else { + assert_int_not_equal(AMstrCmp(key1, key_back1), 0); + } + AMitem* item2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key1, &v1), NULL, NULL); + AMitem* item_back2 = AMstackItem(stack_ptr, AMmapGet(doc1, AM_ROOT, key_back1, &v1), NULL, NULL); + /** \note An item returned from an `AM...Get()` call doesn't include the + index used to retrieve it. */ + assert_false(AMitemIdxType(item2)); + assert_false(AMitemIdxType(item_back2)); + assert_true(AMitemEqual(item1, item2)); + assert_true(AMobjIdEqual(AMitemObjId(item1), AMitemObjId(item2))); + assert_true(AMitemEqual(item_back1, item_back2)); + assert_true(AMobjIdEqual(AMitemObjId(item_back1), AMitemObjId(item_back2))); + AMresultFree(AMstackPop(stack_ptr, NULL)); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMobjItems values = AMpush(&stack, - AMobjValues(doc1, AM_ROOT, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); - AMobjItem const* value = NULL; - while ((map_item = AMmapItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); + /* Map range vs. object range: complete current. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + AMitem *item, *obj_item; + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } - range = AMpush(&stack, - AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - values = AMpush(&stack, - AMobjValues(doc1, AM_ROOT, &v1), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - assert_int_equal(AMmapItemsSize(&range), AMobjItemsSize(&values)); - while ((map_item = AMmapItemsNext(&range, 1)) != NULL && - (value = AMobjItemsNext(&values, 1)) != NULL) { - AMvalue const val1 = AMmapItemValue(map_item); - AMvalue const val2 = AMobjItemValue(value); - assert_true(AMvalueEqual(&val1, &val2)); - assert_true(AMobjIdEqual(AMmapItemObjId(map_item), AMobjItemObjId(value))); + /* Map range vs. object range: complete historical. */ + range = AMstackItems(stack_ptr, AMmapRange(doc1, AM_ROOT, AMstr(NULL), AMstr(NULL), &v1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, AM_ROOT, &v1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&range), AMitemsSize(&obj_items)); + + for (item = NULL, obj_item = NULL; item && obj_item; + item = AMitemsNext(&range, 1), obj_item = AMitemsNext(&obj_items, 1)) { + /** \note Object iteration doesn't yield any item indices. */ + assert_true(AMitemIdxType(item)); + assert_false(AMitemIdxType(obj_item)); + assert_true(AMitemEqual(item, obj_item)); + assert_true(AMobjIdEqual(AMitemObjId(item), AMitemObjId(obj_item))); } } @@ -1418,19 +1565,18 @@ int run_map_tests(void) { cmocka_unit_test(test_AMmapPutObject(List)), cmocka_unit_test(test_AMmapPutObject(Map)), cmocka_unit_test(test_AMmapPutObject(Text)), - cmocka_unit_test(test_AMmapPutObject(Void)), cmocka_unit_test(test_AMmapPutStr), cmocka_unit_test(test_AMmapPut(Timestamp)), cmocka_unit_test(test_AMmapPut(Uint)), - cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_range_iter_map, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_get_range_values, setup_stack, teardown_stack), + cmocka_unit_test_setup_teardown(test_get_NUL_key, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_NUL_string_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_range_iter_map, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_single, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_back_and_forth_double, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_single, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_map_range_at_back_and_forth_double, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_get_range_values, setup_base, teardown_base), }; - return cmocka_run_group_tests(tests, group_setup, group_teardown); + return cmocka_run_group_tests(tests, setup_doc, teardown_doc); } diff --git a/rust/automerge-c/test/ported_wasm/basic_tests.c b/rust/automerge-c/test/ported_wasm/basic_tests.c index e2659d62..b83ff132 100644 --- a/rust/automerge-c/test/ported_wasm/basic_tests.c +++ b/rust/automerge-c/test/ported_wasm/basic_tests.c @@ -11,7 +11,10 @@ /* local */ #include -#include "../stack_utils.h" +#include +#include +#include "../base_state.h" +#include "../cmocka_utils.h" /** * \brief default import init() should return a promise @@ -22,163 +25,171 @@ static void test_default_import_init_should_return_a_promise(void** state); * \brief should create, clone and free */ static void test_create_clone_and_free(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create() */ - AMdoc* const doc1 = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const doc2 = doc1.clone() */ - AMdoc* const doc2 = AMpush(&stack, AMclone(doc1), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); } /** * \brief should be able to start and commit */ static void test_start_and_commit(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* doc.commit() */ - AMpush(&stack, AMemptyChange(doc, AMstr(NULL), NULL), AM_VALUE_CHANGE_HASHES, cmocka_cb); + AMstackItems(stack_ptr, AMemptyChange(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); } /** * \brief getting a nonexistent prop does not throw an error */ static void test_getting_a_nonexistent_prop_does_not_throw_an_error(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* const result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, undefined) */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should be able to set and get a simple value */ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc: Automerge = create("aabbcc") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aabbcc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* let result */ /* */ /* doc.put(root, "hello", "world") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number1", 5, "uint") */ - AMfree(AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5)); + AMstackItem(NULL, AMmapPutUint(doc, AM_ROOT, AMstr("number1"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number2", 5) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number2"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number3", 5.5) */ - AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5)); + AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number3"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number4", 5.5, "f64") */ - AMfree(AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5)); + AMstackItem(NULL, AMmapPutF64(doc, AM_ROOT, AMstr("number4"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "number5", 5.5, "int") */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("number5"), 5.5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "bool", true) */ - AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true)); + AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), true), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "time1", 1000, "timestamp") */ - AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000)); + AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time1"), 1000), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put(root, "time2", new Date(1001)) */ - AMfree(AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001)); + AMstackItem(NULL, AMmapPutTimestamp(doc, AM_ROOT, AMstr("time2"), 1001), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.putObject(root, "list", []); */ - AMfree(AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST)); + AMstackItem(NULL, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); /* doc.put(root, "null", null) */ - AMfree(AMmapPutNull(doc, AM_ROOT, AMstr("null"))); + AMstackItem(NULL, AMmapPutNull(doc, AM_ROOT, AMstr("null")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* result = doc.getWithType(root, "hello") */ /* assert.deepEqual(result, ["str", "world"]) */ /* assert.deepEqual(doc.get("/", "hello"), "world") */ - AMbyteSpan str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("hello"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, strlen("world")); assert_memory_equal(str.src, "world", str.count); /* assert.deepEqual(doc.get("/", "hello"), "world") */ /* */ /* result = doc.getWithType(root, "number1") */ /* assert.deepEqual(result, ["uint", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 5); + uint64_t uint; + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 5); /* assert.deepEqual(doc.get("/", "number1"), 5) */ /* */ /* result = doc.getWithType(root, "number2") */ /* assert.deepEqual(result, ["int", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), - AM_VALUE_INT, - cmocka_cb).int_, 5); + int64_t int_; + assert_true(AMitemToInt( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), + &int_)); + assert_int_equal(int_, 5); /* */ /* result = doc.getWithType(root, "number3") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - assert_float_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), - AM_VALUE_F64, - cmocka_cb).f64, 5.5, DBL_EPSILON); + double f64; + assert_true(AMitemToF64( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number3"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), + &f64)); + assert_float_equal(f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number4") */ /* assert.deepEqual(result, ["f64", 5.5]) */ - assert_float_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), - AM_VALUE_F64, - cmocka_cb).f64, 5.5, DBL_EPSILON); + assert_true(AMitemToF64( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number4"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_F64)), + &f64)); + assert_float_equal(f64, 5.5, DBL_EPSILON); /* */ /* result = doc.getWithType(root, "number5") */ /* assert.deepEqual(result, ["int", 5]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), - AM_VALUE_INT, - cmocka_cb).int_, 5); + assert_true(AMitemToInt( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("number5"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_INT)), + &int_)); + assert_int_equal(int_, 5); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", true]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), - AM_VALUE_BOOLEAN, - cmocka_cb).boolean, true); + bool boolean; + assert_true(AMitemToBool( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), + &boolean)); + assert_true(boolean); /* */ /* doc.put(root, "bool", false, "boolean") */ - AMfree(AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false)); + AMstackItem(NULL, AMmapPutBool(doc, AM_ROOT, AMstr("bool"), false), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* result = doc.getWithType(root, "bool") */ /* assert.deepEqual(result, ["boolean", false]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), - AM_VALUE_BOOLEAN, - cmocka_cb).boolean, false); + assert_true(AMitemToBool( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("bool"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BOOL)), + &boolean)); + assert_false(boolean); /* */ /* result = doc.getWithType(root, "time1") */ /* assert.deepEqual(result, ["timestamp", new Date(1000)]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), - AM_VALUE_TIMESTAMP, - cmocka_cb).timestamp, 1000); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time1"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_TIMESTAMP)), + ×tamp)); + assert_int_equal(timestamp, 1000); /* */ /* result = doc.getWithType(root, "time2") */ /* assert.deepEqual(result, ["timestamp", new Date(1001)]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), - AM_VALUE_TIMESTAMP, - cmocka_cb).timestamp, 1001); + assert_true(AMitemToTimestamp(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("time2"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_TIMESTAMP)), + ×tamp)); + assert_int_equal(timestamp, 1001); /* */ /* result = doc.getWithType(root, "list") */ /* assert.deepEqual(result, ["list", "10@aabbcc"]); */ - AMobjId const* const list = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = AMitemObjId( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("list"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); assert_int_equal(AMobjIdCounter(list), 10); str = AMactorIdStr(AMobjIdActorId(list)); assert_int_equal(str.count, strlen("aabbcc")); @@ -186,38 +197,39 @@ static void test_should_be_able_to_set_and_get_a_simple_value(void** state) { /* */ /* result = doc.getWithType(root, "null") */ /* assert.deepEqual(result, ["null", null]); */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), - AM_VALUE_NULL, - cmocka_cb); + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("null"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_NULL)); } /** * \brief should be able to use bytes */ static void test_should_be_able_to_use_bytes(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* doc.put("_root", "data1", new Uint8Array([10, 11, 12])); */ static uint8_t const DATA1[] = {10, 11, 12}; - AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1)))); + AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data1"), AMbytes(DATA1, sizeof(DATA1))), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "data2", new Uint8Array([13, 14, 15]), "bytes"); */ static uint8_t const DATA2[] = {13, 14, 15}; - AMfree(AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2)))); + AMstackItem(NULL, AMmapPutBytes(doc, AM_ROOT, AMstr("data2"), AMbytes(DATA2, sizeof(DATA2))), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const value1 = doc.getWithType("_root", "data1") */ - AMbyteSpan const value1 = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan value1; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data1"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &value1)); /* assert.deepEqual(value1, ["bytes", new Uint8Array([10, 11, 12])]); */ assert_int_equal(value1.count, sizeof(DATA1)); assert_memory_equal(value1.src, DATA1, sizeof(DATA1)); /* const value2 = doc.getWithType("_root", "data2") */ - AMbyteSpan const value2 = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan value2; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("data2"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &value2)); /* assert.deepEqual(value2, ["bytes", new Uint8Array([13, 14, 15])]); */ assert_int_equal(value2.count, sizeof(DATA2)); assert_memory_equal(value2.src, DATA2, sizeof(DATA2)); @@ -227,103 +239,92 @@ static void test_should_be_able_to_use_bytes(void** state) { * \brief should be able to make subobjects */ static void test_should_be_able_to_make_subobjects(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* let result */ /* */ /* const submap = doc.putObject(root, "submap", {}) */ - AMobjId const* const submap = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const submap = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("submap"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.put(submap, "number", 6, "uint") */ - AMfree(AMmapPutUint(doc, submap, AMstr("number"), 6)); + AMstackItem(NULL, AMmapPutUint(doc, submap, AMstr("number"), 6), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.strictEqual(doc.pendingOps(), 2) */ assert_int_equal(AMpendingOps(doc), 2); /* */ /* result = doc.getWithType(root, "submap") */ /* assert.deepEqual(result, ["map", submap]) */ - assert_true(AMobjIdEqual(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id, + assert_true(AMobjIdEqual(AMitemObjId(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("submap"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), submap)); /* */ /* result = doc.getWithType(submap, "number") */ /* assert.deepEqual(result, ["uint", 6]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, submap, AMstr("number"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, - 6); + uint64_t uint; + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(doc, submap, AMstr("number"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 6); } /** * \brief should be able to make lists */ static void test_should_be_able_to_make_lists(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "numbers", []) */ - AMobjId const* const sublist = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const sublist = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("numbers"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.insert(sublist, 0, "a"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 1, "b"); */ - AMfree(AMlistPutStr(doc, sublist, 1, true, AMstr("b"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 1, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 2, "c"); */ - AMfree(AMlistPutStr(doc, sublist, 2, true, AMstr("c"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 0, "z"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("z"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* assert.deepEqual(doc.getWithType(sublist, 0), ["str", "z"]) */ - AMbyteSpan str = AMpush(&stack, - AMlistGet(doc, sublist, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); /* assert.deepEqual(doc.getWithType(sublist, 1), ["str", "a"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 1, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 2, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); /* assert.deepEqual(doc.getWithType(sublist, 3), ["str", "c"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 3, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 3, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 4); /* */ /* doc.put(sublist, 2, "b v2"); */ - AMfree(AMlistPutStr(doc, sublist, 2, false, AMstr("b v2"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 2, false, AMstr("b v2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* assert.deepEqual(doc.getWithType(sublist, 2), ["str", "b v2"]) */ - str = AMpush(&stack, - AMlistGet(doc, sublist, 2, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, sublist, 2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "b v2", str.count); /* assert.deepEqual(doc.length(sublist), 4) */ @@ -334,233 +335,217 @@ static void test_should_be_able_to_make_lists(void** state) { * \brief lists have insert, set, splice, and push ops */ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* const sublist = doc.putObject(root, "letters", []) */ - AMobjId const* const sublist = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const sublist = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("letters"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.insert(sublist, 0, "a"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("a"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.insert(sublist, 0, "b"); */ - AMfree(AMlistPutStr(doc, sublist, 0, true, AMstr("b"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.materialize(), { letters: ["b", "a"] }) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); - AMbyteSpan key = AMmapItemKey(doc_item); + AMitem* doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&list_items), 2); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.push(sublist, "c"); */ - AMfree(AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c"))); + AMstackItem(NULL, AMlistPutStr(doc, sublist, SIZE_MAX, true, AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const heads = doc.getHeads() */ - AMchangeHashes const heads = AMpush(&stack, - AMgetHeads(doc), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads = AMstackItems(stack_ptr, AMgetHeads(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c"] }) */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&list_items), 3); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.push(sublist, 3, "timestamp"); */ - AMfree(AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3)); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMstackItem(NULL, AMlistPutTimestamp(doc, sublist, SIZE_MAX, true, 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "a", "c", new + * Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + assert_int_equal(AMitemsSize(&list_items), 4); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.splice(sublist, 1, 1, ["d", "e", "f"]); */ - static AMvalue const DATA[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "d", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "e", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "f", .count = 1}}}; - AMfree(AMsplice(doc, sublist, 1, 1, DATA, sizeof(DATA)/sizeof(AMvalue))); - /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMresult* data = AMstackResult( + stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("d")), AMitemFromStr(AMstr("e")), AMitemFromStr(AMstr("f"))), + NULL, NULL); + AMstackItem(NULL, AMsplice(doc, sublist, 1, 1, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["b", "d", "e", "f", "c", + * new Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } /* doc.put(sublist, 0, "z"); */ - AMfree(AMlistPutStr(doc, sublist, 0, false, AMstr("z"))); - /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", new Date(3)] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + AMstackItem(NULL, AMlistPutStr(doc, sublist, 0, false, AMstr("z")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(doc.materialize(), { letters: ["z", "d", "e", "f", "c", + * new Date(3)] } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&list_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&list_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&list_items, 1)); } - /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new Date(3)] */ - AMlistItems sublist_items = AMpush( - &stack, - AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + /* assert.deepEqual(doc.materialize(sublist), ["z", "d", "e", "f", "c", new + * Date(3)] */ + AMitems sublist_items = AMstackItems(stack_ptr, AMlistRange(doc, sublist, 0, SIZE_MAX, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR | AM_VAL_TYPE_TIMESTAMP)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "z", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "f", str.count); - str = AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&sublist_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&sublist_items, 1)).timestamp, - 3); - assert_null(AMlistItemsNext(&sublist_items, 1)); + int64_t timestamp; + assert_true(AMitemToTimestamp(AMitemsNext(&sublist_items, 1), ×tamp)); + assert_int_equal(timestamp, 3); + assert_null(AMitemsNext(&sublist_items, 1)); /* assert.deepEqual(doc.length(sublist), 6) */ assert_int_equal(AMobjSize(doc, sublist, NULL), 6); - /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] } */ - doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + /* assert.deepEqual(doc.materialize("/", heads), { letters: ["b", "a", "c"] + * } */ + doc_item = AMstackItem(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("letters")); assert_memory_equal(key.src, "letters", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, &heads), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, &heads), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "c", str.count); - assert_null(AMlistItemsNext(&list_items, 1)); + assert_null(AMitemsNext(&list_items, 1)); } } @@ -568,67 +553,54 @@ static void test_lists_have_insert_set_splice_and_push_ops(void** state) { * \brief should be able to delete non-existent props */ static void test_should_be_able_to_delete_non_existent_props(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* */ /* doc.put("_root", "foo", "bar") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "bip", "bap") */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("bip"), AMstr("bap")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash1 = doc.commit() */ - AMchangeHashes const hash1 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash1 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* assert.deepEqual(doc.keys("_root"), ["bip", "foo"]) */ - AMstrs keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - AMbyteSpan str = AMstrsNext(&keys, 1); + AMitems keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - str = AMstrsNext(&keys, 1); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* */ /* doc.delete("_root", "foo") */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("foo"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("foo")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.delete("_root", "baz") */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("baz"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("baz")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash2 = doc.commit() */ - AMchangeHashes const hash2 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash2 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* assert.deepEqual(doc.keys("_root"), ["bip"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); /* assert.deepEqual(doc.keys("_root", [hash1]), ["bip", "foo"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, &hash1), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); - str = AMstrsNext(&keys, 1); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "foo", str.count); /* assert.deepEqual(doc.keys("_root", [hash2]), ["bip"]) */ - keys = AMpush(&stack, - AMkeys(doc, AM_ROOT, &hash2), - AM_VALUE_STRS, - cmocka_cb).strs; - str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc, AM_ROOT, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bip", str.count); } @@ -636,123 +608,114 @@ static void test_should_be_able_to_delete_non_existent_props(void** state) { /** * \brief should be able to del */ -static void test_should_be_able_to_del(void **state) { - AMresultStack* stack = *state; +static void test_should_be_able_to_del(void** state) { + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* doc.put(root, "xxx", "xxx"); */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("xxx"), AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "xxx"), ["str", "xxx"]) */ - AMbyteSpan const str = AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "xxx", str.count); /* doc.delete(root, "xxx"); */ - AMfree(AMmapDelete(doc, AM_ROOT, AMstr("xxx"))); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("xxx")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "xxx"), undefined) */ - AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapGet(doc, AM_ROOT, AMstr("xxx"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should be able to use counters */ static void test_should_be_able_to_use_counters(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root" */ /* */ /* doc.put(root, "counter", 10, "counter"); */ - AMfree(AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10)); + AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 10]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 10); + int64_t counter; + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 10); /* doc.increment(root, "counter", 10); */ - AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10)); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 20]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 20); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 20); /* doc.increment(root, "counter", -5); */ - AMfree(AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5)); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), -5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(root, "counter"), ["counter", 15]) */ - assert_int_equal(AMpush(&stack, - AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), - AM_VALUE_COUNTER, - cmocka_cb).counter, 15); + assert_true(AMitemToCounter(AMstackItem(stack_ptr, AMmapGet(doc, AM_ROOT, AMstr("counter"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)), + &counter)); + assert_int_equal(counter, 15); } /** * \brief should be able to splice text */ static void test_should_be_able_to_splice_text(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const root = "_root"; */ /* */ /* const text = doc.putObject(root, "text", ""); */ - AMobjId const* const text = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.splice(text, 0, 0, "hello ") */ - AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello "))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.splice(text, 6, 0, "world") */ - AMfree(AMspliceText(doc, text, 6, 0, AMstr("world"))); + AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.splice(text, 11, 0, "!?") */ - AMfree(AMspliceText(doc, text, 11, 0, AMstr("!?"))); + AMstackItem(NULL, AMspliceText(doc, text, 11, 0, AMstr("!?")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.getWithType(text, 0), ["str", "h"]) */ - AMbyteSpan str = AMpush(&stack, - AMlistGet(doc, text, 0, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "h", str.count); /* assert.deepEqual(doc.getWithType(text, 1), ["str", "e"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 1, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "e", str.count); /* assert.deepEqual(doc.getWithType(text, 9), ["str", "l"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 9, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMlistGet(doc, text, 9, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "l", str.count); /* assert.deepEqual(doc.getWithType(text, 10), ["str", "d"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 10, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 10, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc.getWithType(text, 11), ["str", "!"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 11, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 11, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "!", str.count); /* assert.deepEqual(doc.getWithType(text, 12), ["str", "?"]) */ - str = AMpush(&stack, - AMlistGet(doc, text, 12, NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMlistGet(doc, text, 12, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "?", str.count); } @@ -761,52 +724,45 @@ static void test_should_be_able_to_splice_text(void** state) { * \brief should be able to save all or incrementally */ static void test_should_be_able_to_save_all_or_incrementally(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* */ /* doc.put("_root", "foo", 1) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("foo"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const save1 = doc.save() */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); /* */ /* doc.put("_root", "bar", 2) */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("bar"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const saveMidway = doc.clone().save(); */ - AMbyteSpan const saveMidway = AMpush(&stack, - AMsave( - AMpush(&stack, - AMclone(doc), - AM_VALUE_DOC, - cmocka_cb).doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMdoc* doc_clone; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc_clone)); + AMbyteSpan saveMidway; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc_clone), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveMidway)); /* */ /* const save2 = doc.saveIncremental(); */ - AMbyteSpan const save2 = AMpush(&stack, - AMsaveIncremental(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save2; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save2)); /* */ /* doc.put("_root", "baz", 3); */ - AMfree(AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3)); + AMstackItem(NULL, AMmapPutInt(doc, AM_ROOT, AMstr("baz"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const save3 = doc.saveIncremental(); */ - AMbyteSpan const save3 = AMpush(&stack, - AMsaveIncremental(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save3; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsaveIncremental(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save3)); /* */ /* const saveA = doc.save(); */ - AMbyteSpan const saveA = AMpush(&stack, - AMsave(doc), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan saveA; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saveA)); /* const saveB = new Uint8Array([...save1, ...save2, ...save3]); */ size_t const saveB_count = save1.count + save2.count + save3.count; uint8_t* const saveB_src = test_malloc(saveB_count); @@ -818,104 +774,83 @@ static void test_should_be_able_to_save_all_or_incrementally(void** state) { assert_memory_not_equal(saveA.src, saveB_src, saveA.count); /* */ /* const docA = load(saveA); */ - AMdoc* const docA = AMpush(&stack, - AMload(saveA.src, saveA.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docA; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveA.src, saveA.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docA)); /* const docB = load(saveB); */ - AMdoc* const docB = AMpush(&stack, - AMload(saveB_src, saveB_count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docB; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveB_src, saveB_count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docB)); test_free(saveB_src); /* const docC = load(saveMidway) */ - AMdoc* const docC = AMpush(&stack, - AMload(saveMidway.src, saveMidway.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* docC; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saveMidway.src, saveMidway.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &docC)); /* docC.loadIncremental(save3) */ - AMfree(AMloadIncremental(docC, save3.src, save3.count)); + AMstackItem(NULL, AMloadIncremental(docC, save3.src, save3.count), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)); /* */ /* assert.deepEqual(docA.keys("_root"), docB.keys("_root")); */ - AMstrs const keysA = AMpush(&stack, - AMkeys(docA, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - AMstrs const keysB = AMpush(&stack, - AMkeys(docB, AM_ROOT, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsCmp(&keysA, &keysB), 0); + AMitems const keysA = AMstackItems(stack_ptr, AMkeys(docA, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems const keysB = AMstackItems(stack_ptr, AMkeys(docB, AM_ROOT, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&keysA, &keysB)); /* assert.deepEqual(docA.save(), docB.save()); */ - AMbyteSpan const save = AMpush(&stack, - AMsave(docA), - AM_VALUE_BYTES, - cmocka_cb).bytes; - assert_memory_equal(save.src, - AMpush(&stack, - AMsave(docB), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.count); + AMbyteSpan docA_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docA), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docA_save)); + AMbyteSpan docB_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docB), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docB_save)); + assert_int_equal(docA_save.count, docB_save.count); + assert_memory_equal(docA_save.src, docB_save.src, docA_save.count); /* assert.deepEqual(docA.save(), docC.save()); */ - assert_memory_equal(save.src, - AMpush(&stack, - AMsave(docC), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.count); + AMbyteSpan docC_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(docC), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &docC_save)); + assert_int_equal(docA_save.count, docC_save.count); + assert_memory_equal(docA_save.src, docC_save.src, docA_save.count); } /** * \brief should be able to splice text #2 */ static void test_should_be_able_to_splice_text_2(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create() */ - AMdoc* const doc = AMpush(&stack, AMcreate(NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const text = doc.putObject("_root", "text", ""); */ - AMobjId const* const text = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const text = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc.splice(text, 0, 0, "hello world"); */ - AMfree(AMspliceText(doc, text, 0, 0, AMstr("hello world"))); + AMstackItem(NULL, AMspliceText(doc, text, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash1 = doc.commit(); */ - AMchangeHashes const hash1 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash1 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc.splice(text, 6, 0, "big bad "); */ - AMfree(AMspliceText(doc, text, 6, 0, AMstr("big bad "))); + AMstackItem(NULL, AMspliceText(doc, text, 6, 0, AMstr("big bad ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const hash2 = doc.commit(); */ - AMchangeHashes const hash2 = AMpush(&stack, - AMcommit(doc, AMstr(NULL), NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const hash2 = + AMstackItems(stack_ptr, AMcommit(doc, AMstr(NULL), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* assert.strictEqual(doc.text(text), "hello big bad world") */ - AMbyteSpan str = AMpush(&stack, - AMtext(doc, text, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text), 19) */ assert_int_equal(AMobjSize(doc, text, NULL), 19); /* assert.strictEqual(doc.text(text, [hash1]), "hello world") */ - str = AMpush(&stack, - AMtext(doc, text, &hash1), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash1), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); /* assert.strictEqual(doc.length(text, [hash1]), 11) */ assert_int_equal(AMobjSize(doc, text, &hash1), 11); /* assert.strictEqual(doc.text(text, [hash2]), "hello big bad world") */ - str = AMpush(&stack, - AMtext(doc, text, &hash2), - AM_VALUE_STR, - cmocka_cb).str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, text, &hash2), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello big bad world")); assert_memory_equal(str.src, "hello big bad world", str.count); /* assert.strictEqual(doc.length(text, [hash2]), 19) */ @@ -926,266 +861,234 @@ static void test_should_be_able_to_splice_text_2(void** state) { * \brief local inc increments all visible counters in a map */ static void test_local_inc_increments_all_visible_counters_in_a_map(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* doc1.put("_root", "hello", "world") */ - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("hello"), AMstr("world")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan const save = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMdoc* const doc2 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc2, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMbyteSpan save; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); + AMdoc* doc2; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* const doc3 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc3, AMpush(&stack, - AMactorIdInitStr(AMstr("cccc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* doc3; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let heads = doc1.getHeads() */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc1.put("_root", "cnt", 20) */ - AMfree(AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20)); + AMstackItem(NULL, AMmapPutInt(doc1, AM_ROOT, AMstr("cnt"), 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put("_root", "cnt", 0, "counter") */ - AMfree(AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0)); + AMstackItem(NULL, AMmapPutCounter(doc2, AM_ROOT, AMstr("cnt"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc3.put("_root", "cnt", 10, "counter") */ - AMfree(AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10)); + AMstackItem(NULL, AMmapPutCounter(doc3, AM_ROOT, AMstr("cnt"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMchanges const changes2 = AMpush(&stack, - AMgetChanges(doc2, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes2)); + AMitems const changes2 = + AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMchanges const changes3 = AMpush(&stack, - AMgetChanges(doc3, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes3)); + AMitems const changes3 = + AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let result = doc1.getAll("_root", "cnt") */ - AMobjItems result = AMpush(&stack, - AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + AMitems result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT | AM_VAL_TYPE_STR)); /* assert.deepEqual(result, [ ['int', 20, '2@aaaa'], ['counter', 0, '2@bbbb'], ['counter', 10, '2@cccc'], ]) */ - AMobjItem const* result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).int_, 20); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + AMitem* result_item = AMitemsNext(&result, 1); + int64_t int_; + assert_true(AMitemToInt(result_item, &int_)); + assert_int_equal(int_, 20); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 0); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + int64_t counter; + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 0); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 10); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 10); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment("_root", "cnt", 5) */ - AMfree(AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5)); + AMstackItem(NULL, AMmapIncrement(doc1, AM_ROOT, AMstr("cnt"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* result = doc1.getAll("_root", "cnt") */ - result = AMpush(&stack, - AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + result = AMstackItems(stack_ptr, AMmapGetAll(doc1, AM_ROOT, AMstr("cnt"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER)); /* assert.deepEqual(result, [ ['counter', 5, '2@bbbb'], ['counter', 15, '2@cccc'], ]) */ - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 5); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 5); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 15); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 2); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 15); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 2); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save1 = doc1.save() */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); /* const doc4 = load(save1) */ - AMdoc* const doc4 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc4; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); /* assert.deepEqual(doc4.save(), save1); */ - assert_memory_equal(AMpush(&stack, - AMsave(doc4), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save1.src, - save1.count); + AMbyteSpan doc4_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); + assert_int_equal(doc4_save.count, save1.count); + assert_memory_equal(doc4_save.src, save1.src, doc4_save.count); } /** * \brief local inc increments all visible counters in a sequence */ static void test_local_inc_increments_all_visible_counters_in_a_sequence(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const seq = doc1.putObject("_root", "seq", []) */ - AMobjId const* const seq = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const seq = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("seq"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* doc1.insert(seq, 0, "hello") */ - AMfree(AMlistPutStr(doc1, seq, 0, true, AMstr("hello"))); + AMstackItem(NULL, AMlistPutStr(doc1, seq, 0, true, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc2 = load(doc1.save(), "bbbb"); */ - AMbyteSpan const save1 = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMdoc* const doc2 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc2, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMbyteSpan save1; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save1)); + AMdoc* doc2; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const doc3 = load(doc1.save(), "cccc"); */ - AMdoc* const doc3 = AMpush(&stack, - AMload(save1.src, save1.count), - AM_VALUE_DOC, - cmocka_cb).doc; - AMfree(AMsetActorId(doc3, AMpush(&stack, - AMactorIdInitStr(AMstr("cccc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* doc3; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(save1.src, save1.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc3)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("cccc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMstackItem(NULL, AMsetActorId(doc3, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let heads = doc1.getHeads() */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* doc1.put(seq, 0, 20) */ - AMfree(AMlistPutInt(doc1, seq, 0, false, 20)); + AMstackItem(NULL, AMlistPutInt(doc1, seq, 0, false, 20), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put(seq, 0, 0, "counter") */ - AMfree(AMlistPutCounter(doc2, seq, 0, false, 0)); + AMstackItem(NULL, AMlistPutCounter(doc2, seq, 0, false, 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc3.put(seq, 0, 10, "counter") */ - AMfree(AMlistPutCounter(doc3, seq, 0, false, 10)); + AMstackItem(NULL, AMlistPutCounter(doc3, seq, 0, false, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc2.getChanges(heads)) */ - AMchanges const changes2 = AMpush(&stack, - AMgetChanges(doc2, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes2)); + AMitems const changes2 = + AMstackItems(stack_ptr, AMgetChanges(doc2, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc1.applyChanges(doc3.getChanges(heads)) */ - AMchanges const changes3 = AMpush(&stack, - AMgetChanges(doc3, &heads1), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(doc1, &changes3)); + AMitems const changes3 = + AMstackItems(stack_ptr, AMgetChanges(doc3, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(doc1, &changes3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let result = doc1.getAll(seq, 0) */ - AMobjItems result = AMpush(&stack, - AMlistGetAll(doc1, seq, 0, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + AMitems result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_COUNTER | AM_VAL_TYPE_INT)); /* assert.deepEqual(result, [ ['int', 20, '3@aaaa'], ['counter', 0, '3@bbbb'], ['counter', 10, '3@cccc'], ]) */ - AMobjItem const* result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).int_, 20); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + AMitem* result_item = AMitemsNext(&result, 1); + int64_t int_; + assert_true(AMitemToInt(result_item, &int_)); + assert_int_equal(int_, 20); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + AMbyteSpan str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "aaaa", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 0); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + int64_t counter; + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 0); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 10); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 10); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "cccc", str.count); /* doc1.increment(seq, 0, 5) */ - AMfree(AMlistIncrement(doc1, seq, 0, 5)); + AMstackItem(NULL, AMlistIncrement(doc1, seq, 0, 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* result = doc1.getAll(seq, 0) */ - result = AMpush(&stack, - AMlistGetAll(doc1, seq, 0, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; + result = AMstackItems(stack_ptr, AMlistGetAll(doc1, seq, 0, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_COUNTER)); /* assert.deepEqual(result, [ ['counter', 5, '3@bbbb'], ['counter', 15, '3@cccc'], ]) */ - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 5); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 5); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_int_equal(str.count, 4); assert_memory_equal(str.src, "bbbb", str.count); - result_item = AMobjItemsNext(&result, 1); - assert_int_equal(AMobjItemValue(result_item).counter, 15); - assert_int_equal(AMobjIdCounter(AMobjItemObjId(result_item)), 3); - str = AMactorIdStr(AMobjIdActorId(AMobjItemObjId(result_item))); + result_item = AMitemsNext(&result, 1); + assert_true(AMitemToCounter(result_item, &counter)); + assert_int_equal(counter, 15); + assert_int_equal(AMobjIdCounter(AMitemObjId(result_item)), 3); + str = AMactorIdStr(AMobjIdActorId(AMitemObjId(result_item))); assert_memory_equal(str.src, "cccc", str.count); /* */ /* const save = doc1.save() */ - AMbyteSpan const save = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan save; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &save)); /* const doc4 = load(save) */ - AMdoc* const doc4 = AMpush(&stack, - AMload(save.src, save.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc4; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMload(save.src, save.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc4)); /* assert.deepEqual(doc4.save(), save); */ - assert_memory_equal(AMpush(&stack, - AMsave(doc4), - AM_VALUE_BYTES, - cmocka_cb).bytes.src, - save.src, - save.count); + AMbyteSpan doc4_save; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc4), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &doc4_save)); + assert_int_equal(doc4_save.count, save.count); + assert_memory_equal(doc4_save.src, save.src, doc4_save.count); } /** @@ -1197,314 +1100,269 @@ static void test_paths_can_be_used_instead_of_objids(void** state); * \brief should be able to fetch changes by hash */ static void test_should_be_able_to_fetch_changes_by_hash(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const doc2 = create("bbbb") */ - AMdoc* const doc2 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("bbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc2; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); /* doc1.put("/", "a", "b") */ - AMfree(AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b"))); + AMstackItem(NULL, AMmapPutStr(doc1, AM_ROOT, AMstr("a"), AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc2.put("/", "b", "c") */ - AMfree(AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c"))); + AMstackItem(NULL, AMmapPutStr(doc2, AM_ROOT, AMstr("b"), AMstr("c")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const head1 = doc1.getHeads() */ - AMchangeHashes head1 = AMpush(&stack, - AMgetHeads(doc1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems head1 = AMstackItems(stack_ptr, AMgetHeads(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const head2 = doc2.getHeads() */ - AMchangeHashes head2 = AMpush(&stack, - AMgetHeads(doc2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems head2 = AMstackItems(stack_ptr, AMgetHeads(doc2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const change1 = doc1.getChangeByHash(head1[0]) - if (change1 === null) { throw new RangeError("change1 should not be null") */ - AMbyteSpan const change_hash1 = AMchangeHashesNext(&head1, 1); - AMchanges change1 = AMpush( - &stack, - AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), - AM_VALUE_CHANGES, - cmocka_cb).changes; + if (change1 === null) { throw new RangeError("change1 should not be + null") */ + AMbyteSpan change_hash1; + assert_true(AMitemToChangeHash(AMitemsNext(&head1, 1), &change_hash1)); + AMchange const* change1; + assert_true(AMitemToChange(AMstackItem(stack_ptr, AMgetChangeByHash(doc1, change_hash1.src, change_hash1.count), + cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)), + &change1)); /* const change2 = doc1.getChangeByHash(head2[0]) assert.deepEqual(change2, null) */ - AMbyteSpan const change_hash2 = AMchangeHashesNext(&head2, 1); - AMpush(&stack, - AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), - AM_VALUE_VOID, - cmocka_cb); + AMbyteSpan change_hash2; + assert_true(AMitemToChangeHash(AMitemsNext(&head2, 1), &change_hash2)); + AMstackItem(NULL, AMgetChangeByHash(doc1, change_hash2.src, change_hash2.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(decodeChange(change1).hash, head1[0]) */ - assert_memory_equal(AMchangeHash(AMchangesNext(&change1, 1)).src, - change_hash1.src, - change_hash1.count); + assert_memory_equal(AMchangeHash(change1).src, change_hash1.src, change_hash1.count); } /** * \brief recursive sets are possible */ static void test_recursive_sets_are_possible(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create("aaaa") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const l1 = doc.putObject("_root", "list", [{ foo: "bar" }, [1, 2, 3]] */ - AMobjId const* const l1 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const l1 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); { - AMobjId const* const map = AMpush( - &stack, - AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar"))); - AMobjId const* const list = AMpush( - &stack, - AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const map = AMitemObjId(AMstackItem( + stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMmapPutStr(doc, map, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, SIZE_MAX, true, AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); for (int value = 1; value != 4; ++value) { - AMfree(AMlistPutInt(doc, list, SIZE_MAX, true, value)); + AMstackItem(NULL, AMlistPutInt(doc, list, SIZE_MAX, true, value), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } } /* const l2 = doc.insertObject(l1, 0, { zip: ["a", "b"] }) */ - AMobjId const* const l2 = AMpush( - &stack, - AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const l2 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, l1, 0, true, AM_OBJ_TYPE_MAP), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); { - AMobjId const* const list = AMpush( - &stack, - AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a"))); - AMfree(AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b"))); + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, l2, AMstr("zip"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("a")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMlistPutStr(doc, list, SIZE_MAX, true, AMstr("b")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } - /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' object */ - AMobjId const* const l3 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, l3, 0, 0, AMstr("hello world"))); + /* const l3 = doc.putObject("_root", "info1", "hello world") // 'text' + * object */ + AMobjId const* const l3 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info1"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, l3, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* doc.put("_root", "info2", "hello world") // 'str' */ - AMfree(AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world"))); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("info2"), AMstr("hello world")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* const l4 = doc.putObject("_root", "info3", "hello world") */ - AMobjId const* const l4 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; - AMfree(AMspliceText(doc, l4, 0, 0, AMstr("hello world"))); + AMobjId const* const l4 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("info3"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); + AMstackItem(NULL, AMspliceText(doc, l4, 0, 0, AMstr("hello world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(doc.materialize(), { "list": [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]], "info1": "hello world", "info2": "hello world", "info3": "hello world", - }) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* doc_item = AMmapItemsNext(&doc_items, 1); - AMbyteSpan key = AMmapItemKey(doc_item); + }) */ + AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMitem* doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info1")); assert_memory_equal(key.src, "info1", key.count); - AMbyteSpan str = AMpush(&stack, - AMtext(doc, AMmapItemObjId(doc_item), NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info2")); assert_memory_equal(key.src, "info2", key.count); - str = AMmapItemValue(doc_item).str; + assert_true(AMitemToStr(doc_item, &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("info3")); assert_memory_equal(key.src, "info3", key.count); - str = AMpush(&stack, - AMtext(doc, AMmapItemObjId(doc_item), NULL), - AM_VALUE_STR, - cmocka_cb).str; + assert_true(AMitemToStr( + AMstackItem(stack_ptr, AMtext(doc, AMitemObjId(doc_item), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); - doc_item = AMmapItemsNext(&doc_items, 1); - key = AMmapItemKey(doc_item); + doc_item = AMitemsNext(&doc_items, 1); + assert_int_equal(AMitemIdxType(doc_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(doc_item, &key)); assert_int_equal(key.count, strlen("list")); assert_memory_equal(key.src, "list", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(doc_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(doc_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE | AM_VAL_TYPE_STR)); + AMitem* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan const str = AMmapItemValue(map_item).str; + AMbyteSpan str; + assert_true(AMitemToStr(map_item, &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 1); - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 2); - assert_int_equal(AMlistItemValue( - AMlistItemsNext(&list_items, 1)).int_, - 3); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); + int64_t int_; + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 1); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 2); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 3); } } /* assert.deepEqual(doc.materialize(l2), { zip: ["a", "b"] }) */ - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - key = AMmapItemKey(map_item); + AMitems map_items = AMstackItems(stack_ptr, AMmapRange(doc, l2, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } - /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" }, [1, 2, 3]] */ - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, l1, 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMlistItem const* list_item = AMlistItemsNext(&list_items, 1); + /* assert.deepEqual(doc.materialize(l1), [{ zip: ["a", "b"] }, { foo: "bar" + * }, [1, 2, 3]] */ + AMitems list_items = + AMstackItems(stack_ptr, AMlistRange(doc, l1, 0, SIZE_MAX, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + AMitem const* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("zip")); assert_memory_equal(key.src, "zip", key.count); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMmapItemObjId(map_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - AMbyteSpan str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(map_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "a", str.count); - str = AMlistItemValue(AMlistItemsNext(&list_items, 1)).str; + assert_true(AMitemToStr(AMitemsNext(&list_items, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "b", str.count); } } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMmapItems map_items = AMpush( - &stack, - AMmapRange(doc, AMlistItemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItem const* map_item = AMmapItemsNext(&map_items, 1); - AMbyteSpan const key = AMmapItemKey(map_item); + AMitems map_items = + AMstackItems(stack_ptr, AMmapRange(doc, AMitemObjId(list_item), AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + AMitem* map_item = AMitemsNext(&map_items, 1); + assert_int_equal(AMitemIdxType(map_item), AM_IDX_TYPE_KEY); + AMbyteSpan key; + assert_true(AMitemKey(map_item, &key)); assert_int_equal(key.count, strlen("foo")); assert_memory_equal(key.src, "foo", key.count); - AMbyteSpan const str = AMmapItemValue(map_item).str; + AMbyteSpan str; + assert_true(AMitemToStr(map_item, &str)); assert_int_equal(str.count, 3); assert_memory_equal(str.src, "bar", str.count); } - list_item = AMlistItemsNext(&list_items, 1); + list_item = AMitemsNext(&list_items, 1); { - AMlistItems list_items = AMpush( - &stack, - AMlistRange(doc, AMlistItemObjId(list_item), 0, SIZE_MAX, NULL), - AM_VALUE_LIST_ITEMS, - cmocka_cb).list_items; - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 1); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 2); - assert_int_equal(AMlistItemValue(AMlistItemsNext(&list_items, 1)).int_, - 3); + AMitems list_items = AMstackItems(stack_ptr, AMlistRange(doc, AMitemObjId(list_item), 0, SIZE_MAX, NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_INT)); + int64_t int_; + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 1); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 2); + assert_true(AMitemToInt(AMitemsNext(&list_items, 1), &int_)); + assert_int_equal(int_, 3); } /* assert.deepEqual(doc.materialize(l4), "hello world") */ - str = AMpush(&stack, AMtext(doc, l4, NULL), AM_VALUE_STR, cmocka_cb).str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(doc, l4, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hello world")); assert_memory_equal(str.src, "hello world", str.count); } @@ -1513,65 +1371,41 @@ static void test_recursive_sets_are_possible(void** state) { * \brief only returns an object id when objects are created */ static void test_only_returns_an_object_id_when_objects_are_created(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc = create("aaaa") */ - AMdoc* const doc = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc)); /* const r1 = doc.put("_root", "foo", "bar") assert.deepEqual(r1, null); */ - AMpush(&stack, - AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapPutStr(doc, AM_ROOT, AMstr("foo"), AMstr("bar")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r2 = doc.putObject("_root", "list", []) */ - AMobjId const* const r2 = AMpush( - &stack, - AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const r2 = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc, AM_ROOT, AMstr("list"), AM_OBJ_TYPE_LIST), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const r3 = doc.put("_root", "counter", 10, "counter") assert.deepEqual(r3, null); */ - AMpush(&stack, - AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapPutCounter(doc, AM_ROOT, AMstr("counter"), 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r4 = doc.increment("_root", "counter", 1) assert.deepEqual(r4, null); */ - AMpush(&stack, - AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapIncrement(doc, AM_ROOT, AMstr("counter"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r5 = doc.delete("_root", "counter") assert.deepEqual(r5, null); */ - AMpush(&stack, - AMmapDelete(doc, AM_ROOT, AMstr("counter")), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMmapDelete(doc, AM_ROOT, AMstr("counter")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r6 = doc.insert(r2, 0, 10); assert.deepEqual(r6, null); */ - AMpush(&stack, - AMlistPutInt(doc, r2, 0, true, 10), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMlistPutInt(doc, r2, 0, true, 10), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const r7 = doc.insertObject(r2, 0, {}); */ - AMobjId const* const r7 = AMpush( - &stack, - AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const r7 = AMitemObjId(AMstackItem(stack_ptr, AMlistPutObject(doc, r2, 0, true, AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const r8 = doc.splice(r2, 1, 0, ["a", "b", "c"]); */ - AMvalue const STRS[] = {{.str_tag = AM_VALUE_STR, .str = {.src = "a", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "b", .count = 1}}, - {.str_tag = AM_VALUE_STR, .str = {.src = "c", .count = 1}}}; - AMpush(&stack, - AMsplice(doc, r2, 1, 0, STRS, sizeof(STRS)/sizeof(AMvalue)), - AM_VALUE_VOID, - cmocka_cb); + AMresult* data = AMstackResult( + stack_ptr, AMresultFrom(3, AMitemFromStr(AMstr("a")), AMitemFromStr(AMstr("b")), AMitemFromStr(AMstr("c"))), + NULL, NULL); + AMstackItem(NULL, AMsplice(doc, r2, 1, 0, AMresultItems(data)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepEqual(r2, "2@aaaa"); */ assert_int_equal(AMobjIdCounter(r2), 2); AMbyteSpan str = AMactorIdStr(AMobjIdActorId(r2)); @@ -1587,75 +1421,58 @@ static void test_only_returns_an_object_id_when_objects_are_created(void** state * \brief objects without properties are preserved */ static void test_objects_without_properties_are_preserved(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const doc1 = create("aaaa") */ - AMdoc* const doc1 = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), &actor_id)); + AMdoc* doc1; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc1)); /* const a = doc1.putObject("_root", "a", {}); */ - AMobjId const* const a = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const a = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("a"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const b = doc1.putObject("_root", "b", {}); */ - AMobjId const* const b = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const b = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("b"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const c = doc1.putObject("_root", "c", {}); */ - AMobjId const* const c = AMpush( - &stack, - AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const c = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(doc1, AM_ROOT, AMstr("c"), AM_OBJ_TYPE_MAP), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* const d = doc1.put(c, "d", "dd"); */ - AMfree(AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd"))); + AMstackItem(NULL, AMmapPutStr(doc1, c, AMstr("d"), AMstr("dd")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const saved = doc1.save(); */ - AMbyteSpan const saved = AMpush(&stack, - AMsave(doc1), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan saved; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(doc1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &saved)); /* const doc2 = load(saved); */ - AMdoc* const doc2 = AMpush(&stack, - AMload(saved.src, saved.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* doc2; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(saved.src, saved.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &doc2)); /* assert.deepEqual(doc2.getWithType("_root", "a"), ["map", a]) */ - AMmapItems doc_items = AMpush(&stack, - AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), a)); + AMitems doc_items = AMstackItems(stack_ptr, AMmapRange(doc2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), a)); /* assert.deepEqual(doc2.keys(a), []) */ - AMstrs keys = AMpush(&stack, - AMkeys(doc1, a, NULL), - AM_VALUE_STRS, - cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 0); + AMitems keys = AMstackItems(stack_ptr, AMkeys(doc1, a, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "b"), ["map", b]) */ - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), b)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), b)); /* assert.deepEqual(doc2.keys(b), []) */ - keys = AMpush(&stack, AMkeys(doc1, b, NULL), AM_VALUE_STRS, cmocka_cb).strs; - assert_int_equal(AMstrsSize(&keys), 0); + keys = AMstackItems(stack_ptr, AMkeys(doc1, b, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_int_equal(AMitemsSize(&keys), 0); /* assert.deepEqual(doc2.getWithType("_root", "c"), ["map", c]) */ - assert_true(AMobjIdEqual(AMmapItemObjId(AMmapItemsNext(&doc_items, 1)), c)); + assert_true(AMobjIdEqual(AMitemObjId(AMitemsNext(&doc_items, 1)), c)); /* assert.deepEqual(doc2.keys(c), ["d"]) */ - keys = AMpush(&stack, AMkeys(doc1, c, NULL), AM_VALUE_STRS, cmocka_cb).strs; - AMbyteSpan str = AMstrsNext(&keys, 1); + keys = AMstackItems(stack_ptr, AMkeys(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMbyteSpan str; + assert_true(AMitemToStr(AMitemsNext(&keys, 1), &str)); assert_int_equal(str.count, 1); assert_memory_equal(str.src, "d", str.count); /* assert.deepEqual(doc2.getWithType(c, "d"), ["str", "dd"]) */ - AMobjItems obj_items = AMpush(&stack, - AMobjValues(doc1, c, NULL), - AM_VALUE_OBJ_ITEMS, - cmocka_cb).obj_items; - str = AMobjItemValue(AMobjItemsNext(&obj_items, 1)).str; + AMitems obj_items = AMstackItems(stack_ptr, AMobjItems(doc1, c, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemToStr(AMitemsNext(&obj_items, 1), &str)); assert_int_equal(str.count, 2); assert_memory_equal(str.src, "dd", str.count); } @@ -1664,177 +1481,162 @@ static void test_objects_without_properties_are_preserved(void** state) { * \brief should allow you to forkAt a heads */ static void test_should_allow_you_to_forkAt_a_heads(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const A = create("aaaaaa") */ - AMdoc* const A = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aaaaaa")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aaaaaa")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* A; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); /* A.put("/", "key1", "val1"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key1"), AMstr("val1")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.put("/", "key2", "val2"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key2"), AMstr("val2")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const heads1 = A.getHeads(); */ - AMchangeHashes const heads1 = AMpush(&stack, - AMgetHeads(A), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads1 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const B = A.fork("bbbbbb") */ - AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; - AMfree(AMsetActorId(B, AMpush(&stack, - AMactorIdInitStr(AMstr("bbbbbb")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMdoc* B; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("bbbbbb")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(B, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.put("/", "key3", "val3"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3"))); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key3"), AMstr("val3")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.put("/", "key4", "val4"); */ - AMfree(AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4"))); + AMstackItem(NULL, AMmapPutStr(B, AM_ROOT, AMstr("key4"), AMstr("val4")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* A.merge(B) */ - AMfree(AMmerge(A, B)); + AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const heads2 = A.getHeads(); */ - AMchangeHashes const heads2 = AMpush(&stack, - AMgetHeads(A), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; + AMitems const heads2 = AMstackItems(stack_ptr, AMgetHeads(A), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* A.put("/", "key5", "val5"); */ - AMfree(AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5"))); - /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", heads1) */ - AMmapItems AforkAt1_items = AMpush( - &stack, - AMmapRange( - AMpush(&stack, AMfork(A, &heads1), AM_VALUE_DOC, cmocka_cb).doc, - AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems A1_items = AMpush(&stack, - AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMmapItemsEqual(&AforkAt1_items, &A1_items)); - /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", heads2) */ - AMmapItems AforkAt2_items = AMpush( - &stack, - AMmapRange( - AMpush(&stack, AMfork(A, &heads2), AM_VALUE_DOC, cmocka_cb).doc, - AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - AMmapItems A2_items = AMpush(&stack, - AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), - AM_VALUE_MAP_ITEMS, - cmocka_cb).map_items; - assert_true(AMmapItemsEqual(&AforkAt2_items, &A2_items)); + AMstackItem(NULL, AMmapPutStr(A, AM_ROOT, AMstr("key5"), AMstr("val5")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* assert.deepEqual(A.forkAt(heads1).materialize("/"), A.materialize("/", + * heads1) */ + AMdoc* A_forkAt1; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads1), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt1)); + AMitems AforkAt1_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt1, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems A1_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads1), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&AforkAt1_items, &A1_items)); + /* assert.deepEqual(A.forkAt(heads2).materialize("/"), A.materialize("/", + * heads2) */ + AMdoc* A_forkAt2; + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, &heads2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A_forkAt2)); + AMitems AforkAt2_items = AMstackItems(stack_ptr, AMmapRange(A_forkAt2, AM_ROOT, AMstr(NULL), AMstr(NULL), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)); + AMitems A2_items = AMstackItems(stack_ptr, AMmapRange(A, AM_ROOT, AMstr(NULL), AMstr(NULL), &heads2), cmocka_cb, + AMexpect(AM_VAL_TYPE_STR)); + assert_true(AMitemsEqual(&AforkAt2_items, &A2_items)); } /** * \brief should handle merging text conflicts then saving & loading */ static void test_should_handle_merging_text_conflicts_then_saving_and_loading(void** state) { - AMresultStack* stack = *state; + BaseState* base_state = *state; + AMstack** stack_ptr = &base_state->stack; /* const A = create("aabbcc") */ - AMdoc* const A = AMpush(&stack, - AMcreate(AMpush(&stack, - AMactorIdInitStr(AMstr("aabbcc")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("aabbcc")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* A; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &A)); /* const At = A.putObject('_root', 'text', "") */ - AMobjId const* const At = AMpush( - &stack, - AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const At = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(A, AM_ROOT, AMstr("text"), AM_OBJ_TYPE_TEXT), cmocka_cb, + AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* A.splice(At, 0, 0, 'hello') */ - AMfree(AMspliceText(A, At, 0, 0, AMstr("hello"))); + AMstackItem(NULL, AMspliceText(A, At, 0, 0, AMstr("hello")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* const B = A.fork() */ - AMdoc* const B = AMpush(&stack, AMfork(A, NULL), AM_VALUE_DOC, cmocka_cb).doc; + AMdoc* B; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMfork(A, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &B)); /* */ /* assert.deepEqual(B.getWithType("_root", "text"), ["text", At]) */ - AMbyteSpan str = AMpush(&stack, - AMtext(B, - AMpush(&stack, - AMmapGet(B, AM_ROOT, AMstr("text"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id, - NULL), - AM_VALUE_STR, - cmocka_cb).str; - AMbyteSpan const str2 = AMpush(&stack, - AMtext(A, At, NULL), - AM_VALUE_STR, - cmocka_cb).str; + AMbyteSpan str; + assert_true( + AMitemToStr(AMstackItem(stack_ptr, + AMtext(B, + AMitemObjId(AMstackItem(stack_ptr, AMmapGet(B, AM_ROOT, AMstr("text"), NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))), + NULL), + cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), + &str)); + AMbyteSpan str2; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(A, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str2)); assert_int_equal(str.count, str2.count); assert_memory_equal(str.src, str2.src, str.count); /* */ /* B.splice(At, 4, 1) */ - AMfree(AMspliceText(B, At, 4, 1, AMstr(NULL))); + AMstackItem(NULL, AMspliceText(B, At, 4, 1, AMstr(NULL)), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 4, 0, '!') */ - AMfree(AMspliceText(B, At, 4, 0, AMstr("!"))); + AMstackItem(NULL, AMspliceText(B, At, 4, 0, AMstr("!")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 5, 0, ' ') */ - AMfree(AMspliceText(B, At, 5, 0, AMstr(" "))); + AMstackItem(NULL, AMspliceText(B, At, 5, 0, AMstr(" ")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* B.splice(At, 6, 0, 'world') */ - AMfree(AMspliceText(B, At, 6, 0, AMstr("world"))); + AMstackItem(NULL, AMspliceText(B, At, 6, 0, AMstr("world")), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* A.merge(B) */ - AMfree(AMmerge(A, B)); + AMstackItem(NULL, AMmerge(A, B), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* const binary = A.save() */ - AMbyteSpan const binary = AMpush(&stack, - AMsave(A), - AM_VALUE_BYTES, - cmocka_cb).bytes; + AMbyteSpan binary; + assert_true(AMitemToBytes(AMstackItem(stack_ptr, AMsave(A), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &binary)); /* */ /* const C = load(binary) */ - AMdoc* const C = AMpush(&stack, - AMload(binary.src, binary.count), - AM_VALUE_DOC, - cmocka_cb).doc; + AMdoc* C; + assert_true(AMitemToDoc( + AMstackItem(stack_ptr, AMload(binary.src, binary.count), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &C)); /* */ /* assert.deepEqual(C.getWithType('_root', 'text'), ['text', '1@aabbcc'] */ - AMobjId const* const C_text = AMpush(&stack, - AMmapGet(C, AM_ROOT, AMstr("text"), NULL), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const C_text = AMitemObjId( + AMstackItem(stack_ptr, AMmapGet(C, AM_ROOT, AMstr("text"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); assert_int_equal(AMobjIdCounter(C_text), 1); str = AMactorIdStr(AMobjIdActorId(C_text)); assert_int_equal(str.count, strlen("aabbcc")); assert_memory_equal(str.src, "aabbcc", str.count); /* assert.deepEqual(C.text(At), 'hell! world') */ - str = AMpush(&stack, AMtext(C, At, NULL), AM_VALUE_STR, cmocka_cb).str; + assert_true(AMitemToStr(AMstackItem(stack_ptr, AMtext(C, At, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_STR)), &str)); assert_int_equal(str.count, strlen("hell! world")); assert_memory_equal(str.src, "hell! world", str.count); } int run_ported_wasm_basic_tests(void) { const struct CMUnitTest tests[] = { - cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_start_and_commit, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_stack, teardown_stack), - cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_stack, teardown_stack) - }; + cmocka_unit_test_setup_teardown(test_create_clone_and_free, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_start_and_commit, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_getting_a_nonexistent_prop_does_not_throw_an_error, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_set_and_get_a_simple_value, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_bytes, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_subobjects, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_make_lists, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_lists_have_insert_set_splice_and_push_ops, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_delete_non_existent_props, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_del, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_use_counters, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_save_all_or_incrementally, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_splice_text_2, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_map, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_local_inc_increments_all_visible_counters_in_a_sequence, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_should_be_able_to_fetch_changes_by_hash, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_recursive_sets_are_possible, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_only_returns_an_object_id_when_objects_are_created, setup_base, + teardown_base), + cmocka_unit_test_setup_teardown(test_objects_without_properties_are_preserved, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_allow_you_to_forkAt_a_heads, setup_base, teardown_base), + cmocka_unit_test_setup_teardown(test_should_handle_merging_text_conflicts_then_saving_and_loading, setup_base, + teardown_base)}; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/rust/automerge-c/test/ported_wasm/suite.c b/rust/automerge-c/test/ported_wasm/suite.c index fc10fadc..440ed899 100644 --- a/rust/automerge-c/test/ported_wasm/suite.c +++ b/rust/automerge-c/test/ported_wasm/suite.c @@ -1,6 +1,6 @@ +#include #include #include -#include #include /* third-party */ @@ -11,8 +11,5 @@ extern int run_ported_wasm_basic_tests(void); extern int run_ported_wasm_sync_tests(void); int run_ported_wasm_suite(void) { - return ( - run_ported_wasm_basic_tests() + - run_ported_wasm_sync_tests() - ); + return (run_ported_wasm_basic_tests() + run_ported_wasm_sync_tests()); } diff --git a/rust/automerge-c/test/ported_wasm/sync_tests.c b/rust/automerge-c/test/ported_wasm/sync_tests.c index a1ddbf3c..099f8dbf 100644 --- a/rust/automerge-c/test/ported_wasm/sync_tests.c +++ b/rust/automerge-c/test/ported_wasm/sync_tests.c @@ -9,10 +9,12 @@ /* local */ #include -#include "../stack_utils.h" +#include +#include "../base_state.h" +#include "../cmocka_utils.h" typedef struct { - AMresultStack* stack; + BaseState* base_state; AMdoc* n1; AMdoc* n2; AMsyncState* s1; @@ -21,43 +23,35 @@ typedef struct { static int setup(void** state) { TestState* test_state = test_calloc(1, sizeof(TestState)); - test_state->n1 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("01234567")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - test_state->n2 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("89abcdef")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - test_state->s1 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; - test_state->s2 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + setup_base((void**)&test_state->base_state); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("01234567")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n1)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + assert_true( + AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &test_state->n2)); + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s1)); + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &test_state->s2)); *state = test_state; return 0; } static int teardown(void** state) { TestState* test_state = *state; - AMfreeStack(&test_state->stack); + teardown_base((void**)&test_state->base_state); test_free(test_state); return 0; } -static void sync(AMdoc* a, - AMdoc* b, - AMsyncState* a_sync_state, - AMsyncState* b_sync_state) { +static void sync(AMdoc* a, AMdoc* b, AMsyncState* a_sync_state, AMsyncState* b_sync_state) { static size_t const MAX_ITER = 10; AMsyncMessage const* a2b_msg = NULL; @@ -66,29 +60,35 @@ static void sync(AMdoc* a, do { AMresult* a2b_msg_result = AMgenerateSyncMessage(a, a_sync_state); AMresult* b2a_msg_result = AMgenerateSyncMessage(b, b_sync_state); - AMvalue value = AMresultValue(a2b_msg_result); - switch (value.tag) { - case AM_VALUE_SYNC_MESSAGE: { - a2b_msg = value.sync_message; - AMfree(AMreceiveSyncMessage(b, b_sync_state, a2b_msg)); - } - break; - case AM_VALUE_VOID: a2b_msg = NULL; break; + AMitem* item = AMresultItem(a2b_msg_result); + switch (AMitemValType(item)) { + case AM_VAL_TYPE_SYNC_MESSAGE: { + AMitemToSyncMessage(item, &a2b_msg); + AMstackResult(NULL, AMreceiveSyncMessage(b, b_sync_state, a2b_msg), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + } break; + case AM_VAL_TYPE_VOID: + a2b_msg = NULL; + break; } - value = AMresultValue(b2a_msg_result); - switch (value.tag) { - case AM_VALUE_SYNC_MESSAGE: { - b2a_msg = value.sync_message; - AMfree(AMreceiveSyncMessage(a, a_sync_state, b2a_msg)); - } - break; - case AM_VALUE_VOID: b2a_msg = NULL; break; + item = AMresultItem(b2a_msg_result); + switch (AMitemValType(item)) { + case AM_VAL_TYPE_SYNC_MESSAGE: { + AMitemToSyncMessage(item, &b2a_msg); + AMstackResult(NULL, AMreceiveSyncMessage(a, a_sync_state, b2a_msg), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + } break; + case AM_VAL_TYPE_VOID: + b2a_msg = NULL; + break; } if (++iter > MAX_ITER) { - fail_msg("Did not synchronize within %d iterations. " - "Do you have a bug causing an infinite loop?", MAX_ITER); + fail_msg( + "Did not synchronize within %d iterations. " + "Do you have a bug causing an infinite loop?", + MAX_ITER); } - } while(a2b_msg || b2a_msg); + } while (a2b_msg || b2a_msg); } static time_t const TIME_0 = 0; @@ -96,151 +96,135 @@ static time_t const TIME_0 = 0; /** * \brief should send a sync message implying no local data */ -static void test_should_send_a_sync_message_implying_no_local_data(void **state) { +static void test_should_send_a_sync_message_implying_no_local_data(void** state) { /* const doc = create() const s1 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = doc.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") } const message: DecodedSyncMessage = decodeSyncMessage(m1) */ - AMsyncMessage const* const m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage( - test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* assert.deepStrictEqual(message.heads, []) */ - AMchangeHashes heads = AMsyncMessageHeads(m1); - assert_int_equal(AMchangeHashesSize(&heads), 0); + AMitems heads = AMstackItems(stack_ptr, AMsyncMessageHeads(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&heads), 0); /* assert.deepStrictEqual(message.need, []) */ - AMchangeHashes needs = AMsyncMessageNeeds(m1); - assert_int_equal(AMchangeHashesSize(&needs), 0); + AMitems needs = AMstackItems(stack_ptr, AMsyncMessageNeeds(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&needs), 0); /* assert.deepStrictEqual(message.have.length, 1) */ - AMsyncHaves haves = AMsyncMessageHaves(m1); - assert_int_equal(AMsyncHavesSize(&haves), 1); + AMitems haves = AMstackItems(stack_ptr, AMsyncMessageHaves(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + assert_int_equal(AMitemsSize(&haves), 1); /* assert.deepStrictEqual(message.have[0].lastSync, []) */ - AMsyncHave const* have0 = AMsyncHavesNext(&haves, 1); - AMchangeHashes last_sync = AMsyncHaveLastSync(have0); - assert_int_equal(AMchangeHashesSize(&last_sync), 0); + AMsyncHave const* have0; + assert_true(AMitemToSyncHave(AMitemsNext(&haves, 1), &have0)); + AMitems last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(have0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&last_sync), 0); /* assert.deepStrictEqual(message.have[0].bloom.byteLength, 0) assert.deepStrictEqual(message.changes, []) */ - AMchanges changes = AMsyncMessageChanges(m1); - assert_int_equal(AMchangesSize(&changes), 0); + AMitems changes = AMstackItems(stack_ptr, AMsyncMessageChanges(m1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&changes), 0); } /** * \brief should not reply if we have no data as well */ -static void test_should_not_reply_if_we_have_no_data_as_well(void **state) { +static void test_should_not_reply_if_we_have_no_data_as_well(void** state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* const m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage( - test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* n2.receiveSyncMessage(s2, m1) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const m2 = n2.generateSyncMessage(s2) assert.deepStrictEqual(m2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief repos with equal heads do not need a reply message */ -static void test_repos_with_equal_heads_do_not_need_a_reply_message(void **state) { +static void test_repos_with_equal_heads_do_not_need_a_reply_message(void** state) { /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* make two nodes with the same changes */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush(&test_state->stack, - AMmapPutObject(test_state->n1, - AM_ROOT, - AMstr("n"), - AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* n2.applyChanges(n1.getChanges([])) */ - AMchanges const changes = AMpush(&test_state->stack, - AMgetChanges(test_state->n1, NULL), - AM_VALUE_CHANGES, - cmocka_cb).changes; - AMfree(AMapplyChanges(test_state->n2, &changes)); + AMitems const items = + AMstackItems(stack_ptr, AMgetChanges(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &items), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* generate a naive sync message */ /* const m1 = n1.generateSyncMessage(s1) if (m1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* m1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + AMsyncMessage const* m1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &m1)); /* assert.deepStrictEqual(s1.lastSentHeads, n1.getHeads()) */ - AMchangeHashes const last_sent_heads = AMsyncStateLastSentHeads( - test_state->s1 - ); - AMchangeHashes const heads = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&last_sent_heads, &heads), 0); + AMitems const last_sent_heads = + AMstackItems(stack_ptr, AMsyncStateLastSentHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems const heads = + AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&last_sent_heads, &heads)); /* */ /* heads are equal so this message should be null */ /* n2.receiveSyncMessage(s2, m1) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, m1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, m1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* const m2 = n2.generateSyncMessage(s2) assert.strictEqual(m2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief n1 should offer all changes to n2 when starting from nothing */ -static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void **state) { +static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(void** state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - + AMstack** stack_ptr = &test_state->base_state->stack; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush( - &test_state->stack, - AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -254,26 +238,24 @@ static void test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing(vo /** * \brief should sync peers where one has commits the other does not */ -static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void **state) { +static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void** state) { /* const n1 = create(), n2 = create() */ TestState* test_state = *state; - + AMstack** stack_ptr = &test_state->base_state->stack; /* make changes for n1 that n2 should request */ /* const list = n1.putObject("_root", "n", []) */ - AMobjId const* const list = AMpush( - &test_state->stack, - AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const list = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("n"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.insert(list, i, i) */ - AMfree(AMlistPutUint(test_state->n1, AM_ROOT, i, true, i)); + AMstackItem(NULL, AMlistPutUint(test_state->n1, list, i, true, i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -287,19 +269,20 @@ static void test_should_sync_peers_where_one_has_commits_the_other_does_not(void /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state(void **state) { +static void test_should_work_with_prior_sync_state(void** state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -308,10 +291,10 @@ static void test_should_work_with_prior_sync_state(void **state) { /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -325,326 +308,333 @@ static void test_should_work_with_prior_sync_state(void **state) { /** * \brief should not generate messages once synced */ -static void test_should_not_generate_messages_once_synced(void **state) { +static void test_should_not_generate_messages_once_synced(void** state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("abc123")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); - AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("def456")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* let message, patch for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* n1 reports what it has */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* message; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* */ /* n2 receives that message and sends changes along with what it has */ /* n2.receiveSyncMessage(s2, message) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - AMchanges message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); + AMitems message_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 5); /* */ /* n1 receives the changes and replies with the changes it now knows that * n2 needs */ /* n1.receiveSyncMessage(s1, message) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 5); + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 5); /* */ /* n2 applies the changes and sends confirmation ending the exchange */ /* n2.receiveSyncMessage(s2, message) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n2.generateSyncMessage(s2) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* */ /* n1 receives the message and has nothing more to say */ /* n1.receiveSyncMessage(s1, message) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, message)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, message), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* message = n1.generateSyncMessage(s1) assert.deepStrictEqual(message, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* //assert.deepStrictEqual(patch, null) // no changes arrived */ /* */ /* n2 also has nothing left to say */ /* message = n2.generateSyncMessage(s2) assert.deepStrictEqual(message, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); } /** * \brief should allow simultaneous messages during synchronization */ -static void test_should_allow_simultaneous_messages_during_synchronization(void **state) { +static void test_should_allow_simultaneous_messages_during_synchronization(void** state) { /* create & synchronize two nodes */ /* const n1 = create('abc123'), n2 = create('def456') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMfree(AMsetActorId(test_state->n1, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("abc123")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); - AMfree(AMsetActorId(test_state->n2, AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("def456")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id)); + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("abc123")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n1, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("def456")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMstackItem(NULL, AMsetActorId(test_state->n2, actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n2.put("_root", "y", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("y"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* const head1 = n1.getHeads()[0], head2 = n2.getHeads()[0] */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMbyteSpan const head1 = AMchangeHashesNext(&heads1, 1); - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMbyteSpan const head2 = AMchangeHashesNext(&heads2, 1); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan head1; + assert_true(AMitemToChangeHash(AMitemsNext(&heads1, 1), &head1)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan head2; + assert_true(AMitemToChangeHash(AMitemsNext(&heads2, 1), &head2)); /* */ /* both sides report what they have but have no shared peer state */ /* let msg1to2, msg2to1 msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* msg1to2; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, - test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + AMsyncMessage const* msg2to1; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - AMchanges msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, 0 */ - AMsyncHaves msg1to2_haves = AMsyncMessageHaves(msg1to2); - AMsyncHave const* msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); - AMchangeHashes msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); - assert_int_equal(AMchangeHashesSize(&msg1to2_last_sync), 0); + AMitems msg1to2_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync.length, + * 0 */ + AMitems msg1to2_haves = + AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + AMsyncHave const* msg1to2_have; + assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); + AMitems msg1to2_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&msg1to2_last_sync), 0); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - AMchanges msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 0); - /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, 0 */ - AMsyncHaves msg2to1_haves = AMsyncMessageHaves(msg2to1); - AMsyncHave const* msg2to1_have = AMsyncHavesNext(&msg2to1_haves, 1); - AMchangeHashes msg2to1_last_sync = AMsyncHaveLastSync(msg2to1_have); - assert_int_equal(AMchangeHashesSize(&msg2to1_last_sync), 0); + AMitems msg2to1_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 0); + /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).have[0].lastSync.length, + * 0 */ + AMitems msg2to1_haves = + AMstackItems(stack_ptr, AMsyncMessageHaves(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + AMsyncHave const* msg2to1_have; + assert_true(AMitemToSyncHave(AMitemsNext(&msg2to1_haves, 1), &msg2to1_have)); + AMitems msg2to1_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg2to1_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&msg2to1_last_sync), 0); /* */ /* n1 and n2 receive that message and update sync state but make no patc */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* */ /* now both reply with their local changes that the other lacks * (standard warning that 1% of the time this will result in a "needs" * message) */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 5) */ - msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 5); + msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 5); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 5) */ - msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 5); + msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 5); /* */ /* both should now apply the changes and update the frontend */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, - test_state->s1, - msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n1.getMissingDeps(), []) */ - AMchangeHashes missing_deps = AMpush(&test_state->stack, - AMgetMissingDeps(test_state->n1, NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesSize(&missing_deps), 0); + AMitems missing_deps = + AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n1, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch1, null) assert.deepStrictEqual(n1.materialize(), { x: 4, y: 4 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); + uint64_t uint; + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("y"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); /* */ /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(n2.getMissingDeps(), []) */ - missing_deps = AMpush(&test_state->stack, - AMgetMissingDeps(test_state->n2, NULL), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesSize(&missing_deps), 0); + missing_deps = + AMstackItems(stack_ptr, AMgetMissingDeps(test_state->n2, NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_int_equal(AMitemsSize(&missing_deps), 0); /* //assert.notDeepStrictEqual(patch2, null) assert.deepStrictEqual(n2.materialize(), { x: 4, y: 4 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n2, AM_ROOT, AMstr("y"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 4); /* */ /* The response acknowledges the changes received and sends no further * changes */ /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).changes.length, 0) */ - msg1to2_changes = AMsyncMessageChanges(msg1to2); - assert_int_equal(AMchangesSize(&msg1to2_changes), 0); + msg1to2_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg1to2_changes), 0); /* msg2to1 = n2.generateSyncMessage(s2) - if (msg2to1 === null) { throw new RangeError("message should not be null") */ - msg2to1 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (msg2to1 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n2, test_state->s2), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg2to1)); /* assert.deepStrictEqual(decodeSyncMessage(msg2to1).changes.length, 0) */ - msg2to1_changes = AMsyncMessageChanges(msg2to1); - assert_int_equal(AMchangesSize(&msg2to1_changes), 0); + msg2to1_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(msg2to1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&msg2to1_changes), 0); /* */ /* After receiving acknowledgements, their shared heads should be equal */ /* n1.receiveSyncMessage(s1, msg2to1) */ - AMfree(AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n1, test_state->s1, msg2to1), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n2.receiveSyncMessage(s2, msg1to2) */ - AMfree(AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2)); + AMstackItem(NULL, AMreceiveSyncMessage(test_state->n2, test_state->s2, msg1to2), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* assert.deepStrictEqual(s1.sharedHeads, [head1, head2].sort()) */ - AMchangeHashes s1_shared_heads = AMsyncStateSharedHeads(test_state->s1); - assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, - head1.src, - head1.count); - assert_memory_equal(AMchangeHashesNext(&s1_shared_heads, 1).src, - head2.src, - head2.count); + AMitems s1_shared_heads = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan s1_shared_change_hash; + assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); + assert_memory_equal(s1_shared_change_hash.src, head1.src, head1.count); + assert_true(AMitemToChangeHash(AMitemsNext(&s1_shared_heads, 1), &s1_shared_change_hash)); + assert_memory_equal(s1_shared_change_hash.src, head2.src, head2.count); /* assert.deepStrictEqual(s2.sharedHeads, [head1, head2].sort()) */ - AMchangeHashes s2_shared_heads = AMsyncStateSharedHeads(test_state->s2); - assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, - head1.src, - head1.count); - assert_memory_equal(AMchangeHashesNext(&s2_shared_heads, 1).src, - head2.src, - head2.count); + AMitems s2_shared_heads = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan s2_shared_change_hash; + assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); + assert_memory_equal(s2_shared_change_hash.src, head1.src, head1.count); + assert_true(AMitemToChangeHash(AMitemsNext(&s2_shared_heads, 1), &s2_shared_change_hash)); + assert_memory_equal(s2_shared_change_hash.src, head2.src, head2.count); /* //assert.deepStrictEqual(patch1, null) //assert.deepStrictEqual(patch2, null) */ /* */ /* We're in sync, no more messages required */ /* msg1to2 = n1.generateSyncMessage(s1) assert.deepStrictEqual(msg1to2, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n1, test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* msg2to1 = n2.generateSyncMessage(s2) assert.deepStrictEqual(msg2to1, null) */ - AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n2, test_state->s2), - AM_VALUE_VOID, - cmocka_cb); + AMstackItem(NULL, AMgenerateSyncMessage(test_state->n2, test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* If we make one more change and start another sync then its lastSync * should be updated */ /* n1.put("_root", "x", 5) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* msg1to2 = n1.generateSyncMessage(s1) - if (msg1to2 === null) { throw new RangeError("message should not be null") */ - msg1to2 = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; - /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, [head1, head2].sort( */ - msg1to2_haves = AMsyncMessageHaves(msg1to2); - msg1to2_have = AMsyncHavesNext(&msg1to2_haves, 1); - msg1to2_last_sync = AMsyncHaveLastSync(msg1to2_have); - AMbyteSpan msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); + if (msg1to2 === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &msg1to2)); + /* assert.deepStrictEqual(decodeSyncMessage(msg1to2).have[0].lastSync, + * [head1, head2].sort( */ + msg1to2_haves = AMstackItems(stack_ptr, AMsyncMessageHaves(msg1to2), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_HAVE)); + assert_true(AMitemToSyncHave(AMitemsNext(&msg1to2_haves, 1), &msg1to2_have)); + msg1to2_last_sync = + AMstackItems(stack_ptr, AMsyncHaveLastSync(msg1to2_have), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMbyteSpan msg1to2_last_sync_next; + assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); assert_int_equal(msg1to2_last_sync_next.count, head1.count); assert_memory_equal(msg1to2_last_sync_next.src, head1.src, head1.count); - msg1to2_last_sync_next = AMchangeHashesNext(&msg1to2_last_sync, 1); + assert_true(AMitemToChangeHash(AMitemsNext(&msg1to2_last_sync, 1), &msg1to2_last_sync_next)); assert_int_equal(msg1to2_last_sync_next.count, head2.count); assert_memory_equal(msg1to2_last_sync_next.src, head2.src, head2.count); } @@ -652,87 +642,89 @@ static void test_should_allow_simultaneous_messages_during_synchronization(void /** * \brief should assume sent changes were received until we hear otherwise */ -static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void **state) { +static void test_should_assume_sent_changes_were_received_until_we_hear_otherwise(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* let message = null */ /* */ /* const items = n1.putObject("_root", "items", []) */ - AMobjId const* items = AMpush(&test_state->stack, - AMmapPutObject(test_state->n1, - AM_ROOT, - AMstr("items"), - AM_OBJ_TYPE_LIST), - AM_VALUE_OBJ_ID, - cmocka_cb).obj_id; + AMobjId const* const items = + AMitemObjId(AMstackItem(stack_ptr, AMmapPutObject(test_state->n1, AM_ROOT, AMstr("items"), AM_OBJ_TYPE_LIST), + cmocka_cb, AMexpect(AM_VAL_TYPE_OBJ_TYPE))); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* n1.push(items, "x") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("x")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - AMsyncMessage const* message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, - test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be null") + */ + AMsyncMessage const* message; + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - AMchanges message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + AMitems message_changes = + AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); /* */ /* n1.push(items, "y") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("y")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); /* */ /* n1.push(items, "z") */ - AMfree(AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z"))); + AMstackItem(NULL, AMlistPutStr(test_state->n1, items, SIZE_MAX, true, AMstr("z")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* message = n1.generateSyncMessage(s1) - if (message === null) { throw new RangeError("message should not be null") */ - message = AMpush(&test_state->stack, - AMgenerateSyncMessage(test_state->n1, test_state->s1), - AM_VALUE_SYNC_MESSAGE, - cmocka_cb).sync_message; + if (message === null) { throw new RangeError("message should not be + null") */ + assert_true(AMitemToSyncMessage(AMstackItem(stack_ptr, AMgenerateSyncMessage(test_state->n1, test_state->s1), + cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_MESSAGE)), + &message)); /* assert.deepStrictEqual(decodeSyncMessage(message).changes.length, 1) */ - message_changes = AMsyncMessageChanges(message); - assert_int_equal(AMchangesSize(&message_changes), 1); + message_changes = AMstackItems(stack_ptr, AMsyncMessageChanges(message), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); + assert_int_equal(AMitemsSize(&message_changes), 1); } /** * \brief should work regardless of who initiates the exchange */ -static void test_should_work_regardless_of_who_initiates_the_exchange(void **state) { +static void test_should_work_regardless_of_who_initiates_the_exchange(void** state) { /* create & synchronize two nodes */ /* const n1 = create(), n2 = create() const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 5; i++) { */ for (size_t i = 0; i != 5; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -742,10 +734,10 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void **sta /* for (let i = 5; i < 10; i++) { */ for (size_t i = 5; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -759,24 +751,26 @@ static void test_should_work_regardless_of_who_initiates_the_exchange(void **sta /** * \brief should work without prior sync state */ -static void test_should_work_without_prior_sync_state(void **state) { - /* Scenario: ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 - * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- c15 <-- c16 <-- c17 - * lastSync is undefined. */ +static void test_should_work_without_prior_sync_state(void** state) { + /* Scenario: ,-- + * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 + * <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- + * c15 <-- c16 <-- c17 lastSync is undefined. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2) */ @@ -785,19 +779,19 @@ static void test_should_work_without_prior_sync_state(void **state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ @@ -805,15 +799,9 @@ static void test_should_work_without_prior_sync_state(void **state) { /* sync(n1, n2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -821,25 +809,27 @@ static void test_should_work_without_prior_sync_state(void **state) { /** * \brief should work with prior sync state */ -static void test_should_work_with_prior_sync_state_2(void **state) { +static void test_should_work_with_prior_sync_state_2(void** state) { /* Scenario: - * ,-- c10 <-- c11 <-- c12 <-- c13 <-- c14 - * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 <-- c9 <-+ - * `-- c15 <-- c16 <-- c17 - * lastSync is c9. */ + * ,-- + * c10 <-- c11 <-- c12 <-- c13 <-- c14 c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 + * <-- c6 <-- c7 <-- c8 <-- c9 <-+ + * `-- + * c15 <-- c16 <-- c17 lastSync is c9. */ /* */ /* create two peers both with divergent commits */ /* const n1 = create('01234567'), n2 = create('89abcdef') let s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 10; i++) { */ for (size_t i = 0; i != 10; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -848,54 +838,44 @@ static void test_should_work_with_prior_sync_state_2(void **state) { /* for (let i = 10; i < 15; i++) { */ for (size_t i = 10; i != 15; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* for (let i = 15; i < 18; i++) { */ for (size_t i = 15; i != 18; ++i) { /* n2.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.commit("", 0) */ - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan encoded = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* s1 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded.src, encoded.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); + AMsyncState* s1; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s1)); /* s2 = decodeSyncState(encodeSyncState(s2)) */ - encoded = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s2), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* s2 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded.src, - encoded.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded)); + AMsyncState* s2; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded.src, encoded.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s2)); /* */ /* assert.notDeepStrictEqual(n1.materialize(), n2.materialize()) */ assert_false(AMequal(test_state->n1, test_state->n2)); /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, s1, s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -903,39 +883,39 @@ static void test_should_work_with_prior_sync_state_2(void **state) { /** * \brief should ensure non-empty state after sync */ -static void test_should_ensure_non_empty_state_after_sync(void **state) { +static void test_should_ensure_non_empty_state_after_sync(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(s1.sharedHeads, n1.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes shared_heads1 = AMsyncStateSharedHeads(test_state->s1); - assert_int_equal(AMchangeHashesCmp(&shared_heads1, &heads1), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems shared_heads1 = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&shared_heads1, &heads1)); /* assert.deepStrictEqual(s2.sharedHeads, n1.getHeads()) */ - AMchangeHashes shared_heads2 = AMsyncStateSharedHeads(test_state->s2); - assert_int_equal(AMchangeHashesCmp(&shared_heads2, &heads1), 0); + AMitems shared_heads2 = + AMstackItems(stack_ptr, AMsyncStateSharedHeads(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&shared_heads2, &heads1)); } /** * \brief should re-sync after one node crashed with data loss */ -static void test_should_resync_after_one_node_crashed_with_data_loss(void **state) { +static void test_should_resync_after_one_node_crashed_with_data_loss(void** state) { /* Scenario: (r) (n2) (n1) * c0 <-- c1 <-- c2 <-- c3 <-- c4 <-- c5 <-- c6 <-- c7 <-- c8 * n2 has changes {c0, c1, c2}, n1's lastSync is c5, and n2's lastSync @@ -946,15 +926,16 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat let s1 = initSyncState() const s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes, which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); @@ -963,28 +944,25 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* let r let rSyncState ;[r, rSyncState] = [n2.clone(), s2.clone()] */ - AMdoc* r = AMpush(&test_state->stack, - AMclone(test_state->n2), - AM_VALUE_DOC, - cmocka_cb).doc; - AMbyteSpan const encoded_s2 = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s2), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* sync_state_r = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_s2.src, - encoded_s2.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMdoc* r; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMclone(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &r)); + AMbyteSpan encoded_s2; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s2), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &encoded_s2)); + AMsyncState* sync_state_r; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s2.src, encoded_s2.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &sync_state_r)); /* */ /* sync another few commits */ /* for (let i = 3; i < 6; i++) { */ for (size_t i = 3; i != 6; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -992,15 +970,9 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* */ /* everyone should be on the same page here */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ @@ -1009,132 +981,106 @@ static void test_should_resync_after_one_node_crashed_with_data_loss(void **stat /* for (let i = 6; i < 9; i++) { */ for (size_t i = 6; i != 9; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* s1 = decodeSyncState(encodeSyncState(s1)) */ - AMbyteSpan const encoded_s1 = AMpush(&test_state->stack, - AMsyncStateEncode(test_state->s1), - AM_VALUE_BYTES, - cmocka_cb).bytes; - AMsyncState* const s1 = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_s1.src, - encoded_s1.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded_s1; + assert_true( + AMitemToBytes(AMstackItem(stack_ptr, AMsyncStateEncode(test_state->s1), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), + &encoded_s1)); + AMsyncState* s1; + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_s1.src, encoded_s1.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &s1)); /* rSyncState = decodeSyncState(encodeSyncState(rSyncState)) */ - AMbyteSpan const encoded_r = AMpush(&test_state->stack, - AMsyncStateEncode(sync_state_r), - AM_VALUE_BYTES, - cmocka_cb).bytes; - sync_state_r = AMpush(&test_state->stack, - AMsyncStateDecode(encoded_r.src, encoded_r.count), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMbyteSpan encoded_r; + assert_true(AMitemToBytes( + AMstackItem(stack_ptr, AMsyncStateEncode(sync_state_r), cmocka_cb, AMexpect(AM_VAL_TYPE_BYTES)), &encoded_r)); + assert_true(AMitemToSyncState(AMstackItem(stack_ptr, AMsyncStateDecode(encoded_r.src, encoded_r.count), cmocka_cb, + AMexpect(AM_VAL_TYPE_SYNC_STATE)), + &sync_state_r)); /* */ /* assert.notDeepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads_r = AMpush(&test_state->stack, - AMgetHeads(r), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_not_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_false(AMitemsEqual(&heads1, &heads_r)); /* assert.notDeepStrictEqual(n1.materialize(), r.materialize()) */ assert_false(AMequal(test_state->n1, r)); /* assert.deepStrictEqual(n1.materialize(), { x: 8 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 8); + uint64_t uint; + assert_true(AMitemToUint(AMstackItem(stack_ptr, AMmapGet(test_state->n1, AM_ROOT, AMstr("x"), NULL), cmocka_cb, + AMexpect(AM_VAL_TYPE_UINT)), + &uint)); + assert_int_equal(uint, 8); /* assert.deepStrictEqual(r.materialize(), { x: 2 }) */ - assert_int_equal(AMpush(&test_state->stack, - AMmapGet(r, AM_ROOT, AMstr("x"), NULL), - AM_VALUE_UINT, - cmocka_cb).uint, 2); + assert_true(AMitemToUint( + AMstackItem(stack_ptr, AMmapGet(r, AM_ROOT, AMstr("x"), NULL), cmocka_cb, AMexpect(AM_VAL_TYPE_UINT)), &uint)); + assert_int_equal(uint, 2); /* sync(n1, r, s1, rSyncState) */ sync(test_state->n1, r, test_state->s1, sync_state_r); /* assert.deepStrictEqual(n1.getHeads(), r.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - heads_r = AMpush(&test_state->stack, - AMgetHeads(r), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads_r), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + heads_r = AMstackItems(stack_ptr, AMgetHeads(r), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads_r)); /* assert.deepStrictEqual(n1.materialize(), r.materialize()) */ assert_true(AMequal(test_state->n1, r)); } /** - * \brief should re-sync after one node experiences data loss without disconnecting + * \brief should re-sync after one node experiences data loss without + * disconnecting */ -static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void **state) { +static void test_should_resync_after_one_node_experiences_data_loss_without_disconnecting(void** state) { /* const n1 = create('01234567'), n2 = create('89abcdef') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; + AMstack** stack_ptr = &test_state->base_state->stack; /* */ /* n1 makes three changes which we sync to n2 */ /* for (let i = 0; i < 3; i++) { */ for (size_t i = 0; i != 3; ++i) { /* n1.put("_root", "x", i) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.commit("", 0) */ - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); - /* { */ + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* */ /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); /* */ /* const n2AfterDataLoss = create('89abcdef') */ - AMdoc* n2_after_data_loss = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("89abcdef")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("89abcdef")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n2_after_data_loss; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), + &n2_after_data_loss)); /* */ /* "n2" now has no data, but n1 still thinks it does. Note we don't do * decodeSyncState(encodeSyncState(s1)) in order to simulate data loss * without disconnecting */ /* sync(n1, n2AfterDataLoss, s1, initSyncState()) */ - AMsyncState* s2_after_data_loss = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMsyncState* s2_after_data_loss; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s2_after_data_loss)); sync(test_state->n1, n2_after_data_loss, test_state->s1, s2_after_data_loss); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1142,33 +1088,33 @@ static void test_should_resync_after_one_node_experiences_data_loss_without_disc /** * \brief should handle changes concurrent to the last sync heads */ -static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void **state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98' */ +static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void** state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = + * create('fedcba98' */ TestState* test_state = *state; - AMdoc* n3 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("fedcba98")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; - /* const s12 = initSyncState(), s21 = initSyncState(), s23 = initSyncState(), s32 = initSyncState( */ + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n3; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); + /* const s12 = initSyncState(), s21 = initSyncState(), s23 = + * initSyncState(), s32 = initSyncState( */ AMsyncState* s12 = test_state->s1; AMsyncState* s21 = test_state->s2; - AMsyncState* s23 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; - AMsyncState* s32 = AMpush(&test_state->stack, - AMsyncStateInit(), - AM_VALUE_SYNC_STATE, - cmocka_cb).sync_state; + AMsyncState* s23; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s23)); + AMsyncState* s32; + assert_true(AMitemToSyncState( + AMstackItem(stack_ptr, AMsyncStateInit(), cmocka_cb, AMexpect(AM_VAL_TYPE_SYNC_STATE)), &s32)); /* */ /* Change 1 is known to all three nodes */ /* //n1 = Automerge.change(n1, {time: 0}, doc => doc.x = 1) */ /* n1.put("_root", "x", 1); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); @@ -1177,47 +1123,38 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void * /* */ /* Change 2 is known to n1 and n2 */ /* n1.put("_root", "x", 2); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* */ /* Each of the three nodes makes one change (changes 3, 4, 5) */ /* n1.put("_root", "x", 3); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "x", 4); n2.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4)); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("x"), 4), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n3.put("_root", "x", 5); n3.commit("", 0) */ - AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5)); - AMfree(AMcommit(n3, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 5), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* Apply n3's latest change to n2. */ /* let change = n3.getLastLocalChange() if (change === null) throw new RangeError("no local change") */ - AMchanges changes = AMpush(&test_state->stack, - AMgetLastLocalChange(n3), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems changes = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change]) */ - AMfree(AMapplyChanges(test_state->n2, &changes)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &changes), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* */ /* Now sync n1 and n2. n3's change is concurrent to n1 and n2's last sync * heads */ /* sync(n1, n2, s12, s21) */ sync(test_state->n1, test_state->n2, s12, s21); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1225,39 +1162,35 @@ static void test_should_handle_changes_concurrrent_to_the_last_sync_heads(void * /** * \brief should handle histories with lots of branching and merging */ -static void test_should_handle_histories_with_lots_of_branching_and_merging(void **state) { - /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = create('fedcba98') - const s1 = initSyncState(), s2 = initSyncState() */ +static void test_should_handle_histories_with_lots_of_branching_and_merging(void** state) { + /* const n1 = create('01234567'), n2 = create('89abcdef'), n3 = + create('fedcba98') const s1 = initSyncState(), s2 = initSyncState() */ TestState* test_state = *state; - AMdoc* n3 = AMpush(&test_state->stack, - AMcreate(AMpush(&test_state->stack, - AMactorIdInitStr(AMstr("fedcba98")), - AM_VALUE_ACTOR_ID, - cmocka_cb).actor_id), - AM_VALUE_DOC, - cmocka_cb).doc; + AMstack** stack_ptr = &test_state->base_state->stack; + AMactorId const* actor_id; + assert_true(AMitemToActorId( + AMstackItem(stack_ptr, AMactorIdFromStr(AMstr("fedcba98")), cmocka_cb, AMexpect(AM_VAL_TYPE_ACTOR_ID)), + &actor_id)); + AMdoc* n3; + assert_true(AMitemToDoc(AMstackItem(stack_ptr, AMcreate(actor_id), cmocka_cb, AMexpect(AM_VAL_TYPE_DOC)), &n3)); /* n1.put("_root", "x", 0); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("x"), 0), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* let change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMchanges change1 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change1 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change1]) */ - AMfree(AMapplyChanges(test_state->n2, &change1)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* let change2 = n1.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMchanges change2 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change2 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n3.applyChanges([change2]) */ - AMfree(AMapplyChanges(n3, &change2)); + AMstackItem(NULL, AMapplyChanges(n3, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n3.put("_root", "x", 1); n3.commit("", 0) */ - AMfree(AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1)); - AMfree(AMcommit(n3, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(n3, AM_ROOT, AMstr("x"), 1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(n3, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* - n1c1 <------ n1c2 <------ n1c3 <-- etc. <-- n1c20 <------ n1c21 * / \/ \/ \/ @@ -1269,28 +1202,24 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void /* for (let i = 1; i < 20; i++) { */ for (size_t i = 1; i != 20; ++i) { /* n1.put("_root", "n1", i); n1.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i)); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n1, AM_ROOT, AMstr("n1"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "n2", i); n2.commit("", 0) */ - AMfree(AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i)); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutUint(test_state->n2, AM_ROOT, AMstr("n2"), i), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* const change1 = n1.getLastLocalChange() if (change1 === null) throw new RangeError("no local change") */ - AMchanges change1 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n1), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change1 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* const change2 = n2.getLastLocalChange() if (change2 === null) throw new RangeError("no local change") */ - AMchanges change2 = AMpush(&test_state->stack, - AMgetLastLocalChange(test_state->n2), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change2 = + AMstackItems(stack_ptr, AMgetLastLocalChange(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n1.applyChanges([change2]) */ - AMfree(AMapplyChanges(test_state->n1, &change2)); + AMstackItem(NULL, AMapplyChanges(test_state->n1, &change2), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n2.applyChanges([change1]) */ - AMfree(AMapplyChanges(test_state->n2, &change1)); - /* { */ + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change1), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); + /* { */ } /* */ /* sync(n1, n2, s1, s2) */ @@ -1300,31 +1229,24 @@ static void test_should_handle_histories_with_lots_of_branching_and_merging(void * the slower code path */ /* const change3 = n2.getLastLocalChange() if (change3 === null) throw new RangeError("no local change") */ - AMchanges change3 = AMpush(&test_state->stack, - AMgetLastLocalChange(n3), - AM_VALUE_CHANGES, - cmocka_cb).changes; + AMitems change3 = AMstackItems(stack_ptr, AMgetLastLocalChange(n3), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE)); /* n2.applyChanges([change3]) */ - AMfree(AMapplyChanges(test_state->n2, &change3)); + AMstackItem(NULL, AMapplyChanges(test_state->n2, &change3), cmocka_cb, AMexpect(AM_VAL_TYPE_VOID)); /* n1.put("_root", "n1", "final"); n1.commit("", 0) */ - AMfree(AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final"))); - AMfree(AMcommit(test_state->n1, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutStr(test_state->n1, AM_ROOT, AMstr("n1"), AMstr("final")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n1, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* n2.put("_root", "n2", "final"); n2.commit("", 0) */ - AMfree(AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final"))); - AMfree(AMcommit(test_state->n2, AMstr(""), &TIME_0)); + AMstackItem(NULL, AMmapPutStr(test_state->n2, AM_ROOT, AMstr("n2"), AMstr("final")), cmocka_cb, + AMexpect(AM_VAL_TYPE_VOID)); + AMstackItem(NULL, AMcommit(test_state->n2, AMstr(""), &TIME_0), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); /* */ /* sync(n1, n2, s1, s2) */ sync(test_state->n1, test_state->n2, test_state->s1, test_state->s2); /* assert.deepStrictEqual(n1.getHeads(), n2.getHeads()) */ - AMchangeHashes heads1 = AMpush(&test_state->stack, - AMgetHeads(test_state->n1), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - AMchangeHashes heads2 = AMpush(&test_state->stack, - AMgetHeads(test_state->n2), - AM_VALUE_CHANGE_HASHES, - cmocka_cb).change_hashes; - assert_int_equal(AMchangeHashesCmp(&heads1, &heads2), 0); + AMitems heads1 = AMstackItems(stack_ptr, AMgetHeads(test_state->n1), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + AMitems heads2 = AMstackItems(stack_ptr, AMgetHeads(test_state->n2), cmocka_cb, AMexpect(AM_VAL_TYPE_CHANGE_HASH)); + assert_true(AMitemsEqual(&heads1, &heads2)); /* assert.deepStrictEqual(n1.materialize(), n2.materialize()) */ assert_true(AMequal(test_state->n1, test_state->n2)); } @@ -1334,20 +1256,26 @@ int run_ported_wasm_sync_tests(void) { cmocka_unit_test_setup_teardown(test_should_send_a_sync_message_implying_no_local_data, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_reply_if_we_have_no_data_as_well, setup, teardown), cmocka_unit_test_setup_teardown(test_repos_with_equal_heads_do_not_need_a_reply_message, setup, teardown), - cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, teardown), + cmocka_unit_test_setup_teardown(test_n1_should_offer_all_changes_to_n2_when_starting_from_nothing, setup, + teardown), + cmocka_unit_test_setup_teardown(test_should_sync_peers_where_one_has_commits_the_other_does_not, setup, + teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_not_generate_messages_once_synced, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_allow_simultaneous_messages_during_synchronization, setup, + teardown), + cmocka_unit_test_setup_teardown(test_should_assume_sent_changes_were_received_until_we_hear_otherwise, setup, + teardown), cmocka_unit_test_setup_teardown(test_should_work_regardless_of_who_initiates_the_exchange, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_without_prior_sync_state, setup, teardown), cmocka_unit_test_setup_teardown(test_should_work_with_prior_sync_state_2, setup, teardown), cmocka_unit_test_setup_teardown(test_should_ensure_non_empty_state_after_sync, setup, teardown), cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_crashed_with_data_loss, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_resync_after_one_node_experiences_data_loss_without_disconnecting, + setup, teardown), cmocka_unit_test_setup_teardown(test_should_handle_changes_concurrrent_to_the_last_sync_heads, setup, teardown), - cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, teardown), + cmocka_unit_test_setup_teardown(test_should_handle_histories_with_lots_of_branching_and_merging, setup, + teardown), }; return cmocka_run_group_tests(tests, NULL, NULL); diff --git a/rust/automerge-c/test/stack_utils.c b/rust/automerge-c/test/stack_utils.c deleted file mode 100644 index f65ea2e5..00000000 --- a/rust/automerge-c/test/stack_utils.c +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include -#include - -/* third-party */ -#include - -/* local */ -#include "cmocka_utils.h" -#include "stack_utils.h" - -void cmocka_cb(AMresultStack** stack, uint8_t discriminant) { - assert_non_null(stack); - assert_non_null(*stack); - assert_non_null((*stack)->result); - if (AMresultStatus((*stack)->result) != AM_STATUS_OK) { - fail_msg_view("%s", AMerrorMessage((*stack)->result)); - } - assert_int_equal(AMresultValue((*stack)->result).tag, discriminant); -} - -int setup_stack(void** state) { - *state = NULL; - return 0; -} - -int teardown_stack(void** state) { - AMresultStack* stack = *state; - AMfreeStack(&stack); - return 0; -} diff --git a/rust/automerge-c/test/stack_utils.h b/rust/automerge-c/test/stack_utils.h deleted file mode 100644 index 473feebc..00000000 --- a/rust/automerge-c/test/stack_utils.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef STACK_UTILS_H -#define STACK_UTILS_H - -#include - -/* local */ -#include - -/** - * \brief Reports an error through a cmocka assertion. - * - * \param[in,out] stack A pointer to a pointer to an `AMresultStack` struct. - * \param[in] discriminant An `AMvalueVariant` enum tag. - * \pre \p stack` != NULL`. - */ -void cmocka_cb(AMresultStack** stack, uint8_t discriminant); - -/** - * \brief Allocates a result stack for storing the results allocated during one - * or more test cases. - * - * \param[in,out] state A pointer to a pointer to an `AMresultStack` struct. - * \pre \p state` != NULL`. - * \warning The `AMresultStack` struct returned through \p state must be - * deallocated with `teardown_stack()` in order to prevent memory leaks. - */ -int setup_stack(void** state); - -/** - * \brief Deallocates a result stack after deallocating any results that were - * stored in it by one or more test cases. - * - * \param[in] state A pointer to a pointer to an `AMresultStack` struct. - * \pre \p state` != NULL`. - */ -int teardown_stack(void** state); - -#endif /* STACK_UTILS_H */ diff --git a/rust/automerge-c/test/str_utils.c b/rust/automerge-c/test/str_utils.c index cc923cb4..2937217a 100644 --- a/rust/automerge-c/test/str_utils.c +++ b/rust/automerge-c/test/str_utils.c @@ -1,5 +1,5 @@ -#include #include +#include /* local */ #include "str_utils.h" diff --git a/rust/automerge-c/test/str_utils.h b/rust/automerge-c/test/str_utils.h index b9985683..14a4af73 100644 --- a/rust/automerge-c/test/str_utils.h +++ b/rust/automerge-c/test/str_utils.h @@ -1,14 +1,17 @@ -#ifndef STR_UTILS_H -#define STR_UTILS_H +#ifndef TESTS_STR_UTILS_H +#define TESTS_STR_UTILS_H /** - * \brief Converts a hexadecimal string into a sequence of bytes. + * \brief Converts a hexadecimal string into an array of bytes. * - * \param[in] hex_str A string. - * \param[in] src A pointer to a contiguous sequence of bytes. - * \param[in] count The number of bytes to copy to \p src. - * \pre \p count `<=` length of \p src. + * \param[in] hex_str A hexadecimal string. + * \param[in] src A pointer to an array of bytes. + * \param[in] count The count of bytes to copy into the array pointed to by + * \p src. + * \pre \p src `!= NULL` + * \pre `sizeof(`\p src `) > 0` + * \pre \p count `<= sizeof(`\p src `)` */ void hex_to_bytes(char const* hex_str, uint8_t* src, size_t const count); -#endif /* STR_UTILS_H */ +#endif /* TESTS_STR_UTILS_H */ diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 57a87167..68b8ec65 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -1,3 +1,4 @@ +use crate::change::LoadError as LoadChangeError; use crate::storage::load::Error as LoadError; use crate::types::{ActorId, ScalarValue}; use crate::value::DataType; @@ -18,6 +19,8 @@ pub enum AutomergeError { Fail, #[error("invalid actor ID `{0}`")] InvalidActorId(String), + #[error(transparent)] + InvalidChangeHashBytes(#[from] InvalidChangeHashSlice), #[error("invalid UTF-8 character at {0}")] InvalidCharacter(usize), #[error("invalid hash {0}")] @@ -39,6 +42,8 @@ pub enum AutomergeError { }, #[error(transparent)] Load(#[from] LoadError), + #[error(transparent)] + LoadChangeError(#[from] LoadChangeError), #[error("increment operations must be against a counter value")] MissingCounter, #[error("hash {0} does not correspond to a change in this document")] diff --git a/scripts/ci/cmake-build b/scripts/ci/cmake-build index f6f9f9b1..25a69756 100755 --- a/scripts/ci/cmake-build +++ b/scripts/ci/cmake-build @@ -16,4 +16,4 @@ C_PROJECT=$THIS_SCRIPT/../../rust/automerge-c; mkdir -p $C_PROJECT/build; cd $C_PROJECT/build; cmake --log-level=ERROR -B . -S .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_SHARED_LIBS=$SHARED_TOGGLE; -cmake --build . --target test_automerge; +cmake --build . --target automerge_test; From 44fa7ac41647fa465ee7baa0bc0ee64e811dded8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 27 Feb 2023 13:12:09 -0700 Subject: [PATCH 40/45] Don't panic on missing deps of change chunks (#538) * Fix doubly-reported ops in load of change chunks Since c3c04128f5f1703007f650ea3104d98334334aab, observers have been called twice when calling Automerge::load() with change chunks. * Better handle change chunks with missing deps Before this change Automerge::load would panic if you passed a change chunk that was missing a dependency, or multiple change chunks not in strict dependency order. After this change these cases will error instead. --- rust/automerge/src/automerge.rs | 38 +++++++++--------- rust/automerge/src/automerge/current_state.rs | 29 ++++++++++++- rust/automerge/src/error.rs | 2 + .../fixtures/two_change_chunks.automerge | Bin 0 -> 177 bytes .../two_change_chunks_compressed.automerge | Bin 0 -> 192 bytes .../two_change_chunks_out_of_order.automerge | Bin 0 -> 177 bytes .../fuzz-crashers/missing_deps.automerge | Bin 0 -> 224 bytes .../missing_deps_compressed.automerge | Bin 0 -> 120 bytes .../missing_deps_subsequent.automerge | Bin 0 -> 180 bytes rust/automerge/tests/test.rs | 13 ++++++ 10 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 rust/automerge/tests/fixtures/two_change_chunks.automerge create mode 100644 rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge create mode 100644 rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge create mode 100644 rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 09c3cc9d..9c45ec51 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -464,6 +464,7 @@ impl Automerge { return Err(load::Error::BadChecksum.into()); } + let mut change: Option = None; let mut am = match first_chunk { storage::Chunk::Document(d) => { tracing::trace!("first chunk is document chunk, inflating"); @@ -501,30 +502,31 @@ impl Automerge { } } storage::Chunk::Change(stored_change) => { - tracing::trace!("first chunk is change chunk, applying"); - let change = Change::new_from_unverified(stored_change.into_owned(), None) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; - let mut am = Self::new(); - am.apply_change(change, &mut observer); - am + tracing::trace!("first chunk is change chunk"); + change = Some( + Change::new_from_unverified(stored_change.into_owned(), None) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, + ); + Self::new() } storage::Chunk::CompressedChange(stored_change, compressed) => { - tracing::trace!("first chunk is compressed change, decompressing and applying"); - let change = Change::new_from_unverified( - stored_change.into_owned(), - Some(compressed.into_owned()), - ) - .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?; - let mut am = Self::new(); - am.apply_change(change, &mut observer); - am + tracing::trace!("first chunk is compressed change"); + change = Some( + Change::new_from_unverified( + stored_change.into_owned(), + Some(compressed.into_owned()), + ) + .map_err(|e| load::Error::InvalidChangeColumns(Box::new(e)))?, + ); + Self::new() } }; - tracing::trace!("first chunk loaded, loading remaining chunks"); + tracing::trace!("loading change chunks"); match load::load_changes(remaining.reset()) { load::LoadedChanges::Complete(c) => { - for change in c { - am.apply_change(change, &mut observer); + am.apply_changes(change.into_iter().chain(c))?; + if !am.queue.is_empty() { + return Err(AutomergeError::MissingDeps); } } load::LoadedChanges::Partial { error, .. } => { diff --git a/rust/automerge/src/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs index 1c1bceed..3f7f4afc 100644 --- a/rust/automerge/src/automerge/current_state.rs +++ b/rust/automerge/src/automerge/current_state.rs @@ -338,9 +338,9 @@ impl<'a, I: Iterator>> Iterator for TextActions<'a, I> { #[cfg(test)] mod tests { - use std::borrow::Cow; + use std::{borrow::Cow, fs}; - use crate::{transaction::Transactable, ObjType, OpObserver, Prop, ReadDoc, Value}; + use crate::{transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, Value}; // Observer ops often carry a "tagged value", which is a value and the OpID of the op which // created that value. For a lot of values (i.e. any scalar value) we don't care about the @@ -887,4 +887,29 @@ mod tests { ]) ); } + + #[test] + fn test_load_changes() { + fn fixture(name: &str) -> Vec { + fs::read("./tests/fixtures/".to_owned() + name).unwrap() + } + + let mut obs = ObserverStub::new(); + let _doc = Automerge::load_with( + &fixture("counter_value_is_ok.automerge"), + crate::OnPartialLoad::Error, + crate::storage::VerificationMode::Check, + Some(&mut obs), + ); + + assert_eq!( + Calls(obs.ops), + Calls(vec![ObserverCall::Put { + obj: crate::ROOT, + prop: "a".into(), + value: ObservedValue::Untagged(crate::ScalarValue::Counter(2000.into()).into()), + conflict: false, + },]) + ); + } } diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 68b8ec65..86dbe9f3 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -48,6 +48,8 @@ pub enum AutomergeError { MissingCounter, #[error("hash {0} does not correspond to a change in this document")] MissingHash(ChangeHash), + #[error("change's deps should already be in the document")] + MissingDeps, #[error("compressed chunk was not a change")] NonChangeCompressed, #[error("id was not an object id")] diff --git a/rust/automerge/tests/fixtures/two_change_chunks.automerge b/rust/automerge/tests/fixtures/two_change_chunks.automerge new file mode 100644 index 0000000000000000000000000000000000000000..1a84b363ccab6161890367b7b6fadd84091acc1a GIT binary patch literal 177 zcmZq8_iCPX___h3C4;~%u8ahNZ6`uDR*U$arwyk>-a69LX7pdFiPNh77Et z%qEOZOkqp~O!bV3jP(p4*a|da`E&KFj46yDlRhQgGJP()b>hw!qP#CRXsF%#9>DfV qvr}yCn>=m|E0~y2tuSKXU}R!qf>{&J2(*Zyo)K&rW4%~XJp%xrEC}cVUk{s_*GfwAzq7vd=R$+BrLv*EZRo)X zjiFO+wp{Gg>{;2ca-QMDjq?~;7#SHD{{L?UnzQ`5`cUCz3gfK9*9|@;-7W5 zAx_SoEv*boUq4)P)0c_q;Jzcx4-GhyGZORCQx%LDI2f6jm_(UP7@e5Hn8FzgnCcno q8S5Dnfw*2Qsh*(~XdB2HMoR_^(-;|1O*3R*g_#622V@2V2m%1g@ISTy literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps.automerge new file mode 100644 index 0000000000000000000000000000000000000000..8a57a0f4c8a82541f9236c878cd22599aefbcce2 GIT binary patch literal 224 zcmZq8_i8>FcHEBfDkJ0W>J_a(?qU6^Yf=o1{~5o&kywAoaLxb!s;Hm{4n%=~4@7_f dT+w7W3mbz0J3uIvfiW3@Dq(DLXvVLM3jvQ0EVKXs literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2c7b123b6805032546ec438597e31a03245b5a79 GIT binary patch literal 120 zcmV-;0EhpDZ%TvQ<(umQZUAHeoBsjf#K?6GIWE|lwH=|kchIwRB>mYqPdl0|$S{b; zlZl!T#tysb@0Cu7tB#1rhSmZA0s_mq^zPs=2xDkrZf9j6G5`nx0s;aR12h3b0#*W7 a0dN9;0Dl300bv1u0e==^e*ggh0RR6R126Ib literal 0 HcmV?d00001 diff --git a/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge b/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge new file mode 100644 index 0000000000000000000000000000000000000000..2fe439afd0c7792801f52a5325a2582478efdd1d GIT binary patch literal 180 zcmZq8_iE-b7ZG8!VGvl5pXAtm&!MU9#x>WYe^O^NGTz(X^8SGVM{-7DUV5s6F$0?@ zvk9XUQy5b?V*yh=Vc^1qZv~et#%p2bkvx1Prjyk;Lcr*+ew Date: Fri, 3 Mar 2023 17:42:40 -0500 Subject: [PATCH 41/45] Suppress clippy warning in parse.rs + bump toolchain (#542) * Fix rust error in parse.rs * Bump toolchain to 1.67.0 --- .github/workflows/ci.yaml | 14 +++++++------- rust/automerge/src/storage/parse.rs | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bfa31bd5..0263f408 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true components: rustfmt - uses: Swatinem/rust-cache@v1 @@ -28,7 +28,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true components: clippy - uses: Swatinem/rust-cache@v1 @@ -42,7 +42,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - name: Build rust docs @@ -118,7 +118,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - name: Install CMocka @@ -136,7 +136,7 @@ jobs: strategy: matrix: toolchain: - - 1.66.0 + - 1.67.0 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -155,7 +155,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - run: ./scripts/ci/build-test @@ -168,7 +168,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.66.0 + toolchain: 1.67.0 default: true - uses: Swatinem/rust-cache@v1 - run: ./scripts/ci/build-test diff --git a/rust/automerge/src/storage/parse.rs b/rust/automerge/src/storage/parse.rs index 54668da4..6751afb4 100644 --- a/rust/automerge/src/storage/parse.rs +++ b/rust/automerge/src/storage/parse.rs @@ -308,6 +308,7 @@ impl<'a> Input<'a> { } /// The bytes behind this input - including bytes which have been consumed + #[allow(clippy::misnamed_getters)] pub(crate) fn bytes(&self) -> &'a [u8] { self.original } From 2c1970f6641ea3fe10976721316ae6d07765e4a1 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Sat, 4 Mar 2023 05:09:08 -0700 Subject: [PATCH 42/45] Fix panic on invalid action (#541) We make the validation on parsing operations in the encoded changes stricter to avoid a possible panic when applying changes. --- rust/automerge/src/automerge.rs | 2 +- rust/automerge/src/change.rs | 2 +- .../src/columnar/encoding/col_error.rs | 2 +- rust/automerge/src/error.rs | 2 +- .../src/storage/change/change_op_columns.rs | 20 ++++++++- rust/automerge/src/types.rs | 40 ++++++++++++------ .../fuzz-crashers/action-is-48.automerge | Bin 0 -> 58 bytes 7 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 rust/automerge/tests/fuzz-crashers/action-is-48.automerge diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index 9c45ec51..0dd82253 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -723,7 +723,7 @@ impl Automerge { obj, Op { id, - action: OpType::from_index_and_value(c.action, c.val).unwrap(), + action: OpType::from_action_and_value(c.action, c.val), key, succ: Default::default(), pred, diff --git a/rust/automerge/src/change.rs b/rust/automerge/src/change.rs index b5cae7df..be467a84 100644 --- a/rust/automerge/src/change.rs +++ b/rust/automerge/src/change.rs @@ -278,7 +278,7 @@ impl From<&Change> for crate::ExpandedChange { let operations = c .iter_ops() .map(|o| crate::legacy::Op { - action: crate::types::OpType::from_index_and_value(o.action, o.val).unwrap(), + action: crate::types::OpType::from_action_and_value(o.action, o.val), insert: o.insert, key: match o.key { StoredKey::Elem(e) if e.is_head() => { diff --git a/rust/automerge/src/columnar/encoding/col_error.rs b/rust/automerge/src/columnar/encoding/col_error.rs index c8d5c5c0..089556b6 100644 --- a/rust/automerge/src/columnar/encoding/col_error.rs +++ b/rust/automerge/src/columnar/encoding/col_error.rs @@ -1,5 +1,5 @@ #[derive(Clone, Debug)] -pub(crate) struct DecodeColumnError { +pub struct DecodeColumnError { path: Path, error: DecodeColErrorKind, } diff --git a/rust/automerge/src/error.rs b/rust/automerge/src/error.rs index 86dbe9f3..62a7b72f 100644 --- a/rust/automerge/src/error.rs +++ b/rust/automerge/src/error.rs @@ -99,7 +99,7 @@ pub struct InvalidElementId(pub String); pub struct InvalidOpId(pub String); #[derive(Error, Debug)] -pub(crate) enum InvalidOpType { +pub enum InvalidOpType { #[error("unrecognized action index {0}")] UnknownAction(u64), #[error("non numeric argument for inc op")] diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index 7c3a65ec..cd1cb150 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -14,6 +14,7 @@ use crate::{ }, }, convert, + error::InvalidOpType, storage::{ change::AsChangeOp, columns::{ @@ -22,6 +23,7 @@ use crate::{ RawColumns, }, types::{ElemId, ObjId, OpId, ScalarValue}, + OpType, }; const OBJ_COL_ID: ColumnId = ColumnId::new(0); @@ -276,7 +278,12 @@ impl ChangeOpsColumns { #[derive(thiserror::Error, Debug)] #[error(transparent)] -pub struct ReadChangeOpError(#[from] DecodeColumnError); +pub enum ReadChangeOpError { + #[error(transparent)] + DecodeError(#[from] DecodeColumnError), + #[error(transparent)] + InvalidOpType(#[from] InvalidOpType), +} #[derive(Clone)] pub(crate) struct ChangeOpsIter<'a> { @@ -308,6 +315,11 @@ impl<'a> ChangeOpsIter<'a> { let action = self.action.next_in_col("action")?; let val = self.val.next_in_col("value")?; let pred = self.pred.next_in_col("pred")?; + + // This check is necessary to ensure that OpType::from_action_and_value + // cannot panic later in the process. + OpType::validate_action_and_value(action, &val)?; + Ok(Some(ChangeOp { obj, key, @@ -458,10 +470,14 @@ mod tests { action in 0_u64..6, obj in opid(), insert in any::()) -> ChangeOp { + + let val = if action == 5 && !(value.is_int() || value.is_uint()) { + ScalarValue::Uint(0) + } else { value }; ChangeOp { obj: obj.into(), key, - val: value, + val, pred, action, insert, diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 870569e9..2978aa97 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -216,23 +216,35 @@ impl OpType { } } - pub(crate) fn from_index_and_value( - index: u64, - value: ScalarValue, - ) -> Result { - match index { - 0 => Ok(Self::Make(ObjType::Map)), - 1 => Ok(Self::Put(value)), - 2 => Ok(Self::Make(ObjType::List)), - 3 => Ok(Self::Delete), - 4 => Ok(Self::Make(ObjType::Text)), + pub(crate) fn validate_action_and_value( + action: u64, + value: &ScalarValue, + ) -> Result<(), error::InvalidOpType> { + match action { + 0..=4 => Ok(()), 5 => match value { - ScalarValue::Int(i) => Ok(Self::Increment(i)), - ScalarValue::Uint(i) => Ok(Self::Increment(i as i64)), + ScalarValue::Int(_) | ScalarValue::Uint(_) => Ok(()), _ => Err(error::InvalidOpType::NonNumericInc), }, - 6 => Ok(Self::Make(ObjType::Table)), - other => Err(error::InvalidOpType::UnknownAction(other)), + 6 => Ok(()), + _ => Err(error::InvalidOpType::UnknownAction(action)), + } + } + + pub(crate) fn from_action_and_value(action: u64, value: ScalarValue) -> OpType { + match action { + 0 => Self::Make(ObjType::Map), + 1 => Self::Put(value), + 2 => Self::Make(ObjType::List), + 3 => Self::Delete, + 4 => Self::Make(ObjType::Text), + 5 => match value { + ScalarValue::Int(i) => Self::Increment(i), + ScalarValue::Uint(i) => Self::Increment(i as i64), + _ => unreachable!("validate_action_and_value returned NonNumericInc"), + }, + 6 => Self::Make(ObjType::Table), + _ => unreachable!("validate_action_and_value returned UnknownAction"), } } } diff --git a/rust/automerge/tests/fuzz-crashers/action-is-48.automerge b/rust/automerge/tests/fuzz-crashers/action-is-48.automerge new file mode 100644 index 0000000000000000000000000000000000000000..16e6f719a13dd6b1d9eff8488ee651ab7f72bfc3 GIT binary patch literal 58 vcmZq8_i8>{b9^SF0fT@6CSYJ-6J<7GbYco)N@OZvGGH_SqI$Lq{Phd~tz-