Fetch, Validate, and Cache an API Response
A cache, a TTL, and a validation step are usually three separate systems that all have to independently agree on what "valid" and "fresh" mean. Here they're one object: it fetches, checks the shape of what it got back, and remembers — so calling it twice in a row doesn't mean two network requests.
One honest note before the code: Http isn't loaded in this browser sandbox (see the Http library page), so simulated_fetch() below stands in for await Http.fetch(...) — same response shape, no real network call. Swapping it for the real thing in a host that has Http loaded is a one-line change; nothing else in the recipe cares which one it's talking to.
Fetch, Then Validate
fn simulated_fetch() -> map {
// stands in for `await Http.fetch(...)`
map(("status", 200), ("text", '{"rate": 1.08}'))
}
#[type]
Rate: {
#[schema((target_value: float): bool => target_value > 0)]
float rate: 1.0
}
#[main]
fn main() {
const resp = self.simulated_fetch();
const parsed = new {};
parse(resp.get("text"), parsed, "json");
pln(<Rate>.schemafy(parsed), parsed.rate);
}Adding the Cache
A fetch_count here just proves the point — in a real version it wouldn't exist, but it's the difference between claiming the cache works and actually watching it:
#[type]
Rate: {
#[schema((target_value: float): bool => target_value > 0)]
float rate: 1.0
}
cache: {
fetched: 0ms
data: null
fetch_count: 0
fn simulated_fetch() -> map {
self.fetch_count += 1;
map(("status", 200), ("text", '{"rate": 1.08}'))
}
fn stale() -> bool {
self.data == null || Time.diff(self.fetched) > 5min
}
fn get() -> obj {
if (self.stale()) {
const resp = self.simulated_fetch();
const parsed = new {};
parse(resp.get("text"), parsed, "json");
drop(self.data); self.data = null;
if (<Rate>.schemafy(parsed)) self.data = parsed;
else drop(parsed);
self.fetched = Time.now();
}
self.data
}
}
#[main]
fn main() {
pln(self.cache.get().rate);
pln(self.cache.get().rate);
pln(self.cache.fetch_count);
}fetch_count stays at 1 even after calling get() twice — the second call found valid, recent data sitting right there and skipped the fetch entirely. root.Rate reaches the schema from inside cache, the same absolute-path mechanism from The Document Graph — cache doesn't need to know or care where the validation rule actually lives.