feat(mcp): scaffold uses real EINHERJAR_SERVER_CORS_ORIGINS; document web.New vs server.New (v1.2.0)
This commit is contained in:
@@ -102,15 +102,14 @@ type JWTConfig struct {
|
||||
// are nested fields; caarlos0/env recurses into them, populating their
|
||||
// EINHERJAR_SERVER_* / EINHERJAR_PG_* tags from the environment.
|
||||
type Config struct {
|
||||
AppEnv string `env:"APP_ENV" envDefault:"local"`
|
||||
CORSOrigins []string `env:"APP_CORS_ORIGINS" envSeparator:","`
|
||||
AppEnv string `env:"APP_ENV" envDefault:"local"`
|
||||
|
||||
JWT JWTConfig
|
||||
|
||||
// Framework component configs — composed verbatim. Their own EINHERJAR_* tags
|
||||
// load through this one env.Parse call.
|
||||
Log logz.Config // EINHERJAR_LOG_*
|
||||
Server server.Config // EINHERJAR_SERVER_*
|
||||
Server server.Config // EINHERJAR_SERVER_* (incl. EINHERJAR_SERVER_CORS_ORIGINS)
|
||||
PG postgres.Config // EINHERJAR_PG_*
|
||||
}
|
||||
|
||||
@@ -149,17 +148,16 @@ After you compose a component, run **`check_env`** with what the app composes: i
|
||||
`EINHERJAR_*` names that don't exist, required vars you forgot to document, and vars set for a
|
||||
config you don't actually compose (dead vars). Prefer the **`composes`** input (exact struct
|
||||
selectors like `web/server/Config`) over `modules` — it catches struct-level dead vars (e.g.
|
||||
`EINHERJAR_SERVER_CORS_ORIGINS` lives on `web.Config`, not `server.Config`). `get_scaffold` already
|
||||
emits a `.env.example` derived from these same tags, so the starting point is correct by construction.
|
||||
`EINHERJAR_HEALTH_CHECK_TIMEOUT` lives on `web/health.Config`, so it is dead if you compose
|
||||
`web/server/Config` but not the health config). `get_scaffold` already emits a `.env.example`
|
||||
derived from these same tags, so the starting point is correct by construction.
|
||||
|
||||
```bash
|
||||
# .env.example — copy to .env for local dev. Every var the app reads lives here.
|
||||
|
||||
# ── App ───────────────────────────────────────────────────────────────────
|
||||
APP_ENV=local
|
||||
# APP_CORS_ORIGINS — explicit origins for non-local envs (comma-separated).
|
||||
# Local uses mw.CORSAllowAll() and ignores this; "*" is rejected by mw.CORS — never use it.
|
||||
APP_CORS_ORIGINS=
|
||||
# CORS: local uses mw.CORSAllowAll(); non-local reads EINHERJAR_SERVER_CORS_ORIGINS (below).
|
||||
APP_JWT_SECRET=change-me
|
||||
APP_JWT_ISSUER=myapp
|
||||
|
||||
@@ -170,6 +168,9 @@ APP_JWT_ISSUER=myapp
|
||||
# ── Einherjar: HTTP server (EINHERJAR_SERVER_*) ───────────────────────────
|
||||
EINHERJAR_SERVER_HOST=0.0.0.0
|
||||
EINHERJAR_SERVER_PORT=8080
|
||||
# EINHERJAR_SERVER_CORS_ORIGINS — explicit origins for non-local envs (comma-separated).
|
||||
# "*" is rejected by mw.CORS; local dev uses mw.CORSAllowAll() and ignores this.
|
||||
# EINHERJAR_SERVER_CORS_ORIGINS=
|
||||
|
||||
# ── Einherjar: PostgreSQL (EINHERJAR_PG_*) ────────────────────────────────
|
||||
EINHERJAR_PG_HOST=localhost
|
||||
@@ -189,6 +190,22 @@ The application entry point. The order below is load-bearing: configuration firs
|
||||
second, infrastructure third, cross-cutting helpers fourth, then the launcher with every component
|
||||
appended, then feature hooks, then `lc.Run()`.
|
||||
|
||||
**`web.New` vs `server.New` — pick the right tier:**
|
||||
|
||||
- **`web.New(logger, web.Config{Server: cfg.Server})`** — batteries-included. It pre-wires the
|
||||
recommended middleware stack (Recover → RequestID → RequestLogger) and applies `mw.CORS` from
|
||||
`EINHERJAR_SERVER_CORS_ORIGINS` automatically (explicit origins only; empty ⇒ CORS off + a log
|
||||
line). Use it for a plain service that just needs the defaults. It does **not** support allow-all
|
||||
CORS or a custom middleware order.
|
||||
- **`server.New(logger, cfg.Server, server.WithMiddleware(...))`** — full control. You compose the
|
||||
middleware list yourself. Use it when you need a **custom middleware order**, extra middleware
|
||||
(auth, enrichment), a custom request-ID generator, or **allow-all CORS in local dev**
|
||||
(`mw.CORSAllowAll`, gated by `AppEnv` — see below). The starter below uses `server.New` precisely
|
||||
because it inserts JWT auth + enrichment into the stack.
|
||||
|
||||
Whichever tier you pick, CORS origins always come from the framework var
|
||||
`EINHERJAR_SERVER_CORS_ORIGINS` (`cfg.Server.CORSOrigins`) — never invent an app-owned CORS var.
|
||||
|
||||
```go
|
||||
package wire
|
||||
|
||||
@@ -231,11 +248,12 @@ func Run() error {
|
||||
db := postgres.New(logger, cfg.PG)
|
||||
|
||||
// CORS convention: allow-all in local dev, explicit origins everywhere else.
|
||||
// mw.CORS panics on "*" (it matches no real origin) — allow-all is mw.CORSAllowAll,
|
||||
// never a "*" in APP_CORS_ORIGINS.
|
||||
// Origins come from the framework's own EINHERJAR_SERVER_CORS_ORIGINS
|
||||
// (cfg.Server.CORSOrigins) — never invent an APP_CORS_ORIGINS var. mw.CORS panics
|
||||
// on "*" (it matches no real origin) — allow-all is mw.CORSAllowAll, never a "*".
|
||||
corsMW := mw.CORSAllowAll()
|
||||
if !strings.EqualFold(cfg.AppEnv, "local") {
|
||||
corsMW = mw.CORS(cfg.CORSOrigins)
|
||||
corsMW = mw.CORS(cfg.Server.CORSOrigins)
|
||||
}
|
||||
|
||||
srv := server.New(logger, cfg.Server,
|
||||
|
||||
Reference in New Issue
Block a user