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:
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 query | Answer |
|---|---|
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:
#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.
#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;
}