Skip to content

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.

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.

See generation and verification and Python bindings.