C Stable API

Full program

Complete document-access program for the C Stable API, with checked C source.

The C Stable API overview maps the opaque API by responsibility. This page contains the complete, compiled example so you can inspect and run every call.

Document access with the stable C API

This is the C implementation of the quickstart's document-access case. The low-level C API runs the same policy, seven request facts and five ground queries. Only the C integration path changes: this version registers a public domain, loads an opaque policy, prepares a session and submits a bounded input EDB.

Both programs pass the following identical Datalog source to the inline loader:

DATALOG
can_read(User, Doc) :-
    owns(User, Doc) or delegated(User, Doc),
    not(blocked(User)).
has_any_document(User) :- owns(User, _).
allow(User, Doc) :- user(User), can_read(User, Doc).

They supply user("alice"), user("bob"), user("mallory"), owns("alice", "roadmap.pdf"), delegated("bob", "roadmap.pdf"), owns("mallory", "roadmap.pdf"), and blocked("mallory") as request facts. Neither program puts a blocked-user fact in policy source.

Ground queryAnswer
allow("alice", "roadmap.pdf")true
allow("bob", "roadmap.pdf")true
allow("mallory", "roadmap.pdf")false
has_any_document("alice")true
has_any_document("bob")false

The complete, fail-closed C program for this API is:

CODE
#include <maelys/datalog.h>
#include <maelys/datalog_builders.h>

#include <stdio.h>
#include <string.h>

static const char source[] =
    "can_read(User, Doc) :-\n"
    "    owns(User, Doc) or delegated(User, Doc),\n"
    "    not(blocked(User)).\n"
    "has_any_document(User) :- owns(User, _).\n"
    "allow(User, Doc) :- user(User), can_read(User, Doc).\n";

static int ok(maelys_datalog_status_t status, const char *step) {
    if (status == MAELYS_DATALOG_STATUS_OK) return 1;
    fprintf(stderr, "%s: %s\n", step, maelys_datalog_status_name(status));
    return 0;
}

static int expect(const maelys_datalog_result_t *result,
                  const char *predicate, const char *first,
                  const char *second, int expected) {
    const maelys_datalog_value_t terms[] = {
        MAELYS_DATALOG_SYMBOL(first), MAELYS_DATALOG_SYMBOL(second),
    };
    int present = 0;
    const size_t arity = second == NULL ? 1u : 2u;
    if (!ok(maelys_datalog_result_query(
                result, predicate, terms, arity, &present), "query")) return 0;
    printf("%s(%s", predicate, first);
    if (second != NULL) printf(", %s", second);
    printf(") = %s\n", present ? "true" : "false");
    return present == expected;
}

int main(void) {
    static const maelys_datalog_predicate_t predicates[] = {
        MAELYS_DATALOG_EDB("user", 1),
        MAELYS_DATALOG_EDB("owns", 2),
        MAELYS_DATALOG_EDB("delegated", 2),
        MAELYS_DATALOG_EDB("blocked", 1),
        MAELYS_DATALOG_IDB("can_read", 2),
        MAELYS_DATALOG_IDB_QUERY("has_any_document", 1),
        MAELYS_DATALOG_IDB_QUERY("allow", 2),
    };
    const maelys_datalog_domain_t domain = {
        "documents", predicates, sizeof(predicates) / sizeof(predicates[0]),
        NULL, 0u,
    };
    maelys_datalog_policy_t *policy = NULL;
    maelys_datalog_session_t *session = NULL;
    maelys_datalog_input_edb_t *edb = NULL;
    maelys_datalog_result_t *result = NULL;
    maelys_datalog_diagnostic_t diagnostic = MAELYS_DATALOG_DIAGNOSTIC_INIT;
    int exit_code = 1;

    if (!ok(maelys_datalog_domain_register(&domain), "register domain"))
        goto cleanup;
    maelys_datalog_diagnostic_clear(&diagnostic);
    if (!ok(maelys_datalog_policy_load_inline(
                domain.name, "documents.main", source, strlen(source),
                &policy, &diagnostic), "load policy")) goto cleanup;
    if (!ok(maelys_datalog_session_create(policy, 0u, &session),
            "create session")) goto cleanup;
    if (!ok(maelys_datalog_policy_free(policy), "free policy")) goto cleanup;
    policy = NULL;

    if (!ok(maelys_datalog_input_edb_create(&edb), "create input EDB"))
        goto cleanup;
    if (!ok(MAELYS_DATALOG_ADD_FACTS(
                edb, &diagnostic,
                MAELYS_DATALOG_FACT("user", "alice"),
                MAELYS_DATALOG_FACT("user", "bob"),
                MAELYS_DATALOG_FACT("user", "mallory"),
                MAELYS_DATALOG_FACT("owns", "alice", "roadmap.pdf"),
                MAELYS_DATALOG_FACT("delegated", "bob", "roadmap.pdf"),
                MAELYS_DATALOG_FACT("owns", "mallory", "roadmap.pdf"),
                MAELYS_DATALOG_FACT("blocked", "mallory")),
            "add request facts")) goto cleanup;

    maelys_datalog_diagnostic_clear(&diagnostic);
    if (!ok(maelys_datalog_session_solve_edb(
                session, edb, &result, &diagnostic), "solve")) goto cleanup;

    int answers_match = 1;
    answers_match &= expect(result, "allow", "alice", "roadmap.pdf", 1);
    answers_match &= expect(result, "allow", "bob", "roadmap.pdf", 1);
    answers_match &= expect(result, "allow", "mallory", "roadmap.pdf", 0);
    answers_match &= expect(result, "has_any_document", "alice", NULL, 1);
    answers_match &= expect(result, "has_any_document", "bob", NULL, 0);
    exit_code = answers_match ? 0 : 2;

cleanup:
    if (result != NULL) (void)maelys_datalog_result_free(result);
    if (edb != NULL) (void)maelys_datalog_input_edb_free(edb);
    if (session != NULL) (void)maelys_datalog_session_free(session);
    if (policy != NULL) (void)maelys_datalog_policy_free(policy);
    return exit_code;
}

