feat(web): initial implementation — server, mw, httputil, health (v1.0.0)
Introduces code.nochebuena.dev/einherjar/web — the HTTP transport layer of the
Einherjar framework. Absorbs httpserver, httpmw, and httputil from micro-lib,
replacing gorilla/mux with chi, adopting SecurityBag-native middleware, and
centralizing error handling through a single httputil.Error function.
server:
- Server interface — embeds lifecycle.Component and chi.Router
- Config struct (EINHERJAR_SERVER_* env vars); DefaultConfig
- New(logger, cfg, opts...) Server; WithMiddleware option
- Binds TCP synchronously in OnStart; logs "server: listening" on success
- Graceful shutdown within ShutdownTimeout on OnStop
mw:
- Recover — catches panics, returns 500, logs at Error
- RequestID — injects UUID v7 (UUID v4 fallback) into context and X-Request-ID header
- RequestLogger — structured access log per request
- CORS / CORSAllowAll — chi-based, applied only when origins non-empty
- IPRateLimit / UserRateLimit — pluggable RateLimiterStore interface
- InMemoryRateLimiterStore — token-bucket backed by golang.org/x/time/rate;
background goroutine evicts stale entries every 5 minutes
- StatusRecorder — wraps ResponseWriter to capture HTTP status code
httputil:
- Handle[Req, Res] / HandleNoBody[Res] / HandleEmpty[Req] — generic handler adapters
- Error(logger, w, r, err) — derives log level from status (≥500→Error, 4xx→Warn,
499→Info); writes standardized JSON body; logz enriches *xerrors.Err automatically
- JSON(w, status, v) / NoContent(w) — response helpers
- HandlerFunc adapter type
health:
- NewHandler / NewHandlerWithConfig — runs all Checkable checks concurrently;
returns JSON {status, components} with per-component latency and error
- Config struct (EINHERJAR_HEALTH_CHECK_TIMEOUT, default 5s)
Root factory:
- web.New(logger, cfg...) Server — composes Recover+RequestID+RequestLogger+CORS
in outermost-first order; CORS applied only when AllowedOrigins non-empty
- server.Server interface and web/server/identifiable.go: embeds observability.Identifiable;
ModulePath and ModuleVersion read via runtime/debug.ReadBuildInfo() — prints in launcher banner
This commit is contained in:
247
CONTRIBUTING.md
Normal file
247
CONTRIBUTING.md
Normal file
@@ -0,0 +1,247 @@
|
||||
# Contributing to Einherjar
|
||||
|
||||
Thank you for your interest in contributing to Einherjar. This document explains everything you need to know before sending your first Pull Request.
|
||||
|
||||
Einherjar is developed and maintained by **NOCHEBUENADEV**, the trade name of its founder operating as a *Persona Física con Actividad Empresarial* (PFAE) under Mexican law. Contributions are welcome and valued — but they are accepted under the terms described here, so please read this document fully before you begin.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Before You Start](#1-before-you-start)
|
||||
2. [Legal: CLA and Copyright](#2-legal-cla-and-copyright)
|
||||
3. [Development Setup](#3-development-setup)
|
||||
4. [Code Standards](#4-code-standards)
|
||||
5. [Commit Messages](#5-commit-messages)
|
||||
6. [Submitting a Pull Request](#6-submitting-a-pull-request)
|
||||
7. [Reporting Bugs](#7-reporting-bugs)
|
||||
8. [Requesting Features](#8-requesting-features)
|
||||
9. [What Gets Accepted](#9-what-gets-accepted)
|
||||
|
||||
---
|
||||
|
||||
## 1. Before You Start
|
||||
|
||||
**Open an issue first for anything non-trivial.**
|
||||
|
||||
Before you write a line of code, open an issue describing what you want to change and why. This protects your time: a change that seems straightforward may conflict with a planned refactor, an architectural decision, or the project's direction. Getting alignment before coding means your PR will not be rejected for reasons unrelated to its quality.
|
||||
|
||||
Exceptions where you can skip the issue:
|
||||
- Typo or documentation-only fix
|
||||
- Test coverage improvement for existing behavior
|
||||
- Trivially obvious bug with a clear, contained fix
|
||||
|
||||
**Do not submit a PR that changes the public API of any module without prior discussion.** Every Einherjar module has a stability contract. Breaking changes require a major version bump and coordinated updates across dependent modules.
|
||||
|
||||
---
|
||||
|
||||
## 2. Legal: CLA and Copyright
|
||||
|
||||
### Contributor License Agreement
|
||||
|
||||
Before your first PR can be merged, you must sign the Contributor License Agreement by **posting a specific comment** on your Pull Request. The required comment text and full instructions are in [CLA.md](CLA.md).
|
||||
|
||||
> Checkboxes in PR descriptions are not used for CLA consent — they can be silently toggled by anyone. A comment is a timestamped, author-attributed record.
|
||||
|
||||
### Copyright
|
||||
|
||||
All original code in Einherjar is copyright **NOCHEBUENADEV**. NOCHEBUENADEV is the registered trade name of its founder, a natural person operating under the Mexican *Persona Física con Actividad Empresarial* (PFAE) regime.
|
||||
|
||||
When you contribute, you retain ownership of what you wrote. By signing the CLA you grant NOCHEBUENADEV a perpetual, irrevocable, worldwide license to use, modify, sublicense, and redistribute your Contribution — including the right to relicense it. See [CLA.md](CLA.md) for the full terms.
|
||||
|
||||
### License
|
||||
|
||||
Einherjar is licensed under the **GNU Affero General Public License v3.0** (AGPL-3.0). Your Contributions will be distributed under the same license unless the Maintainers exercise their relicensing rights under the CLA.
|
||||
|
||||
---
|
||||
|
||||
## 3. Development Setup
|
||||
|
||||
Einherjar uses a Go workspace (`go.work`) that spans all modules. You do not need to `go get` anything — local replacements are wired automatically.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Go 1.26+
|
||||
- Git
|
||||
|
||||
### Clone and initialize
|
||||
|
||||
```bash
|
||||
git clone https://code.nochebuena.dev/einherjar/<module-name>
|
||||
cd <module-name>
|
||||
|
||||
# If working across multiple modules, clone the workspace root instead
|
||||
# and all modules will resolve from disk via go.work.
|
||||
```
|
||||
|
||||
### Verify your setup
|
||||
|
||||
```bash
|
||||
go build ./... # must compile clean
|
||||
go vet ./... # no warnings
|
||||
go test ./... # all tests pass
|
||||
gofmt -l . # no output (no unformatted files)
|
||||
```
|
||||
|
||||
All four commands must produce clean output before a PR will be reviewed.
|
||||
|
||||
---
|
||||
|
||||
## 4. Code Standards
|
||||
|
||||
These are non-negotiable. Every PR is checked against them.
|
||||
|
||||
### One exported type per file (CT-6)
|
||||
|
||||
Each non-test `.go` file may contain **at most one exported TypeSpec** (type, struct, or interface declaration). Unexported helpers, constants, and functions may coexist in the same file. `_test.go` files are exempt.
|
||||
|
||||
This rule exists to keep the codebase navigable: a developer who knows the type name can immediately predict the file name.
|
||||
|
||||
```
|
||||
provider.go ← type Provider interface { ... } ✓ one exported type
|
||||
component.go ← type Component interface { ... } ✓ one exported type
|
||||
new.go ← func New(...) + unexported impl ✓ zero exported types
|
||||
```
|
||||
|
||||
### Formatting
|
||||
|
||||
All code must be formatted with `gofmt`. No exceptions. If `gofmt -l .` produces output, the PR will not be merged.
|
||||
|
||||
Do not configure your editor to use `goimports` as a replacement — it may add import groups that diverge from the project style. Use `gofmt` + manual import management.
|
||||
|
||||
### Naming conventions
|
||||
|
||||
- Follow standard Go naming: `CamelCase` for exported, `camelCase` for unexported.
|
||||
- Interfaces that represent a capability are named with an agent noun: `Provider`, `Sender`, `Checkable`.
|
||||
- Interfaces that represent a full component are named `Component`.
|
||||
- Config structs are named `Config`. One config struct per module root.
|
||||
- Constructors are named `New` (main) or `NewXxx` (adapters and variants).
|
||||
|
||||
### Error handling
|
||||
|
||||
- Use `core/xerrors` for all errors returned from public API. Never return raw `errors.New` or `fmt.Errorf` from exported functions.
|
||||
- Error codes must map to the gRPC canonical set defined in `xerrors`. If you need a new code, open an issue first.
|
||||
- Do not swallow errors silently. Log at the appropriate level or return them.
|
||||
|
||||
### No comments unless necessary
|
||||
|
||||
Do not add comments that restate what the code already says. Only add a comment when the **why** is non-obvious: a hidden constraint, a subtle invariant, a workaround for a known upstream bug. If removing the comment would not confuse a future reader, do not write it.
|
||||
|
||||
### Dependencies
|
||||
|
||||
Do not add new external dependencies without opening an issue and getting explicit approval first. Einherjar modules are deliberately lean. Every new dependency increases the blast radius for downstream consumers.
|
||||
|
||||
---
|
||||
|
||||
## 5. Commit Messages
|
||||
|
||||
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
|
||||
|
||||
```
|
||||
<type>(<scope>): <short description>
|
||||
|
||||
[optional body]
|
||||
```
|
||||
|
||||
| Type | When to use |
|
||||
|---|---|
|
||||
| `feat` | New exported function, type, or behavior |
|
||||
| `fix` | Bug fix in existing behavior |
|
||||
| `docs` | Documentation only |
|
||||
| `test` | Tests only, no production code change |
|
||||
| `refactor` | Code restructure with no behavior change |
|
||||
| `chore` | Build system, CI, dependency updates |
|
||||
|
||||
**Scope** is the module name without the `einherjar/` prefix: `core`, `web`, `db-postgres`, `cache-valkey`, etc.
|
||||
|
||||
Examples:
|
||||
```
|
||||
feat(cache-valkey): add IncrWithTTL for atomic fixed-window counters
|
||||
fix(db-postgres): handle pgconn deadline exceeded as ErrDeadlineExceeded
|
||||
docs(web): document rate limiter fail-open behavior
|
||||
```
|
||||
|
||||
Keep the subject line under 72 characters. Write in the imperative mood ("add", "fix", "remove" — not "added", "fixes", "removed").
|
||||
|
||||
---
|
||||
|
||||
## 6. Submitting a Pull Request
|
||||
|
||||
1. **Open an issue first** (see §1) unless the change is trivial.
|
||||
2. Fork the repository and create a branch from `main`:
|
||||
```bash
|
||||
git checkout -b feat/your-feature-name
|
||||
```
|
||||
3. Make your changes following the standards in §4.
|
||||
4. Ensure all verification commands pass (§3).
|
||||
5. Open the PR against `main` using the provided PR template.
|
||||
6. **Post the CLA comment** on the PR before requesting review (see §2 and [CLA.md](CLA.md)).
|
||||
7. Respond to review feedback. Keep the review cycle short by addressing all comments before re-requesting review.
|
||||
|
||||
### Branch naming
|
||||
|
||||
| Prefix | Use for |
|
||||
|---|---|
|
||||
| `feat/` | New features |
|
||||
| `fix/` | Bug fixes |
|
||||
| `docs/` | Documentation changes |
|
||||
| `test/` | Test additions or improvements |
|
||||
| `refactor/` | Refactors without behavior change |
|
||||
|
||||
### PR size
|
||||
|
||||
Keep PRs focused. A PR that does one thing is easier to review, faster to merge, and safer to revert if needed. If your change naturally spans multiple concerns, split it into multiple PRs.
|
||||
|
||||
---
|
||||
|
||||
## 7. Reporting Bugs
|
||||
|
||||
Open an issue with the following information:
|
||||
|
||||
- **Module** affected (`einherjar/db-postgres`, `einherjar/web`, etc.)
|
||||
- **Go version** (`go version`)
|
||||
- **Minimal reproduction** — the smallest code snippet that demonstrates the problem
|
||||
- **Expected behavior** vs **actual behavior**
|
||||
- **Error output** if applicable (sanitize any credentials or sensitive data)
|
||||
|
||||
Do not open a PR to fix a bug without first opening an issue. The bug may be intentional behavior, already fixed on `main`, or caused by something outside the module.
|
||||
|
||||
---
|
||||
|
||||
## 8. Requesting Features
|
||||
|
||||
Open an issue with:
|
||||
|
||||
- **What** you want to add and **why** it belongs in the framework (not in application code)
|
||||
- **Which module** it affects, or whether it requires a new module
|
||||
- **API sketch** — what the interface, function signature, or config field would look like
|
||||
- **Alternative approaches** you considered
|
||||
|
||||
Feature requests that add a new external dependency, change a public interface, or cross module boundaries require longer discussion before approval.
|
||||
|
||||
---
|
||||
|
||||
## 9. What Gets Accepted
|
||||
|
||||
Einherjar is a **focused framework**. It covers a specific, well-defined set of infrastructure concerns. Contributions that fall outside that scope — however well-written — will not be merged.
|
||||
|
||||
What fits:
|
||||
- Bug fixes in existing behavior
|
||||
- Performance improvements with benchmarks
|
||||
- Missing error mappings for existing drivers
|
||||
- Documentation improvements and example corrections
|
||||
- Test coverage for untested edge cases
|
||||
|
||||
What does not fit without prior architectural agreement:
|
||||
- New modules (open an issue first)
|
||||
- New external dependencies
|
||||
- Changes to public interfaces in any module
|
||||
- Features that belong in application code rather than the framework
|
||||
|
||||
If you are unsure, open an issue and ask. It costs nothing and saves everyone time.
|
||||
|
||||
---
|
||||
|
||||
*Einherjar was built for those who come after. Contributions that hold to that standard — clear, documented, tested, designed for the developer who was never in the room — are always welcome.*
|
||||
|
||||
— **NOCHEBUENADEV**
|
||||
Reference in New Issue
Block a user