loadDatabase loads the database from the local cache file or falls back to fetching from the models.dev API. When allowFetch is false the network is never touched: a fresh cache is returned as-is, a stale cache is returned regardless of age, and a missing cache falls back to the snapshot embedded a
(ctx context.Context, cacheFile string, allowFetch bool, fetch fetcher)
| 259 | // this to avoid memoizing the fallback so a later lookup can retry once the |
| 260 | // cache or network recovers. |
| 261 | func loadDatabase(ctx context.Context, cacheFile string, allowFetch bool, fetch fetcher) (db *Database, authoritative bool) { |
| 262 | // Try to load from cache first |
| 263 | cached, err := loadFromCache(cacheFile) |
| 264 | if err == nil && (!allowFetch || time.Since(cached.LastRefresh) < refreshInterval) { |
| 265 | return &cached.Database, true |
| 266 | } |
| 267 | |
| 268 | if !allowFetch { |
| 269 | // No fresh cache and fetching is disallowed: use a stale cache if we |
| 270 | // have one, otherwise fall back to the snapshot baked into the binary |
| 271 | // so the caller still resolves against a real catalog with no network |
| 272 | // call. |
| 273 | if cached != nil { |
| 274 | return &cached.Database, true |
| 275 | } |
| 276 | return embeddedSnapshot(), false |
| 277 | } |
| 278 | |
| 279 | // Cache is stale or doesn't exist — try a conditional fetch with the ETag. |
| 280 | var etag string |
| 281 | if cached != nil { |
| 282 | etag = cached.ETag |
| 283 | } |
| 284 | |
| 285 | database, newETag, fetchErr := fetch(ctx, etag) |
| 286 | if fetchErr != nil { |
| 287 | // If API fetch fails but we have cached data, use it regardless of age. |
| 288 | if cached != nil { |
| 289 | slog.DebugContext(ctx, "API fetch failed, using stale cache", "error", fetchErr) |
| 290 | return &cached.Database, true |
| 291 | } |
| 292 | // No cache either — fall back to the snapshot embedded at build time |
| 293 | // instead of failing outright, so a fresh binary on a machine that |
| 294 | // can't reach models.dev still has a usable catalog. |
| 295 | slog.DebugContext(ctx, "API fetch failed and no cache available, using embedded snapshot", "error", fetchErr) |
| 296 | return embeddedSnapshot(), false |
| 297 | } |
| 298 | |
| 299 | // database is nil when the server returned 304 Not Modified. |
| 300 | if database == nil && cached != nil { |
| 301 | // Bump LastRefresh so we don't re-check until the next interval. |
| 302 | cached.LastRefresh = time.Now() |
| 303 | if saveErr := saveToCache(cacheFile, &cached.Database, cached.ETag); saveErr != nil { |
| 304 | slog.WarnContext(ctx, "Failed to update cache timestamp", "error", saveErr) |
| 305 | } |
| 306 | return &cached.Database, true |
| 307 | } |
| 308 | |
| 309 | // Save the fresh data to cache. |
| 310 | if saveErr := saveToCache(cacheFile, database, newETag); saveErr != nil { |
| 311 | slog.WarnContext(ctx, "Failed to save to cache", "error", saveErr) |
| 312 | } |
| 313 | |
| 314 | return database, true |
| 315 | } |
| 316 | |
| 317 | // fetchFromAPI fetches the models.dev database. |
| 318 | // If etag is non-empty it is sent as If-None-Match; a 304 response |
no test coverage detected