The opaque policy handle can be released after the session has prepared it. The input EDB owns copies of request strings; the result is released before its session. A failed load, fact insertion, solve or query never counts as authorization.

Complete minimal integration

This independent observed/1 smoke test follows the installed-consumer test in the engine repository. It is the smallest one-predicate integration, not the document-access comparison above; keep the latter when comparing stable and low-level C calls.

CODE
#include <maelys/datalog.h>

#include <stdio.h>
#include <string.h>

int main(void) {
    static const maelys_datalog_predicate_t predicates[] = {
        {"observed", 1u, MAELYS_DATALOG_PREDICATE_EDB},
        {"allow", 1u,
         MAELYS_DATALOG_PREDICATE_IDB |
         MAELYS_DATALOG_PREDICATE_QUERY},
    };
    const maelys_datalog_domain_t domain = {
        "example", predicates, 2u, NULL, 0u,
    };
    const char policy_source[] = "allow(X) :- observed(X).\n";

    maelys_datalog_policy_t *policy = NULL;
    maelys_datalog_session_t *session = NULL;
    maelys_datalog_result_t *result = NULL;

    if (maelys_datalog_domain_register(&domain) !=
        MAELYS_DATALOG_STATUS_OK) return 1;

    if (maelys_datalog_policy_load_inline(
            domain.name,
            "example.main",
            policy_source,
            strlen(policy_source),
            &policy,
            NULL) != MAELYS_DATALOG_STATUS_OK) return 1;

    if (maelys_datalog_session_create(policy, 0u, &session) !=
        MAELYS_DATALOG_STATUS_OK) return 1;

    /* The session owns its prepared policy state. */
    if (maelys_datalog_policy_free(policy) !=
        MAELYS_DATALOG_STATUS_OK) return 1;
    policy = NULL;

    maelys_datalog_fact_t fact = {0};
    fact.predicate = "observed";
    fact.arity = 1u;
    fact.terms[0].kind = MAELYS_DATALOG_VALUE_SYMBOL;
    fact.terms[0].as.symbol = "alice";

    if (maelys_datalog_session_solve(
            session, &fact, 1u, &result, NULL) !=
        MAELYS_DATALOG_STATUS_OK) return 1;

    int allowed = 0;
    if (maelys_datalog_result_query(
            result, "allow", fact.terms, 1u, &allowed) !=
        MAELYS_DATALOG_STATUS_OK) return 1;

    printf("allow(alice) = %s\n", allowed ? "true" : "false");

    if (maelys_datalog_result_free(result) !=
        MAELYS_DATALOG_STATUS_OK) return 1;
    if (maelys_datalog_session_free(session) !=
        MAELYS_DATALOG_STATUS_OK) return 1;
    return allowed ? 0 : 1;
}