mirror of
https://github.com/caddyserver/caddy.git
synced 2026-04-24 01:49:32 -04:00
* chore: add `AGENTS.md` Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> * Apply suggestions from code review Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com> * review feedback Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> --------- Signed-off-by: Mohammed Al Sahaf <msaa1990@gmail.com> Co-authored-by: Francis Lavoie <lavofr@gmail.com> Co-authored-by: Matt Holt <mholt@users.noreply.github.com>
218 lines
6.8 KiB
Markdown
218 lines
6.8 KiB
Markdown
# Caddy Project Guidelines
|
||
|
||
## Mission
|
||
|
||
**Every site on HTTPS.** Caddy is a security-first, modular, extensible server platform.
|
||
|
||
## Code Style
|
||
|
||
### Go Idioms
|
||
|
||
Follow [Go Code Review Comments](https://go.dev/wiki/CodeReviewComments):
|
||
|
||
- **Error flow**: Early return, indent error handling—not else blocks
|
||
```go
|
||
if err != nil {
|
||
return err
|
||
}
|
||
// normal code
|
||
```
|
||
- **Naming**: initialisms (`URL`, `HTTP`, `ID`—not `Url`, `Http`, `Id`)
|
||
- **Receiver names**: 1–2 letters reflecting type (`c` for `Client`, `h` for `Handler`)
|
||
- **Error strings**: Lowercase, no trailing punctuation (`"something failed"` not `"Something failed."`)
|
||
- **Doc comments**: Full sentences starting with the name being documented
|
||
```go
|
||
// Handler serves HTTP requests for the file server.
|
||
type Handler struct { ... }
|
||
```
|
||
- **Empty slices**: `var t []string` (nil slice), not `t := []string{}` (non-nil zero-length)
|
||
- **Don't panic**: Use error returns for normal error handling
|
||
|
||
### Caddy Patterns
|
||
|
||
**Module registration**:
|
||
```go
|
||
func init() {
|
||
caddy.RegisterModule(MyModule{})
|
||
}
|
||
|
||
func (MyModule) CaddyModule() caddy.ModuleInfo {
|
||
return caddy.ModuleInfo{
|
||
ID: "namespace.category.name",
|
||
New: func() caddy.Module { return new(MyModule) },
|
||
}
|
||
}
|
||
```
|
||
|
||
**Module lifecycle**: `New()` → JSON unmarshal → `Provision()` → `Validate()` → use → `Cleanup()`
|
||
|
||
**Interface guards** — compile-time verification that modules implement required interfaces:
|
||
```go
|
||
var (
|
||
_ caddy.Provisioner = (*MyModule)(nil)
|
||
_ caddy.Validator = (*MyModule)(nil)
|
||
_ caddyfile.Unmarshaler = (*MyModule)(nil)
|
||
)
|
||
```
|
||
|
||
**Structured logging** — use the module-scoped logger from context:
|
||
```go
|
||
func (m *MyModule) Provision(ctx caddy.Context) error {
|
||
m.logger = ctx.Logger()
|
||
m.logger.Debug("provisioning", zap.String("field", m.Field))
|
||
return nil
|
||
}
|
||
```
|
||
|
||
**Caddyfile support** — implement `UnmarshalCaddyfile(*caddyfile.Dispenser)` using the `Dispenser` API:
|
||
```go
|
||
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
||
//
|
||
// directive [arg1] [arg2] {
|
||
// subdir value
|
||
// }
|
||
func (m *MyModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||
d.Next() // consume directive name
|
||
for d.NextArg() {
|
||
// handle inline arguments
|
||
}
|
||
for nesting := d.Nesting(); d.NextBlock(nesting); {
|
||
switch d.Val() {
|
||
case "subdir":
|
||
if !d.NextArg() {
|
||
return d.ArgErr()
|
||
}
|
||
m.Field = d.Val()
|
||
default:
|
||
return d.Errf("unrecognized subdirective: %s", d.Val())
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
```
|
||
|
||
**Admin API**: Implement `caddy.AdminRouter` for custom endpoints.
|
||
|
||
**Context**: Use `caddy.Context` for accessing other apps/modules and logging—don't store contexts in structs.
|
||
|
||
## Architecture
|
||
|
||
Caddy is built around a **module system** where everything is a module registered via `caddy.RegisterModule()`:
|
||
|
||
- **Apps** (`caddy.App`): Top-level modules like `http`, `tls`, `pki` that Caddy loads and runs
|
||
- **Modules** (`caddy.Module`): Extensible components with namespaced IDs (e.g., `http.handlers.file_server`)
|
||
- **Configuration**: Native JSON with adapters (Caddyfile → JSON via `caddyconfig/httpcaddyfile`)
|
||
|
||
| Directory | Purpose |
|
||
|-----------|---------|
|
||
| `modules/` | All standard modules (HTTP, TLS, PKI, etc.) |
|
||
| `modules/standard/imports.go` | Standard module registry |
|
||
| `caddyconfig/httpcaddyfile/` | Caddyfile → JSON adapter for HTTP |
|
||
| `caddytest/` | Test utilities and integration tests |
|
||
| `cmd/caddy/` | CLI entry point with module imports |
|
||
|
||
### Critical Packages
|
||
|
||
`caddyhttp` and `caddytls` require **extra scrutiny** in code review—these are security-critical.
|
||
|
||
## Quality Gates
|
||
|
||
|
||
**All required before PR is merge-ready:**
|
||
|
||
| Gate | Command | Notes |
|
||
|------|---------|-------|
|
||
| Tests pass | `go test -race -short ./...` | Race detection enabled |
|
||
| Lint clean | `golangci-lint run --timeout 10m` | No warnings in changed files |
|
||
| Builds | `go build ./...` | Must compile |
|
||
| Benchmarks | `go test -bench=. -benchmem` | Required for optimizations |
|
||
|
||
CI runs tests on **Linux, macOS, and Windows**—ensure cross-platform compatibility.
|
||
|
||
### Build & Test
|
||
|
||
```bash
|
||
# Build
|
||
cd cmd/caddy && go build
|
||
|
||
# Tests with race detection (matches CI)
|
||
go test -race -short ./...
|
||
|
||
# Integration tests
|
||
go test ./caddytest/integration/...
|
||
|
||
# Lint (matches CI)
|
||
golangci-lint run --timeout 10m
|
||
```
|
||
|
||
## Testing Conventions
|
||
|
||
**Table-driven tests** (preferred pattern):
|
||
```go
|
||
func TestFeature(t *testing.T) {
|
||
for i, tc := range []struct {
|
||
input string
|
||
expected string
|
||
wantErr bool
|
||
}{
|
||
{input: "valid", expected: "result", wantErr: false},
|
||
{input: "invalid", expected: "", wantErr: true},
|
||
} {
|
||
actual, err := Function(tc.input)
|
||
if tc.wantErr && err == nil {
|
||
t.Errorf("Test %d: expected error but got none", i)
|
||
}
|
||
if !tc.wantErr && err != nil {
|
||
t.Errorf("Test %d: unexpected error: %v", i, err)
|
||
}
|
||
if actual != tc.expected {
|
||
t.Errorf("Test %d: expected %q, got %q", i, tc.expected, actual)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**Integration tests** use `caddytest.Tester`:
|
||
```go
|
||
func TestHTTPFeature(t *testing.T) {
|
||
tester := caddytest.NewTester(t)
|
||
tester.InitServer(`
|
||
{
|
||
admin localhost:2999
|
||
http_port 9080
|
||
}
|
||
localhost:9080 {
|
||
respond "hello"
|
||
}`, "caddyfile")
|
||
|
||
tester.AssertGetResponse("http://localhost:9080/", 200, "hello")
|
||
}
|
||
```
|
||
|
||
Use non-standard ports (9080, 9443, 2999) to avoid conflicts with running servers.
|
||
|
||
## AI Contribution Policy
|
||
|
||
Per [CONTRIBUTING.md](.github/CONTRIBUTING.md), AI-assisted code **MUST** be:
|
||
|
||
1. **Disclosed** — Tell reviewers when code was AI-generated or AI-assisted, mentioning which agent/model is used
|
||
2. **Fully comprehended** — You must be able to explain every line
|
||
3. **Tested** — Automated tests when feasible, thorough manual tests otherwise
|
||
4. **Licensed** — Verify AI output doesn't include plagiarized or incompatibly-licensed code
|
||
5. **Contributor License Agreement (CLA)** — The CLA must be signed by the human user
|
||
|
||
**Do NOT submit code you cannot fully explain.** Contributors are responsible for their submissions.
|
||
|
||
## Dependencies
|
||
|
||
- **Avoid new dependencies** — Justify any additions; tiny deps can be inlined
|
||
- **No exported dependency types** — Caddy must not export types defined by external packages
|
||
- Use Go modules; check with `go mod tidy`
|
||
|
||
## Further Reading
|
||
|
||
- [CONTRIBUTING.md](.github/CONTRIBUTING.md) — Full PR process and expectations
|
||
- [Extending Caddy](https://caddyserver.com/docs/extending-caddy) — Module development guide
|
||
- [JSON Config](https://caddyserver.com/docs/json/) — Native configuration reference
|
||
- [Caddyfile](https://caddyserver.com/docs/caddyfile/concepts) — Caddyfile syntax guide
|