use automerge as am; use automerge::transaction::{CommitOptions, Transactable}; 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::obj::{to_obj_id, AMobjId, AMobjType}; use crate::result::{to_result, AMresult, AMvalue}; 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) }}; } 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(), } }}; } /// \struct AMdoc /// \installed_headerfile /// \brief A JSON-like CRDT. #[derive(Clone)] pub struct AMdoc(am::AutoCommit); impl AMdoc { pub fn new(auto_commit: am::AutoCommit) -> Self { Self(auto_commit) } } impl AsRef for AMdoc { fn as_ref(&self) -> &am::AutoCommit { &self.0 } } impl Deref for AMdoc { type Target = am::AutoCommit; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for AMdoc { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// changes must be a valid pointer to an AMchanges. #[no_mangle] pub unsafe extern "C" fn AMapplyChanges( doc: *mut AMdoc, changes: *const AMchanges, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let changes = to_changes!(changes); to_result(doc.apply_changes(changes.as_ref().to_vec())) } /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMclone(doc: *const AMdoc) -> *mut AMresult { let doc = to_doc!(doc); to_result(doc.as_ref().clone()) } /// \memberof AMdoc /// \brief Allocates a new document and initializes it with defaults. /// /// \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. /// \internal /// /// # Safety /// actor_id must be a valid pointer to an AMactorId or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMcreate(actor_id: *const AMactorId) -> *mut AMresult { to_result(match actor_id.as_ref() { Some(actor_id) => am::AutoCommit::new().with_actor(actor_id.as_ref().clone()), None => am::AutoCommit::new(), }) } /// \memberof AMdoc /// \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] 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMcommit( doc: *mut AMdoc, message: AMbyteSpan, timestamp: *const i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let mut options = CommitOptions::default(); if !message.is_null() { options.set_message(to_str!(message)); } if let Some(timestamp) = timestamp.as_ref() { options.set_time(*timestamp); } to_result(doc.commit_with(options)) } /// \memberof AMdoc /// \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. /// /// \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. /// /// \param[in,out] 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMemptyChange( doc: *mut AMdoc, message: AMbyteSpan, timestamp: *const i64, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let mut options = CommitOptions::default(); if !message.is_null() { options.set_message(to_str!(message)); } if let Some(timestamp) = timestamp.as_ref() { options.set_time(*timestamp); } to_result(doc.empty_change(options)) } /// \memberof AMdoc /// \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. /// \return `true` if \p doc1 `==` \p doc2 and `false` otherwise. /// \pre \p doc1 `!= NULL`. /// \pre \p doc2 `!= NULL`. /// \internal /// /// #Safety /// doc1 must be a valid pointer to an AMdoc /// doc2 must be a valid pointer to an AMdoc #[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, } } /// \memberof AMdoc /// \brief Forks this document at the 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMfork(doc: *mut AMdoc, heads: *const AMchangeHashes) -> *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())), } } /// \memberof AMdoc /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// sync_state must be a valid pointer to an AMsyncState #[no_mangle] pub unsafe extern "C" fn AMgenerateSyncMessage( doc: *mut AMdoc, sync_state: *mut AMsyncState, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let sync_state = to_sync_state_mut!(sync_state); to_result(doc.generate_sync_message(sync_state.as_mut())) } /// \memberof AMdoc /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMgetActorId(doc: *const AMdoc) -> *mut AMresult { let doc = to_doc!(doc); to_result(Ok::( doc.get_actor().clone(), )) } /// \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] 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// src must be a byte array of size `>= automerge::types::HASH_SIZE` #[no_mangle] pub unsafe extern "C" fn AMgetChangeByHash( doc: *mut AMdoc, src: *const u8, count: usize, ) -> *mut AMresult { let doc = to_doc_mut!(doc); 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(), } } /// \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. /// \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 { 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, }; 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. /// \internal /// /// # Safety /// doc1 must be a valid pointer to an AMdoc /// doc2 must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMgetChangesAdded(doc1: *mut AMdoc, doc2: *mut AMdoc) -> *mut AMresult { let doc1 = to_doc_mut!(doc1); let doc2 = to_doc_mut!(doc2); to_result(doc1.get_changes_added(doc2)) } /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMgetHeads(doc: *mut AMdoc) -> *mut AMresult { let doc = to_doc_mut!(doc); to_result(Ok::, am::AutomergeError>( doc.get_heads(), )) } /// \memberof AMdoc /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// heads must be a valid pointer to an AMchangeHashes or std::ptr::null() #[no_mangle] pub unsafe extern "C" fn AMgetMissingDeps( doc: *mut AMdoc, heads: *const AMchangeHashes, ) -> *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, }; to_result(doc.get_missing_deps(heads)) } /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMgetLastLocalChange(doc: *mut AMdoc) -> *mut AMresult { let doc = to_doc_mut!(doc); to_result(doc.get_last_local_change()) } /// \memberof AMdoc /// \brief Gets the current or historical keys of 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] 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. /// \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() #[no_mangle] pub unsafe extern "C" fn AMkeys( doc: *const AMdoc, obj_id: *const AMobjId, heads: *const AMchangeHashes, ) -> *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())), } } /// \memberof AMdoc /// \brief Allocates storage for a document and initializes it with the compact /// 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. /// \internal /// /// # Safety /// src must be a byte array of size `>= 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)) } /// \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] 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// src must be a byte array of size `>= count` #[no_mangle] pub unsafe extern "C" fn AMloadIncremental( doc: *mut AMdoc, src: *const u8, 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)) } /// \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. /// \internal /// /// # Safety /// dest must be a valid pointer to an AMdoc /// src must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMmerge(dest: *mut AMdoc, src: *mut AMdoc) -> *mut AMresult { let dest = to_doc_mut!(dest); to_result(dest.merge(to_doc_mut!(src))) } /// \memberof AMdoc /// \brief Gets the current or historical size of an 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 /// size or `NULL` for current size. /// \return A 64-bit unsigned integer. /// \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() #[no_mangle] pub unsafe extern "C" fn AMobjSize( doc: *const AMdoc, obj_id: *const AMobjId, heads: *const AMchangeHashes, ) -> 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()), } } else { 0 } } /// \memberof AMdoc /// \brief Gets the type of an object. /// /// \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`. /// \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() #[no_mangle] 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(), } } else { AMobjType::Void } } /// \memberof AMdoc /// \brief Gets the current or historical values of an object within its entire /// 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] 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. /// \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() #[no_mangle] pub unsafe extern "C" fn AMobjValues( doc: *const AMdoc, obj_id: *const AMobjId, heads: *const AMchangeHashes, ) -> *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())), } } /// \memberof AMdoc /// \brief Gets the number of pending operations added during a document's /// current transaction. /// /// \param[in] doc A pointer to an `AMdoc` struct. /// \return The count of pending operations for \p doc. /// \pre \p doc `!= NULL`. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMpendingOps(doc: *const AMdoc) -> usize { if let Some(doc) = doc.as_ref() { doc.pending_ops() } else { 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] 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`. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// sync_state must be a valid pointer to an AMsyncState /// sync_message must be a valid pointer to an AMsyncMessage #[no_mangle] pub unsafe extern "C" fn AMreceiveSyncMessage( doc: *mut AMdoc, sync_state: *mut AMsyncState, sync_message: *const AMsyncMessage, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let sync_state = to_sync_state_mut!(sync_state); let sync_message = to_sync_message!(sync_message); to_result(doc.receive_sync_message(sync_state.as_mut(), sync_message.as_ref().clone())) } /// \memberof AMdoc /// \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. /// \return The count of pending operations for \p doc that were cancelled. /// \pre \p doc `!= NULL`. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMrollback(doc: *mut AMdoc) -> usize { if let Some(doc) = doc.as_mut() { doc.rollback() } else { 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMsave(doc: *mut AMdoc) -> *mut AMresult { let doc = to_doc_mut!(doc); to_result(Ok(doc.save())) } /// \memberof AMdoc /// \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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc #[no_mangle] pub unsafe extern "C" fn AMsaveIncremental(doc: *mut AMdoc) -> *mut AMresult { let doc = to_doc_mut!(doc); to_result(Ok(doc.save_incremental())) } /// \memberof AMdoc /// \brief Puts the actor identifier of a document. /// /// \param[in,out] 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. /// \internal /// /// # Safety /// doc must be a valid pointer to an AMdoc /// actor_id must be a valid pointer to an AMactorId #[no_mangle] pub unsafe extern "C" fn AMsetActorId( doc: *mut AMdoc, actor_id: *const AMactorId, ) -> *mut AMresult { let doc = to_doc_mut!(doc); let actor_id = to_actor_id!(actor_id); doc.set_actor(actor_id.as_ref().clone()); to_result(Ok(())) } /// \memberof AMdoc /// \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] 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 /// 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. /// \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() #[no_mangle] pub unsafe extern "C" fn AMsplice( doc: *mut AMdoc, obj_id: *const AMobjId, pos: usize, del: usize, src: *const AMvalue, count: usize, ) -> *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(); } } } } 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] 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. /// \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() #[no_mangle] pub unsafe extern "C" fn AMspliceText( doc: *mut AMdoc, obj_id: *const AMobjId, pos: usize, del: usize, text: AMbyteSpan, ) -> *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"); to_result(doc.splice_text(obj_id, pos, del, to_str!(text))) } /// \memberof AMdoc /// \brief Gets the current or historical string represented by a text 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 /// 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. /// \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() #[no_mangle] pub unsafe extern "C" fn AMtext( doc: *const AMdoc, obj_id: *const AMobjId, heads: *const AMchangeHashes, ) -> *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())), } }