Compare commits

...

4 commits
v0.0.3 ... main

Author SHA1 Message Date
Snider
84dff4f708 chore: bump forge.lthn.ai dep versions to latest tags
All checks were successful
Security Scan / security (push) Successful in 19s
Test / test (push) Successful in 4m42s
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-26 05:34:22 +00:00
Snider
280f920410 fix: skip timeout tests under race detector (upstream gin-contrib/timeout data race)
All checks were successful
Security Scan / security (push) Successful in 13s
Test / test (push) Successful in 3m54s
gin-contrib/timeout@v1.1.0 has a known data race on Context.index between
the timeout goroutine and the handler goroutine. Use build-tag conditional
compilation to detect -race and skip affected tests.

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 08:05:16 +00:00
Snider
d89e859323 chore: bump forge.lthn.ai dep versions to latest tags
Some checks failed
Security Scan / security (push) Successful in 13s
Test / test (push) Failing after 4m7s
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 06:49:38 +00:00
Snider
0dcafa988f chore: add Go repo norms (badges, contributing, lint, taskfile, editorconfig)
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-23 06:45:32 +00:00
10 changed files with 153 additions and 10 deletions

12
.editorconfig Normal file
View file

@ -0,0 +1,12 @@
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.{md,yml,yaml,json,txt}]
indent_style = space
indent_size = 2

22
.golangci.yml Normal file
View file

@ -0,0 +1,22 @@
run:
timeout: 5m
go: "1.26"
linters:
enable:
- govet
- errcheck
- staticcheck
- unused
- gosimple
- ineffassign
- typecheck
- gocritic
- gofmt
disable:
- exhaustive
- wrapcheck
issues:
exclude-use-default: false
max-same-issues: 0

35
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,35 @@
# Contributing
Thank you for your interest in contributing!
## Requirements
- **Go Version**: 1.26 or higher is required.
- **Tools**: `golangci-lint` and `task` (Taskfile.dev) are recommended.
## Development Workflow
1. **Testing**: Ensure all tests pass before submitting changes.
```bash
go test ./...
```
2. **Code Style**: All code must follow standard Go formatting.
```bash
gofmt -w .
go vet ./...
```
3. **Linting**: We use `golangci-lint` to maintain code quality.
```bash
golangci-lint run ./...
```
## Commit Message Format
We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
- `feat`: A new feature
- `fix`: A bug fix
- `docs`: Documentation changes
- `refactor`: A code change that neither fixes a bug nor adds a feature
- `chore`: Changes to the build process or auxiliary tools and libraries
Example: `feat: add new endpoint for health check`
## License
By contributing to this project, you agree that your contributions will be licensed under the **European Union Public Licence (EUPL-1.2)**.

View file

