[{"data":1,"prerenderedAt":2199},["ShallowReactive",2],{"search-sections-fig":3,"nav-fig":946,"content-tree-fig":999,"footer-resources":1023,"content-/v0.0.4/learn/overview":2047,"surround-/v0.0.4/learn/overview":2197},[4,10,14,20,25,30,35,40,44,49,54,59,64,69,74,79,83,88,92,97,101,107,112,117,122,127,132,137,141,145,150,154,159,164,169,173,178,183,188,192,197,201,206,211,216,220,225,230,235,240,245,250,255,259,264,268,272,277,282,287,292,297,301,306,311,316,321,326,330,335,340,345,349,353,357,362,367,372,377,382,387,392,397,401,406,410,415,419,424,429,434,438,443,448,453,456,460,465,469,474,479,484,488,493,498,502,507,512,517,521,526,530,534,539,543,548,553,558,563,567,572,576,582,587,592,597,602,607,612,617,621,625,630,634,638,642,645,650,655,659,664,669,674,677,681,686,691,696,700,705,709,714,718,722,726,729,733,738,741,745,749,753,756,760,763,767,772,777,781,786,790,795,799,803,806,810,815,820,824,829,834,839,844,847,851,855,859,863,868,872,877,880,883,887,891,895,899,903,907,912,917,922,927,932,937,942],{"id":5,"title":6,"titles":7,"content":8,"level":9},"/v0.0.4/learn/overview","Overview",[],"Configuration loading from struct tags with secret provider support",1,{"id":11,"title":6,"titles":12,"content":13,"level":9},"/v0.0.4/learn/overview#overview",[],"fig loads configuration from environment variables, secret providers, and defaults using Go struct tags.",{"id":15,"title":16,"titles":17,"content":18,"level":19},"/v0.0.4/learn/overview#the-idea","The Idea",[6],"Configuration should be declarative. You define what a field needs—its source, its default, whether it's required—and the library handles the rest. No builder chains, no option structs, no multi-step initialization. One function call, predictable resolution order.",2,{"id":21,"title":22,"titles":23,"content":24,"level":19},"/v0.0.4/learn/overview#the-implementation","The Implementation",[6],"fig provides: Struct tag parsing — env, secret, default, and required tagsDeterministic resolution — secret → env → default → zero value, every timeSecret provider abstraction — plug in Vault, AWS Secrets Manager, GCP Secret Manager, or your ownType conversion — strings, ints, floats, bools, durations, slices, and TextUnmarshalerValidation hooks — implement Validator for post-load checksNested struct support — automatic recursion into embedded configurations",{"id":26,"title":27,"titles":28,"content":29,"level":19},"/v0.0.4/learn/overview#what-it-enables","What It Enables",[6],"Single-source-of-truth configuration structsSecure secret injection without environment variable sprawlTestable configuration with mock providersConsistent loading across services in a microservices architecture fig integrates with: HashiCorp Vault — KV secrets engineAWS Secrets Manager — standard AWS credential chainGCP Secret Manager — project-scoped secrets",{"id":31,"title":32,"titles":33,"content":34,"level":19},"/v0.0.4/learn/overview#next-steps","Next Steps",[6],"Quickstart — get productive in minutesConcepts — understand resolution order and struct tagsReference — full API documentation",{"id":36,"title":37,"titles":38,"content":39,"level":9},"/v0.0.4/learn/quickstart","Quickstart",[],"Get productive with fig in minutes",{"id":41,"title":37,"titles":42,"content":43,"level":9},"/v0.0.4/learn/quickstart#quickstart",[],"",{"id":45,"title":46,"titles":47,"content":48,"level":19},"/v0.0.4/learn/quickstart#installation","Installation",[37],"go get github.com/zoobzio/fig Requires Go 1.24+.",{"id":50,"title":51,"titles":52,"content":53,"level":19},"/v0.0.4/learn/quickstart#basic-usage","Basic Usage",[37],"Define a struct with tags and call Load: package main\n\nimport (\n    \"log\"\n\n    \"github.com/zoobzio/fig\"\n)\n\ntype Config struct {\n    Host     string   `env:\"APP_HOST\" default:\"localhost\"`\n    Port     int      `env:\"APP_PORT\" default:\"8080\"`\n    Debug    bool     `env:\"APP_DEBUG\"`\n    Tags     []string `env:\"APP_TAGS\"`\n    APIKey   string   `env:\"API_KEY\" required:\"true\"`\n}\n\nfunc main() {\n    var cfg Config\n    if err := fig.Load(&cfg); err != nil {\n        log.Fatal(err)\n    }\n    // cfg is now populated\n} Run with: API_KEY=secret123 go run main.go",{"id":55,"title":56,"titles":57,"content":58,"level":19},"/v0.0.4/learn/quickstart#struct-tags","Struct Tags",[37],"fig recognises four tags: TagPurposeExampleenvEnvironment variable nameenv:\"DATABASE_URL\"secretSecret provider keysecret:\"db/password\"defaultFallback valuedefault:\"localhost\"requiredFail if no value foundrequired:\"true\" Resolution order: secret → env → default → zero value.",{"id":60,"title":61,"titles":62,"content":63,"level":19},"/v0.0.4/learn/quickstart#adding-secrets","Adding Secrets",[37],"Pass a secret provider to load secrets from external stores: import (\n    \"log\"\n\n    \"github.com/zoobzio/fig\"\n    \"github.com/zoobzio/fig/vault\"\n)\n\ntype Config struct {\n    Password string `secret:\"db/password\"`\n}\n\nfunc main() {\n    provider, err := vault.New()\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    var cfg Config\n    if err := fig.Load(&cfg, provider); err != nil {\n        log.Fatal(err)\n    }\n} Secrets take precedence over environment variables. See Secret Providers for details.",{"id":65,"title":66,"titles":67,"content":68,"level":19},"/v0.0.4/learn/quickstart#validation","Validation",[37],"Implement the Validator interface for post-load validation: type Config struct {\n    Port int `env:\"APP_PORT\" default:\"8080\"`\n}\n\nfunc (c *Config) Validate() error {\n    if c.Port \u003C= 0 || c.Port > 65535 {\n        return errors.New(\"port must be between 1 and 65535\")\n    }\n    return nil\n} fig calls Validate() automatically after loading. See Validation for patterns.",{"id":70,"title":71,"titles":72,"content":73,"level":19},"/v0.0.4/learn/quickstart#nested-structs","Nested Structs",[37],"fig recurses into embedded structs: type DatabaseConfig struct {\n    Host string `env:\"DB_HOST\" default:\"localhost\"`\n    Port int    `env:\"DB_PORT\" default:\"5432\"`\n}\n\ntype Config struct {\n    Database DatabaseConfig\n}",{"id":75,"title":76,"titles":77,"content":78,"level":19},"/v0.0.4/learn/quickstart#context-support","Context Support",[37],"Use LoadContext for secret provider timeouts: ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\n\nif err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n    log.Fatal(err)\n}",{"id":80,"title":32,"titles":81,"content":82,"level":19},"/v0.0.4/learn/quickstart#next-steps",[37],"Concepts — understand resolution order and abstraction layersArchitecture — learn how fig works internallyTesting — test code that uses figReference — full API documentation html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":84,"title":85,"titles":86,"content":87,"level":9},"/v0.0.4/learn/concepts","Concepts",[],"Core abstractions and mental models for fig",{"id":89,"title":85,"titles":90,"content":91,"level":9},"/v0.0.4/learn/concepts#concepts",[],"This page covers the mental models for understanding fig: how configuration sources are prioritised, how struct tags declare intent, and how extension points work.",{"id":93,"title":94,"titles":95,"content":96,"level":19},"/v0.0.4/learn/concepts#resolution-order","Resolution Order",[85],"fig resolves each field in a fixed order: Secret — query the secret provider (if configured)Environment variable — check os.GetenvDefault — use the default tag valueZero value — leave the field uninitialised This order is intentional. Secrets take precedence because they represent the most secure and specific source. Environment variables come next as the standard twelve-factor approach. Defaults provide sensible fallbacks. type Config struct {\n    // If secret \"db/pass\" exists, use it\n    // Otherwise, check DB_PASSWORD env var\n    // Otherwise, use \"changeme\"\n    Password string `secret:\"db/pass\" env:\"DB_PASSWORD\" default:\"changeme\"`\n} Resolution stops at the first successful source. If a secret is found, the environment variable is never checked.",{"id":98,"title":56,"titles":99,"content":100,"level":19},"/v0.0.4/learn/concepts#struct-tags",[85],"fig uses four struct tags to declare configuration sources:",{"id":102,"title":103,"titles":104,"content":105,"level":106},"/v0.0.4/learn/concepts#env","env",[85,56],"Maps a field to an environment variable: Host string `env:\"DATABASE_HOST\"` The value is read via os.Getenv. Empty environment variables are treated as unset—fig continues to the next source.",3,{"id":108,"title":109,"titles":110,"content":111,"level":106},"/v0.0.4/learn/concepts#secret","secret",[85,56],"Maps a field to a secret provider key: Password string `secret:\"database/credentials:password\"` The key format is provider-specific. See Secret Providers for conventions.",{"id":113,"title":114,"titles":115,"content":116,"level":106},"/v0.0.4/learn/concepts#default","default",[85,56],"Provides a fallback value: Port int `default:\"5432\"` Defaults are parsed using the same type conversion as environment variables.",{"id":118,"title":119,"titles":120,"content":121,"level":106},"/v0.0.4/learn/concepts#required","required",[85,56],"Marks a field as mandatory: APIKey string `env:\"API_KEY\" required:\"true\"` If no value is found from any source, Load returns a FieldError wrapping ErrRequired.",{"id":123,"title":124,"titles":125,"content":126,"level":19},"/v0.0.4/learn/concepts#secretprovider-interface","SecretProvider Interface",[85],"Secret providers implement a single method: type SecretProvider interface {\n    Get(ctx context.Context, key string) (string, error)\n} The interface is intentionally minimal. Providers handle their own authentication, caching, and retry logic. fig only cares about the final string value. Error handling: Providers should return ErrSecretNotFound when a secret doesn't exist. Other errors propagate and fail the load. This distinction lets fig continue to environment variables when a secret is simply absent, while failing fast on actual errors (network issues, permission denied, etc.). Built-in providers: vault.Provider — HashiCorp Vaultawssm.Provider — AWS Secrets Managergcpsm.Provider — GCP Secret Manager",{"id":128,"title":129,"titles":130,"content":131,"level":19},"/v0.0.4/learn/concepts#validator-interface","Validator Interface",[85],"Validation happens after all fields are populated: type Validator interface {\n    Validate() error\n} Implement this on your config struct for cross-field validation, range checks, or business logic: func (c *Config) Validate() error {\n    if c.MaxConns \u003C c.MinConns {\n        return errors.New(\"max connections must be >= min connections\")\n    }\n    return nil\n} Validation errors are returned directly—they're not wrapped in FieldError because they may involve multiple fields.",{"id":133,"title":134,"titles":135,"content":136,"level":19},"/v0.0.4/learn/concepts#type-conversion","Type Conversion",[85],"fig converts string values to the target type. Supported types include primitives (string, int, uint, float, bool), time.Duration, []string (comma-separated), and any type implementing encoding.TextUnmarshaler. Conversion errors result in a FieldError wrapping ErrInvalidType. See Types Reference for the complete list with parsing details.",{"id":138,"title":71,"titles":139,"content":140,"level":19},"/v0.0.4/learn/concepts#nested-structs",[85],"fig recurses into nested structs automatically: type DatabaseConfig struct {\n    Host string `env:\"DB_HOST\"`\n    Port int    `env:\"DB_PORT\"`\n}\n\ntype Config struct {\n    Database DatabaseConfig  // fig descends into this\n} The nested struct must not have any fig tags on the parent field. Tagged fields are treated as leaf values, not containers.",{"id":142,"title":32,"titles":143,"content":144,"level":19},"/v0.0.4/learn/concepts#next-steps",[85],"Architecture — internal implementation detailsSecret Providers — implementing custom providersValidation — validation patternsReference — complete type documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"id":146,"title":147,"titles":148,"content":149,"level":9},"/v0.0.4/learn/architecture","Architecture",[],"Internal implementation details and design decisions",{"id":151,"title":147,"titles":152,"content":153,"level":9},"/v0.0.4/learn/architecture#architecture",[],"This page covers fig's internal structure for contributors and those who need to understand the implementation.",{"id":155,"title":156,"titles":157,"content":158,"level":19},"/v0.0.4/learn/architecture#component-overview","Component Overview",[147],"┌─────────────────────────────────────────────────────────────┐\n│                         fig.Load                            │\n├─────────────────────────────────────────────────────────────┤\n│                                                             │\n│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │\n│  │   sentinel   │───▶│   resolve    │───▶│   convert    │  │\n│  │  (metadata)  │    │ (sourcing)   │    │  (parsing)   │  │\n│  └──────────────┘    └──────────────┘    └──────────────┘  │\n│                              │                              │\n│                              ▼                              │\n│                    ┌──────────────────┐                     │\n│                    │ SecretProvider   │                     │\n│                    │    (optional)    │                     │\n│                    └──────────────────┘                     │\n│                                                             │\n├─────────────────────────────────────────────────────────────┤\n│                        Validator                            │\n│                       (optional)                            │\n└─────────────────────────────────────────────────────────────┘",{"id":160,"title":161,"titles":162,"content":163,"level":19},"/v0.0.4/learn/architecture#sentinel-integration","Sentinel Integration",[147],"fig uses sentinel for struct tag parsing and metadata caching. On package init, fig registers its tags: func init() {\n    sentinel.Tag(\"env\")\n    sentinel.Tag(\"secret\")\n    sentinel.Tag(\"default\")\n    sentinel.Tag(\"required\")\n} When Load is called, sentinel scans the struct once and caches the metadata. Subsequent loads of the same type reuse the cached metadata, avoiding reflection overhead. Sentinel provides: TryScan[T]() — extract struct metadata with error handlingLookup(fqdn) — retrieve cached metadata for nested structsFieldMetadata — per-field information including tags, type, and index",{"id":165,"title":166,"titles":167,"content":168,"level":19},"/v0.0.4/learn/architecture#resolution-flow","Resolution Flow",[147],"The resolve.go file handles value sourcing: Parse tags — extract env, secret, default, required from sentinel metadataQuery sources — try secret provider, then environment, then defaultCheck required — if no value found and required:\"true\", return errorConvert — parse string to target typeSet — assign the converted value to the struct field func resolveField(ctx context.Context, tags fieldTags, provider SecretProvider) (string, bool, error) {\n    // 1. Try secret provider\n    if tags.secret != \"\" && provider != nil {\n        val, err := provider.Get(ctx, tags.secret)\n        if err == nil {\n            return val, true, nil\n        }\n        if err != ErrSecretNotFound {\n            return \"\", false, err  // Propagate real errors\n        }\n    }\n\n    // 2. Try environment variable\n    if tags.env != \"\" {\n        if val := os.Getenv(tags.env); val != \"\" {\n            return val, true, nil\n        }\n    }\n\n    // 3. Try default value\n    if tags.defValue != \"\" {\n        return tags.defValue, true, nil\n    }\n\n    return \"\", false, nil\n} The bool return indicates whether a value was found. This distinguishes \"no value\" from \"empty string value\".",{"id":170,"title":134,"titles":171,"content":172,"level":19},"/v0.0.4/learn/architecture#type-conversion",[147],"The convert.go file handles string-to-type parsing: Pointer types — unwrap, convert element, wrap in pointerTextUnmarshaler — delegate to the type's UnmarshalText methodPrimitive types — use strconv functionsDuration — special case using time.ParseDurationSlices — comma-split for []string Conversion uses reflection to inspect the target type and select the appropriate parser.",{"id":174,"title":175,"titles":176,"content":177,"level":19},"/v0.0.4/learn/architecture#nested-struct-handling","Nested Struct Handling",[147],"When a field is a struct without fig tags, fig treats it as a container and recurses: if field.Kind == sentinel.KindStruct {\n    tags := parseFieldTags(field)\n    if tags.env == \"\" && tags.secret == \"\" && tags.defValue == \"\" && !tags.required {\n        fqdn := field.ReflectType.PkgPath() + \".\" + field.ReflectType.Name()\n        if nestedMeta, found := sentinel.Lookup(fqdn); found {\n            loadFromMetadata(ctx, fieldVal, nestedMeta, provider)\n        }\n        continue\n    }\n} Nested struct metadata is looked up from sentinel's cache using the fully-qualified type name.",{"id":179,"title":180,"titles":181,"content":182,"level":19},"/v0.0.4/learn/architecture#design-qa","Design Q&A",[147],"Why use sentinel instead of parsing tags directly? Sentinel provides caching. Parsing struct tags via reflection is expensive. For applications that load configuration once at startup, this doesn't matter. For libraries that might call Load repeatedly, caching prevents redundant work. Why is there only one secret provider? Simplicity. Most applications use one secret store. If you need multiple providers, compose them into a single provider that delegates. See Composing Providers for patterns. Why does secret take precedence over env? Secrets represent the most specific, secure configuration source. They're typically managed by infrastructure teams and override developer defaults. Environment variables are convenient but less secure—they appear in process listings, crash dumps, and logs. Why return FieldError instead of just error? Field context is essential for debugging. When loading a config with 20 fields, knowing which one failed saves time. FieldError wraps the underlying error while preserving field name.",{"id":184,"title":185,"titles":186,"content":187,"level":19},"/v0.0.4/learn/architecture#performance","Performance",[147],"OperationComplexityFirst Load of a typeO(n) where n = number of fieldsSubsequent Load of same typeO(n) with cached metadataSecret provider callProvider-dependentType conversionO(1) for primitives, O(m) for slices Memory allocation is minimal—fig doesn't allocate intermediate structures beyond what's needed for the config struct itself.",{"id":189,"title":32,"titles":190,"content":191,"level":19},"/v0.0.4/learn/architecture#next-steps",[147],"Reference — full API documentationTesting — test code that uses figSecret Providers — implementing custom providers html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}",{"id":193,"title":194,"titles":195,"content":196,"level":9},"/v0.0.4/guides/testing","Testing",[],"How to test code that uses fig",{"id":198,"title":194,"titles":199,"content":200,"level":9},"/v0.0.4/guides/testing#testing",[],"This guide covers testing strategies for code that uses fig, including mocking secret providers and managing environment variables in tests.",{"id":202,"title":203,"titles":204,"content":205,"level":19},"/v0.0.4/guides/testing#test-utilities","Test Utilities",[194],"fig provides test helpers in the testing package: import figtesting \"github.com/zoobzio/fig/testing\"",{"id":207,"title":208,"titles":209,"content":210,"level":106},"/v0.0.4/guides/testing#mockprovider","MockProvider",[194,203],"A simple in-memory secret provider for tests: func TestConfigWithSecrets(t *testing.T) {\n    provider := figtesting.NewMockProvider(t, map[string]string{\n        \"db/password\":  \"test-password\",\n        \"api/key\":      \"test-api-key\",\n    })\n\n    var cfg Config\n    if err := fig.Load(&cfg, provider); err != nil {\n        t.Fatal(err)\n    }\n\n    if cfg.Password != \"test-password\" {\n        t.Errorf(\"Password = %q, want %q\", cfg.Password, \"test-password\")\n    }\n} MockProvider returns ErrSecretNotFound for missing keys, matching the expected provider behaviour.",{"id":212,"title":213,"titles":214,"content":215,"level":106},"/v0.0.4/guides/testing#setenv","SetEnv",[194,203],"Sets an environment variable for the test's duration: func TestConfigFromEnv(t *testing.T) {\n    figtesting.SetEnv(t, \"APP_HOST\", \"test.example.com\")\n    figtesting.SetEnv(t, \"APP_PORT\", \"9000\")\n\n    var cfg Config\n    if err := fig.Load(&cfg); err != nil {\n        t.Fatal(err)\n    }\n\n    if cfg.Host != \"test.example.com\" {\n        t.Errorf(\"Host = %q, want %q\", cfg.Host, \"test.example.com\")\n    }\n} The helper uses t.Setenv internally, so variables are automatically restored after the test.",{"id":217,"title":218,"titles":219,"content":43,"level":19},"/v0.0.4/guides/testing#testing-patterns","Testing Patterns",[194],{"id":221,"title":222,"titles":223,"content":224,"level":106},"/v0.0.4/guides/testing#testing-default-values","Testing Default Values",[194,218],"Verify defaults by loading without environment variables: func TestDefaults(t *testing.T) {\n    var cfg Config\n    if err := fig.Load(&cfg); err != nil {\n        t.Fatal(err)\n    }\n\n    if cfg.Port != 8080 {\n        t.Errorf(\"Port = %d, want default 8080\", cfg.Port)\n    }\n}",{"id":226,"title":227,"titles":228,"content":229,"level":106},"/v0.0.4/guides/testing#testing-required-fields","Testing Required Fields",[194,218],"Verify that missing required fields fail: func TestRequiredFields(t *testing.T) {\n    var cfg struct {\n        APIKey string `env:\"TEST_API_KEY\" required:\"true\"`\n    }\n\n    err := fig.Load(&cfg)\n    if err == nil {\n        t.Fatal(\"expected error for missing required field\")\n    }\n\n    var fieldErr *fig.FieldError\n    if !errors.As(err, &fieldErr) {\n        t.Fatalf(\"expected FieldError, got %T\", err)\n    }\n    if fieldErr.Field != \"APIKey\" {\n        t.Errorf(\"FieldError.Field = %q, want %q\", fieldErr.Field, \"APIKey\")\n    }\n}",{"id":231,"title":232,"titles":233,"content":234,"level":106},"/v0.0.4/guides/testing#testing-validation","Testing Validation",[194,218],"Test the Validator interface: type validatedConfig struct {\n    Port int `env:\"TEST_PORT\" default:\"8080\"`\n}\n\nfunc (c *validatedConfig) Validate() error {\n    if c.Port \u003C= 0 || c.Port > 65535 {\n        return errors.New(\"port must be between 1 and 65535\")\n    }\n    return nil\n}\n\nfunc TestValidation(t *testing.T) {\n    figtesting.SetEnv(t, \"TEST_PORT\", \"-1\")\n\n    var cfg validatedConfig\n    err := fig.Load(&cfg)\n    if err == nil {\n        t.Fatal(\"expected validation error\")\n    }\n    if err.Error() != \"port must be between 1 and 65535\" {\n        t.Errorf(\"unexpected error: %v\", err)\n    }\n}",{"id":236,"title":237,"titles":238,"content":239,"level":106},"/v0.0.4/guides/testing#testing-secret-provider-errors","Testing Secret Provider Errors",[194,218],"Test that provider errors propagate correctly: type failingProvider struct {\n    err error\n}\n\nfunc (p *failingProvider) Get(ctx context.Context, key string) (string, error) {\n    return \"\", p.err\n}\n\nfunc TestProviderError(t *testing.T) {\n    provider := &failingProvider{err: errors.New(\"network timeout\")}\n\n    var cfg struct {\n        Secret string `secret:\"db/password\"`\n    }\n\n    err := fig.Load(&cfg, provider)\n    if err == nil {\n        t.Fatal(\"expected error from provider\")\n    }\n\n    var fieldErr *fig.FieldError\n    if !errors.As(err, &fieldErr) {\n        t.Fatalf(\"expected FieldError, got %T\", err)\n    }\n}",{"id":241,"title":242,"titles":243,"content":244,"level":106},"/v0.0.4/guides/testing#testing-resolution-order","Testing Resolution Order",[194,218],"Verify that secrets take precedence over environment variables: func TestSecretPrecedence(t *testing.T) {\n    figtesting.SetEnv(t, \"DB_PASSWORD\", \"env-password\")\n    provider := figtesting.NewMockProvider(t, map[string]string{\n        \"db/password\": \"secret-password\",\n    })\n\n    var cfg struct {\n        Password string `secret:\"db/password\" env:\"DB_PASSWORD\"`\n    }\n\n    if err := fig.Load(&cfg, provider); err != nil {\n        t.Fatal(err)\n    }\n\n    if cfg.Password != \"secret-password\" {\n        t.Errorf(\"Password = %q, want secret to take precedence\", cfg.Password)\n    }\n}",{"id":246,"title":247,"titles":248,"content":249,"level":19},"/v0.0.4/guides/testing#integration-testing","Integration Testing",[194],"For integration tests against real secret providers, use build tags: //go:build integration\n\npackage myapp_test\n\nimport (\n    \"context\"\n    \"testing\"\n\n    \"github.com/zoobzio/fig\"\n    \"github.com/zoobzio/fig/vault\"\n)\n\nfunc TestVaultIntegration(t *testing.T) {\n    provider, err := vault.New()\n    if err != nil {\n        t.Skip(\"Vault not available\")\n    }\n\n    var cfg Config\n    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n    defer cancel()\n\n    if err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n        t.Fatal(err)\n    }\n\n    // Assert expected values from test Vault instance\n} Run with: go test -tags=integration ./...",{"id":251,"title":252,"titles":253,"content":254,"level":19},"/v0.0.4/guides/testing#table-driven-tests","Table-Driven Tests",[194],"For comprehensive configuration testing: func TestConfigScenarios(t *testing.T) {\n    tests := []struct {\n        name     string\n        env      map[string]string\n        secrets  map[string]string\n        wantHost string\n        wantPort int\n        wantErr  bool\n    }{\n        {\n            name:     \"defaults\",\n            wantHost: \"localhost\",\n            wantPort: 8080,\n        },\n        {\n            name:     \"env override\",\n            env:      map[string]string{\"APP_HOST\": \"prod.example.com\"},\n            wantHost: \"prod.example.com\",\n            wantPort: 8080,\n        },\n        {\n            name:    \"missing required\",\n            wantErr: true,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            for k, v := range tt.env {\n                figtesting.SetEnv(t, k, v)\n            }\n            provider := figtesting.NewMockProvider(t, tt.secrets)\n\n            var cfg Config\n            err := fig.Load(&cfg, provider)\n\n            if tt.wantErr {\n                if err == nil {\n                    t.Error(\"expected error\")\n                }\n                return\n            }\n            if err != nil {\n                t.Fatalf(\"unexpected error: %v\", err)\n            }\n\n            if cfg.Host != tt.wantHost {\n                t.Errorf(\"Host = %q, want %q\", cfg.Host, tt.wantHost)\n            }\n            if cfg.Port != tt.wantPort {\n                t.Errorf(\"Port = %d, want %d\", cfg.Port, tt.wantPort)\n            }\n        })\n    }\n}",{"id":256,"title":32,"titles":257,"content":258,"level":19},"/v0.0.4/guides/testing#next-steps",[194],"Troubleshooting — common errors and debuggingReference — full API documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}",{"id":260,"title":261,"titles":262,"content":263,"level":9},"/v0.0.4/guides/troubleshooting","Troubleshooting",[],"Common errors, edge cases, and debugging strategies",{"id":265,"title":261,"titles":266,"content":267,"level":9},"/v0.0.4/guides/troubleshooting#troubleshooting",[],"This guide covers common errors, their causes, and how to resolve them.",{"id":269,"title":270,"titles":271,"content":43,"level":19},"/v0.0.4/guides/troubleshooting#error-types","Error Types",[261],{"id":273,"title":274,"titles":275,"content":276,"level":106},"/v0.0.4/guides/troubleshooting#errrequired","ErrRequired",[261,270],"Message: fig: required field not set Cause: A field marked required:\"true\" has no value from any source (secret, env, or default). Wrapped in: FieldError with the field name. Example: type Config struct {\n    APIKey string `env:\"API_KEY\" required:\"true\"`\n}\n\nvar cfg Config\nerr := fig.Load(&cfg)\n// Error: fig: field APIKey: fig: required field not set Solutions: Set the environment variable: export API_KEY=your-keyAdd a default value: default:\"fallback-key\"Provide the value via secret providerRemove required:\"true\" if the field is optional",{"id":278,"title":279,"titles":280,"content":281,"level":106},"/v0.0.4/guides/troubleshooting#errinvalidtype","ErrInvalidType",[261,270],"Message: fig: invalid type conversion Cause: A string value could not be parsed into the target type. Wrapped in: FieldError with the field name. Examples: // \"abc\" is not a valid integer\ntype Config struct {\n    Port int `env:\"PORT\"`\n}\n// PORT=abc → fig: field Port: fig: invalid type conversion\n\n// \"maybe\" is not a valid boolean\ntype Config struct {\n    Debug bool `env:\"DEBUG\"`\n}\n// DEBUG=maybe → fig: field Debug: fig: invalid type conversion Solutions: Fix the environment variable valueUse valid formats:\nIntegers: 123, -456Floats: 1.5, -2.7Bools: true, false, 1, 0, t, fDurations: 30s, 5m, 1h30m",{"id":283,"title":284,"titles":285,"content":286,"level":106},"/v0.0.4/guides/troubleshooting#errnotstruct","ErrNotStruct",[261,270],"Message: fig: type must be a struct Cause: Load was called with a non-struct type. Example: var s string\nerr := fig.Load(&s)\n// Error: fig: type must be a struct Solution: Pass a pointer to a struct.",{"id":288,"title":289,"titles":290,"content":291,"level":106},"/v0.0.4/guides/troubleshooting#errsecretnotfound","ErrSecretNotFound",[261,270],"Message: fig: secret not found Cause: The secret provider could not find the requested key. Behaviour: This error is handled gracefully—fig continues to the next source (env, then default). It only becomes a problem if the field is required and no other source provides a value. When you see it: Typically wrapped in FieldError when a required secret-only field has no value: type Config struct {\n    Password string `secret:\"db/password\" required:\"true\"`\n}\n// No provider, or provider doesn't have \"db/password\"\n// Error: fig: field Password: fig: required field not set",{"id":293,"title":294,"titles":295,"content":296,"level":19},"/v0.0.4/guides/troubleshooting#fielderror","FieldError",[261],"All field-level errors are wrapped in FieldError: type FieldError struct {\n    Field string\n    Err   error\n} Extracting field information: err := fig.Load(&cfg)\nif err != nil {\n    var fieldErr *fig.FieldError\n    if errors.As(err, &fieldErr) {\n        fmt.Printf(\"Field: %s\\n\", fieldErr.Field)\n        fmt.Printf(\"Cause: %v\\n\", fieldErr.Err)\n    }\n} Checking for specific errors: if errors.Is(err, fig.ErrRequired) {\n    // Handle missing required field\n}\nif errors.Is(err, fig.ErrInvalidType) {\n    // Handle type conversion failure\n}",{"id":298,"title":299,"titles":300,"content":43,"level":19},"/v0.0.4/guides/troubleshooting#common-issues","Common Issues",[261],{"id":302,"title":303,"titles":304,"content":305,"level":106},"/v0.0.4/guides/troubleshooting#empty-environment-variables","Empty Environment Variables",[261,299],"Empty environment variables are treated as unset: // APP_HOST=\"\" (empty string)\ntype Config struct {\n    Host string `env:\"APP_HOST\" default:\"localhost\"`\n}\n// cfg.Host = \"localhost\" (falls through to default) If you need to distinguish between \"unset\" and \"empty string\", use a pointer: type Config struct {\n    Host *string `env:\"APP_HOST\"`\n}\n// APP_HOST=\"\" → cfg.Host = ptr to \"\"\n// APP_HOST unset → cfg.Host = nil",{"id":307,"title":308,"titles":309,"content":310,"level":106},"/v0.0.4/guides/troubleshooting#unexported-fields","Unexported Fields",[261,299],"Unexported fields cannot be set: type Config struct {\n    host string `env:\"APP_HOST\"` // lowercase, unexported\n}\n// cfg.host remains zero value Solution: Export the field (capitalise the first letter).",{"id":312,"title":313,"titles":314,"content":315,"level":106},"/v0.0.4/guides/troubleshooting#missing-nested-struct-metadata","Missing Nested Struct Metadata",[261,299],"Nested structs must be defined in a way that sentinel can scan them: // Works: named struct type\ntype DatabaseConfig struct {\n    Host string `env:\"DB_HOST\"`\n}\n\ntype Config struct {\n    Database DatabaseConfig\n}\n\n// Doesn't work: anonymous struct\ntype Config struct {\n    Database struct {\n        Host string `env:\"DB_HOST\"`\n    }\n} Solution: Use named struct types for nested configuration.",{"id":317,"title":318,"titles":319,"content":320,"level":106},"/v0.0.4/guides/troubleshooting#tagged-nested-structs","Tagged Nested Structs",[261,299],"If a nested struct field has fig tags, it's treated as a leaf, not a container: type Config struct {\n    // This is treated as a single field, not recursed into\n    Database DatabaseConfig `env:\"DATABASE_JSON\"`\n} Solution: Remove tags from the parent field to enable recursion.",{"id":322,"title":323,"titles":324,"content":325,"level":106},"/v0.0.4/guides/troubleshooting#provider-errors-vs-not-found","Provider Errors vs Not Found",[261,299],"Providers should return ErrSecretNotFound only when the secret doesn't exist. Other errors (network, permissions) should propagate: func (p *MyProvider) Get(ctx context.Context, key string) (string, error) {\n    val, err := p.client.Get(key)\n    if err == client.ErrNotFound {\n        return \"\", fig.ErrSecretNotFound  // fig continues to next source\n    }\n    if err != nil {\n        return \"\", err  // fig fails with this error\n    }\n    return val, nil\n} If your provider returns ErrSecretNotFound for all errors, you'll mask real problems.",{"id":327,"title":328,"titles":329,"content":43,"level":19},"/v0.0.4/guides/troubleshooting#debugging-strategies","Debugging Strategies",[261],{"id":331,"title":332,"titles":333,"content":334,"level":106},"/v0.0.4/guides/troubleshooting#print-resolution","Print Resolution",[261,328],"Add logging to understand which sources are being used: type loggingProvider struct {\n    inner fig.SecretProvider\n}\n\nfunc (p *loggingProvider) Get(ctx context.Context, key string) (string, error) {\n    val, err := p.inner.Get(ctx, key)\n    if err != nil {\n        log.Printf(\"secret %q: not found\", key)\n    } else {\n        log.Printf(\"secret %q: found\", key)\n    }\n    return val, err\n}",{"id":336,"title":337,"titles":338,"content":339,"level":106},"/v0.0.4/guides/troubleshooting#inspect-fielderror-chain","Inspect FieldError Chain",[261,328],"Walk the error chain to understand the full context: err := fig.Load(&cfg)\nif err != nil {\n    for e := err; e != nil; e = errors.Unwrap(e) {\n        fmt.Printf(\"%T: %v\\n\", e, e)\n    }\n}",{"id":341,"title":342,"titles":343,"content":344,"level":106},"/v0.0.4/guides/troubleshooting#use-struct-literals-for-testing","Use Struct Literals for Testing",[261,328],"When debugging, create a known-good config to compare against: expected := Config{\n    Host: \"localhost\",\n    Port: 8080,\n}\n\nvar actual Config\nfig.Load(&actual)\n\nif actual != expected {\n    t.Errorf(\"got %+v, want %+v\", actual, expected)\n}",{"id":346,"title":32,"titles":347,"content":348,"level":19},"/v0.0.4/guides/troubleshooting#next-steps",[261],"Testing — testing patterns for figReference — complete type documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"id":350,"title":66,"titles":351,"content":352,"level":9},"/v0.0.4/guides/validation",[],"Custom validation patterns with the Validator interface",{"id":354,"title":66,"titles":355,"content":356,"level":9},"/v0.0.4/guides/validation#validation",[],"fig supports custom validation through the Validator interface. This guide covers implementation patterns and best practices.",{"id":358,"title":359,"titles":360,"content":361,"level":19},"/v0.0.4/guides/validation#the-validator-interface","The Validator Interface",[66],"type Validator interface {\n    Validate() error\n} If your config struct implements Validator, fig calls Validate() after all fields are populated. Validation errors are returned directly from Load.",{"id":363,"title":364,"titles":365,"content":366,"level":19},"/v0.0.4/guides/validation#basic-validation","Basic Validation",[66],"Range checks and simple constraints: type ServerConfig struct {\n    Port    int    `env:\"PORT\" default:\"8080\"`\n    Timeout int    `env:\"TIMEOUT\" default:\"30\"`\n    Host    string `env:\"HOST\" default:\"0.0.0.0\"`\n}\n\nfunc (c *ServerConfig) Validate() error {\n    if c.Port \u003C= 0 || c.Port > 65535 {\n        return errors.New(\"port must be between 1 and 65535\")\n    }\n    if c.Timeout \u003C= 0 {\n        return errors.New(\"timeout must be positive\")\n    }\n    return nil\n}",{"id":368,"title":369,"titles":370,"content":371,"level":19},"/v0.0.4/guides/validation#cross-field-validation","Cross-Field Validation",[66],"Validate relationships between fields: type PoolConfig struct {\n    MinConns int `env:\"POOL_MIN\" default:\"5\"`\n    MaxConns int `env:\"POOL_MAX\" default:\"25\"`\n}\n\nfunc (c *PoolConfig) Validate() error {\n    if c.MinConns > c.MaxConns {\n        return fmt.Errorf(\"min connections (%d) cannot exceed max (%d)\",\n            c.MinConns, c.MaxConns)\n    }\n    if c.MinConns \u003C 0 {\n        return errors.New(\"min connections cannot be negative\")\n    }\n    return nil\n}",{"id":373,"title":374,"titles":375,"content":376,"level":19},"/v0.0.4/guides/validation#multiple-errors","Multiple Errors",[66],"Collect all validation errors before returning: import (\n    \"errors\"\n    \"fmt\"\n    \"strings\"\n)\n\ntype Config struct {\n    Host     string `env:\"HOST\"`\n    Port     int    `env:\"PORT\"`\n    LogLevel string `env:\"LOG_LEVEL\"`\n}\n\nfunc (c *Config) Validate() error {\n    var errs []string\n\n    if c.Host == \"\" {\n        errs = append(errs, \"host is required\")\n    }\n    if c.Port \u003C= 0 || c.Port > 65535 {\n        errs = append(errs, \"port must be between 1 and 65535\")\n    }\n\n    validLevels := map[string]bool{\"debug\": true, \"info\": true, \"warn\": true, \"error\": true}\n    if c.LogLevel != \"\" && !validLevels[c.LogLevel] {\n        errs = append(errs, fmt.Sprintf(\"invalid log level: %s\", c.LogLevel))\n    }\n\n    if len(errs) > 0 {\n        return errors.New(strings.Join(errs, \"; \"))\n    }\n    return nil\n}",{"id":378,"title":379,"titles":380,"content":381,"level":19},"/v0.0.4/guides/validation#validation-vs-required","Validation vs Required",[66],"Use required:\"true\" for presence checks, Validate() for semantic checks: type Config struct {\n    // Use required for \"must be set\"\n    APIKey string `env:\"API_KEY\" required:\"true\"`\n\n    // Use validation for \"must be valid format\"\n    Email string `env:\"ADMIN_EMAIL\"`\n}\n\nfunc (c *Config) Validate() error {\n    if c.Email != \"\" && !strings.Contains(c.Email, \"@\") {\n        return errors.New(\"admin email must be valid\")\n    }\n    return nil\n} When to use required: The field must have a non-zero valueAbsence is a configuration error When to use Validate: The value must meet specific criteriaCross-field dependencies existFormat validation is needed",{"id":383,"title":384,"titles":385,"content":386,"level":19},"/v0.0.4/guides/validation#conditional-validation","Conditional Validation",[66],"Validate based on other field values: type Config struct {\n    Mode     string `env:\"MODE\" default:\"development\"`\n    TLSCert  string `env:\"TLS_CERT\"`\n    TLSKey   string `env:\"TLS_KEY\"`\n}\n\nfunc (c *Config) Validate() error {\n    if c.Mode == \"production\" {\n        if c.TLSCert == \"\" || c.TLSKey == \"\" {\n            return errors.New(\"TLS cert and key required in production mode\")\n        }\n    }\n    return nil\n}",{"id":388,"title":389,"titles":390,"content":391,"level":19},"/v0.0.4/guides/validation#nested-struct-validation","Nested Struct Validation",[66],"Each nested struct can implement its own Validator: type DatabaseConfig struct {\n    Host string `env:\"DB_HOST\" default:\"localhost\"`\n    Port int    `env:\"DB_PORT\" default:\"5432\"`\n}\n\nfunc (c *DatabaseConfig) Validate() error {\n    if c.Port \u003C= 0 {\n        return errors.New(\"database port must be positive\")\n    }\n    return nil\n}\n\ntype AppConfig struct {\n    Database DatabaseConfig\n    Name     string `env:\"APP_NAME\" required:\"true\"`\n}\n\nfunc (c *AppConfig) Validate() error {\n    // Explicitly validate nested struct\n    if err := c.Database.Validate(); err != nil {\n        return fmt.Errorf(\"database config: %w\", err)\n    }\n\n    if len(c.Name) > 64 {\n        return errors.New(\"app name too long\")\n    }\n    return nil\n} Note: fig only calls Validate() on the top-level struct. Nested validation must be called explicitly.",{"id":393,"title":394,"titles":395,"content":396,"level":19},"/v0.0.4/guides/validation#external-validation-libraries","External Validation Libraries",[66],"Integrate with validation libraries: import \"github.com/go-playground/validator/v10\"\n\nvar validate = validator.New()\n\ntype Config struct {\n    Email string `env:\"EMAIL\" validate:\"required,email\"`\n    URL   string `env:\"URL\" validate:\"required,url\"`\n}\n\nfunc (c *Config) Validate() error {\n    return validate.Struct(c)\n}",{"id":398,"title":232,"titles":399,"content":400,"level":19},"/v0.0.4/guides/validation#testing-validation",[66],"Test validation logic independently: func TestConfigValidation(t *testing.T) {\n    tests := []struct {\n        name    string\n        config  Config\n        wantErr bool\n    }{\n        {\n            name:    \"valid config\",\n            config:  Config{Port: 8080, Host: \"localhost\"},\n            wantErr: false,\n        },\n        {\n            name:    \"invalid port\",\n            config:  Config{Port: -1, Host: \"localhost\"},\n            wantErr: true,\n        },\n        {\n            name:    \"port out of range\",\n            config:  Config{Port: 70000, Host: \"localhost\"},\n            wantErr: true,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            err := tt.config.Validate()\n            if (err != nil) != tt.wantErr {\n                t.Errorf(\"Validate() error = %v, wantErr %v\", err, tt.wantErr)\n            }\n        })\n    }\n}",{"id":402,"title":403,"titles":404,"content":405,"level":19},"/v0.0.4/guides/validation#error-handling","Error Handling",[66],"Validation errors are not wrapped in FieldError: err := fig.Load(&cfg)\nif err != nil {\n    var fieldErr *fig.FieldError\n    if errors.As(err, &fieldErr) {\n        // This is a field-level error (required, type conversion)\n        fmt.Printf(\"Field %s: %v\\n\", fieldErr.Field, fieldErr.Err)\n    } else {\n        // This is a validation error or other error\n        fmt.Printf(\"Validation: %v\\n\", err)\n    }\n}",{"id":407,"title":32,"titles":408,"content":409,"level":19},"/v0.0.4/guides/validation#next-steps",[66],"Troubleshooting — common errors and debuggingTesting — testing patterns for figReference — full API documentation html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":411,"title":412,"titles":413,"content":414,"level":9},"/v0.0.4/guides/secret-providers","Secret Providers",[],"Implementing and using custom secret providers",{"id":416,"title":412,"titles":417,"content":418,"level":9},"/v0.0.4/guides/secret-providers#secret-providers",[],"This guide covers the SecretProvider interface, implementing custom providers, and best practices for secret management.",{"id":420,"title":421,"titles":422,"content":423,"level":19},"/v0.0.4/guides/secret-providers#the-secretprovider-interface","The SecretProvider Interface",[412],"type SecretProvider interface {\n    Get(ctx context.Context, key string) (string, error)\n} Providers are simple: given a key, return a value. The context supports timeouts and cancellation for network-based providers.",{"id":425,"title":426,"titles":427,"content":428,"level":19},"/v0.0.4/guides/secret-providers#built-in-providers","Built-in Providers",[412],"fig includes providers for common secret stores: PackageBackendKey Formatfig/vaultHashiCorp Vaultpath/to/secret:fieldfig/awssmAWS Secrets Managersecret-name:json-fieldfig/gcpsmGCP Secret Managersecret-name:json-field See the integration docs for Vault, AWS Secrets Manager, and GCP Secret Manager for detailed documentation.",{"id":430,"title":431,"titles":432,"content":433,"level":19},"/v0.0.4/guides/secret-providers#using-a-provider","Using a Provider",[412],"Pass the provider to Load: provider, err := vault.New()\nif err != nil {\n    log.Fatal(err)\n}\n\nvar cfg Config\nif err := fig.Load(&cfg, provider); err != nil {\n    log.Fatal(err)\n} Only one provider is accepted. For multiple backends, see Composing Providers.",{"id":435,"title":436,"titles":437,"content":43,"level":19},"/v0.0.4/guides/secret-providers#implementing-a-custom-provider","Implementing a Custom Provider",[412],{"id":439,"title":440,"titles":441,"content":442,"level":106},"/v0.0.4/guides/secret-providers#basic-implementation","Basic Implementation",[412,436],"type EnvSecretProvider struct {\n    prefix string\n}\n\nfunc NewEnvSecretProvider(prefix string) *EnvSecretProvider {\n    return &EnvSecretProvider{prefix: prefix}\n}\n\nfunc (p *EnvSecretProvider) Get(ctx context.Context, key string) (string, error) {\n    // Convert \"db/password\" to \"SECRET_DB_PASSWORD\"\n    envKey := p.prefix + strings.ToUpper(strings.ReplaceAll(key, \"/\", \"_\"))\n\n    val := os.Getenv(envKey)\n    if val == \"\" {\n        return \"\", fig.ErrSecretNotFound\n    }\n    return val, nil\n}",{"id":444,"title":445,"titles":446,"content":447,"level":106},"/v0.0.4/guides/secret-providers#with-caching","With Caching",[412,436],"type CachingProvider struct {\n    inner fig.SecretProvider\n    cache map[string]string\n    mu    sync.RWMutex\n    ttl   time.Duration\n}\n\nfunc NewCachingProvider(inner fig.SecretProvider, ttl time.Duration) *CachingProvider {\n    return &CachingProvider{\n        inner: inner,\n        cache: make(map[string]string),\n        ttl:   ttl,\n    }\n}\n\nfunc (p *CachingProvider) Get(ctx context.Context, key string) (string, error) {\n    p.mu.RLock()\n    if val, ok := p.cache[key]; ok {\n        p.mu.RUnlock()\n        return val, nil\n    }\n    p.mu.RUnlock()\n\n    val, err := p.inner.Get(ctx, key)\n    if err != nil {\n        return \"\", err\n    }\n\n    p.mu.Lock()\n    p.cache[key] = val\n    p.mu.Unlock()\n\n    return val, nil\n}",{"id":449,"title":450,"titles":451,"content":452,"level":106},"/v0.0.4/guides/secret-providers#with-retry-logic","With Retry Logic",[412,436],"type RetryProvider struct {\n    inner      fig.SecretProvider\n    maxRetries int\n    backoff    time.Duration\n}\n\nfunc (p *RetryProvider) Get(ctx context.Context, key string) (string, error) {\n    var lastErr error\n\n    for i := 0; i \u003C= p.maxRetries; i++ {\n        val, err := p.inner.Get(ctx, key)\n        if err == nil {\n            return val, nil\n        }\n\n        // Don't retry for \"not found\"\n        if err == fig.ErrSecretNotFound {\n            return \"\", err\n        }\n\n        lastErr = err\n\n        select {\n        case \u003C-ctx.Done():\n            return \"\", ctx.Err()\n        case \u003C-time.After(p.backoff * time.Duration(i+1)):\n        }\n    }\n\n    return \"\", fmt.Errorf(\"after %d retries: %w\", p.maxRetries, lastErr)\n}",{"id":454,"title":403,"titles":455,"content":43,"level":19},"/v0.0.4/guides/secret-providers#error-handling",[412],{"id":457,"title":289,"titles":458,"content":459,"level":106},"/v0.0.4/guides/secret-providers#errsecretnotfound",[412,403],"Return fig.ErrSecretNotFound when the secret doesn't exist: func (p *MyProvider) Get(ctx context.Context, key string) (string, error) {\n    val, err := p.client.Fetch(key)\n    if errors.Is(err, client.ErrNotFound) {\n        return \"\", fig.ErrSecretNotFound  // fig continues to env/default\n    }\n    if err != nil {\n        return \"\", err  // fig fails with this error\n    }\n    return val, nil\n} Why this matters: ErrSecretNotFound tells fig to continue to the next source. Other errors cause Load to fail immediately.",{"id":461,"title":462,"titles":463,"content":464,"level":106},"/v0.0.4/guides/secret-providers#propagating-context-errors","Propagating Context Errors",[412,403],"Respect context cancellation: func (p *MyProvider) Get(ctx context.Context, key string) (string, error) {\n    select {\n    case \u003C-ctx.Done():\n        return \"\", ctx.Err()\n    default:\n    }\n\n    // ... fetch secret\n}",{"id":466,"title":467,"titles":468,"content":43,"level":19},"/v0.0.4/guides/secret-providers#key-format-conventions","Key Format Conventions",[412],{"id":470,"title":471,"titles":472,"content":473,"level":106},"/v0.0.4/guides/secret-providers#path-based-keys","Path-Based Keys",[412,467],"Use forward slashes for hierarchical keys: Password string `secret:\"database/credentials/password\"`\nAPIKey   string `secret:\"services/stripe/api-key\"`",{"id":475,"title":476,"titles":477,"content":478,"level":106},"/v0.0.4/guides/secret-providers#field-extraction","Field Extraction",[412,467],"Use colons to extract fields from structured secrets: // For JSON secrets like {\"username\": \"admin\", \"password\": \"secret\"}\nUsername string `secret:\"database/credentials:username\"`\nPassword string `secret:\"database/credentials:password\"`",{"id":480,"title":481,"titles":482,"content":483,"level":106},"/v0.0.4/guides/secret-providers#provider-specific-prefixes","Provider-Specific Prefixes",[412,467],"Some providers support prefixes to route to different backends: // Custom provider that routes based on prefix\nVaultSecret string `secret:\"vault:db/password\"`\nAWSSecret   string `secret:\"aws:api-key\"`",{"id":485,"title":486,"titles":487,"content":43,"level":19},"/v0.0.4/guides/secret-providers#composing-providers","Composing Providers",[412],{"id":489,"title":490,"titles":491,"content":492,"level":106},"/v0.0.4/guides/secret-providers#multi-backend-provider","Multi-Backend Provider",[412,486],"Route to different providers based on key prefix: type MultiProvider struct {\n    providers map[string]fig.SecretProvider\n    fallback  fig.SecretProvider\n}\n\nfunc (m *MultiProvider) Get(ctx context.Context, key string) (string, error) {\n    prefix, actualKey, found := strings.Cut(key, \":\")\n    if found {\n        if p, ok := m.providers[prefix]; ok {\n            return p.Get(ctx, actualKey)\n        }\n    }\n\n    if m.fallback != nil {\n        return m.fallback.Get(ctx, key)\n    }\n\n    return \"\", fig.ErrSecretNotFound\n} Usage: provider := &MultiProvider{\n    providers: map[string]fig.SecretProvider{\n        \"vault\": vaultProvider,\n        \"aws\":   awsProvider,\n    },\n}\n\ntype Config struct {\n    DBPassword  string `secret:\"vault:database/password\"`\n    StripeKey   string `secret:\"aws:stripe-api-key\"`\n}",{"id":494,"title":495,"titles":496,"content":497,"level":106},"/v0.0.4/guides/secret-providers#fallback-chain","Fallback Chain",[412,486],"Try multiple providers in order: type ChainProvider struct {\n    providers []fig.SecretProvider\n}\n\nfunc (c *ChainProvider) Get(ctx context.Context, key string) (string, error) {\n    for _, p := range c.providers {\n        val, err := p.Get(ctx, key)\n        if err == nil {\n            return val, nil\n        }\n        if err != fig.ErrSecretNotFound {\n            return \"\", err  // Propagate real errors\n        }\n    }\n    return \"\", fig.ErrSecretNotFound\n}",{"id":499,"title":500,"titles":501,"content":43,"level":19},"/v0.0.4/guides/secret-providers#security-considerations","Security Considerations",[412],{"id":503,"title":504,"titles":505,"content":506,"level":106},"/v0.0.4/guides/secret-providers#logging","Logging",[412,500],"Never log secret values: func (p *MyProvider) Get(ctx context.Context, key string) (string, error) {\n    log.Printf(\"fetching secret: %s\", key)  // OK: log key\n    val, err := p.fetch(key)\n    // log.Printf(\"got value: %s\", val)      // BAD: never log values\n    return val, err\n}",{"id":508,"title":509,"titles":510,"content":511,"level":106},"/v0.0.4/guides/secret-providers#memory","Memory",[412,500],"Consider clearing secrets from memory when no longer needed: type Config struct {\n    Password string `secret:\"db/password\"`\n}\n\nfunc (c *Config) Clear() {\n    c.Password = \"\"\n}",{"id":513,"title":514,"titles":515,"content":516,"level":106},"/v0.0.4/guides/secret-providers#context-timeouts","Context Timeouts",[412,500],"Always use context for network operations: ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\n\nif err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n    log.Fatal(err)\n}",{"id":518,"title":32,"titles":519,"content":520,"level":19},"/v0.0.4/guides/secret-providers#next-steps",[412],"Vault Integration — HashiCorp Vault setupAWS Secrets Manager — AWS integrationGCP Secret Manager — GCP integrationTesting — testing with mock providers html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .skxcq, html code.shiki .skxcq{--shiki-default:var(--shiki-builtin)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}",{"id":522,"title":523,"titles":524,"content":525,"level":9},"/v0.0.4/integrations/vault","HashiCorp Vault",[],"Using fig with HashiCorp Vault KV secrets engine",{"id":527,"title":523,"titles":528,"content":529,"level":9},"/v0.0.4/integrations/vault#hashicorp-vault",[],"The vault package provides a fig.SecretProvider backed by HashiCorp Vault's KV v2 secrets engine.",{"id":531,"title":46,"titles":532,"content":533,"level":19},"/v0.0.4/integrations/vault#installation",[523],"go get github.com/zoobzio/fig/vault",{"id":535,"title":536,"titles":537,"content":538,"level":19},"/v0.0.4/integrations/vault#quick-start","Quick Start",[523],"import (\n    \"log\"\n\n    \"github.com/zoobzio/fig\"\n    \"github.com/zoobzio/fig/vault\"\n)\n\ntype Config struct {\n    DBPassword string `secret:\"database/credentials:password\"`\n    APIKey     string `secret:\"services/api:key\"`\n}\n\nfunc main() {\n    provider, err := vault.New()\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    var cfg Config\n    if err := fig.Load(&cfg, provider); err != nil {\n        log.Fatal(err)\n    }\n}",{"id":540,"title":541,"titles":542,"content":43,"level":19},"/v0.0.4/integrations/vault#configuration","Configuration",[523],{"id":544,"title":545,"titles":546,"content":547,"level":106},"/v0.0.4/integrations/vault#environment-variables","Environment Variables",[523,541],"The provider uses Vault's standard environment variables: VariableDescriptionRequiredVAULT_ADDRVault server addressYesVAULT_TOKENAuthentication tokenNo (can be empty for unauthenticated access)VAULT_CACERTCA certificate path for TLSNo (uses system CAs)",{"id":549,"title":550,"titles":551,"content":552,"level":106},"/v0.0.4/integrations/vault#mount-path","Mount Path",[523,541],"By default, the provider uses the secret mount. Configure a different mount: provider, err := vault.New(vault.WithMount(\"kv\"))",{"id":554,"title":555,"titles":556,"content":557,"level":19},"/v0.0.4/integrations/vault#key-format","Key Format",[523],"Keys follow the format path/to/secret:field: // Vault path: secret/data/database/credentials\n// Field: password\nPassword string `secret:\"database/credentials:password\"` If no field is specified, value is used: // Vault path: secret/data/api-key\n// Field: value\nAPIKey string `secret:\"api-key\"`",{"id":559,"title":560,"titles":561,"content":562,"level":106},"/v0.0.4/integrations/vault#example-vault-data","Example Vault Data",[523,555],"vault kv put secret/database/credentials \\\n    username=admin \\\n    password=supersecret\n\nvault kv put secret/api-key \\\n    value=sk_live_12345 Corresponding config: type Config struct {\n    DBUser     string `secret:\"database/credentials:username\"`\n    DBPassword string `secret:\"database/credentials:password\"`\n    APIKey     string `secret:\"api-key\"`  // uses \"value\" field\n}",{"id":564,"title":565,"titles":566,"content":43,"level":19},"/v0.0.4/integrations/vault#api-reference","API Reference",[523],{"id":568,"title":569,"titles":570,"content":571,"level":106},"/v0.0.4/integrations/vault#new","New",[523,565],"func New(opts ...Option) (*Provider, error) Creates a provider using environment variable configuration. Returns: (*Provider, error) — error if VAULT_ADDR is not set.",{"id":573,"title":574,"titles":575,"content":43,"level":106},"/v0.0.4/integrations/vault#options","Options",[523,565],{"id":577,"title":578,"titles":579,"content":580,"level":581},"/v0.0.4/integrations/vault#withmount","WithMount",[523,565,574],"func WithMount(mount string) Option Sets the KV secrets engine mount path. Default: \"secret\".",4,{"id":583,"title":584,"titles":585,"content":586,"level":581},"/v0.0.4/integrations/vault#withaddress","WithAddress",[523,565,574],"func WithAddress(addr string) Option Sets the Vault server address. Overrides VAULT_ADDR.",{"id":588,"title":589,"titles":590,"content":591,"level":581},"/v0.0.4/integrations/vault#withtoken","WithToken",[523,565,574],"func WithToken(token string) Option Sets the Vault token. Overrides VAULT_TOKEN.",{"id":593,"title":594,"titles":595,"content":596,"level":581},"/v0.0.4/integrations/vault#withhttpclient","WithHTTPClient",[523,565,574],"func WithHTTPClient(client *http.Client) Option Sets a custom HTTP client. Useful for testing or custom TLS configuration.",{"id":598,"title":599,"titles":600,"content":601,"level":19},"/v0.0.4/integrations/vault#authentication","Authentication",[523],"The provider uses token-based authentication. Set the token via environment variable or option.",{"id":603,"title":604,"titles":605,"content":606,"level":106},"/v0.0.4/integrations/vault#environment-variable","Environment Variable",[523,599],"export VAULT_ADDR=\"https://vault.example.com:8200\"\nexport VAULT_TOKEN=\"hvs.your-token-here\"",{"id":608,"title":609,"titles":610,"content":611,"level":106},"/v0.0.4/integrations/vault#token-file","Token File",[523,599],"token, _ := os.ReadFile(\"/var/run/secrets/vault-token\")\nprovider, _ := vault.New(vault.WithToken(string(token)))",{"id":613,"title":614,"titles":615,"content":616,"level":106},"/v0.0.4/integrations/vault#kubernetes","Kubernetes",[523,599],"In Kubernetes, mount the token as a secret and read it at startup: # Pod spec\nvolumes:\n  - name: vault-token\n    secret:\n      secretName: vault-token\ncontainers:\n  - volumeMounts:\n      - name: vault-token\n        mountPath: /var/run/secrets/vault-token\n        subPath: token\n        readOnly: true token, _ := os.ReadFile(\"/var/run/secrets/vault-token\")\nprovider, _ := vault.New(vault.WithToken(string(token))) For Vault Agent sidecar patterns, the agent writes a token file that your application reads.",{"id":618,"title":403,"titles":619,"content":620,"level":19},"/v0.0.4/integrations/vault#error-handling",[523],"The provider returns fig.ErrSecretNotFound when: The secret path doesn't existThe secret data is emptyThe requested field doesn't exist Other errors (network, authentication) are returned as-is and cause fig.Load to fail.",{"id":622,"title":32,"titles":623,"content":624,"level":19},"/v0.0.4/integrations/vault#next-steps",[523],"Secret Providers Guide — implementing custom providersAWS Secrets Manager — AWS integrationGCP Secret Manager — GCP integration html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}",{"id":626,"title":627,"titles":628,"content":629,"level":9},"/v0.0.4/integrations/awssm","AWS Secrets Manager",[],"Using fig with AWS Secrets Manager",{"id":631,"title":627,"titles":632,"content":633,"level":9},"/v0.0.4/integrations/awssm#aws-secrets-manager",[],"The awssm package provides a fig.SecretProvider backed by AWS Secrets Manager.",{"id":635,"title":46,"titles":636,"content":637,"level":19},"/v0.0.4/integrations/awssm#installation",[627],"go get github.com/zoobzio/fig/awssm",{"id":639,"title":536,"titles":640,"content":641,"level":19},"/v0.0.4/integrations/awssm#quick-start",[627],"import (\n    \"context\"\n    \"log\"\n\n    \"github.com/zoobzio/fig\"\n    \"github.com/zoobzio/fig/awssm\"\n)\n\ntype Config struct {\n    DBPassword string `secret:\"prod/database:password\"`\n    APIKey     string `secret:\"prod/api-key\"`\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    provider, err := awssm.New(ctx)\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    var cfg Config\n    if err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n        log.Fatal(err)\n    }\n}",{"id":643,"title":541,"titles":644,"content":43,"level":19},"/v0.0.4/integrations/awssm#configuration",[627],{"id":646,"title":647,"titles":648,"content":649,"level":106},"/v0.0.4/integrations/awssm#credentials","Credentials",[627,541],"The provider uses the standard AWS SDK credential chain: Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)Shared credentials file (~/.aws/credentials)IAM role (EC2, ECS, Lambda)",{"id":651,"title":652,"titles":653,"content":654,"level":106},"/v0.0.4/integrations/awssm#region","Region",[627,541],"Set via environment or shared config: export AWS_REGION=us-east-1 Or in ~/.aws/config: [default]\nregion = us-east-1",{"id":656,"title":555,"titles":657,"content":658,"level":19},"/v0.0.4/integrations/awssm#key-format",[627],"Keys follow the format secret-name or secret-name:json-field:",{"id":660,"title":661,"titles":662,"content":663,"level":106},"/v0.0.4/integrations/awssm#plain-text-secrets","Plain Text Secrets",[627,555],"// AWS secret name: prod/api-key\n// Value: sk_live_12345\nAPIKey string `secret:\"prod/api-key\"`",{"id":665,"title":666,"titles":667,"content":668,"level":106},"/v0.0.4/integrations/awssm#json-secrets","JSON Secrets",[627,555],"// AWS secret name: prod/database\n// Value: {\"username\": \"admin\", \"password\": \"secret\"}\nDBUser     string `secret:\"prod/database:username\"`\nDBPassword string `secret:\"prod/database:password\"`",{"id":670,"title":671,"titles":672,"content":673,"level":106},"/v0.0.4/integrations/awssm#creating-secrets","Creating Secrets",[627,555],"# Plain text\naws secretsmanager create-secret \\\n    --name prod/api-key \\\n    --secret-string \"sk_live_12345\"\n\n# JSON\naws secretsmanager create-secret \\\n    --name prod/database \\\n    --secret-string '{\"username\":\"admin\",\"password\":\"secret\"}'",{"id":675,"title":565,"titles":676,"content":43,"level":19},"/v0.0.4/integrations/awssm#api-reference",[627],{"id":678,"title":569,"titles":679,"content":680,"level":106},"/v0.0.4/integrations/awssm#new",[627,565],"func New(ctx context.Context, opts ...Option) (*Provider, error) Creates a provider using the default AWS configuration. Parameters: ctx — context for loading AWS configopts — optional configuration Returns: (*Provider, error) — error if AWS config loading fails.",{"id":682,"title":683,"titles":684,"content":685,"level":106},"/v0.0.4/integrations/awssm#newwithclient","NewWithClient",[627,565],"func NewWithClient(client *secretsmanager.Client, opts ...Option) *Provider Creates a provider with a pre-configured Secrets Manager client. Example: import (\n    \"github.com/aws/aws-sdk-go-v2/config\"\n    \"github.com/aws/aws-sdk-go-v2/service/secretsmanager\"\n)\n\ncfg, _ := config.LoadDefaultConfig(ctx,\n    config.WithRegion(\"eu-west-1\"),\n)\n\nclient := secretsmanager.NewFromConfig(cfg)\nprovider := awssm.NewWithClient(client)",{"id":687,"title":688,"titles":689,"content":690,"level":19},"/v0.0.4/integrations/awssm#iam-permissions","IAM Permissions",[627],"Required IAM policy: {\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"secretsmanager:GetSecretValue\"\n            ],\n            \"Resource\": [\n                \"arn:aws:secretsmanager:*:*:secret:prod/*\"\n            ]\n        }\n    ]\n} Restrict Resource to specific secret ARNs in production.",{"id":692,"title":693,"titles":694,"content":695,"level":19},"/v0.0.4/integrations/awssm#cross-account-access","Cross-Account Access",[627],"For secrets in another AWS account: import (\n    \"github.com/aws/aws-sdk-go-v2/config\"\n    \"github.com/aws/aws-sdk-go-v2/credentials/stscreds\"\n    \"github.com/aws/aws-sdk-go-v2/service/secretsmanager\"\n    \"github.com/aws/aws-sdk-go-v2/service/sts\"\n)\n\ncfg, _ := config.LoadDefaultConfig(ctx)\nstsClient := sts.NewFromConfig(cfg)\n\nassumeRoleCfg, _ := config.LoadDefaultConfig(ctx,\n    config.WithCredentialsProvider(\n        stscreds.NewAssumeRoleProvider(stsClient, \"arn:aws:iam::123456789012:role/SecretsReader\"),\n    ),\n)\n\nclient := secretsmanager.NewFromConfig(assumeRoleCfg)\nprovider := awssm.NewWithClient(client)",{"id":697,"title":403,"titles":698,"content":699,"level":19},"/v0.0.4/integrations/awssm#error-handling",[627],"The provider returns fig.ErrSecretNotFound when: The secret doesn't existThe secret value is nilThe requested JSON field doesn't existThe JSON field is not a string Other errors (network, permissions) are returned as-is.",{"id":701,"title":702,"titles":703,"content":704,"level":19},"/v0.0.4/integrations/awssm#binary-secrets","Binary Secrets",[627],"AWS Secrets Manager supports binary secrets, but fig only handles string values. For binary secrets, store them as base64-encoded strings and decode after loading: type Config struct {\n    CertBase64 string `secret:\"tls/certificate\"`\n}\n\ncfg := Config{}\nfig.Load(&cfg, provider)\n\ncert, _ := base64.StdEncoding.DecodeString(cfg.CertBase64)",{"id":706,"title":32,"titles":707,"content":708,"level":19},"/v0.0.4/integrations/awssm#next-steps",[627],"Secret Providers Guide — implementing custom providersHashiCorp Vault — Vault integrationGCP Secret Manager — GCP integration html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":710,"title":711,"titles":712,"content":713,"level":9},"/v0.0.4/integrations/gcpsm","GCP Secret Manager",[],"Using fig with Google Cloud Secret Manager",{"id":715,"title":711,"titles":716,"content":717,"level":9},"/v0.0.4/integrations/gcpsm#gcp-secret-manager",[],"The gcpsm package provides a fig.SecretProvider backed by Google Cloud Secret Manager.",{"id":719,"title":46,"titles":720,"content":721,"level":19},"/v0.0.4/integrations/gcpsm#installation",[711],"go get github.com/zoobzio/fig/gcpsm",{"id":723,"title":536,"titles":724,"content":725,"level":19},"/v0.0.4/integrations/gcpsm#quick-start",[711],"import (\n    \"context\"\n    \"log\"\n\n    \"github.com/zoobzio/fig\"\n    \"github.com/zoobzio/fig/gcpsm\"\n)\n\ntype Config struct {\n    DBPassword string `secret:\"database-password\"`\n    APIKey     string `secret:\"api-key\"`\n}\n\nfunc main() {\n    ctx := context.Background()\n\n    provider, err := gcpsm.New(ctx, \"my-project-id\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    var cfg Config\n    if err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n        log.Fatal(err)\n    }\n}",{"id":727,"title":541,"titles":728,"content":43,"level":19},"/v0.0.4/integrations/gcpsm#configuration",[711],{"id":730,"title":599,"titles":731,"content":732,"level":106},"/v0.0.4/integrations/gcpsm#authentication",[711,541],"The provider uses Application Default Credentials (ADC): GOOGLE_APPLICATION_CREDENTIALS environment variableUser credentials from gcloud auth application-default loginAttached service account (GCE, Cloud Run, GKE)",{"id":734,"title":735,"titles":736,"content":737,"level":106},"/v0.0.4/integrations/gcpsm#project-id","Project ID",[711,541],"The project ID is required: provider, err := gcpsm.New(ctx, \"my-project-id\") Get project ID from metadata server (on GCP): import \"cloud.google.com/go/compute/metadata\"\n\nprojectID, _ := metadata.ProjectID()\nprovider, _ := gcpsm.New(ctx, projectID)",{"id":739,"title":555,"titles":740,"content":658,"level":19},"/v0.0.4/integrations/gcpsm#key-format",[711],{"id":742,"title":661,"titles":743,"content":744,"level":106},"/v0.0.4/integrations/gcpsm#plain-text-secrets",[711,555],"// Secret name: api-key\n// Value: sk_live_12345\nAPIKey string `secret:\"api-key\"`",{"id":746,"title":666,"titles":747,"content":748,"level":106},"/v0.0.4/integrations/gcpsm#json-secrets",[711,555],"// Secret name: database-credentials\n// Value: {\"username\": \"admin\", \"password\": \"secret\"}\nDBUser     string `secret:\"database-credentials:username\"`\nDBPassword string `secret:\"database-credentials:password\"`",{"id":750,"title":671,"titles":751,"content":752,"level":106},"/v0.0.4/integrations/gcpsm#creating-secrets",[711,555],"# Create secret\ngcloud secrets create api-key --replication-policy=\"automatic\"\n\n# Add version\necho -n \"sk_live_12345\" | gcloud secrets versions add api-key --data-file=-\n\n# JSON secret\necho -n '{\"username\":\"admin\",\"password\":\"secret\"}' | \\\n    gcloud secrets versions add database-credentials --data-file=-",{"id":754,"title":565,"titles":755,"content":43,"level":19},"/v0.0.4/integrations/gcpsm#api-reference",[711],{"id":757,"title":569,"titles":758,"content":759,"level":106},"/v0.0.4/integrations/gcpsm#new",[711,565],"func New(ctx context.Context, project string, opts ...Option) (*Provider, error) Creates a provider using Application Default Credentials. Parameters: ctx — context for credential discoveryproject — GCP project IDopts — optional configuration Returns: (*Provider, error) — error if credentials cannot be found.",{"id":761,"title":574,"titles":762,"content":43,"level":106},"/v0.0.4/integrations/gcpsm#options",[711,565],{"id":764,"title":594,"titles":765,"content":766,"level":581},"/v0.0.4/integrations/gcpsm#withhttpclient",[711,565,574],"func WithHTTPClient(client *http.Client) Option Sets a custom HTTP client. Useful for testing or custom transport configuration.",{"id":768,"title":769,"titles":770,"content":771,"level":106},"/v0.0.4/integrations/gcpsm#close","Close",[711,565],"func (p *Provider) Close() error No-op, retained for interface compatibility. The provider uses HTTP/1.1 and doesn't require cleanup.",{"id":773,"title":774,"titles":775,"content":776,"level":19},"/v0.0.4/integrations/gcpsm#secret-versions","Secret Versions",[711],"The provider always fetches the latest version. For specific versions, implement a wrapper: type VersionedProvider struct {\n    inner   *gcpsm.Provider\n    version string  // e.g., \"1\", \"2\", or \"latest\"\n}\n\nfunc (p *VersionedProvider) Get(ctx context.Context, key string) (string, error) {\n    // Modify key to include version\n    versionedKey := fmt.Sprintf(\"%s@%s\", key, p.version)\n    // Would require modifying gcpsm to support this\n    return p.inner.Get(ctx, key)\n}",{"id":778,"title":688,"titles":779,"content":780,"level":19},"/v0.0.4/integrations/gcpsm#iam-permissions",[711],"Required role: roles/secretmanager.secretAccessor gcloud secrets add-iam-policy-binding api-key \\\n    --member=\"serviceAccount:my-service@my-project.iam.gserviceaccount.com\" \\\n    --role=\"roles/secretmanager.secretAccessor\" Or at project level: gcloud projects add-iam-policy-binding my-project \\\n    --member=\"serviceAccount:my-service@my-project.iam.gserviceaccount.com\" \\\n    --role=\"roles/secretmanager.secretAccessor\"",{"id":782,"title":783,"titles":784,"content":785,"level":19},"/v0.0.4/integrations/gcpsm#workload-identity-gke","Workload Identity (GKE)",[711],"For GKE with Workload Identity: # Create KSA\nkubectl create serviceaccount my-app\n\n# Bind to GSA\ngcloud iam service-accounts add-iam-policy-binding \\\n    my-service@my-project.iam.gserviceaccount.com \\\n    --role roles/iam.workloadIdentityUser \\\n    --member \"serviceAccount:my-project.svc.id.goog[default/my-app]\"\n\n# Annotate KSA\nkubectl annotate serviceaccount my-app \\\n    iam.gke.io/gcp-service-account=my-service@my-project.iam.gserviceaccount.com The provider automatically uses the workload identity when running in GKE.",{"id":787,"title":403,"titles":788,"content":789,"level":19},"/v0.0.4/integrations/gcpsm#error-handling",[711],"The provider returns fig.ErrSecretNotFound when: The secret doesn't existThe secret version doesn't existThe requested JSON field doesn't existThe JSON field is not a string Other errors (network, permissions) are returned as-is.",{"id":791,"title":792,"titles":793,"content":794,"level":19},"/v0.0.4/integrations/gcpsm#regional-vs-global-replication","Regional vs Global Replication",[711],"The provider works with both replication policies. The latest version is fetched regardless of replication strategy. For multi-region applications, consider using automatic replication: gcloud secrets create my-secret --replication-policy=\"automatic\"",{"id":796,"title":32,"titles":797,"content":798,"level":19},"/v0.0.4/integrations/gcpsm#next-steps",[711],"Secret Providers Guide — implementing custom providersHashiCorp Vault — Vault integrationAWS Secrets Manager — AWS integration html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}",{"id":800,"title":565,"titles":801,"content":802,"level":9},"/v0.0.4/reference/api",[],"Complete API documentation for fig",{"id":804,"title":565,"titles":805,"content":43,"level":9},"/v0.0.4/reference/api#api-reference",[],{"id":807,"title":808,"titles":809,"content":43,"level":19},"/v0.0.4/reference/api#functions","Functions",[565],{"id":811,"title":812,"titles":813,"content":814,"level":106},"/v0.0.4/reference/api#load","Load",[565,808],"func Load[T any](cfg *T, provider ...SecretProvider) error Populates a struct from environment variables, secrets, and defaults. Type Parameters: T — any struct type Parameters: cfg — pointer to the struct to populateprovider — optional secret provider (only the first is used) Returns: error — nil on success, or: ErrNotStruct if T is not a struct type*FieldError wrapping ErrRequired for missing required fields*FieldError wrapping ErrInvalidType for type conversion failures*FieldError wrapping provider errorsValidation error if the struct implements Validator Resolution Order: secret → env → default → zero value Example: type Config struct {\n    Host string `env:\"APP_HOST\" default:\"localhost\"`\n    Port int    `env:\"APP_PORT\" default:\"8080\"`\n}\n\nvar cfg Config\nif err := fig.Load(&cfg); err != nil {\n    log.Fatal(err)\n} With secret provider: provider, _ := vault.New()\n\nvar cfg Config\nif err := fig.Load(&cfg, provider); err != nil {\n    log.Fatal(err)\n}",{"id":816,"title":817,"titles":818,"content":819,"level":106},"/v0.0.4/reference/api#loadcontext","LoadContext",[565,808],"func LoadContext[T any](ctx context.Context, cfg *T, provider ...SecretProvider) error Populates a struct with context support for secret provider timeouts. Type Parameters: T — any struct type Parameters: ctx — context for secret provider callscfg — pointer to the struct to populateprovider — optional secret provider Returns: Same as Load, plus context errors (context.Canceled, context.DeadlineExceeded) Example: ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\n\nvar cfg Config\nif err := fig.LoadContext(ctx, &cfg, provider); err != nil {\n    log.Fatal(err)\n}",{"id":821,"title":822,"titles":823,"content":43,"level":19},"/v0.0.4/reference/api#interfaces","Interfaces",[565],{"id":825,"title":826,"titles":827,"content":828,"level":106},"/v0.0.4/reference/api#secretprovider","SecretProvider",[565,822],"type SecretProvider interface {\n    Get(ctx context.Context, key string) (string, error)\n} Defines the interface for secret backends. Methods:",{"id":830,"title":831,"titles":832,"content":833,"level":581},"/v0.0.4/reference/api#get","Get",[565,822,826],"Retrieves a secret by key. Parameters: ctx — context for cancellation and timeoutskey — the secret key (format is provider-specific) Returns: (string, nil) — the secret value on success(\"\", ErrSecretNotFound) — if the secret doesn't exist (fig continues to next source)(\"\", error) — for other errors (fig fails immediately) Implementation Notes: Return ErrSecretNotFound only for missing secretsReturn other errors for network issues, permission denied, etc.Respect context cancellation Example Implementation: type MyProvider struct {\n    client *MySecretClient\n}\n\nfunc (p *MyProvider) Get(ctx context.Context, key string) (string, error) {\n    val, err := p.client.Fetch(ctx, key)\n    if errors.Is(err, client.ErrNotFound) {\n        return \"\", fig.ErrSecretNotFound\n    }\n    return val, err\n}",{"id":835,"title":836,"titles":837,"content":838,"level":106},"/v0.0.4/reference/api#validator","Validator",[565,822],"type Validator interface {\n    Validate() error\n} Optional interface for config validation after loading. Methods:",{"id":840,"title":841,"titles":842,"content":843,"level":581},"/v0.0.4/reference/api#validate","Validate",[565,822,836],"Validates the configuration after all fields are populated. Returns: nil — validation passederror — validation failed (returned from Load) Implementation Notes: Called after all fields are set, before Load returnsErrors are not wrapped in FieldErrorUse for cross-field validation, range checks, business logic Example: type Config struct {\n    MinConns int `env:\"MIN_CONNS\" default:\"5\"`\n    MaxConns int `env:\"MAX_CONNS\" default:\"25\"`\n}\n\nfunc (c *Config) Validate() error {\n    if c.MinConns > c.MaxConns {\n        return fmt.Errorf(\"min connections (%d) exceeds max (%d)\",\n            c.MinConns, c.MaxConns)\n    }\n    return nil\n}",{"id":845,"title":56,"titles":846,"content":43,"level":19},"/v0.0.4/reference/api#struct-tags",[565],{"id":848,"title":103,"titles":849,"content":850,"level":106},"/v0.0.4/reference/api#env",[565,56],"Maps a field to an environment variable. Host string `env:\"DATABASE_HOST\"` Value is read via os.GetenvEmpty strings are treated as unset",{"id":852,"title":109,"titles":853,"content":854,"level":106},"/v0.0.4/reference/api#secret",[565,56],"Maps a field to a secret provider key. Password string `secret:\"database/credentials:password\"` Only queried if a provider is passed to LoadKey format is provider-specific",{"id":856,"title":114,"titles":857,"content":858,"level":106},"/v0.0.4/reference/api#default",[565,56],"Provides a fallback value. Port int `default:\"5432\"` Parsed using the same type conversion as environment variablesUsed when secret and env both return no value",{"id":860,"title":119,"titles":861,"content":862,"level":106},"/v0.0.4/reference/api#required",[565,56],"Marks a field as mandatory. APIKey string `env:\"API_KEY\" required:\"true\"` Only \"true\" is recognised; any other value is ignoredReturns ErrRequired if no value found from any source",{"id":864,"title":865,"titles":866,"content":867,"level":19},"/v0.0.4/reference/api#tag-combinations","Tag Combinations",[565],"Tags can be combined for flexible resolution: type Config struct {\n    // Try secret, then env, then default\n    Password string `secret:\"db/pass\" env:\"DB_PASSWORD\" default:\"changeme\"`\n\n    // Env with default\n    Port int `env:\"PORT\" default:\"8080\"`\n\n    // Required with env\n    APIKey string `env:\"API_KEY\" required:\"true\"`\n\n    // Secret only (fails if no provider or secret not found)\n    Token string `secret:\"auth/token\" required:\"true\"`\n}",{"id":869,"title":32,"titles":870,"content":871,"level":19},"/v0.0.4/reference/api#next-steps",[565],"Types Reference — error types and supported typesConcepts — mental models for fig html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}",{"id":873,"title":874,"titles":875,"content":876,"level":9},"/v0.0.4/reference/types","Types Reference",[],"Error types, supported types, and type conversion",{"id":878,"title":874,"titles":879,"content":43,"level":9},"/v0.0.4/reference/types#types-reference",[],{"id":881,"title":270,"titles":882,"content":43,"level":19},"/v0.0.4/reference/types#error-types",[874],{"id":884,"title":294,"titles":885,"content":886,"level":106},"/v0.0.4/reference/types#fielderror",[874,270],"type FieldError struct {\n    Field string\n    Err   error\n} Wraps an error with field context. Fields: Field — the struct field name where the error occurredErr — the underlying error Methods: func (e *FieldError) Error() string\nfunc (e *FieldError) Unwrap() error Usage: err := fig.Load(&cfg)\nif err != nil {\n    var fieldErr *fig.FieldError\n    if errors.As(err, &fieldErr) {\n        fmt.Printf(\"Field: %s\\n\", fieldErr.Field)\n        fmt.Printf(\"Cause: %v\\n\", fieldErr.Err)\n    }\n} Example Output: fig: field Password: fig: required field not set",{"id":888,"title":889,"titles":890,"content":43,"level":19},"/v0.0.4/reference/types#sentinel-errors","Sentinel Errors",[874],{"id":892,"title":274,"titles":893,"content":894,"level":106},"/v0.0.4/reference/types#errrequired",[874,889],"var ErrRequired = errors.New(\"fig: required field not set\") Returned when a field marked required:\"true\" has no value from any source. Wrapped in: FieldError Check: if errors.Is(err, fig.ErrRequired) {\n    // Handle missing required field\n}",{"id":896,"title":279,"titles":897,"content":898,"level":106},"/v0.0.4/reference/types#errinvalidtype",[874,889],"var ErrInvalidType = errors.New(\"fig: invalid type conversion\") Returned when a string value cannot be converted to the target type. Wrapped in: FieldError Common causes: Non-numeric string for int/float fieldsInvalid boolean string (not true, false, 1, 0, etc.)Invalid duration formatUnsupported target type Check: if errors.Is(err, fig.ErrInvalidType) {\n    // Handle type conversion failure\n}",{"id":900,"title":284,"titles":901,"content":902,"level":106},"/v0.0.4/reference/types#errnotstruct",[874,889],"var ErrNotStruct = errors.New(\"fig: type must be a struct\") Returned when Load is called with a non-struct type. Not wrapped: Returned directly from Load. Example: var s string\nerr := fig.Load(&s)  // Returns ErrNotStruct",{"id":904,"title":289,"titles":905,"content":906,"level":106},"/v0.0.4/reference/types#errsecretnotfound",[874,889],"var ErrSecretNotFound = errors.New(\"fig: secret not found\") Returned by secret providers when a secret doesn't exist. Behaviour: When a provider returns this error, fig continues to the next source (env, then default). It only becomes a Load error if the field is required and no other source provides a value. For provider implementers: Return this error only for missing secrets. Return other errors for network issues, permission denied, etc.",{"id":908,"title":909,"titles":910,"content":911,"level":19},"/v0.0.4/reference/types#supported-types","Supported Types",[874],"fig converts string values to these types: TypeParsingExamplestringDirect assignment\"hello\"intstrconv.ParseInt\"42\"int8strconv.ParseInt, converted to target\"-128\" to \"127\"int16strconv.ParseInt, converted to target\"-32768\" to \"32767\"int32strconv.ParseInt, converted to target\"-2147483648\" to \"2147483647\"int64strconv.ParseInt\"9223372036854775807\"uintstrconv.ParseUint\"42\"uint8strconv.ParseUint, converted to target\"0\" to \"255\"uint16strconv.ParseUint, converted to target\"0\" to \"65535\"uint32strconv.ParseUint, converted to target\"0\" to \"4294967295\"uint64strconv.ParseUint\"18446744073709551615\"float32strconv.ParseFloat\"3.14\"float64strconv.ParseFloat\"3.141592653589793\"boolstrconv.ParseBool\"true\", \"false\", \"1\", \"0\"time.Durationtime.ParseDuration\"30s\", \"5m\", \"1h30m\"[]stringComma-separated split\"a,b,c\" → [\"a\", \"b\", \"c\"]*TPointer to any supported typeSame as Tencoding.TextUnmarshalerUnmarshalText methodCustom types",{"id":913,"title":914,"titles":915,"content":916,"level":106},"/v0.0.4/reference/types#boolean-values","Boolean Values",[874,909],"Accepted values for bool fields: TrueFalsetruefalseTRUEFALSETrueFalse10tfTF",{"id":918,"title":919,"titles":920,"content":921,"level":106},"/v0.0.4/reference/types#duration-format","Duration Format",[874,909],"Duration strings use Go's time.ParseDuration format: UnitSuffixNanosecondsnsMicrosecondsus or µsMillisecondsmsSecondssMinutesmHoursh Examples: \"100ms\", \"30s\", \"5m\", \"1h30m\", \"2h45m30s\"",{"id":923,"title":924,"titles":925,"content":926,"level":106},"/v0.0.4/reference/types#string-slices","String Slices",[874,909],"Comma-separated values are split and trimmed: Tags []string `env:\"TAGS\"`  // TAGS=\"api, v2, prod\" → [\"api\", \"v2\", \"prod\"] Empty parts are removed: \"a,,b\" → [\"a\", \"b\"]",{"id":928,"title":929,"titles":930,"content":931,"level":106},"/v0.0.4/reference/types#textunmarshaler","TextUnmarshaler",[874,909],"Types implementing encoding.TextUnmarshaler are supported: type LogLevel int\n\nfunc (l *LogLevel) UnmarshalText(text []byte) error {\n    switch string(text) {\n    case \"debug\":\n        *l = 0\n    case \"info\":\n        *l = 1\n    case \"warn\":\n        *l = 2\n    case \"error\":\n        *l = 3\n    default:\n        return fmt.Errorf(\"unknown log level: %s\", text)\n    }\n    return nil\n}\n\ntype Config struct {\n    Level LogLevel `env:\"LOG_LEVEL\" default:\"info\"`\n}",{"id":933,"title":934,"titles":935,"content":936,"level":106},"/v0.0.4/reference/types#pointer-types","Pointer Types",[874,909],"Pointers to supported types are handled automatically: type Config struct {\n    Port    *int    `env:\"PORT\"`           // nil if unset\n    Host    *string `env:\"HOST\"`           // nil if unset\n    Timeout *time.Duration `env:\"TIMEOUT\"` // nil if unset\n} Use pointers to distinguish between \"unset\" and \"zero value\".",{"id":938,"title":939,"titles":940,"content":941,"level":19},"/v0.0.4/reference/types#unsupported-types","Unsupported Types",[874],"The following types return ErrInvalidType: Maps (map[K]V)Slices other than []stringChannelsFunctionsComplex numbersInterfaces (except encoding.TextUnmarshaler) For complex types, consider JSON encoding: type Config struct {\n    SettingsJSON string `env:\"SETTINGS\"`\n}\n\nfunc (c *Config) Settings() (map[string]any, error) {\n    var m map[string]any\n    return m, json.Unmarshal([]byte(c.SettingsJSON), &m)\n}",{"id":943,"title":32,"titles":944,"content":945,"level":19},"/v0.0.4/reference/types#next-steps",[874],"API Reference — function signatures and interfacesTroubleshooting — common errors and debugging html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html pre.shiki code .scyPU, html code.shiki .scyPU{--shiki-default:var(--shiki-placeholder)}html pre.shiki code .suWN2, html code.shiki .suWN2{--shiki-default:var(--shiki-tag)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",[947],{"title":948,"path":949,"stem":950,"children":951,"page":965},"V004","/v0.0.4","v0.0.4",[952,966,979,990],{"title":953,"path":954,"stem":955,"children":956,"page":965},"Learn","/v0.0.4/learn","v0.0.4/1.learn",[957,959,961,963],{"title":6,"path":5,"stem":958,"description":8},"v0.0.4/1.learn/1.overview",{"title":37,"path":36,"stem":960,"description":39},"v0.0.4/1.learn/2.quickstart",{"title":85,"path":84,"stem":962,"description":87},"v0.0.4/1.learn/3.concepts",{"title":147,"path":146,"stem":964,"description":149},"v0.0.4/1.learn/4.architecture",false,{"title":967,"path":968,"stem":969,"children":970,"page":965},"Guides","/v0.0.4/guides","v0.0.4/2.guides",[971,973,975,977],{"title":194,"path":193,"stem":972,"description":196},"v0.0.4/2.guides/1.testing",{"title":261,"path":260,"stem":974,"description":263},"v0.0.4/2.guides/2.troubleshooting",{"title":66,"path":350,"stem":976,"description":352},"v0.0.4/2.guides/3.validation",{"title":412,"path":411,"stem":978,"description":414},"v0.0.4/2.guides/4.secret-providers",{"title":980,"path":981,"stem":982,"children":983,"page":965},"Integrations","/v0.0.4/integrations","v0.0.4/3.integrations",[984,986,988],{"title":523,"path":522,"stem":985,"description":525},"v0.0.4/3.integrations/1.vault",{"title":627,"path":626,"stem":987,"description":629},"v0.0.4/3.integrations/2.awssm",{"title":711,"path":710,"stem":989,"description":713},"v0.0.4/3.integrations/3.gcpsm",{"title":991,"path":992,"stem":993,"children":994,"page":965},"Reference","/v0.0.4/reference","v0.0.4/4.reference",[995,997],{"title":565,"path":800,"stem":996,"description":802},"v0.0.4/4.reference/1.api",{"title":874,"path":873,"stem":998,"description":876},"v0.0.4/4.reference/2.types",[1000],{"title":948,"path":949,"stem":950,"children":1001,"page":965},[1002,1008,1014,1019],{"title":953,"path":954,"stem":955,"children":1003,"page":965},[1004,1005,1006,1007],{"title":6,"path":5,"stem":958},{"title":37,"path":36,"stem":960},{"title":85,"path":84,"stem":962},{"title":147,"path":146,"stem":964},{"title":967,"path":968,"stem":969,"children":1009,"page":965},[1010,1011,1012,1013],{"title":194,"path":193,"stem":972},{"title":261,"path":260,"stem":974},{"title":66,"path":350,"stem":976},{"title":412,"path":411,"stem":978},{"title":980,"path":981,"stem":982,"children":1015,"page":965},[1016,1017,1018],{"title":523,"path":522,"stem":985},{"title":627,"path":626,"stem":987},{"title":711,"path":710,"stem":989},{"title":991,"path":992,"stem":993,"children":1020,"page":965},[1021,1022],{"title":565,"path":800,"stem":996},{"title":874,"path":873,"stem":998},[1024,1930,1980],{"id":1025,"title":1026,"body":1027,"description":43,"extension":1923,"icon":1924,"meta":1925,"navigation":1163,"path":1926,"seo":1927,"stem":1928,"__hash__":1929},"resources/readme.md","README",{"type":1028,"value":1029,"toc":1909},"minimark",[1030,1034,1102,1105,1108,1113,1137,1140,1143,1398,1409,1413,1503,1507,1543,1546,1552,1615,1619,1622,1677,1766,1769,1772,1778,1880,1884,1887,1891,1898,1902,1905],[1031,1032,1033],"h1",{"id":1033},"fig",[1035,1036,1037,1048,1056,1064,1072,1080,1087,1094],"p",{},[1038,1039,1043],"a",{"href":1040,"rel":1041},"https://github.com/zoobz-io/fig/actions/workflows/ci.yml",[1042],"nofollow",[1044,1045],"img",{"alt":1046,"src":1047},"CI","https://github.com/zoobz-io/fig/actions/workflows/ci.yml/badge.svg",[1038,1049,1052],{"href":1050,"rel":1051},"https://codecov.io/gh/zoobz-io/fig",[1042],[1044,1053],{"alt":1054,"src":1055},"codecov","https://codecov.io/gh/zoobz-io/fig/branch/main/graph/badge.svg",[1038,1057,1060],{"href":1058,"rel":1059},"https://goreportcard.com/report/github.com/zoobz-io/fig",[1042],[1044,1061],{"alt":1062,"src":1063},"Go Report Card","https://goreportcard.com/badge/github.com/zoobz-io/fig",[1038,1065,1068],{"href":1066,"rel":1067},"https://github.com/zoobz-io/fig/actions/workflows/codeql.yml",[1042],[1044,1069],{"alt":1070,"src":1071},"CodeQL","https://github.com/zoobz-io/fig/actions/workflows/codeql.yml/badge.svg",[1038,1073,1076],{"href":1074,"rel":1075},"https://pkg.go.dev/github.com/zoobz-io/fig",[1042],[1044,1077],{"alt":1078,"src":1079},"Go Reference","https://pkg.go.dev/badge/github.com/zoobz-io/fig.svg",[1038,1081,1083],{"href":1082},"LICENSE",[1044,1084],{"alt":1085,"src":1086},"License: MIT","https://img.shields.io/badge/License-MIT-blue.svg",[1038,1088,1090],{"href":1089},"go.mod",[1044,1091],{"alt":1092,"src":1093},"Go Version","https://img.shields.io/github/go-mod/go-version/zoobz-io/fig",[1038,1095,1098],{"href":1096,"rel":1097},"https://github.com/zoobz-io/fig/releases/latest",[1042],[1044,1099],{"alt":1100,"src":1101},"Release","https://img.shields.io/github/v/release/zoobz-io/fig",[1035,1103,1104],{},"Struct tags in, configuration out.",[1035,1106,1107],{},"fig loads configuration from environment variables, secret providers, and defaults using Go struct tags. One function call, predictable resolution order.",[1109,1110,1112],"h2",{"id":1111},"install","Install",[1114,1115,1119],"pre",{"className":1116,"code":1117,"language":1118,"meta":43,"style":43},"language-bash shiki shiki-themes","go get github.com/zoobz-io/fig\n","bash",[1120,1121,1122],"code",{"__ignoreMap":43},[1123,1124,1126,1130,1134],"span",{"class":1125,"line":9},"line",[1123,1127,1129],{"class":1128},"s5klm","go",[1123,1131,1133],{"class":1132},"sxAnc"," get",[1123,1135,1136],{"class":1132}," github.com/zoobz-io/fig\n",[1035,1138,1139],{},"Requires Go 1.24+.",[1109,1141,536],{"id":1142},"quick-start",[1114,1144,1147],{"className":1145,"code":1146,"language":1129,"meta":43,"style":43},"language-go shiki shiki-themes","package main\n\nimport (\n    \"log\"\n\n    \"github.com/zoobz-io/fig\"\n)\n\ntype Config struct {\n    Host     string   `env:\"APP_HOST\" default:\"localhost\"`\n    Port     int      `env:\"APP_PORT\" default:\"8080\"`\n    Password string   `secret:\"db/password\"`\n    Tags     []string `env:\"APP_TAGS\"`\n    APIKey   string   `env:\"API_KEY\" required:\"true\"`\n}\n\nfunc main() {\n    var cfg Config\n    if err := fig.Load(&cfg); err != nil {\n        log.Fatal(err)\n    }\n    // cfg is now populated\n}\n",[1120,1148,1149,1159,1165,1174,1179,1184,1190,1196,1201,1217,1230,1242,1254,1269,1281,1287,1292,1306,1319,1362,1380,1386,1393],{"__ignoreMap":43},[1123,1150,1151,1155],{"class":1125,"line":9},[1123,1152,1154],{"class":1153},"sUt3r","package",[1123,1156,1158],{"class":1157},"sYBwO"," main\n",[1123,1160,1161],{"class":1125,"line":19},[1123,1162,1164],{"emptyLinePlaceholder":1163},true,"\n",[1123,1166,1167,1170],{"class":1125,"line":106},[1123,1168,1169],{"class":1153},"import",[1123,1171,1173],{"class":1172},"soy-K"," (\n",[1123,1175,1176],{"class":1125,"line":581},[1123,1177,1178],{"class":1132},"    \"log\"\n",[1123,1180,1182],{"class":1125,"line":1181},5,[1123,1183,1164],{"emptyLinePlaceholder":1163},[1123,1185,1187],{"class":1125,"line":1186},6,[1123,1188,1189],{"class":1132},"    \"github.com/zoobz-io/fig\"\n",[1123,1191,1193],{"class":1125,"line":1192},7,[1123,1194,1195],{"class":1172},")\n",[1123,1197,1199],{"class":1125,"line":1198},8,[1123,1200,1164],{"emptyLinePlaceholder":1163},[1123,1202,1204,1207,1210,1213],{"class":1125,"line":1203},9,[1123,1205,1206],{"class":1153},"type",[1123,1208,1209],{"class":1157}," Config",[1123,1211,1212],{"class":1153}," struct",[1123,1214,1216],{"class":1215},"sq5bi"," {\n",[1123,1218,1220,1224,1227],{"class":1125,"line":1219},10,[1123,1221,1223],{"class":1222},"sBGCq","    Host",[1123,1225,1226],{"class":1157},"     string",[1123,1228,1229],{"class":1132},"   `env:\"APP_HOST\" default:\"localhost\"`\n",[1123,1231,1233,1236,1239],{"class":1125,"line":1232},11,[1123,1234,1235],{"class":1222},"    Port",[1123,1237,1238],{"class":1157},"     int",[1123,1240,1241],{"class":1132},"      `env:\"APP_PORT\" default:\"8080\"`\n",[1123,1243,1245,1248,1251],{"class":1125,"line":1244},12,[1123,1246,1247],{"class":1222},"    Password",[1123,1249,1250],{"class":1157}," string",[1123,1252,1253],{"class":1132},"   `secret:\"db/password\"`\n",[1123,1255,1257,1260,1263,1266],{"class":1125,"line":1256},13,[1123,1258,1259],{"class":1222},"    Tags",[1123,1261,1262],{"class":1215},"     []",[1123,1264,1265],{"class":1157},"string",[1123,1267,1268],{"class":1132}," `env:\"APP_TAGS\"`\n",[1123,1270,1272,1275,1278],{"class":1125,"line":1271},14,[1123,1273,1274],{"class":1222},"    APIKey",[1123,1276,1277],{"class":1157},"   string",[1123,1279,1280],{"class":1132},"   `env:\"API_KEY\" required:\"true\"`\n",[1123,1282,1284],{"class":1125,"line":1283},15,[1123,1285,1286],{"class":1215},"}\n",[1123,1288,1290],{"class":1125,"line":1289},16,[1123,1291,1164],{"emptyLinePlaceholder":1163},[1123,1293,1295,1298,1301,1304],{"class":1125,"line":1294},17,[1123,1296,1297],{"class":1153},"func",[1123,1299,1300],{"class":1128}," main",[1123,1302,1303],{"class":1215},"()",[1123,1305,1216],{"class":1215},[1123,1307,1309,1312,1316],{"class":1125,"line":1308},18,[1123,1310,1311],{"class":1153},"    var",[1123,1313,1315],{"class":1314},"sh8_p"," cfg",[1123,1317,1318],{"class":1157}," Config\n",[1123,1320,1322,1326,1329,1332,1335,1338,1340,1343,1346,1349,1352,1354,1357,1360],{"class":1125,"line":1321},19,[1123,1323,1325],{"class":1324},"sW3Qg","    if",[1123,1327,1328],{"class":1314}," err",[1123,1330,1331],{"class":1314}," :=",[1123,1333,1334],{"class":1314}," fig",[1123,1336,1337],{"class":1215},".",[1123,1339,812],{"class":1128},[1123,1341,1342],{"class":1215},"(",[1123,1344,1345],{"class":1324},"&",[1123,1347,1348],{"class":1314},"cfg",[1123,1350,1351],{"class":1215},");",[1123,1353,1328],{"class":1314},[1123,1355,1356],{"class":1324}," !=",[1123,1358,1359],{"class":1153}," nil",[1123,1361,1216],{"class":1215},[1123,1363,1365,1368,1370,1373,1375,1378],{"class":1125,"line":1364},20,[1123,1366,1367],{"class":1314},"        log",[1123,1369,1337],{"class":1215},[1123,1371,1372],{"class":1128},"Fatal",[1123,1374,1342],{"class":1215},[1123,1376,1377],{"class":1314},"err",[1123,1379,1195],{"class":1215},[1123,1381,1383],{"class":1125,"line":1382},21,[1123,1384,1385],{"class":1215},"    }\n",[1123,1387,1389],{"class":1125,"line":1388},22,[1123,1390,1392],{"class":1391},"sLkEo","    // cfg is now populated\n",[1123,1394,1396],{"class":1125,"line":1395},23,[1123,1397,1286],{"class":1215},[1035,1399,1400,1401,1403,1404,1403,1406,1408],{},"Resolution order: ",[1120,1402,109],{}," → ",[1120,1405,103],{},[1120,1407,114],{}," → zero value.",[1109,1410,1412],{"id":1411},"capabilities","Capabilities",[1414,1415,1416,1429],"table",{},[1417,1418,1419],"thead",{},[1420,1421,1422,1426],"tr",{},[1423,1424,1425],"th",{},"Feature",[1423,1427,1428],{},"Description",[1430,1431,1432,1444,1455,1465,1475,1483,1493],"tbody",{},[1420,1433,1434,1438],{},[1435,1436,1437],"td",{},"Environment variables",[1435,1439,1440,1443],{},[1120,1441,1442],{},"env:\"VAR_NAME\""," tag",[1420,1445,1446,1449],{},[1435,1447,1448],{},"Secret providers",[1435,1450,1451,1454],{},[1120,1452,1453],{},"secret:\"path/to/secret\""," tag with pluggable backends",[1420,1456,1457,1460],{},[1435,1458,1459],{},"Default values",[1435,1461,1462,1443],{},[1120,1463,1464],{},"default:\"value\"",[1420,1466,1467,1470],{},[1435,1468,1469],{},"Required fields",[1435,1471,1472,1443],{},[1120,1473,1474],{},"required:\"true\"",[1420,1476,1477,1480],{},[1435,1478,1479],{},"Nested structs",[1435,1481,1482],{},"Automatic recursion into embedded structs",[1420,1484,1485,1487],{},[1435,1486,66],{},[1435,1488,1489,1490,1492],{},"Implement ",[1120,1491,836],{}," interface for custom checks",[1420,1494,1495,1498],{},[1435,1496,1497],{},"Context support",[1435,1499,1500,1502],{},[1120,1501,817],{}," for secret provider timeouts",[1504,1505,909],"h3",{"id":1506},"supported-types",[1035,1508,1509,1511,1512,1511,1515,1511,1518,1511,1521,1511,1524,1511,1527,1511,1530,1511,1533,1511,1536,1539,1540,1337],{},[1120,1510,1265],{},", ",[1120,1513,1514],{},"int",[1120,1516,1517],{},"int8-64",[1120,1519,1520],{},"uint",[1120,1522,1523],{},"uint8-64",[1120,1525,1526],{},"float32",[1120,1528,1529],{},"float64",[1120,1531,1532],{},"bool",[1120,1534,1535],{},"time.Duration",[1120,1537,1538],{},"[]string"," (comma-separated), and any type implementing ",[1120,1541,1542],{},"encoding.TextUnmarshaler",[1109,1544,412],{"id":1545},"secret-providers",[1035,1547,1548,1549,1551],{},"Pass a provider implementing ",[1120,1550,826],{}," to load secrets:",[1114,1553,1555],{"className":1145,"code":1554,"language":1129,"meta":43,"style":43},"type SecretProvider interface {\n    Get(ctx context.Context, key string) (string, error)\n}\n",[1120,1556,1557,1569,1611],{"__ignoreMap":43},[1123,1558,1559,1561,1564,1567],{"class":1125,"line":9},[1123,1560,1206],{"class":1153},[1123,1562,1563],{"class":1157}," SecretProvider",[1123,1565,1566],{"class":1153}," interface",[1123,1568,1216],{"class":1215},[1123,1570,1571,1574,1576,1580,1583,1585,1588,1591,1594,1596,1599,1602,1604,1606,1609],{"class":1125,"line":19},[1123,1572,1573],{"class":1128},"    Get",[1123,1575,1342],{"class":1215},[1123,1577,1579],{"class":1578},"sSYET","ctx",[1123,1581,1582],{"class":1157}," context",[1123,1584,1337],{"class":1215},[1123,1586,1587],{"class":1157},"Context",[1123,1589,1590],{"class":1215},",",[1123,1592,1593],{"class":1578}," key",[1123,1595,1250],{"class":1157},[1123,1597,1598],{"class":1215},")",[1123,1600,1601],{"class":1215}," (",[1123,1603,1265],{"class":1157},[1123,1605,1590],{"class":1215},[1123,1607,1608],{"class":1157}," error",[1123,1610,1195],{"class":1215},[1123,1612,1613],{"class":1125,"line":106},[1123,1614,1286],{"class":1215},[1504,1616,1618],{"id":1617},"available-providers","Available Providers",[1035,1620,1621],{},"Each provider is a separate module — import only what you need:",[1114,1623,1625],{"className":1116,"code":1624,"language":1118,"meta":43,"style":43},"# AWS Secrets Manager\ngo get github.com/zoobz-io/fig/awssm\n\n# GCP Secret Manager\ngo get github.com/zoobz-io/fig/gcpsm\n\n# HashiCorp Vault\ngo get github.com/zoobz-io/fig/vault\n",[1120,1626,1627,1632,1641,1645,1650,1659,1663,1668],{"__ignoreMap":43},[1123,1628,1629],{"class":1125,"line":9},[1123,1630,1631],{"class":1391},"# AWS Secrets Manager\n",[1123,1633,1634,1636,1638],{"class":1125,"line":19},[1123,1635,1129],{"class":1128},[1123,1637,1133],{"class":1132},[1123,1639,1640],{"class":1132}," github.com/zoobz-io/fig/awssm\n",[1123,1642,1643],{"class":1125,"line":106},[1123,1644,1164],{"emptyLinePlaceholder":1163},[1123,1646,1647],{"class":1125,"line":581},[1123,1648,1649],{"class":1391},"# GCP Secret Manager\n",[1123,1651,1652,1654,1656],{"class":1125,"line":1181},[1123,1653,1129],{"class":1128},[1123,1655,1133],{"class":1132},[1123,1657,1658],{"class":1132}," github.com/zoobz-io/fig/gcpsm\n",[1123,1660,1661],{"class":1125,"line":1186},[1123,1662,1164],{"emptyLinePlaceholder":1163},[1123,1664,1665],{"class":1125,"line":1192},[1123,1666,1667],{"class":1391},"# HashiCorp Vault\n",[1123,1669,1670,1672,1674],{"class":1125,"line":1198},[1123,1671,1129],{"class":1128},[1123,1673,1133],{"class":1132},[1123,1675,1676],{"class":1132}," github.com/zoobz-io/fig/vault\n",[1114,1678,1680],{"className":1145,"code":1679,"language":1129,"meta":43,"style":43},"import \"github.com/zoobz-io/fig/vault\"\n\np, err := vault.New()\nif err != nil {\n    log.Fatal(err)\n}\nfig.Load(&cfg, p)\n",[1120,1681,1682,1689,1693,1713,1726,1741,1745],{"__ignoreMap":43},[1123,1683,1684,1686],{"class":1125,"line":9},[1123,1685,1169],{"class":1153},[1123,1687,1688],{"class":1132}," \"github.com/zoobz-io/fig/vault\"\n",[1123,1690,1691],{"class":1125,"line":19},[1123,1692,1164],{"emptyLinePlaceholder":1163},[1123,1694,1695,1697,1699,1701,1703,1706,1708,1710],{"class":1125,"line":106},[1123,1696,1035],{"class":1314},[1123,1698,1590],{"class":1215},[1123,1700,1328],{"class":1314},[1123,1702,1331],{"class":1314},[1123,1704,1705],{"class":1314}," vault",[1123,1707,1337],{"class":1215},[1123,1709,569],{"class":1128},[1123,1711,1712],{"class":1215},"()\n",[1123,1714,1715,1718,1720,1722,1724],{"class":1125,"line":581},[1123,1716,1717],{"class":1324},"if",[1123,1719,1328],{"class":1314},[1123,1721,1356],{"class":1324},[1123,1723,1359],{"class":1153},[1123,1725,1216],{"class":1215},[1123,1727,1728,1731,1733,1735,1737,1739],{"class":1125,"line":1181},[1123,1729,1730],{"class":1314},"    log",[1123,1732,1337],{"class":1215},[1123,1734,1372],{"class":1128},[1123,1736,1342],{"class":1215},[1123,1738,1377],{"class":1314},[1123,1740,1195],{"class":1215},[1123,1742,1743],{"class":1125,"line":1186},[1123,1744,1286],{"class":1215},[1123,1746,1747,1749,1751,1753,1755,1757,1759,1761,1764],{"class":1125,"line":1192},[1123,1748,1033],{"class":1314},[1123,1750,1337],{"class":1215},[1123,1752,812],{"class":1128},[1123,1754,1342],{"class":1215},[1123,1756,1345],{"class":1324},[1123,1758,1348],{"class":1314},[1123,1760,1590],{"class":1215},[1123,1762,1763],{"class":1314}," p",[1123,1765,1195],{"class":1215},[1035,1767,1768],{},"Secrets take precedence over environment variables, allowing secure overrides.",[1109,1770,66],{"id":1771},"validation",[1035,1773,1774,1775,1777],{},"Implement the ",[1120,1776,836],{}," interface for custom validation after loading:",[1114,1779,1781],{"className":1145,"code":1780,"language":1129,"meta":43,"style":43},"func (c *Config) Validate() error {\n    if c.Port \u003C= 0 || c.Port > 65535 {\n        return errors.New(\"port must be between 1 and 65535\")\n    }\n    return nil\n}\n",[1120,1782,1783,1809,1845,1864,1868,1876],{"__ignoreMap":43},[1123,1784,1785,1787,1789,1792,1795,1798,1800,1803,1805,1807],{"class":1125,"line":9},[1123,1786,1297],{"class":1153},[1123,1788,1601],{"class":1215},[1123,1790,1791],{"class":1578},"c ",[1123,1793,1794],{"class":1324},"*",[1123,1796,1797],{"class":1157},"Config",[1123,1799,1598],{"class":1215},[1123,1801,1802],{"class":1128}," Validate",[1123,1804,1303],{"class":1215},[1123,1806,1608],{"class":1157},[1123,1808,1216],{"class":1215},[1123,1810,1811,1813,1816,1818,1821,1824,1828,1831,1833,1835,1837,1840,1843],{"class":1125,"line":19},[1123,1812,1325],{"class":1324},[1123,1814,1815],{"class":1314}," c",[1123,1817,1337],{"class":1215},[1123,1819,1820],{"class":1314},"Port",[1123,1822,1823],{"class":1324}," \u003C=",[1123,1825,1827],{"class":1826},"sMAmT"," 0",[1123,1829,1830],{"class":1324}," ||",[1123,1832,1815],{"class":1314},[1123,1834,1337],{"class":1215},[1123,1836,1820],{"class":1314},[1123,1838,1839],{"class":1324}," >",[1123,1841,1842],{"class":1826}," 65535",[1123,1844,1216],{"class":1215},[1123,1846,1847,1850,1853,1855,1857,1859,1862],{"class":1125,"line":106},[1123,1848,1849],{"class":1324},"        return",[1123,1851,1852],{"class":1314}," errors",[1123,1854,1337],{"class":1215},[1123,1856,569],{"class":1128},[1123,1858,1342],{"class":1215},[1123,1860,1861],{"class":1132},"\"port must be between 1 and 65535\"",[1123,1863,1195],{"class":1215},[1123,1865,1866],{"class":1125,"line":581},[1123,1867,1385],{"class":1215},[1123,1869,1870,1873],{"class":1125,"line":1181},[1123,1871,1872],{"class":1324},"    return",[1123,1874,1875],{"class":1153}," nil\n",[1123,1877,1878],{"class":1125,"line":1186},[1123,1879,1286],{"class":1215},[1109,1881,1883],{"id":1882},"why-fig","Why fig?",[1035,1885,1886],{},"No config files. No YAML. No JSON. No builder chains. One function, one resolution order, done.",[1109,1888,1890],{"id":1889},"contributing","Contributing",[1035,1892,1893,1894,1337],{},"See ",[1038,1895,1897],{"href":1896},"CONTRIBUTING","CONTRIBUTING.md",[1109,1899,1901],{"id":1900},"license","License",[1035,1903,1904],{},"MIT",[1906,1907,1908],"style",{},"html pre.shiki code .s5klm, html code.shiki .s5klm{--shiki-default:var(--shiki-function)}html pre.shiki code .sxAnc, html code.shiki .sxAnc{--shiki-default:var(--shiki-string)}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sUt3r, html code.shiki .sUt3r{--shiki-default:var(--shiki-keyword)}html pre.shiki code .sYBwO, html code.shiki .sYBwO{--shiki-default:var(--shiki-type)}html pre.shiki code .soy-K, html code.shiki .soy-K{--shiki-default:#BBBBBB}html pre.shiki code .sq5bi, html code.shiki .sq5bi{--shiki-default:var(--shiki-punctuation)}html pre.shiki code .sBGCq, html code.shiki .sBGCq{--shiki-default:var(--shiki-property)}html pre.shiki code .sh8_p, html code.shiki .sh8_p{--shiki-default:var(--shiki-text)}html pre.shiki code .sW3Qg, html code.shiki .sW3Qg{--shiki-default:var(--shiki-operator)}html pre.shiki code .sLkEo, html code.shiki .sLkEo{--shiki-default:var(--shiki-comment)}html pre.shiki code .sSYET, html code.shiki .sSYET{--shiki-default:var(--shiki-parameter)}html pre.shiki code .sMAmT, html code.shiki .sMAmT{--shiki-default:var(--shiki-number)}",{"title":43,"searchDepth":19,"depth":19,"links":1910},[1911,1912,1913,1916,1919,1920,1921,1922],{"id":1111,"depth":19,"text":1112},{"id":1142,"depth":19,"text":536},{"id":1411,"depth":19,"text":1412,"children":1914},[1915],{"id":1506,"depth":106,"text":909},{"id":1545,"depth":19,"text":412,"children":1917},[1918],{"id":1617,"depth":106,"text":1618},{"id":1771,"depth":19,"text":66},{"id":1882,"depth":19,"text":1883},{"id":1889,"depth":19,"text":1890},{"id":1900,"depth":19,"text":1901},"md","book-open",{},"/readme",{"title":1026,"description":43},"readme","DI6cgLBPgR7YzTJubeMy-PShuUBxeO9NMtEbhbj6Dzo",{"id":1931,"title":1932,"body":1933,"description":43,"extension":1923,"icon":1974,"meta":1975,"navigation":1163,"path":1976,"seo":1977,"stem":1978,"__hash__":1979},"resources/security.md","Security",{"type":1028,"value":1934,"toc":1970},[1935,1939,1943,1950,1953,1957],[1031,1936,1938],{"id":1937},"security-policy","Security Policy",[1109,1940,1942],{"id":1941},"reporting-a-vulnerability","Reporting a Vulnerability",[1035,1944,1945,1946,1337],{},"Please report security vulnerabilities by emailing ",[1038,1947,1949],{"href":1948},"mailto:security@zoobzio.com","security@zoobzio.com",[1035,1951,1952],{},"Do not open a public issue for security vulnerabilities.",[1109,1954,1956],{"id":1955},"response-timeline","Response Timeline",[1958,1959,1960,1964,1967],"ul",{},[1961,1962,1963],"li",{},"Acknowledgment: within 48 hours",[1961,1965,1966],{},"Initial assessment: within 1 week",[1961,1968,1969],{},"Fix timeline: depends on severity",{"title":43,"searchDepth":19,"depth":19,"links":1971},[1972,1973],{"id":1941,"depth":19,"text":1942},{"id":1955,"depth":19,"text":1956},"shield",{},"/security",{"title":1932,"description":43},"security","6BvZhKRNN9RxQpAWeuz3P-Iotz_T7Z7ugTXcNo-LTyU",{"id":1981,"title":1890,"body":1982,"description":43,"extension":1923,"icon":1120,"meta":2043,"navigation":1163,"path":2044,"seo":2045,"stem":1889,"__hash__":2046},"resources/contributing.md",{"type":1028,"value":1983,"toc":2039},[1984,1986,2008,2012,2032,2036],[1031,1985,1890],{"id":1889},[1987,1988,1989,1992,1995,1998,2005],"ol",{},[1961,1990,1991],{},"Fork the repository",[1961,1993,1994],{},"Create a feature branch",[1961,1996,1997],{},"Make changes with tests",[1961,1999,2000,2001,2004],{},"Run ",[1120,2002,2003],{},"make check"," to verify",[1961,2006,2007],{},"Submit a pull request",[1109,2009,2011],{"id":2010},"development","Development",[1958,2013,2014,2020,2026],{},[1961,2015,2016,2019],{},[1120,2017,2018],{},"make help"," — list available commands",[1961,2021,2022,2025],{},[1120,2023,2024],{},"make test"," — run all tests",[1961,2027,2028,2031],{},[1120,2029,2030],{},"make lint"," — run linters",[1109,2033,2035],{"id":2034},"code-of-conduct","Code of Conduct",[1035,2037,2038],{},"Be respectful. We're all here to build good software.",{"title":43,"searchDepth":19,"depth":19,"links":2040},[2041,2042],{"id":2010,"depth":19,"text":2011},{"id":2034,"depth":19,"text":2035},{},"/contributing",{"title":1890,"description":43},"Uh51sWT_BFO8Ft7nBSzGxo0pXQxY65rXPhK3VQXlFxg",{"id":2048,"title":6,"author":2049,"body":2050,"description":8,"extension":1923,"meta":2189,"navigation":1163,"path":5,"published":2190,"readtime":2191,"seo":2192,"stem":958,"tags":2193,"updated":2190,"__hash__":2196},"fig/v0.0.4/1.learn/1.overview.md","zoobzio",{"type":1028,"value":2051,"toc":2183},[2052,2055,2057,2060,2062,2065,2068,2120,2123,2137,2140,2160,2163],[1031,2053,6],{"id":2054},"overview",[1035,2056,13],{},[1109,2058,16],{"id":2059},"the-idea",[1035,2061,18],{},[1109,2063,22],{"id":2064},"the-implementation",[1035,2066,2067],{},"fig provides:",[1958,2069,2070,2087,2093,2099,2105,2114],{},[1961,2071,2072,2076,2077,1511,2079,1511,2081,2083,2084,2086],{},[2073,2074,2075],"strong",{},"Struct tag parsing"," — ",[1120,2078,103],{},[1120,2080,109],{},[1120,2082,114],{},", and ",[1120,2085,119],{}," tags",[1961,2088,2089,2092],{},[2073,2090,2091],{},"Deterministic resolution"," — secret → env → default → zero value, every time",[1961,2094,2095,2098],{},[2073,2096,2097],{},"Secret provider abstraction"," — plug in Vault, AWS Secrets Manager, GCP Secret Manager, or your own",[1961,2100,2101,2104],{},[2073,2102,2103],{},"Type conversion"," — strings, ints, floats, bools, durations, slices, and TextUnmarshaler",[1961,2106,2107,2110,2111,2113],{},[2073,2108,2109],{},"Validation hooks"," — implement ",[1120,2112,836],{}," for post-load checks",[1961,2115,2116,2119],{},[2073,2117,2118],{},"Nested struct support"," — automatic recursion into embedded configurations",[1109,2121,27],{"id":2122},"what-it-enables",[1958,2124,2125,2128,2131,2134],{},[1961,2126,2127],{},"Single-source-of-truth configuration structs",[1961,2129,2130],{},"Secure secret injection without environment variable sprawl",[1961,2132,2133],{},"Testable configuration with mock providers",[1961,2135,2136],{},"Consistent loading across services in a microservices architecture",[1035,2138,2139],{},"fig integrates with:",[1958,2141,2142,2148,2154],{},[1961,2143,2144,2147],{},[1038,2145,523],{"href":2146},"../integrations/vault"," — KV secrets engine",[1961,2149,2150,2153],{},[1038,2151,627],{"href":2152},"../integrations/awssm"," — standard AWS credential chain",[1961,2155,2156,2159],{},[1038,2157,711],{"href":2158},"../integrations/gcpsm"," — project-scoped secrets",[1109,2161,32],{"id":2162},"next-steps",[1958,2164,2165,2171,2177],{},[1961,2166,2167,2170],{},[1038,2168,37],{"href":2169},"quickstart"," — get productive in minutes",[1961,2172,2173,2176],{},[1038,2174,85],{"href":2175},"concepts"," — understand resolution order and struct tags",[1961,2178,2179,2182],{},[1038,2180,991],{"href":2181},"../reference/api"," — full API documentation",{"title":43,"searchDepth":19,"depth":19,"links":2184},[2185,2186,2187,2188],{"id":2059,"depth":19,"text":16},{"id":2064,"depth":19,"text":22},{"id":2122,"depth":19,"text":27},{"id":2162,"depth":19,"text":32},{},"2026-01-24T00:00:00.000Z",null,{"title":6,"description":8},[2194,1129,2195],"configuration","secrets","ROH-pbVpUIFTgii-250tz45eqbz_0iHmbTuutnRiAJU",[2191,2198],{"title":37,"path":36,"stem":960,"description":39,"children":-1},1776115359290]