{"id":20286466,"url":"https://github.com/spandigital/with","last_synced_at":"2026-03-12T16:03:15.393Z","repository":{"id":251389184,"uuid":"809775212","full_name":"SPANDigital/with","owner":"SPANDigital","description":"SPAN Digital's implementation of the Functional Options Pattern using Go Generics","archived":false,"fork":false,"pushed_at":"2025-10-16T07:31:37.000Z","size":67,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"develop","last_synced_at":"2025-10-29T15:31:51.702Z","etag":null,"topics":["configuration","functional-options-pattern","go","options","with"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SPANDigital.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-06-03T12:31:50.000Z","updated_at":"2025-10-16T07:30:22.000Z","dependencies_parsed_at":"2025-04-11T09:10:34.145Z","dependency_job_id":"4397f06e-c8f4-408a-a5a6-fa38b9cf48c6","html_url":"https://github.com/SPANDigital/with","commit_stats":null,"previous_names":["spandigital/with"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/SPANDigital/with","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SPANDigital%2Fwith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SPANDigital%2Fwith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SPANDigital%2Fwith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SPANDigital%2Fwith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SPANDigital","download_url":"https://codeload.github.com/SPANDigital/with/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SPANDigital%2Fwith/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30431562,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T14:34:45.044Z","status":"ssl_error","status_checked_at":"2026-03-12T14:09:33.793Z","response_time":114,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["configuration","functional-options-pattern","go","options","with"],"created_at":"2024-11-14T14:34:21.025Z","updated_at":"2026-03-12T16:03:15.377Z","avatar_url":"https://github.com/SPANDigital.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# with\n\n**A type-safe, generic implementation of the Functional Options Pattern for Go**\n\n[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers\u0026message=Open\u0026color=blue\u0026logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/SPANDigital/with)\n![Develop Go Action Workflow Status](https://img.shields.io/github/actions/workflow/status/spandigital/with/go.yml?branch=develop\u0026label=develop)\n![Main Go Action Workflow Status](https://img.shields.io/github/actions/workflow/status/spandigital/with/go.yml?branch=main\u0026label=main)\n![Release status](https://img.shields.io/github/v/tag/SPANDigital/with)\n\n## What is this?\n\nThe `with` package makes it easy to create clean, flexible APIs in Go using the [Functional Options Pattern](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html). Originally introduced by Rob Pike and popularized by [Dave Cheney](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis), this pattern allows you to write constructors that are:\n\n- **Easy to use** - Simple cases stay simple, complex cases are possible\n- **Future-proof** - Add new options without breaking existing code\n- **Type-safe** - Leverage Go's generics for compile-time safety\n- **Self-documenting** - Options are explicit and readable\n\n## Why use this package?\n\nInstead of writing boilerplate for each type, `with` provides generic helpers that handle:\n\n✅ Setting default values\n✅ Applying functional options\n✅ Validating the final configuration\n✅ Clear error messages when something goes wrong\n\n## Quick Start\n\n### Installation\n\n```bash\ngo get github.com/spandigital/with\n```\n\n### Simple Example\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"time\"\n    \"github.com/spandigital/with\"\n)\n\n// 1. Define your options struct\ntype ServerOptions struct {\n    Host    string\n    Port    int\n    Timeout time.Duration\n}\n\n// 2. (Optional) Add defaults\nfunc (o *ServerOptions) SetDefaults() {\n    o.Host = \"localhost\"\n    o.Port = 8080\n    o.Timeout = 30 * time.Second\n}\n\n// 3. (Optional) Add validation\nfunc (o *ServerOptions) Validate() error {\n    if o.Port \u003c 1 || o.Port \u003e 65535 {\n        return fmt.Errorf(\"port must be between 1-65535\")\n    }\n    return nil\n}\n\n// 4. Create option functions\nfunc WithHost(host string) with.Func[ServerOptions] {\n    return func(o *ServerOptions) error {\n        o.Host = host\n        return nil\n    }\n}\n\nfunc WithPort(port int) with.Func[ServerOptions] {\n    return func(o *ServerOptions) error {\n        if port \u003c 1 || port \u003e 65535 {\n            return fmt.Errorf(\"invalid port: %d\", port)\n        }\n        o.Port = port\n        return nil\n    }\n}\n\n// 5. Use in your constructor\nfunc NewServer(opts ...with.Func[ServerOptions]) (*Server, error) {\n    o := \u0026ServerOptions{}\n    if err := with.DefaultThenAddWith(o, opts); err != nil {\n        return nil, err\n    }\n    return \u0026Server{options: o}, nil\n}\n\n// Usage - clean and readable!\nfunc main() {\n    // Use defaults\n    server1, _ := NewServer()\n\n    // Override specific options\n    server2, _ := NewServer(\n        WithHost(\"0.0.0.0\"),\n        WithPort(3000),\n    )\n}\n```\n\n## Features\n\n### 🎯 Core Capabilities\n\n- **Generic Type Safety** - Uses Go 1.18+ generics for type-safe option functions\n- **Default Values** - Implement `SetDefaults()` to provide sensible defaults\n- **Validation** - Implement `Validate()` for comprehensive validation after configuration\n- **Error Handling** - Clear error messages with context about which option failed\n- **Flexible Usage** - Support both functional options and struct-based initialization\n- **Must Variants** - Panic-on-error variants for initialization code (`MustAddWith`, `MustDefaultThenAddWith`)\n\n### 🚀 Two Ways to Configure\n\n**1. Start with defaults, override what you need:**\n\n```go\nfunc NewServer(opts ...with.Func[ServerOptions]) (*Server, error) {\n    o := \u0026ServerOptions{}\n    if err := with.DefaultThenAddWith(o, opts); err != nil {\n        return nil, err\n    }\n    return \u0026Server{options: o}, nil\n}\n```\n\n**2. Start with a struct, apply additional options:**\n\n```go\nfunc NewServerFromConfig(config *ServerOptions, opts ...with.Func[ServerOptions]) (*Server, error) {\n    if err := with.AddWith(config, opts); err != nil {\n        return nil, err\n    }\n    return \u0026Server{options: config}, nil\n}\n```\n\n## API Reference\n\n### Main Functions\n\n| Function | Description |\n|----------|-------------|\n| `with.DefaultThenAddWith(opts, funcs)` | Apply defaults, then options, then validate |\n| `with.AddWith(opts, funcs)` | Apply options and validate (no defaults) |\n| `with.MustDefaultThenAddWith(opts, funcs)` | Like `DefaultThenAddWith` but panics on error |\n| `with.MustAddWith(opts, funcs)` | Like `AddWith` but panics on error |\n| `with.Nop[T]()` | Returns a no-op option function |\n\n### Interfaces\n\n| Interface | Method | Description |\n|-----------|--------|-------------|\n| `Defaulted` | `SetDefaults()` | Called to set default values |\n| `Validated` | `Validate() error` | Called to validate the final configuration |\n\n## Complete Example\n\nSee the [samples/server](./samples/server) directory for a working example. Here's a taste:\n\n```go\n// Create with defaults\nserver, _ := NewServer()\n\n// Override specific options\nserver, _ := NewServer(\n    WithHost(\"0.0.0.0\"),\n    WithPort(3000),\n    WithTimeout(60 * time.Second),\n)\n\n// Mix struct initialization with options\nserver, _ := NewServerFromOptions(\n    \u0026Options{Host: \"localhost\", Port: 8080},\n    WithTimeout(30 * time.Second),\n)\n\n```\n\n## Why Functional Options?\n\nThe Functional Options Pattern, introduced by Rob Pike in his 2014 post [Self-referential functions and the design of options](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html), solves a common problem in API design: how to make constructors that are both simple to use and flexible to extend.\n\n### The Problem\n\nTraditional approaches have drawbacks:\n\n```go\n// 😞 Too many parameters - hard to remember order\nNewServer(\"localhost\", 8080, 30*time.Second, true, false, \"INFO\")\n\n// 😞 Config struct - requires nil checks, verbose for simple cases\nNewServer(\u0026Config{Host: \"localhost\", Port: 8080, ...})\n\n// 😞 Builder pattern - too much ceremony\nNewServer().WithHost(\"localhost\").WithPort(8080).Build()\n```\n\n### The Solution\n\nFunctional options provide a clean middle ground:\n\n```go\n// 😊 Clean, readable, self-documenting\nserver, _ := NewServer(\n    WithHost(\"localhost\"),\n    WithPort(8080),\n)\n```\n\n**Benefits:**\n- ✅ **Backward compatible** - Adding new options doesn't break existing code\n- ✅ **Simple by default** - `NewServer()` works with zero configuration\n- ✅ **Self-documenting** - Options are explicit: `WithTimeout(30*time.Second)`\n- ✅ **Flexible** - Complex configurations are just as easy as simple ones\n- ✅ **Type-safe** - Compiler catches mistakes at build time\n\n### Learn More\n\n- 📖 [Rob Pike's original post](https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html) - The genesis of the pattern\n- 📖 [Dave Cheney's guide](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis) - Popularized the approach\n- 📖 [Uber's Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md#functional-options) - Best practices from industry\n\n## Performance\n\nThe pattern is lightweight with minimal overhead:\n\n```\nBenchmarkAddWith_SingleOption-12        91,654,365     12.82 ns/op    24 B/op    1 allocs/op\nBenchmarkAddWith_FourOptions-12         34,602,283     34.55 ns/op    72 B/op    4 allocs/op\nBenchmarkDirectInit-12              1,000,000,000      0.22 ns/op     0 B/op    0 allocs/op\n```\n\nEach option adds ~10-15ns. For configuration code that runs once at startup, this overhead is negligible.\n\n## Contributing\n\nWe welcome contributions! See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.\n\n## License\n\nMIT License - see [LICENSE](./LICENSE) for details.\n\n---\n\n**Made with ❤️ by [SPAN Digital](https://spandigital.com)**\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspandigital%2Fwith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspandigital%2Fwith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspandigital%2Fwith/lists"}