@ -1,3 +1,7 @@
[![Go Reference](https://pkg.go.dev/badge/forge.lthn.ai/core/go-api.svg)](https://pkg.go.dev/forge.lthn.ai/core/go-api)
[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](LICENSE.md)
[![Go Version](https://img.shields.io/badge/Go-1.26-00ADD8?style=flat&logo=go)](go.mod)
# go-api # go-api
REST framework + OpenAPI SDK generation for the Lethean Go ecosystem. REST framework + OpenAPI SDK generation for the Lethean Go ecosystem.

46
Taskfile.yml Normal file
View file

@ -0,0 +1,46 @@
version: '3'
tasks:
test:
desc: Run all tests
cmds:
- go test ./...
lint:
desc: Run golangci-lint
cmds:
- golangci-lint run ./...
fmt:
desc: Format all Go files
cmds:
- gofmt -w .
vet:
desc: Run go vet
cmds:
- go vet ./...
build:
desc: Build all Go packages
cmds:
- go build ./...
cov:
desc: Run tests with coverage and open HTML report
cmds:
- go test -coverprofile=coverage.out ./...
- go tool cover -html=coverage.out
tidy:
desc: Tidy go.mod
cmds:
- go mod tidy
check:
desc: Run fmt, vet, lint, and test in sequence
cmds:
- task: fmt
- task: vet
- task: lint
- task: test

7
go.mod
View file

@ -3,7 +3,7 @@ module forge.lthn.ai/core/go-api
go 1.26.0 go 1.26.0
require ( require (
forge.lthn.ai/core/cli v0.0.3 forge.lthn.ai/core/cli v0.1.0
github.com/99designs/gqlgen v0.17.87 github.com/99designs/gqlgen v0.17.87
github.com/andybalholm/brotli v1.2.0 github.com/andybalholm/brotli v1.2.0
github.com/casbin/casbin/v2 v2.135.0 github.com/casbin/casbin/v2 v2.135.0
@ -35,8 +35,8 @@ require (
) )
require ( require (
forge.lthn.ai/core/go v0.0.1 // indirect forge.lthn.ai/core/go v0.1.0 // indirect
forge.lthn.ai/core/go-crypt v0.0.2 // indirect forge.lthn.ai/core/go-crypt v0.1.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/agnivade/levenshtein v1.2.1 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect
@ -113,7 +113,6 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/arch v0.23.0 // indirect golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect

12
go.sum
View file

@ -1,9 +1,9 @@
forge.lthn.ai/core/cli v0.0.3 h1:Qd/ACf8as4cwWoqAzkLIPF86NhXzfkkt1t/6xmgznVw= forge.lthn.ai/core/cli v0.1.0 h1:2XRiEMVzUElnQlZnHYDyfKIKQVPcCzGuYHlnz55GjsM=
forge.lthn.ai/core/cli v0.0.3/go.mod h1:xa3Nqw3sUtYYJ1k+1jYul18tgs6sBevCUsGsHJI1hHA= forge.lthn.ai/core/cli v0.1.0/go.mod h1:mZ7dzccfzo0BP2dE7Mwuw9dXuIowiEd1G5ZGMoLuxVc=
forge.lthn.ai/core/go v0.0.1 h1:ubk4nmkA3treOUNgPS28wKd1jB6cUlEQUV7jDdGa3zM= forge.lthn.ai/core/go v0.1.0 h1:Ow/1NTajrrNPO0zgkskEyEGdx4SKpiNqTaqM0txNOYI=
forge.lthn.ai/core/go v0.0.1/go.mod h1:59YsnuMaAGQUxIhX68oK2/HnhQJEPWL1iEZhDTrNCbY= forge.lthn.ai/core/go v0.1.0/go.mod h1:lwi0tccAlg5j3k6CfoNJEueBc5l9mUeSBX/x6uY8ZbQ=
forge.lthn.ai/core/go-crypt v0.0.2 h1:m8mCIrmC0tserVx9bfmrB8h4GtgAgQeedBPeNBvCxx0= forge.lthn.ai/core/go-crypt v0.1.0 h1:92gwdQi7iAwktpvZhL/8Cu+QS6xKCtGP4FJfyInPGnw=
forge.lthn.ai/core/go-crypt v0.0.2/go.mod h1:+JoZ4mwjzTklysI/DI7f6/0iocdJoJG2ZF/nQy6HTuI= forge.lthn.ai/core/go-crypt v0.1.0/go.mod h1:zVAgx6ZiGtC+dbX4R/VKvEPqsEqjyuLl4gQZH9SXBUw=
github.com/99designs/gqlgen v0.17.87 h1:pSnCIMhBQezAE8bc1GNmfdLXFmnWtWl1GRDFEE/nHP8= github.com/99designs/gqlgen v0.17.87 h1:pSnCIMhBQezAE8bc1GNmfdLXFmnWtWl1GRDFEE/nHP8=
github.com/99designs/gqlgen v0.17.87/go.mod h1:fK05f1RqSNfQpd4CfW5qk/810Tqi4/56Wf6Nem0khAg= github.com/99designs/gqlgen v0.17.87/go.mod h1:fK05f1RqSNfQpd4CfW5qk/810Tqi4/56Wf6Nem0khAg=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=

6
norace_test.go Normal file
View file

@ -0,0 +1,6 @@
// SPDX-License-Identifier: EUPL-1.2
//go:build !race
package api_test
const raceDetectorEnabled = false

6
race_test.go Normal file
View file

@ -0,0 +1,6 @@
// SPDX-License-Identifier: EUPL-1.2
//go:build race
package api_test
const raceDetectorEnabled = true

View file

@ -14,6 +14,16 @@ import (
api "forge.lthn.ai/core/go-api" api "forge.lthn.ai/core/go-api"
) )
// skipIfRaceDetector skips the test when the race detector is enabled.
// gin-contrib/timeout@v1.1.0 has a known data race on Context.index
// between the timeout goroutine and the handler goroutine.
func skipIfRaceDetector(t *testing.T) {
t.Helper()
if raceDetectorEnabled {
t.Skip("skipping: gin-contrib/timeout has known data race (upstream bug)")
}
}
// ── Helpers ───────────────────────────────────────────────────────────── // ── Helpers ─────────────────────────────────────────────────────────────
// slowGroup provides a route that sleeps longer than the test timeout. // slowGroup provides a route that sleeps longer than the test timeout.
@ -57,6 +67,7 @@ func TestWithTimeout_Good_FastRequestSucceeds(t *testing.T) {
} }
func TestWithTimeout_Good_SlowRequestTimesOut(t *testing.T) { func TestWithTimeout_Good_SlowRequestTimesOut(t *testing.T) {
skipIfRaceDetector(t)
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
e, _ := api.New(api.WithTimeout(50 * time.Millisecond)) e, _ := api.New(api.WithTimeout(50 * time.Millisecond))
e.Register(&slowGroup{}) e.Register(&slowGroup{})
@ -72,6 +83,7 @@ func TestWithTimeout_Good_SlowRequestTimesOut(t *testing.T) {
} }
func TestWithTimeout_Good_TimeoutResponseEnvelope(t *testing.T) { func TestWithTimeout_Good_TimeoutResponseEnvelope(t *testing.T) {
skipIfRaceDetector(t)
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
e, _ := api.New(api.WithTimeout(50 * time.Millisecond)) e, _ := api.New(api.WithTimeout(50 * time.Millisecond))
e.Register(&slowGroup{}) e.Register(&slowGroup{})
@ -136,6 +148,7 @@ func TestWithTimeout_Good_CombinesWithOtherMiddleware(t *testing.T) {
} }
func TestWithTimeout_Ugly_ZeroDurationDoesNotPanic(t *testing.T) { func TestWithTimeout_Ugly_ZeroDurationDoesNotPanic(t *testing.T) {
skipIfRaceDetector(t)
gin.SetMode(gin.TestMode) gin.SetMode(gin.TestMode)
defer func() { defer func() {