harn_capability_contracts/
lib.rs1use std::sync::OnceLock;
9
10use harn_builtin_meta::{BuiltinContract, BuiltinSignature};
11use harn_builtin_registry::BuiltinManifestEntry;
12
13#[derive(Debug, Clone, Copy)]
15pub struct CapabilityMethodDef {
16 pub signature: BuiltinSignature,
17 pub contract: BuiltinContract,
18 pub doc: &'static str,
19 pub signature_text: Option<&'static str>,
20}
21
22pub static ALL_CAPABILITY_METHOD_DEFS: &[&CapabilityMethodDef] =
26 include!(concat!(env!("OUT_DIR"), "/capability_method_defs.rs"));
27
28#[doc(hidden)]
30pub mod support {
31 pub use crate::{CapabilityMethodDef, ALL_CAPABILITY_METHOD_DEFS};
32 pub use harn_builtin_meta::{
33 shapes, BuiltinContract, BuiltinExposure, BuiltinSignature, CapabilityId, EffectAccess,
34 EffectAuthorization, EffectKind, EffectSpec, Param, ResourceSelector, ShapeFieldDescriptor,
35 Ty, TY_ANY, TY_BOOL, TY_BYTES, TY_BYTES_OR_NIL, TY_CLOSURE, TY_DICT, TY_DICT_OR_NIL,
36 TY_DURATION, TY_FLOAT, TY_INT, TY_INT_OR_NIL, TY_LIST, TY_NEVER, TY_NIL, TY_NUMBER,
37 TY_RESOURCE, TY_STRING, TY_STRING_OR_NIL,
38 };
39}
40
41use harn_builtin_macros::harn_capability_contract as capability_method;
42
43mod vm_declared;
44
45include!("ai.rs");
46include!("data.rs");
47include!("host.rs");
48include!("io.rs");
49
50pub fn manifest() -> &'static [&'static BuiltinManifestEntry] {
53 static MANIFEST: OnceLock<Vec<&'static BuiltinManifestEntry>> = OnceLock::new();
54 MANIFEST
55 .get_or_init(|| {
56 let mut defs = ALL_CAPABILITY_METHOD_DEFS.to_vec();
57 defs.sort_by_key(|def| def.signature.name);
58 defs.into_iter()
59 .map(|def| {
60 Box::leak(Box::new(BuiltinManifestEntry {
61 name: def.signature.name,
62 canonical_name: def.signature.name,
63 signature: &def.signature,
64 contract: def.contract,
65 })) as &'static BuiltinManifestEntry
66 })
67 .collect()
68 })
69 .as_slice()
70}
71
72pub fn capability_method_entry(field: &str, method: &str) -> Option<&'static BuiltinManifestEntry> {
75 let capability = harn_builtin_meta::CapabilityId::from_field_name(field)?;
76 manifest().iter().copied().find(|entry| {
77 matches!(
78 entry.contract.exposure,
79 harn_builtin_meta::BuiltinExposure::HarnessMethod {
80 capability: candidate,
81 method: candidate_method,
82 } if candidate == capability && candidate_method == method
83 )
84 })
85}
86
87#[must_use]
100pub fn is_declared_capability_method(field: &str, method: &str) -> bool {
101 if capability_method_entry(field, method).is_some() {
102 return true;
103 }
104 if vm_declared::VM_DECLARED_CAPABILITY_METHODS
105 .binary_search(&(field, method))
106 .is_ok()
107 {
108 return true;
109 }
110 harn_builtin_meta::CapabilityId::from_field_name(field).is_some_and(|capability| {
115 harn_builtin_meta::host_capabilities::is_host_capability_method(capability, method)
116 })
117}
118
119#[must_use]
125pub fn declared_capability_method_names(field: &str) -> Vec<&'static str> {
126 let mut names: Vec<&'static str> = vm_declared::VM_DECLARED_CAPABILITY_METHODS
127 .iter()
128 .filter(|(capability, _)| *capability == field)
129 .map(|(_, method)| *method)
130 .collect();
131 if let Some(capability) = harn_builtin_meta::CapabilityId::from_field_name(field) {
132 names.extend(
133 manifest()
134 .iter()
135 .filter_map(|entry| match entry.contract.exposure {
136 harn_builtin_meta::BuiltinExposure::HarnessMethod {
137 capability: candidate,
138 method,
139 } if candidate == capability => Some(method),
140 _ => None,
141 }),
142 );
143 names.extend(
144 harn_builtin_meta::host_capabilities::all_host_capability_groups()
145 .filter(|group| group.capability == capability)
146 .flat_map(|group| group.methods.iter().copied()),
147 );
148 }
149 names.sort_unstable();
150 names.dedup();
151 names
152}
153
154#[cfg(test)]
155mod tests {
156 #[test]
160 fn vm_declared_methods_are_sorted_and_unique() {
161 let table = super::vm_declared::VM_DECLARED_CAPABILITY_METHODS;
162 assert!(!table.is_empty());
163 assert!(
164 table.windows(2).all(|pair| pair[0] < pair[1]),
165 "the generated table must be sorted and free of duplicates"
166 );
167 }
168
169 #[test]
172 fn a_vm_only_method_is_declared_without_the_vm() {
173 assert!(super::capability_method_entry("runtime", "shared_cell").is_none());
174 assert!(super::is_declared_capability_method(
175 "runtime",
176 "shared_cell"
177 ));
178 }
179
180 #[test]
181 fn a_contract_owned_method_is_declared() {
182 assert!(super::is_declared_capability_method("fs", "read_text"));
183 }
184
185 #[test]
190 fn a_host_bridged_method_is_declared() {
191 assert!(super::capability_method_entry("workspace", "search").is_none());
192 assert!(super::is_declared_capability_method("workspace", "search"));
193 assert!(super::declared_capability_method_names("workspace").contains(&"search"));
194 }
195
196 #[test]
197 fn a_typo_is_not_declared() {
198 assert!(!super::is_declared_capability_method("fs", "bogus_method"));
199 assert!(!super::is_declared_capability_method(
200 "runtime",
201 "shared_cel"
202 ));
203 assert!(!super::is_declared_capability_method(
204 "workspace",
205 "searchh"
206 ));
207 }
208}