Skip to content

Commit fa02f1e

Browse files
authored
Merge pull request #8 from KARTIKrocks/feature-config
add config module
2 parents 82a3f40 + c1226bf commit fa02f1e

File tree

8 files changed

+1481
-0
lines changed

8 files changed

+1481
-0
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.8.0] - 2026-02-25
9+
10+
### Added
11+
12+
- **config** — New `config` package: load application configuration from environment variables, `.env` files, and JSON config files into typed Go structs
13+
- **config**`Load(dst, ...Option)` populates a struct from sources in priority order (env vars > .env file > JSON file > default tags) and validates with `request.ValidateStruct`
14+
- **config**`MustLoad(dst, ...Option)` calls `Load` and panics on error, for use in `main`/`init`
15+
- **config** — Struct tags: `env:"VAR_NAME"` maps fields to env vars, `default:"value"` sets fallbacks, `validate:"..."` reuses existing request validators
16+
- **config** — Options: `WithPrefix(p)` for prefixed env vars (e.g., `APP_PORT`), `WithEnvFile(path)` for `.env` files, `WithJSONFile(path)` for JSON base config, `WithRequired()` to error on missing files
17+
- **config** — Supported types: `string`, `bool`, `int/int8/16/32/64`, `uint` variants, `float32/64`, `time.Duration`, `[]string`, `[]int`
18+
- **config** — Nested struct support: automatically flattened to env var names (e.g., `DB.Host``DB_HOST`)
19+
- **config**`.env` file parsing: comments, blank lines, quoted values (single/double), inline comments
20+
- **config** — JSON config flattening: nested objects become underscore-separated uppercase keys
21+
- **config** — Structured error messages using `*errors.Error` for parse errors, validation failures, and missing files
22+
823
## [0.7.0] - 2026-02-25
924

1025
### Added

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A production-ready Go toolkit for building REST APIs. Zero mandatory dependencie
1515
- **`router`** — Route grouping with `.Get()`/`.Post()` method helpers, prefix groups, and per-group middleware on top of `http.ServeMux`
1616
- **`server`** — Graceful shutdown wrapper with signal handling, lifecycle hooks, and TLS support
1717
- **`health`** — Health check endpoint builder with dependency checks, timeouts, and liveness/readiness probes
18+
- **`config`** — Load configuration from env vars, `.env` files, and JSON files into typed structs with validation
1819
- **`sqlbuilder`** — Fluent SQL query builder for PostgreSQL, MySQL, and SQLite with JOINs, CTEs, UNION, upsert, and `request` package integration
1920
- **`apitest`** — Fluent test helpers for recording and asserting HTTP handler responses
2021

