package postgres import ( "context" "database/sql" "fmt" "net/url" "strings" "time" _ "github.com/lib/pq" ) const initTimeout = 5 * time.Second var ( sqlOpen = sql.Open pingDB = func(ctx context.Context, db *sql.DB) error { return db.PingContext(ctx) } ) // ConnConfig describes the minimal connection settings shared by feedkit's // Postgres readers and writers. type ConnConfig struct { URI string Username string Password string } // BuildDSN validates a Postgres URI and injects credentials into it. func BuildDSN(cfg ConnConfig) (string, error) { u, err := url.Parse(strings.TrimSpace(cfg.URI)) if err != nil { return "", fmt.Errorf("invalid uri: %w", err) } if u.Scheme == "" { return "", fmt.Errorf("invalid uri: missing scheme") } if u.Host == "" { return "", fmt.Errorf("invalid uri: missing host") } u.User = url.UserPassword(cfg.Username, cfg.Password) return u.String(), nil } // Open builds a DSN, opens a database handle, and verifies connectivity with a // bounded ping before returning the handle. func Open(ctx context.Context, cfg ConnConfig) (*sql.DB, error) { dsn, err := BuildDSN(cfg) if err != nil { return nil, err } db, err := sqlOpen("postgres", dsn) if err != nil { return nil, err } pingCtx, cancel := context.WithTimeout(ctx, initTimeout) defer cancel() if err := pingDB(pingCtx, db); err != nil { _ = db.Close() return nil, err } return db, nil }