C and C++ bindings¶
For C applications, use the C API: one
fatoora.h, ordinary UTF-8 strings, named-field options, and shared status/error
handling. Compile bindings/c/fatoora.c once and link fatoora_ffi.
The complete invoice example
shows the builder workflow and cleanup.
The fatoora-ffi crate also provides Diplomat-generated bindings. Raw C headers are in
bindings/c/generated, and C++ headers are in
bindings/cpp. Include the header for each type you use
and link fatoora_ffi. C++ classes live in namespace fatoora.
The generated ABI replaces the former handwritten ABI. Its symbols follow
fatoora_Type_method, such as fatoora_Config_new. Distribute matching headers
and libraries, and recompile callers when migrating. The former handwritten ABI,
its FfiResult, and its headers were removed. The new fatoora.h is a
source facade over Diplomat and is not compatible with the old handwritten API.
C API example¶
Include one header, check the shared status, and release returned owners. Passing
NULL as the error argument is supported when only the status matters. The
deliberately incomplete invoice below fails validation.
#include "fatoora.h"
#include <stdio.h>
int main(void) {
FatooraConfig *config = NULL;
FatooraError *error = NULL;
bool valid = false;
FatooraStatus status = fatoora_config_new(FATOORA_NON_PRODUCTION, &config, &error);
if (status == FATOORA_OK)
status = fatoora_xml_validate(config, "<Invoice></Invoice>", &valid, &error);
if (status == FATOORA_OK) printf("valid: %s\n", valid ? "true" : "false");
else fprintf(stderr, "error %d: %s\n", (int)status, fatoora_error_message(error));
fatoora_error_free(error);
fatoora_config_free(config);
return status == FATOORA_OK && valid ? 0 : 1;
}
String getters return owned char * values through output pointers; release
them with fatoora_string_free(). Optional text returns NULL for absence and
an allocated empty string for a present empty value. Decimal values are exact
strings. Constructors and line items use named-field option structs. See the
complete builder example
and ownership rules.
Raw C results and ownership¶
A fallible function returns a generated result type. For example, Config.h
defines:
typedef struct fatoora_Config_new_result {
union { Config *ok; BindingError *err; };
bool is_ok;
} fatoora_Config_new_result;
Inspect is_ok before reading the union. A successful object pointer owns its
value; a failed result owns its BindingError. Destroy each owned pointer exactly
once with the corresponding function, such as fatoora_Config_destroy or
fatoora_BindingError_destroy. Void-success results contain only an error member;
scalar-success results contain the scalar in ok.
Opaque objects have no caller-visible fields. Optional results use the specific representation in their generated header; an optional object can be a null success pointer. Absence and an empty string are distinct.
Text inputs use DiplomatStringView, a pointer and byte length. The pointer must
identify valid readable storage for the call. The bridge validates UTF-8 and
rejects embedded NUL with an InvalidInput result. Numeric enum-like inputs are
validated too. Decimal quantities and amounts cross as exact decimal strings.
Raw text and byte output¶
Text accessors write into a DiplomatWrite. Use diplomat_simple_write with a
caller-owned buffer, or allocate a growable writer with
diplomat_buffer_write_create. Read its byte pointer and length before destroying
it with diplomat_buffer_write_destroy. Copy the bytes if they must outlive the
writer. Check grow_failed when using an accessor whose signature returns void.
Bytes owns a byte buffer. fatoora_Bytes_as_slice returns a read-only
DiplomatU8View borrowed from that owner; copy the data before destroying the
Bytes. BytesList::get returns an independent owned Bytes object. Invoice,
party, address, and response getters also return owned snapshots that survive
destruction of their source object.
Raw C example¶
This example validates XML and prints a structured binding error's code and message on failure. The deliberately incomplete invoice should fail validation.
#include "BindingError.h"
#include "Config.h"
#include "Xml.h"
#include <stdio.h>
#include <string.h>
static void report_error(BindingError *error) {
DiplomatWrite *message = diplomat_buffer_write_create(0);
fatoora_BindingError_message(error, message);
fprintf(stderr, "error %d: ", (int)fatoora_BindingError_code(error));
if (message->grow_failed) {
fputs("message buffer exhausted", stderr);
} else {
fwrite(diplomat_buffer_write_get_bytes(message), 1,
diplomat_buffer_write_len(message), stderr);
}
fputc('\n', stderr);
diplomat_buffer_write_destroy(message);
fatoora_BindingError_destroy(error);
}
int main(void) {
fatoora_Config_new_result created = fatoora_Config_new(0);
if (!created.is_ok) {
report_error(created.err);
return 1;
}
const char *xml = "<Invoice></Invoice>";
DiplomatStringView input = {xml, strlen(xml)};
fatoora_Xml_validate_result result = fatoora_Xml_validate(created.ok, input);
fatoora_Config_destroy(created.ok);
if (!result.is_ok) {
report_error(result.err);
return 1;
}
printf("valid: %s\n", result.ok ? "true" : "false");
return result.ok ? 0 : 1;
}
C++ ownership¶
Generated C++ headers provide their own C-linkage declarations. Include them
directly; no external extern "C" wrapper is needed. They return
diplomat::result and manage objects with std::unique_ptr. Inspect is_ok(),
then move the result's .ok() or .err() value to take ownership. Owners release
their native objects automatically.
#include "fatoora/BindingError.hpp"
#include "fatoora/Config.hpp"
#include "fatoora/Xml.hpp"
#include <iostream>
#include <utility>
int main() {
auto created = fatoora::Config::new_(0);
if (!created.is_ok()) {
auto error = std::move(created).err().value();
std::cerr << error->code() << ": " << error->message() << '\n';
return 1;
}
auto config = std::move(created).ok().value();
auto result = fatoora::Xml::validate(*config, "<Invoice></Invoice>");
if (!result.is_ok()) {
auto error = std::move(result).err().value();
std::cerr << error->code() << ": " << error->message() << '\n';
return 1;
}
return std::move(result).ok().value() ? 0 : 1;
}
Fallible C++ text accessors return a result containing std::string; error text
accessors return std::string directly. Owned strings survive destruction of
their source objects. Byte spans still borrow from their Bytes owner.
Consumption and safety¶
Builder input errors allow correction. build() consumes the builder's inner
state even when final validation fails. Signing consumes a finalized invoice,
and SignedInvoice::into_xml consumes a signed invoice. Subsequent operations
on a consumed owner return an error; the owner remains safe to destroy.
Callers must provide valid pointers, preserve borrowed storage lifetimes, and synchronize mutable access, consumption, and destruction. Malformed bytes are validated only after the caller has supplied a valid byte view. Invalid pointers, double destruction, and concurrent conflicting access violate the native contract.
Fallible operations catch unwinding Rust panics and return Internal errors. Abort-mode panics and allocation aborts cannot be recovered this way. A panic can leave an operation consumed; discard the affected owner after an Internal failure. See errors for numeric codes and structured details.
Build and link¶
Standalone C++ clients require C++17. The Python extension uses C++20. For example, on Linux:
cargo build -p fatoora-ffi --release --locked
cc -std=c11 validate.c -I bindings/c/generated -L target/release -lfatoora_ffi -o validate
c++ -std=c++17 validate.cpp -I bindings/cpp -L target/release -lfatoora_ffi -o validate_cpp
The C command above builds the raw example. For the C API example:
cc -std=c11 validate.c bindings/c/fatoora.c \
-Ibindings/c -Ltarget/release -lfatoora_ffi -o validate
Configure the runtime loader to find the matching shared library. macOS uses the
.dylib; Windows uses the DLL and its import library. Complete executable
contracts are in diplomat_contract.c
and diplomat_contract.cpp.