@@ -567,6 +568,50 @@ fmt.Println(resp.Status) // "healthy", "degraded", or "unhealthy"
567568
}
568569
```
569570

571+
### config
572+
573+
Load application configuration from environment variables, `.env` files, and JSON config files into typed Go structs.
574+
575+
```go
576+
import "github.com/KARTIKrocks/apikit/config"
577+
578+
type AppConfig struct {
579+
Host string `env:"HOST" default:"localhost" validate:"required"`
580+
Port int `env:"PORT" default:"8080" validate:"required,min=1,max=65535"`
581+
Debug bool `env:"DEBUG" default:"false"`
582+
DBUrl string `env:"DB_URL" validate:"required,url"`
583+
LogLevel string `env:"LOG_LEVEL" default:"info" validate:"oneof=debug info warn error"`
584+
Timeout time.Duration `env:"TIMEOUT" default:"30s"`
585+
Tags []string `env:"TAGS"`
586+
}
587+
588+
var cfg AppConfig
589+
config.MustLoad(&cfg,
590+
config.WithPrefix("APP"), // reads APP_HOST, APP_PORT, etc.
591+
config.WithEnvFile(".env"), // load .env file (won't override real env vars)
592+
config.WithJSONFile("config.json"), // JSON as base layer
593+
)
594+
595+
// Sources are applied in priority order:
596+
// 1. Environment variables (highest)
597+
// 2. .env file
598+
// 3. JSON file
599+
// 4. default:"..." tags (lowest)
600+
```
601+
602+
Nested structs are flattened automatically:
603+
604+
```go
605+
type Config struct {
606+
DB struct {
607+
Host string `env:"HOST" default:"localhost"`
608+
Port int `env:"PORT" default:"5432"`
609+
}
610+
}
611+
612+
// Reads DB_HOST, DB_PORT (or APP_DB_HOST, APP_DB_PORT with WithPrefix("APP"))
613+
```
614+
570615
### sqlbuilder
571616

572617
Fluent SQL query builder for PostgreSQL, MySQL, and SQLite. Produces `(string, []any)` pairs — no `database/sql` dependency.

config/config.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package config
2+
3+
import (
4+
stderrors "errors"
5+
"fmt"
6+
7+
"github.com/KARTIKrocks/apikit/errors"
8+
"github.com/KARTIKrocks/apikit/request"
9+
)
10+
11+
// Option configures the behavior of Load.
12+
type Option func(*options)
13+
14+
type options struct {
15+
prefix string
16+
envFile string
17+
jsonFile string
18+
required bool
19+
}
20+
21+
// WithPrefix sets a prefix for all environment variable lookups.
22+
// For example, WithPrefix("APP") causes field with env:"PORT" to read APP_PORT.
23+
func WithPrefix(prefix string) Option {
24+
return func(o *options) {
25+
o.prefix = prefix
26+
}
27+
}
28+
29+
// WithEnvFile loads environment variables from a file (e.g., ".env").
30+
// Values from the file do not override existing environment variables.
31+
func WithEnvFile(path string) Option {
32+
return func(o *options) {
33+
o.envFile = path
34+
}
35+
}
36+
37+
// WithJSONFile loads configuration from a JSON file as the base layer.
38+
// Environment variables and .env values take precedence over JSON values.
39+
func WithJSONFile(path string) Option {
40+
return func(o *options) {
41+
o.jsonFile = path
42+
}
43+
}
44+
45+
// WithRequired causes Load to return an error if any specified files
46+
// (env file or JSON file) do not exist.
47+
func WithRequired() Option {
48+
return func(o *options) {
49+
o.required = true
50+
}
51+
}
52+
53+
// Load populates dst from environment variables, optional .env files, and
54+
// optional JSON config files, then validates the result using struct tags.
55+
//
56+
// dst must be a non-nil pointer to a struct.
57+
//
58+
// Sources are applied in priority order (high to low):
59+
// 1. Environment variables
60+
// 2. .env file values (do not override existing env vars)
61+
// 3. JSON file values
62+
// 4. default:"..." struct tags
63+
func Load(dst any, opts ...Option) error {
64+
o := &options{}
65+
for _, opt := range opts {
66+
opt(o)
67+
}
68+
69+
// Load JSON file (base layer).
70+
var jsonValues map[string]string
71+
if o.jsonFile != "" {
72+
var err error
73+
jsonValues, err = loadJSONFile(o.jsonFile, o.required)
74+
if err != nil {
75+
return err
76+
}
77+
}
78+
79+
// Load .env file into a map (does not modify process environment).
80+
var envFileValues map[string]string
81+
if o.envFile != "" {
82+
var err error
83+
envFileValues, err = loadEnvFile(o.envFile, o.required)
84+
if err != nil {
85+
return err
86+
}
87+
}
88+
89+
// Resolve all values into the struct.
90+
if err := resolve(dst, o.prefix, envFileValues, jsonValues); err != nil {
91+
return err
92+
}
93+
94+
// Validate using request package tag validators.
95+
if err := request.ValidateStruct(dst); err != nil {
96+
// Wrap the validation error with config context.
97+
var apiErr *errors.Error
98+
if stderrors.As(err, &apiErr) {
99+
return errors.Validation("config: validation failed", apiErr.Fields)
100+
}
101+
return err
102+
}
103+
104+
return nil
105+
}
106+
107+
// MustLoad calls Load and panics if an error occurs.
108+
// It is intended for use in main() or init() functions.
109+
func MustLoad(dst any, opts ...Option) {
110+
if err := Load(dst, opts...); err != nil {
111+
panic(fmt.Sprintf("config: %v", err))
112+
}
113+
}

0 commit comments

Comments
 (0)