C API¶
Include fatoora.h from bindings/c. The C API covers the same
invoice, crypto, XML, and ZATCA operations as the generated bindings. Public
signatures use ordinary C strings, opaque owners, named-field options, and a
shared status convention. Diplomat writers, views, and result unions stay inside
the implementation.
The implementation is a C source adapter over fatoora_ffi.
Compile fatoora.c once into your application or a static library, and link the
existing Rust library. Its allocations use that C translation unit's allocator;
Rust-owned objects are always released through their native destructors. This
also avoids mixing C and Rust allocators or Windows CRT heaps.
Build and run¶
From a checkout:
cargo build -p fatoora-ffi --locked
cc -std=c11 -Ibindings/c \
bindings/c/examples/build_invoice.c bindings/c/fatoora.c \
-Ltarget/debug -lfatoora_ffi -Wl,-rpath,"$PWD/target/debug" \
-o /tmp/build-invoice
/tmp/build-invoice
The example constructs a simplified invoice with an exact 0.3333 unit price,
builds it, and prints its XML. It demonstrates cleanup on both success and error.
This is invoice construction; the example does not establish ZATCA compliance or
submit an invoice.
The other guide examples use the same compile command: replace
examples/build_invoice.c and the executable name with the selected example.
| Example | Arguments | Output |
|---|---|---|
csr |
csr.properties |
Private key PEM and CSR PEM |
invoice_signing |
invoice.xml certificate.der private-key.der |
Signed XML |
qr |
signed-invoice.xml |
Embedded base64 QR payload |
validation |
invoice.xml |
XSD validation result for the original XML |
api |
signed-invoice.xml token secret |
Reports a simplified invoice to the non-production gateway |
See the CSR, signing,
QR, validation, and
API guides for complete source examples. Calling api with
no arguments prints help and performs no request.
Native release archives contain include/fatoora.h, src/fatoora.c,
examples/c, and the underlying headers in include/generated. Compile
src/fatoora.c with -Iinclude; it locates the generated headers internally.
For C++ applications, compile the
adapter as C and link that object; the public header supports C++17.
Common conventions¶
All operations return FatooraStatus: FATOORA_OK on success, otherwise the
native numeric error classification. Unknown future codes remain representable.
The optional last parameter receives an owned FatooraError; pass NULL when
only the status matters. Error message and JSON details are borrowed strings
valid until fatoora_error_free(). Structured native error details are preserved.
FatooraError *error = NULL;
char *xml = NULL;
FatooraStatus status = fatoora_finalized_invoice_xml(invoice, &xml, &error);
if (status == FATOORA_OK) {
puts(xml);
fatoora_string_free(xml);
} else {
fprintf(stderr, "%s\n", fatoora_error_message(error));
fatoora_error_free(error);
}
Output slots are required and cleared on entry, including on failure. Release
previous values before reusing a slot: clearing a slot does not free its previous
owner. An error slot follows the same rule. Allocation failure can leave an error
object or its diagnostic strings unavailable, so always use the returned status
to decide success. fatoora_error_message(NULL) returns an empty string, and
fatoora_error_details_json(NULL) returns {}. Native error classifications
survive diagnostic allocation failure; output allocation failure returns
FATOORA_INTERNAL, never truncated success.
Text inputs are borrowed, NUL-terminated UTF-8 strings. Required strings reject
NULL; optional strings use NULL for absence and "" for a present empty
value. The C string API ends at the first NUL. Use the raw length-delimited ABI
if you need to validate a buffer that may contain embedded NUL. Input text is
copied or processed within the call; it is not retained as a borrowed pointer.
Decimal inputs and outputs are decimal strings, preserving the Rust/Python exact
decimal contract. Enum-like inputs accept named FATOORA_* constants; unknown
values are validated before mutation, including values that would otherwise wrap
when narrowed to a byte. Invoice flags combine FATOORA_FLAG_* constants with |.
Options structs support C designated initializers. Zero-initialize them, then set required fields:
FatooraInvoiceOptions options = {
.kind = FATOORA_TAX,
.subtype = FATOORA_SIMPLIFIED
};
FatooraInvoiceBuilder *builder = NULL;
FatooraStatus status = fatoora_invoice_builder_new(&options, &builder, &error);
FatooraInvoiceOptions: tax/prepayment require kind/subtype; credit/debit notes additionally requireoriginal_idandreason. UUID/date are optional.FatooraAddressOptions: country, city, street, building number, and postal code are required; additional street/number and district are optional.FatooraPartyOptions: name/address are required; seller VAT ID is required, buyer VAT ID is optional, and other ID/scheme are optional.FatooraLineItemOptions: description, quantity, unit code, unit price, and VAT rate are required; category uses aFATOORA_VAT_*constant.FatooraCsrOptions: all text fields are required, matching the CSR constructor.
The native validation rules still apply to values supplied in these fields.
Ownership and consumption¶
Every returned handle is owned and has a matching fatoora_<type>_free().
Strings are owned NUL-terminated copies released by fatoora_string_free().
Optional text getters return a nullable owned string directly, without exposing
an intermediate Text owner. Optional handle getters likewise return NULL for
absence. HTTP status uses a separate has_value flag, since zero is not absence.
Every free function accepts NULL.
Invoice data, totals, parties, addresses, line items, and response getters return
independent snapshots. They survive releasing their source owner.
fatoora_bytes_as_slice() returns a read-only FatooraBytesView borrowed from its
immutable bytes owner; copy its contents before freeing that owner. Binary input
uses pointer + length; NULL is accepted only with a zero length.
Consumption follows the existing Rust bridge:
fatoora_invoice_builder_build()consumes inner state on success and invoice validation failure. Invalid arguments rejected by the facade do not consume it.fatoora_signer_sign()consumes the finalized invoice when native signing starts, including signing failure.fatoora_signed_invoice_into_xml()consumes the signed invoice when the native call starts. Preferfatoora_signed_invoice_xml()for a non-consuming copy.
The outer handle always remains yours to free. Further operations after consumption return an error. Builder setters validate before updating state, so an invalid decimal can be corrected. If output allocation fails after a consuming native call, consumption is not rolled back.
ZATCA calls are synchronous. Callers must synchronize shared owners, with exclusive access during mutation, consumption, or freeing. Null checks cannot validate dangling pointers, wrong handle types, or unterminated input strings.
Maintenance¶
The facade generator reads the generated C headers. generate_bindings.py also
regenerates/checks the facade, so native signature changes cannot silently leave
stale wrappers. Edit scripts/generate_c_facade.py or its runtime template
scripts/c_facade_runtime.c, not the generated facade files.
python scripts/generate_c_facade.py --check
python scripts/check_native_bindings.py
python scripts/check_c_examples.py
The native checks run the generated C/C++ contracts, C ownership/error and allocation-failure contracts, and the complete invoice example. The raw ABI remains available for generated C++, Python, and .NET bindings and advanced C callers.