diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c2d469d5..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 @@ -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: 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 @@ -136,9 +138,7 @@ jobs: strategy: matrix: toolchain: - - 1.66.0 - - nightly - continue-on-error: ${{ matrix.toolchain == 'nightly' }} + - 1.67.0 steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 @@ -157,7 +157,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 @@ -170,7 +170,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/README.md b/README.md index 94e1bbb8..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 @@ -113,9 +112,16 @@ 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 +# 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/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/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" diff --git a/javascript/package.json b/javascript/package.json index 017c5a54..79309907 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", "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.25", "uuid": "^9.0.0" } } diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index 3b328240..e83b127f 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" @@ -294,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" * }) * ``` @@ -305,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"]) @@ -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 */ diff --git a/javascript/test/basic_test.ts b/javascript/test/basic_test.ts index 5aa1ac34..e34484c4 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/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/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/.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..0c35eebd 100644 --- a/rust/automerge-c/CMakeLists.txt +++ b/rust/automerge-c/CMakeLists.txt @@ -1,97 +1,297 @@ -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) + +# 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 --target=${CARGO_TARGET}) +else() + set(CARGO_BUILD_TYPE "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_TARGET}/${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} 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 + 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 +300,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..0b1c86a1 --- /dev/null +++ b/rust/automerge-c/test/byte_span_tests.c @@ -0,0 +1,119 @@ +#include +#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-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/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/package.json b/rust/automerge-wasm/package.json index cce3199f..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.23", + "version": "0.1.25", "license": "MIT", "files": [ "README.md", 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-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/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/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 cd7c0b98..0dd82253 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; @@ -26,6 +25,8 @@ use crate::{ }; use serde::Serialize; +mod current_state; + #[cfg(test)] mod tests; @@ -35,6 +36,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 @@ -71,26 +81,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, - /// Mapping from change hash to vector clock at this state. - pub(crate) clocks: HashMap, + history_index: HashMap, + /// Graph of changes + change_graph: ChangeGraph, /// 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 { @@ -100,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(), @@ -111,6 +121,50 @@ impl Automerge { } } + pub(crate) fn ops_mut(&mut self) -> &mut OpSet { + &mut self.ops + } + + pub(crate) fn ops(&self) -> &OpSet { + &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. + /// 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 @@ -376,20 +430,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 { @@ -404,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"); @@ -412,23 +473,18 @@ 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(); + 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 { @@ -436,7 +492,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(), @@ -446,33 +502,41 @@ 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, .. } => 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); } Ok(am) } @@ -494,6 +558,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, .. } => { @@ -534,6 +610,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) { @@ -543,7 +624,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); } @@ -551,7 +636,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(()) @@ -629,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, @@ -672,7 +766,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, @@ -688,7 +782,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, @@ -698,11 +792,6 @@ impl Automerge { bytes } - /// Mark the document as saved - pub fn mark_saved(&mut self) { - self.saved = self.get_heads(); - } - /// 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 @@ -736,16 +825,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(()) } @@ -753,7 +834,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 @@ -787,26 +868,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 { @@ -832,24 +895,10 @@ 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); history_index @@ -906,7 +955,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 { @@ -1109,9 +1158,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) } @@ -1135,10 +1183,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) } @@ -1162,10 +1209,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) } @@ -1184,20 +1230,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 { @@ -1215,18 +1261,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 { @@ -1250,7 +1296,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 { @@ -1325,7 +1371,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/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs new file mode 100644 index 00000000..3f7f4afc --- /dev/null +++ b/rust/automerge/src/automerge/current_state.rs @@ -0,0 +1,915 @@ +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, fs}; + + 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 + // 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()), + }, + ]) + ); + } + + #[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/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/change.rs b/rust/automerge/src/change.rs index e8761148..be467a84 100644 --- a/rust/automerge/src/change.rs +++ b/rust/automerge/src/change.rs @@ -112,17 +112,6 @@ impl Change { self.stored.iter_ops() } - pub fn difference(&self) -> (u32, u32) { - self.iter_ops() - .map(|op| match op.action { - 1 => (1, 0), - 3 => (0, 1), - _ => (0, 0), - }) - .reduce(|acc, x| (acc.0 + x.0, acc.1 + x.1)) - .unwrap_or_default() - } - pub fn extra_bytes(&self) -> &[u8] { self.stored.extra_bytes() } @@ -289,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/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/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/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/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/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/error.rs b/rust/automerge/src/error.rs index 0f024d86..62a7b72f 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; @@ -7,7 +8,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}")] @@ -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,10 +42,14 @@ 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")] 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")] @@ -92,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/lib.rs b/rust/automerge/src/lib.rs index bafd8983..cbb535af 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; @@ -264,7 +264,6 @@ mod op_tree; mod parents; mod query; mod read; -mod sequence_tree; mod storage; pub mod sync; pub mod transaction; @@ -274,7 +273,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}; @@ -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; 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 6cc64e79..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 opset = Automerge::new(); - for (obj, op) in self.ops { - opset.insert_op_with_observer(&obj, op, self.observer); - } - opset.ops - } -} diff --git a/rust/automerge/src/op_tree/node.rs b/rust/automerge/src/op_tree/node.rs index ea7fbf48..ed1b7646 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 = 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; } } - 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) } } } 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 7c3a65ec..86ec59c2 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,14 @@ 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), + #[error("counter too large")] + CounterTooLarge, +} #[derive(Clone)] pub(crate) struct ChangeOpsIter<'a> { @@ -308,6 +317,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 +472,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/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/storage/parse.rs b/rust/automerge/src/storage/parse.rs index 64419fda..6751afb4 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>; @@ -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 } 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), + } + }); } } diff --git a/rust/automerge/src/sync.rs b/rust/automerge/src/sync.rs index 5d71d989..d6dc2580 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 @@ -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, diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index 7e7db17d..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; @@ -98,7 +97,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 +152,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 +272,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 +327,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 +341,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 +375,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 +393,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 +420,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 +485,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 +546,7 @@ impl TransactionInner { index, del, values, - splice_type: SpliceType::Text(text, doc.text_encoding), + splice_type: SpliceType::Text(text, doc.text_encoding()), }, ) } @@ -568,13 +563,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 +585,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 +604,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 +615,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 +625,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 +637,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 +658,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 +681,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); } } diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 870569e9..468986ec 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"), } } } @@ -427,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 00000000..700342a2 Binary files /dev/null and b/rust/automerge/tests/fixtures/64bit_obj_id_change.automerge differ 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 00000000..6beb57fe Binary files /dev/null and b/rust/automerge/tests/fixtures/64bit_obj_id_doc.automerge differ 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 00000000..2290b446 Binary files /dev/null and b/rust/automerge/tests/fixtures/counter_value_has_incorrect_meta.automerge differ diff --git a/rust/automerge/tests/fixtures/counter_value_is_ok.automerge b/rust/automerge/tests/fixtures/counter_value_is_ok.automerge new file mode 100644 index 00000000..fdc59896 Binary files /dev/null and b/rust/automerge/tests/fixtures/counter_value_is_ok.automerge differ 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 00000000..831346f7 Binary files /dev/null and b/rust/automerge/tests/fixtures/counter_value_is_overlong.automerge differ 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 00000000..1a84b363 Binary files /dev/null and b/rust/automerge/tests/fixtures/two_change_chunks.automerge differ diff --git a/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge b/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge new file mode 100644 index 00000000..9e3f305f Binary files /dev/null and b/rust/automerge/tests/fixtures/two_change_chunks_compressed.automerge differ diff --git a/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge b/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge new file mode 100644 index 00000000..9ba0355f Binary files /dev/null and b/rust/automerge/tests/fixtures/two_change_chunks_out_of_order.automerge differ 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 00000000..16e6f719 Binary files /dev/null and b/rust/automerge/tests/fuzz-crashers/action-is-48.automerge differ 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 00000000..8a57a0f4 Binary files /dev/null and b/rust/automerge/tests/fuzz-crashers/missing_deps.automerge differ 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 00000000..2c7b123b Binary files /dev/null and b/rust/automerge/tests/fuzz-crashers/missing_deps_compressed.automerge differ 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 00000000..2fe439af Binary files /dev/null and b/rust/automerge/tests/fuzz-crashers/missing_deps_subsequent.automerge differ diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index ca6c64c0..3be6725e 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -1412,12 +1412,55 @@ 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 load() { + fn check_fixture(name: &str) { + let doc = Automerge::load(&fixture(name)).unwrap(); + let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; + assert_eq!(doc.get(map_id, "a").unwrap().unwrap().0, "b".into()); + } + + check_fixture("two_change_chunks.automerge"); + check_fixture("two_change_chunks_compressed.automerge"); + check_fixture("two_change_chunks_out_of_order.automerge"); +} + #[test] fn negative_64() { let mut doc = Automerge::new(); assert!(doc.transact(|d| { d.put(ROOT, "a", -64_i64) }).is_ok()) } +#[test] +fn obj_id_64bits() { + // this change has an opId of 2**42, which when cast to a 32-bit int gives 0. + // The file should either fail to load (a limit of ~4 billion ops per doc seems reasonable), or be handled correctly. + if let Ok(doc) = Automerge::load(&fixture("64bit_obj_id_change.automerge")) { + let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; + assert!(map_id != ROOT) + } + + // this fixture is the same as the above, but as a document chunk. + if let Ok(doc) = Automerge::load(&fixture("64bit_obj_id_doc.automerge")) { + let map_id = doc.get(ROOT, "a").unwrap().unwrap().1; + assert!(map_id != ROOT) + } +} + #[test] fn bad_change_on_optree_node_boundary() { let mut doc = Automerge::new(); diff --git a/rust/deny.toml b/rust/deny.toml index 54a68a60..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, @@ -172,6 +175,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 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;