1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
//! A plain C ABI, for functions written in something other than Rust.
//!
//! The [main contract](crate) is expressed in [`abi_stable`] types — `RString`,
//! `RResult`, `#[sabi_trait]` vtables, a root module whose header carries type
//! layout metadata the host verifies at load time. That machinery is what makes
//! the Rust-to-Rust boundary safe across compiler versions, but it is not
//! something you can hand-write in C, Zig or Go.
//!
//! So a library may instead export the six plain C symbols below. The host tries
//! the `abi_stable` root module first and falls back to these, wrapping whatever
//! it finds in the same [`Function`](crate::Function) trait object — so a C
//! function is mounted, authenticated, documented in the OpenAPI spec and usable
//! as a lifecycle hook exactly like a Rust one.
//!
//! ## What the library exports
//!
//! ```c
//! uint32_t apiplant_abi_version(void);
//! const char *apiplant_manifest(void);
//! int32_t apiplant_invoke(const char *name, const char *input_json,
//! const ApiplantHost *host, char **out);
//! void apiplant_free(char *string);
//! ```
//!
//! `apiplant_manifest` returns a JSON **array** — one object per function the
//! library provides, mirroring what `function!` generates on the Rust side:
//!
//! ```json
//! [{ "name": "hello", "version": "1.0.0", "description": "Greets someone.",
//! "visibility": "public", "method": "POST",
//! "input_schema": { "type": "object" }, "output_schema": { "type": "object" } }]
//! ```
//!
//! Only `name` is required. `visibility` takes the same strings as a resource's
//! permissions (`"public"`, `"authenticated"`, `"role:admin"`, `"private"`, and
//! it defaults to `"private"` — the safe direction, so a typo hides an endpoint
//! rather than exposing it). `method` defaults to `"POST"`. The two schema fields
//! are optional and may be given as an object or as a JSON string; they only feed
//! the generated docs.
//!
//! ## Memory
//!
//! Each side frees what it allocated, because the two do not share an allocator:
//!
//! * The string `apiplant_invoke` writes to `*out` is released by the host
//! calling the library's own [`apiplant_free`](FreeFn).
//! * Strings the host returns from [`Host::config`], [`Host::query`],
//! [`Host::principal_id`], [`Host::hook`], [`Host::send_email`] and
//! [`Host::cache`] are released by the library calling [`Host::free_string`].
//!
//! The pointer from `apiplant_manifest` is never freed, so it must be static.
//!
//! ## Growing the host
//!
//! [`Host`] gains callbacks at its **end**, never in the middle: the host is
//! what allocates the struct, so a library compiled against an older, shorter
//! definition still finds every field it knows at the offset it expects, and
//! simply never reads the ones added since. That is why [`ABI_VERSION`] does
//! not change when a callback is appended — and why it must change if one is
//! ever removed or reordered.
//!
//! ## Faults
//!
//! [`Host`]'s callbacks never unwind — the host catches its own panics before
//! they reach C. In the other direction the return code separates a bad request
//! from a broken function, which is what the string-prefix convention
//! ([`INTERNAL_ERROR_PREFIX`](crate::INTERNAL_ERROR_PREFIX)) expresses in Rust:
//!
//! | code | meaning | response |
//! |------|---------|----------|
//! | [`OK`] | `*out` is the JSON response body | `200` |
//! | [`ERR_REQUEST`] | `*out` is a message for the caller | `400` |
//! | [`ERR_INTERNAL`] | `*out` is a message for the log | `500`, message withheld |
use ;
/// Version of this C contract. The host refuses a library reporting anything
/// else, so a breaking change here is a clean load error rather than a crash.
pub const ABI_VERSION: u32 = 1;
/// `*out` holds the JSON response body.
pub const OK: i32 = 0;
/// `*out` holds a message describing what was wrong with the request (`400`).
pub const ERR_REQUEST: i32 = 1;
/// `*out` holds a message describing how the function broke (`500`). The host
/// logs it and does not echo it to the caller.
pub const ERR_INTERNAL: i32 = 2;
/// Severities accepted by [`Host::log`]; matches [`LogLevel`](crate::LogLevel).
/// Services the host lends to a C function for the duration of one call.
///
/// The host fills this in and passes a pointer that is valid **only** until
/// `apiplant_invoke` returns; `ctx` must be handed back to every callback
/// untouched. Each `char *` the host returns is owned by the callee and must be
/// released with [`free_string`](Host::free_string).
/// Reports the contract the library was built against; must return
/// [`ABI_VERSION`].
pub type AbiVersionFn = unsafe extern "C" fn ;
/// Returns a static, NUL-terminated JSON array of manifests. Never freed.
pub type ManifestFn = unsafe extern "C" fn ;
/// Handles one request. Writes a NUL-terminated string to `*out` and returns
/// [`OK`], [`ERR_REQUEST`] or [`ERR_INTERNAL`]. Must not unwind or longjmp.
pub type InvokeFn = unsafe extern "C" fn ;
/// Releases a string produced by [`InvokeFn`].
pub type FreeFn = unsafe extern "C" fn;
/// Symbol the host looks up to decide a library speaks this ABI.
pub const SYM_ABI_VERSION: & = b"apiplant_abi_version\0";
/// Symbol for [`ManifestFn`].
pub const SYM_MANIFEST: & = b"apiplant_manifest\0";
/// Symbol for [`InvokeFn`].
pub const SYM_INVOKE: & = b"apiplant_invoke\0";
/// Symbol for [`FreeFn`].
pub const SYM_FREE: & = b"apiplant_free\0";