module

Hwaro::Core::Build::RemoteData

Constants

CACHE_DIR = ".hwaro/remote_data"
DEFAULT_FETCH_DEADLINE = 120.seconds

Overall wall-clock budget for one entry, spanning every redirect hop and every body read. READ_TIMEOUT bounds a single read OPERATION, so a source dripping one byte just inside it never trips a timeout at all and stalls the build indefinitely.

DEFAULT_MAX_BYTES = (20_i64 * 1024) * 1024

20 MiB: generous for a data payload, small enough that a misconfigured URL (a tarball, a video) can't balloon the build.

ENV_PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/

The braced no-default form of an env reference. Deliberately the ONLY form checked: ${VAR:-fallback} was already resolved to its fallback by the file-wide pass, and bare $WORD keeps its warn-only behavior so a literal dollar sign in a URL can't break a build.

Instance methods

load(entry : Models::RemoteDataConfig, cache_dir : String = CACHE_DIR, now : Time = Time.utc, max_bytes : Int64 = DEFAULT_MAX_BYTES, deadline : Time::Span = DEFAULT_FETCH_DEADLINE) : Result | Nil

Load one [[data.remote]] entry: serve it from a fresh disk cache when the TTL allows, otherwise fetch it. Returns nil only for on_error = "warn-and-skip" failures; fail (and an unusable cache under warn-and-use-cache) raise a classified HwaroError.

now, max_bytes and deadline are parameters for the specs; production callers use the defaults.

Source
parse_body(body : String, format : String) : Crinja::Value

Parse a payload the same way data/ files parse, so a key can move between a local file and a remote source without templates noticing — both routes go through the one shared dispatcher. The BOM strip is this caller's, matching data/: JSON and TOML both reject a leading BOM outright.

Source
request_digest(entry : Models::RemoteDataConfig) : String

The identity of the request entry makes, for the same two caches. Headers are part of it: Accept-Language = "ko", a tenant header or a rotated token asks the same url for a different payload, and a fresh cache of the old one kept answering until the TTL ran out. Header-less entries keep the plain url digest, so caches written before headers counted stay valid.

Source
require_resolved_env_vars!(entry : Models::RemoteDataConfig) : Nil

${VAR} interpolation runs file-wide BEFORE config.toml is parsed, so an unset variable simply leaves the literal ${VAR} text in the value — which is what this detects.

It used to be a hard error inside Config.load, which meant hwaro deploy, hwaro new and hwaro tool ... — none of which ever fetch — aborted on a remote source they would never touch, while serve (which rescues a failed config load) silently skipped its [build] merge instead. Config load now validates shape only and defers to here, the moment the entry is actually about to be used: same code, same wording, same hint, blast radius of one entry.

Never echoes the value — header values hold credentials. Public so the serve-session memo can run it before deciding to reuse a payload: unsetting a header's variable mid-session is a config mistake, not a flaky source, and must not be papered over.

Source
resolve_format(explicit : String | Nil, content_type : String | Nil, url : String) : String | Nil

Explicit format wins; otherwise the response Content-Type, then the URL's path extension. Returns nil when nothing matches.

Source
sanitized_url(url : String) : String

Log-safe form of a url (see RemoteFetch.sanitized_url).

Source
url_digest(url : String) : String

Public because the serve-session memo keys on it too: an edited url in config.toml must miss both the disk cache and the memo.

Source

Nested types