// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A stored response and what may be done with it, then a redirect and what it
/// costs — the two decisions a client makes before it sends anything.
///
/// moon run src/examples/15-cache
#coverage.skip
fn main raise {
let sent = @moondate.http("Sun, 06 Nov 1994 08:49:37 GMT")
let received = sent
let request = [("Accept-Encoding", "gzip")]
let response = [
("Cache-Control", "max-age=60"),
("Date", "Sun, 06 Nov 1994 08:49:37 GMT"),
("ETag", "\"v1\""),
("Vary", "Accept-Encoding"),
("Content-Type", "text/plain"),
]
let entry = @cache.Entry::read(
status=200,
headers=response,
request~,
sent~,
received~,
)
println("lifetime -> \{entry.lifetime().unwrap_or(-1)}s")
println(
"fresh at -> \{entry.fresh(now=at(sent, 30))}, \{entry.fresh(now=at(sent, 90))}",
)
println("age at 90s -> \{entry.current_age(now=at(sent, 90))}s")
let nothing = @cache.control
for seconds in [0, 30, 90] {
let now = at(sent, seconds)
println(
"verdict at \{seconds}s -> \{name(entry.verdict(request=nothing, presented=request, now~))}",
)
}
// A client that says it will take something stale gets it.
println(
"max-stale -> \{name(entry.verdict(request=@cache.Control::decode("max-stale=60"), presented=request, now=at(sent, 90)))}",
)
// And what a cache sends when it decides to ask instead.
for (name, value) in entry.conditional() {
println("conditional -> " + name + ": " + value)
}
// A redirect: where, with which method, and which credentials stop here.
let from = @moonurl.Url::decode("https://example.com/a") catch { _ => return }
let step = @client.follow(
status=302,
location="https://other.example/b",
from~,
verb=@method.Method::Post,
visited=[from][:],
body=true,
) catch {
e => {
println("refused -> " + e.to_string())
return
}
}
println("redirect -> \{step.verb.name()} \{step.url.encode()}")
println("body -> resent: \{step.body}")
println("strip -> " + join(step.strip))
// An alternative service, as a server advertises one.
match @client.altsvc("h3=\":443\"; ma=3600"[:]) {
Some(alternatives) =>
for alternative in alternatives {
println("alt-svc -> " + alternative.encode())
}
None => println("alt-svc -> clear")
}
}
///|
fn join(items : Array[String]) -> String {
let out = StringBuilder()
for i in 0.. 0 {
out.write_string(", ")
}
out.write_string(items[i])
}
out.to_string()
}
///|
/// A moment this many seconds after another.
fn at(base : @moondate.Moment, seconds : Int32) -> @moondate.Moment {
base.plus(@moondate.Span::new(seconds=seconds.to_int64()))
}
///|
fn name(verdict : @cache.Verdict) -> String {
match verdict {
@cache.Serve => "serve"
@cache.Validate => "validate"
@cache.Miss => "miss"
}
}