feat(httpclient): initial implementation — HTTP client with retry and circuit breaker (v1.0.0)
Introduces code.nochebuena.dev/einherjar/httpclient — the outbound HTTP client starter for the Einherjar framework. Absorbs the httpclient package from micro-lib, replacing fmt.Errorf wrapping with core/xerrors and adding generic JSON helpers. Interfaces: - Provider — Do(req *http.Request) (*http.Response, error) Implementation: - New(logger, cfg) Provider — configures net.Dialer + retry + circuit breaker - NewWithDefaults(logger) Provider — convenience constructor with default config - Retry: avast/retry-go; configurable MaxRetries and RetryDelay; retries on network errors and 5xx responses; logs each retry attempt at Warn level - Circuit breaker: sony/gobreaker; opens after CBThreshold consecutive failures within CBTimeout window; returns ErrUnavailable when open - DoJSON[T](ctx, client, req) (*T, error) — executes request, decodes JSON body - DoJSONRequest[Req, Resp](ctx, client, method, rawURL, body) (*Resp, error) — marshals body, builds request, executes, decodes response - MapStatusToError(code, msg) error — maps HTTP status codes to xerrors values Config (EINHERJAR_HTTP_* env vars): Name(http), Timeout(30s), DialTimeout(5s), MaxRetries(3), RetryDelay(1s), CBThreshold(10), CBTimeout(1m) - identifiable.go: package-level Module variable (observability.Identifiable) for version identification — httpclient is a stateless provider; not registered with the launcher
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