claudient 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +42 -0
- package/CONTEXT.md +58 -0
- package/README.md +165 -0
- package/agents/build-resolvers/de/python-resolver.md +64 -0
- package/agents/build-resolvers/de/typescript-resolver.md +65 -0
- package/agents/build-resolvers/es/python-resolver.md +64 -0
- package/agents/build-resolvers/es/typescript-resolver.md +65 -0
- package/agents/build-resolvers/fr/python-resolver.md +64 -0
- package/agents/build-resolvers/fr/typescript-resolver.md +65 -0
- package/agents/build-resolvers/nl/python-resolver.md +64 -0
- package/agents/build-resolvers/nl/typescript-resolver.md +65 -0
- package/agents/build-resolvers/python-resolver.md +62 -0
- package/agents/build-resolvers/typescript-resolver.md +63 -0
- package/agents/core/architect.md +64 -0
- package/agents/core/code-reviewer.md +78 -0
- package/agents/core/de/architect.md +66 -0
- package/agents/core/de/code-reviewer.md +80 -0
- package/agents/core/de/planner.md +63 -0
- package/agents/core/de/security-reviewer.md +93 -0
- package/agents/core/es/architect.md +66 -0
- package/agents/core/es/code-reviewer.md +80 -0
- package/agents/core/es/planner.md +63 -0
- package/agents/core/es/security-reviewer.md +93 -0
- package/agents/core/fr/architect.md +66 -0
- package/agents/core/fr/code-reviewer.md +80 -0
- package/agents/core/fr/planner.md +63 -0
- package/agents/core/fr/security-reviewer.md +93 -0
- package/agents/core/nl/architect.md +66 -0
- package/agents/core/nl/code-reviewer.md +80 -0
- package/agents/core/nl/planner.md +63 -0
- package/agents/core/nl/security-reviewer.md +93 -0
- package/agents/core/planner.md +61 -0
- package/agents/core/security-reviewer.md +91 -0
- package/guides/agent-orchestration.md +231 -0
- package/guides/de/agent-orchestration.md +174 -0
- package/guides/de/getting-started.md +164 -0
- package/guides/de/hooks-cookbook.md +160 -0
- package/guides/de/memory-management.md +153 -0
- package/guides/de/security.md +180 -0
- package/guides/de/skill-authoring.md +214 -0
- package/guides/de/token-optimization.md +156 -0
- package/guides/es/agent-orchestration.md +174 -0
- package/guides/es/getting-started.md +164 -0
- package/guides/es/hooks-cookbook.md +160 -0
- package/guides/es/memory-management.md +153 -0
- package/guides/es/security.md +180 -0
- package/guides/es/skill-authoring.md +214 -0
- package/guides/es/token-optimization.md +156 -0
- package/guides/fr/agent-orchestration.md +174 -0
- package/guides/fr/getting-started.md +164 -0
- package/guides/fr/hooks-cookbook.md +227 -0
- package/guides/fr/memory-management.md +169 -0
- package/guides/fr/security.md +180 -0
- package/guides/fr/skill-authoring.md +214 -0
- package/guides/fr/token-optimization.md +158 -0
- package/guides/getting-started.md +164 -0
- package/guides/hooks-cookbook.md +423 -0
- package/guides/memory-management.md +192 -0
- package/guides/nl/agent-orchestration.md +174 -0
- package/guides/nl/getting-started.md +164 -0
- package/guides/nl/hooks-cookbook.md +160 -0
- package/guides/nl/memory-management.md +153 -0
- package/guides/nl/security.md +180 -0
- package/guides/nl/skill-authoring.md +214 -0
- package/guides/nl/token-optimization.md +156 -0
- package/guides/security.md +229 -0
- package/guides/skill-authoring.md +226 -0
- package/guides/token-optimization.md +169 -0
- package/hooks/lifecycle/cost-tracker.md +49 -0
- package/hooks/lifecycle/cost-tracker.sh +59 -0
- package/hooks/lifecycle/pre-compact-save.md +56 -0
- package/hooks/lifecycle/pre-compact-save.sh +37 -0
- package/hooks/lifecycle/session-start.md +50 -0
- package/hooks/lifecycle/session-start.sh +47 -0
- package/hooks/post-tool-use/audit-log.md +53 -0
- package/hooks/post-tool-use/audit-log.sh +53 -0
- package/hooks/post-tool-use/prettier.md +53 -0
- package/hooks/post-tool-use/prettier.sh +49 -0
- package/hooks/pre-tool-use/block-dangerous.md +48 -0
- package/hooks/pre-tool-use/block-dangerous.sh +76 -0
- package/hooks/pre-tool-use/git-push-confirm.md +46 -0
- package/hooks/pre-tool-use/git-push-confirm.sh +36 -0
- package/mcp/configs/github.json +11 -0
- package/mcp/configs/postgres.json +11 -0
- package/mcp/de/recommended-servers.md +170 -0
- package/mcp/es/recommended-servers.md +170 -0
- package/mcp/fr/recommended-servers.md +170 -0
- package/mcp/nl/recommended-servers.md +170 -0
- package/mcp/recommended-servers.md +168 -0
- package/package.json +45 -0
- package/prompts/project-starters/de/fastapi-project.md +62 -0
- package/prompts/project-starters/de/nextjs-project.md +82 -0
- package/prompts/project-starters/es/fastapi-project.md +62 -0
- package/prompts/project-starters/es/nextjs-project.md +82 -0
- package/prompts/project-starters/fastapi-project.md +60 -0
- package/prompts/project-starters/fr/fastapi-project.md +62 -0
- package/prompts/project-starters/fr/nextjs-project.md +82 -0
- package/prompts/project-starters/nextjs-project.md +80 -0
- package/prompts/project-starters/nl/fastapi-project.md +62 -0
- package/prompts/project-starters/nl/nextjs-project.md +82 -0
- package/prompts/system-prompts/ai-product.md +80 -0
- package/prompts/system-prompts/data-pipeline.md +76 -0
- package/prompts/system-prompts/de/ai-product.md +82 -0
- package/prompts/system-prompts/de/data-pipeline.md +78 -0
- package/prompts/system-prompts/de/saas-backend.md +71 -0
- package/prompts/system-prompts/es/ai-product.md +82 -0
- package/prompts/system-prompts/es/data-pipeline.md +78 -0
- package/prompts/system-prompts/es/saas-backend.md +71 -0
- package/prompts/system-prompts/fr/ai-product.md +82 -0
- package/prompts/system-prompts/fr/data-pipeline.md +78 -0
- package/prompts/system-prompts/fr/saas-backend.md +71 -0
- package/prompts/system-prompts/nl/ai-product.md +82 -0
- package/prompts/system-prompts/nl/data-pipeline.md +78 -0
- package/prompts/system-prompts/nl/saas-backend.md +71 -0
- package/prompts/system-prompts/saas-backend.md +69 -0
- package/prompts/task-specific/changelog.md +81 -0
- package/prompts/task-specific/de/changelog.md +83 -0
- package/prompts/task-specific/de/debugging.md +78 -0
- package/prompts/task-specific/de/pr-description.md +69 -0
- package/prompts/task-specific/debugging.md +76 -0
- package/prompts/task-specific/es/changelog.md +83 -0
- package/prompts/task-specific/es/debugging.md +78 -0
- package/prompts/task-specific/es/pr-description.md +69 -0
- package/prompts/task-specific/fr/changelog.md +83 -0
- package/prompts/task-specific/fr/debugging.md +78 -0
- package/prompts/task-specific/fr/pr-description.md +69 -0
- package/prompts/task-specific/nl/changelog.md +83 -0
- package/prompts/task-specific/nl/debugging.md +78 -0
- package/prompts/task-specific/nl/pr-description.md +69 -0
- package/prompts/task-specific/pr-description.md +67 -0
- package/rules/common/coding-style.md +45 -0
- package/rules/common/de/coding-style.md +47 -0
- package/rules/common/de/git.md +48 -0
- package/rules/common/de/performance.md +40 -0
- package/rules/common/de/security.md +45 -0
- package/rules/common/de/testing.md +45 -0
- package/rules/common/es/coding-style.md +47 -0
- package/rules/common/es/git.md +48 -0
- package/rules/common/es/performance.md +40 -0
- package/rules/common/es/security.md +45 -0
- package/rules/common/es/testing.md +45 -0
- package/rules/common/fr/coding-style.md +47 -0
- package/rules/common/fr/git.md +48 -0
- package/rules/common/fr/performance.md +40 -0
- package/rules/common/fr/security.md +45 -0
- package/rules/common/fr/testing.md +45 -0
- package/rules/common/git.md +46 -0
- package/rules/common/nl/coding-style.md +47 -0
- package/rules/common/nl/git.md +48 -0
- package/rules/common/nl/performance.md +40 -0
- package/rules/common/nl/security.md +45 -0
- package/rules/common/nl/testing.md +45 -0
- package/rules/common/performance.md +38 -0
- package/rules/common/security.md +43 -0
- package/rules/common/testing.md +43 -0
- package/rules/language-specific/de/go.md +48 -0
- package/rules/language-specific/de/python.md +38 -0
- package/rules/language-specific/de/typescript.md +51 -0
- package/rules/language-specific/es/go.md +48 -0
- package/rules/language-specific/es/python.md +38 -0
- package/rules/language-specific/es/typescript.md +51 -0
- package/rules/language-specific/fr/go.md +48 -0
- package/rules/language-specific/fr/python.md +38 -0
- package/rules/language-specific/fr/typescript.md +51 -0
- package/rules/language-specific/go.md +46 -0
- package/rules/language-specific/nl/go.md +48 -0
- package/rules/language-specific/nl/python.md +38 -0
- package/rules/language-specific/nl/typescript.md +51 -0
- package/rules/language-specific/python.md +36 -0
- package/rules/language-specific/typescript.md +49 -0
- package/scripts/cli.js +161 -0
- package/scripts/link-skills.sh +35 -0
- package/scripts/list-skills.sh +34 -0
- package/skills/ai-engineering/agent-construction.md +285 -0
- package/skills/ai-engineering/claude-api.md +248 -0
- package/skills/ai-engineering/de/agent-construction.md +287 -0
- package/skills/ai-engineering/de/claude-api.md +250 -0
- package/skills/ai-engineering/es/agent-construction.md +287 -0
- package/skills/ai-engineering/es/claude-api.md +250 -0
- package/skills/ai-engineering/fr/agent-construction.md +287 -0
- package/skills/ai-engineering/fr/claude-api.md +250 -0
- package/skills/ai-engineering/nl/agent-construction.md +287 -0
- package/skills/ai-engineering/nl/claude-api.md +250 -0
- package/skills/backend/dotnet/csharp.md +304 -0
- package/skills/backend/dotnet/de/csharp.md +306 -0
- package/skills/backend/dotnet/es/csharp.md +306 -0
- package/skills/backend/dotnet/fr/csharp.md +306 -0
- package/skills/backend/dotnet/nl/csharp.md +306 -0
- package/skills/backend/go/de/go.md +307 -0
- package/skills/backend/go/es/go.md +307 -0
- package/skills/backend/go/fr/go.md +307 -0
- package/skills/backend/go/go.md +305 -0
- package/skills/backend/go/nl/go.md +307 -0
- package/skills/backend/nodejs/de/nestjs.md +274 -0
- package/skills/backend/nodejs/de/nextjs.md +222 -0
- package/skills/backend/nodejs/es/nestjs.md +274 -0
- package/skills/backend/nodejs/es/nextjs.md +222 -0
- package/skills/backend/nodejs/fr/nestjs.md +274 -0
- package/skills/backend/nodejs/fr/nextjs.md +222 -0
- package/skills/backend/nodejs/nestjs.md +272 -0
- package/skills/backend/nodejs/nextjs.md +220 -0
- package/skills/backend/nodejs/nl/nestjs.md +274 -0
- package/skills/backend/nodejs/nl/nextjs.md +222 -0
- package/skills/backend/python/de/django.md +285 -0
- package/skills/backend/python/de/fastapi.md +244 -0
- package/skills/backend/python/django.md +283 -0
- package/skills/backend/python/es/django.md +285 -0
- package/skills/backend/python/es/fastapi.md +244 -0
- package/skills/backend/python/fastapi.md +242 -0
- package/skills/backend/python/fr/django.md +285 -0
- package/skills/backend/python/fr/fastapi.md +244 -0
- package/skills/backend/python/nl/django.md +285 -0
- package/skills/backend/python/nl/fastapi.md +244 -0
- package/skills/data-ml/dbt-data-pipelines.md +155 -0
- package/skills/data-ml/de/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/de/pandas-polars.md +147 -0
- package/skills/data-ml/de/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/es/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/es/pandas-polars.md +147 -0
- package/skills/data-ml/es/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/fr/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/fr/pandas-polars.md +147 -0
- package/skills/data-ml/fr/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/nl/dbt-data-pipelines.md +157 -0
- package/skills/data-ml/nl/pandas-polars.md +147 -0
- package/skills/data-ml/nl/pytorch-tensorflow.md +171 -0
- package/skills/data-ml/pandas-polars.md +145 -0
- package/skills/data-ml/pytorch-tensorflow.md +169 -0
- package/skills/database/de/graphql.md +181 -0
- package/skills/database/es/graphql.md +181 -0
- package/skills/database/fr/graphql.md +181 -0
- package/skills/database/graphql.md +179 -0
- package/skills/database/nl/graphql.md +181 -0
- package/skills/devops-infra/de/docker.md +133 -0
- package/skills/devops-infra/de/github-actions.md +179 -0
- package/skills/devops-infra/de/kubernetes.md +129 -0
- package/skills/devops-infra/de/terraform.md +130 -0
- package/skills/devops-infra/docker.md +131 -0
- package/skills/devops-infra/es/docker.md +133 -0
- package/skills/devops-infra/es/github-actions.md +179 -0
- package/skills/devops-infra/es/kubernetes.md +129 -0
- package/skills/devops-infra/es/terraform.md +130 -0
- package/skills/devops-infra/fr/docker.md +133 -0
- package/skills/devops-infra/fr/github-actions.md +179 -0
- package/skills/devops-infra/fr/kubernetes.md +129 -0
- package/skills/devops-infra/fr/terraform.md +130 -0
- package/skills/devops-infra/github-actions.md +177 -0
- package/skills/devops-infra/kubernetes.md +127 -0
- package/skills/devops-infra/nl/docker.md +133 -0
- package/skills/devops-infra/nl/github-actions.md +179 -0
- package/skills/devops-infra/nl/kubernetes.md +129 -0
- package/skills/devops-infra/nl/terraform.md +130 -0
- package/skills/devops-infra/terraform.md +128 -0
- package/skills/finance-payments/de/stripe.md +187 -0
- package/skills/finance-payments/es/stripe.md +187 -0
- package/skills/finance-payments/fr/stripe.md +187 -0
- package/skills/finance-payments/nl/stripe.md +187 -0
- package/skills/finance-payments/stripe.md +185 -0
- package/workflows/code-review.md +151 -0
- package/workflows/de/code-review.md +153 -0
- package/workflows/de/debugging-session.md +146 -0
- package/workflows/de/feature-development.md +155 -0
- package/workflows/de/new-project-bootstrap.md +175 -0
- package/workflows/de/refactor-safely.md +150 -0
- package/workflows/debugging-session.md +144 -0
- package/workflows/es/code-review.md +153 -0
- package/workflows/es/debugging-session.md +146 -0
- package/workflows/es/feature-development.md +155 -0
- package/workflows/es/new-project-bootstrap.md +175 -0
- package/workflows/es/refactor-safely.md +150 -0
- package/workflows/feature-development.md +153 -0
- package/workflows/fr/code-review.md +153 -0
- package/workflows/fr/debugging-session.md +146 -0
- package/workflows/fr/feature-development.md +155 -0
- package/workflows/fr/new-project-bootstrap.md +175 -0
- package/workflows/fr/refactor-safely.md +150 -0
- package/workflows/new-project-bootstrap.md +173 -0
- package/workflows/nl/code-review.md +153 -0
- package/workflows/nl/debugging-session.md +146 -0
- package/workflows/nl/feature-development.md +155 -0
- package/workflows/nl/new-project-bootstrap.md +175 -0
- package/workflows/nl/refactor-safely.md +150 -0
- package/workflows/refactor-safely.md +148 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
> 🇫🇷 This is the French translation. [English version](../go.md).
|
|
2
|
+
|
|
3
|
+
# Compétence Go
|
|
4
|
+
|
|
5
|
+
## Quand activer
|
|
6
|
+
- Construire un service HTTP Go ou un outil CLI
|
|
7
|
+
- Structurer un projet Go avec `cmd/`, `internal/`, `pkg/`
|
|
8
|
+
- Rédiger des handlers HTTP avec `net/http`, chi, ou gin
|
|
9
|
+
- Implémenter des interfaces, de l'embedding et des patterns de composition
|
|
10
|
+
- Rédiger du code concurrent avec des goroutines, des channels et des primitives `sync`
|
|
11
|
+
- Gestion des erreurs, wrapping et erreurs sentinelles
|
|
12
|
+
- Rédiger des tests table-driven
|
|
13
|
+
|
|
14
|
+
## Quand NE PAS utiliser
|
|
15
|
+
- Services Node.js, Python ou autres langages — écosystèmes différents
|
|
16
|
+
- Services Protobuf/gRPC sans contexte de configuration gRPC préalable
|
|
17
|
+
- Tâches de scripting pur — shell ou Python sont de meilleurs choix
|
|
18
|
+
|
|
19
|
+
## Instructions
|
|
20
|
+
|
|
21
|
+
### Structure du projet
|
|
22
|
+
```
|
|
23
|
+
myapp/
|
|
24
|
+
├── cmd/
|
|
25
|
+
│ └── server/
|
|
26
|
+
│ └── main.go # Point d'entrée — minimal, câble et démarre seulement
|
|
27
|
+
├── internal/ # Packages privés — non importables de l'extérieur
|
|
28
|
+
│ ├── config/
|
|
29
|
+
│ ├── handler/
|
|
30
|
+
│ ├── service/
|
|
31
|
+
│ └── store/
|
|
32
|
+
├── pkg/ # Packages publics — importables par d'autres modules
|
|
33
|
+
│ └── apierr/
|
|
34
|
+
├── go.mod
|
|
35
|
+
└── go.sum
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### main.go — câblage uniquement
|
|
39
|
+
```go
|
|
40
|
+
// cmd/server/main.go
|
|
41
|
+
package main
|
|
42
|
+
|
|
43
|
+
import (
|
|
44
|
+
"context"
|
|
45
|
+
"log/slog"
|
|
46
|
+
"net/http"
|
|
47
|
+
"os"
|
|
48
|
+
"os/signal"
|
|
49
|
+
"syscall"
|
|
50
|
+
"time"
|
|
51
|
+
|
|
52
|
+
"myapp/internal/config"
|
|
53
|
+
"myapp/internal/handler"
|
|
54
|
+
"myapp/internal/store"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
func main() {
|
|
58
|
+
cfg := config.MustLoad()
|
|
59
|
+
db := store.MustConnect(cfg.DatabaseURL)
|
|
60
|
+
defer db.Close()
|
|
61
|
+
|
|
62
|
+
mux := handler.NewMux(db, cfg)
|
|
63
|
+
|
|
64
|
+
srv := &http.Server{
|
|
65
|
+
Addr: ":" + cfg.Port,
|
|
66
|
+
Handler: mux,
|
|
67
|
+
ReadTimeout: 10 * time.Second,
|
|
68
|
+
WriteTimeout: 30 * time.Second,
|
|
69
|
+
IdleTimeout: 60 * time.Second,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
go func() {
|
|
73
|
+
slog.Info("server starting", "addr", srv.Addr)
|
|
74
|
+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
|
75
|
+
slog.Error("server error", "err", err)
|
|
76
|
+
os.Exit(1)
|
|
77
|
+
}
|
|
78
|
+
}()
|
|
79
|
+
|
|
80
|
+
quit := make(chan os.Signal, 1)
|
|
81
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
82
|
+
<-quit
|
|
83
|
+
|
|
84
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
85
|
+
defer cancel()
|
|
86
|
+
srv.Shutdown(ctx)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Gestion des erreurs
|
|
91
|
+
```go
|
|
92
|
+
// Définir des erreurs sentinelles pour les conditions attendues
|
|
93
|
+
var (
|
|
94
|
+
ErrNotFound = errors.New("not found")
|
|
95
|
+
ErrDuplicate = errors.New("duplicate entry")
|
|
96
|
+
ErrForbidden = errors.New("forbidden")
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Envelopper les erreurs avec du contexte — utiliser %w pour préserver le désenveloppement
|
|
100
|
+
func (s *UserStore) GetByID(ctx context.Context, id string) (*User, error) {
|
|
101
|
+
var u User
|
|
102
|
+
err := s.db.QueryRowContext(ctx, `SELECT id, email FROM users WHERE id = $1`, id).
|
|
103
|
+
Scan(&u.ID, &u.Email)
|
|
104
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
105
|
+
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
|
|
106
|
+
}
|
|
107
|
+
if err != nil {
|
|
108
|
+
return nil, fmt.Errorf("user store get: %w", err)
|
|
109
|
+
}
|
|
110
|
+
return &u, nil
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// L'appelant vérifie le comportement, pas les chaînes de message
|
|
114
|
+
user, err := store.GetByID(ctx, id)
|
|
115
|
+
if errors.Is(err, ErrNotFound) {
|
|
116
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Handlers HTTP avec chi
|
|
122
|
+
```go
|
|
123
|
+
// internal/handler/users.go
|
|
124
|
+
type UserHandler struct {
|
|
125
|
+
svc UserService
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
func (h *UserHandler) Register(r chi.Router) {
|
|
129
|
+
r.Get("/users/{id}", h.GetUser)
|
|
130
|
+
r.Post("/users", h.CreateUser)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
134
|
+
id := chi.URLParam(r, "id")
|
|
135
|
+
user, err := h.svc.GetUser(r.Context(), id)
|
|
136
|
+
if err != nil {
|
|
137
|
+
renderError(w, err)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
renderJSON(w, http.StatusOK, user)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func renderJSON(w http.ResponseWriter, code int, v any) {
|
|
144
|
+
w.Header().Set("Content-Type", "application/json")
|
|
145
|
+
w.WriteHeader(code)
|
|
146
|
+
json.NewEncoder(w).Encode(v)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
func renderError(w http.ResponseWriter, err error) {
|
|
150
|
+
switch {
|
|
151
|
+
case errors.Is(err, ErrNotFound):
|
|
152
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
153
|
+
case errors.Is(err, ErrForbidden):
|
|
154
|
+
http.Error(w, "forbidden", http.StatusForbidden)
|
|
155
|
+
default:
|
|
156
|
+
slog.Error("internal error", "err", err)
|
|
157
|
+
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Interfaces
|
|
163
|
+
```go
|
|
164
|
+
// Définir les interfaces là où elles sont utilisées (côté consommateur), pas là où elles sont implémentées
|
|
165
|
+
// internal/handler/users.go
|
|
166
|
+
type UserService interface {
|
|
167
|
+
GetUser(ctx context.Context, id string) (*User, error)
|
|
168
|
+
CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Garder les interfaces petites — 1-3 méthodes préférées
|
|
172
|
+
// io.Reader, io.Writer sont les exemples canoniques
|
|
173
|
+
|
|
174
|
+
// Embedding pour la composition
|
|
175
|
+
type ReadWriter interface {
|
|
176
|
+
io.Reader
|
|
177
|
+
io.Writer
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Patterns de concurrence
|
|
182
|
+
```go
|
|
183
|
+
// Fan-out avec errgroup
|
|
184
|
+
import "golang.org/x/sync/errgroup"
|
|
185
|
+
|
|
186
|
+
func fetchAll(ctx context.Context, ids []string) ([]*User, error) {
|
|
187
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
188
|
+
results := make([]*User, len(ids))
|
|
189
|
+
|
|
190
|
+
for i, id := range ids {
|
|
191
|
+
i, id := i, id // capturer les variables de boucle
|
|
192
|
+
g.Go(func() error {
|
|
193
|
+
u, err := store.GetByID(ctx, id)
|
|
194
|
+
if err != nil {
|
|
195
|
+
return err
|
|
196
|
+
}
|
|
197
|
+
results[i] = u
|
|
198
|
+
return nil
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if err := g.Wait(); err != nil {
|
|
203
|
+
return nil, err
|
|
204
|
+
}
|
|
205
|
+
return results, nil
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Pool de workers
|
|
209
|
+
func processJobs(ctx context.Context, jobs <-chan Job) {
|
|
210
|
+
const workers = 10
|
|
211
|
+
var wg sync.WaitGroup
|
|
212
|
+
for range workers {
|
|
213
|
+
wg.Add(1)
|
|
214
|
+
go func() {
|
|
215
|
+
defer wg.Done()
|
|
216
|
+
for job := range jobs {
|
|
217
|
+
if err := process(ctx, job); err != nil {
|
|
218
|
+
slog.Error("job failed", "id", job.ID, "err", err)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}()
|
|
222
|
+
}
|
|
223
|
+
wg.Wait()
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Propagation du contexte
|
|
228
|
+
```go
|
|
229
|
+
// Toujours propaguer le contexte comme premier argument
|
|
230
|
+
func (s *Service) DoWork(ctx context.Context, id string) error {
|
|
231
|
+
// Passer ctx à tous les appels en aval
|
|
232
|
+
user, err := s.store.GetByID(ctx, id)
|
|
233
|
+
if err != nil {
|
|
234
|
+
return err
|
|
235
|
+
}
|
|
236
|
+
return s.notifier.Send(ctx, user.Email)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Stocker les valeurs de portée de requête dans le contexte uniquement pour les préoccupations transversales
|
|
240
|
+
// (IDs de traçage, informations d'auth) — jamais pour des paramètres de fonction optionnels
|
|
241
|
+
type contextKey string
|
|
242
|
+
const requestIDKey contextKey = "request_id"
|
|
243
|
+
|
|
244
|
+
func WithRequestID(ctx context.Context, id string) context.Context {
|
|
245
|
+
return context.WithValue(ctx, requestIDKey, id)
|
|
246
|
+
}
|
|
247
|
+
func GetRequestID(ctx context.Context) string {
|
|
248
|
+
id, _ := ctx.Value(requestIDKey).(string)
|
|
249
|
+
return id
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Tests table-driven
|
|
254
|
+
```go
|
|
255
|
+
func TestGetUser(t *testing.T) {
|
|
256
|
+
tests := []struct {
|
|
257
|
+
name string
|
|
258
|
+
id string
|
|
259
|
+
want *User
|
|
260
|
+
wantErr error
|
|
261
|
+
}{
|
|
262
|
+
{"found", "123", &User{ID: "123", Email: "a@b.com"}, nil},
|
|
263
|
+
{"not found", "999", nil, ErrNotFound},
|
|
264
|
+
{"empty id", "", nil, ErrNotFound},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for _, tt := range tests {
|
|
268
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
269
|
+
svc := newTestService(t)
|
|
270
|
+
got, err := svc.GetUser(context.Background(), tt.id)
|
|
271
|
+
if !errors.Is(err, tt.wantErr) {
|
|
272
|
+
t.Fatalf("err = %v, want %v", err, tt.wantErr)
|
|
273
|
+
}
|
|
274
|
+
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
275
|
+
t.Errorf("mismatch (-want +got):\n%s", diff)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Logging avec slog
|
|
283
|
+
```go
|
|
284
|
+
// Logging structuré — jamais fmt.Println dans le code de production
|
|
285
|
+
slog.Info("request completed", "method", r.Method, "path", r.URL.Path, "status", 200, "duration_ms", elapsed.Milliseconds())
|
|
286
|
+
slog.Error("db query failed", "query", "getUserByID", "err", err)
|
|
287
|
+
|
|
288
|
+
// Configurer le logger JSON dans main
|
|
289
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
290
|
+
Level: slog.LevelInfo,
|
|
291
|
+
}))
|
|
292
|
+
slog.SetDefault(logger)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Exemple
|
|
296
|
+
|
|
297
|
+
**Utilisateur :** Construire une API HTTP Go pour une liste de tâches : créer, lister et compléter des tâches. Backend PostgreSQL, router chi, logging structuré, arrêt gracieux.
|
|
298
|
+
|
|
299
|
+
**Sortie attendue :**
|
|
300
|
+
- `cmd/server/main.go` — câble le store, le handler, démarre le serveur avec arrêt gracieux
|
|
301
|
+
- `internal/store/task.go` — `TaskStore` avec méthodes `Create`, `List`, `Complete` utilisant `database/sql`
|
|
302
|
+
- `internal/handler/tasks.go` — `TaskHandler` avec `Register(r chi.Router)`, réponses JSON
|
|
303
|
+
- `internal/handler/tasks_test.go` — tests table-driven avec un mock store
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
> **Travaillez avec nous :** Claudient est soutenu par [Uitbreiden](https://uitbreiden.com/) — nous construisons des produits IA et des solutions B2B avec des communautés de développeurs. [uitbreiden.com](https://uitbreiden.com/)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# Go Skill
|
|
2
|
+
|
|
3
|
+
## When to activate
|
|
4
|
+
- Building a Go HTTP service or CLI tool
|
|
5
|
+
- Structuring a Go project with `cmd/`, `internal/`, `pkg/`
|
|
6
|
+
- Writing HTTP handlers with `net/http`, chi, or gin
|
|
7
|
+
- Implementing interfaces, embedding, and composition patterns
|
|
8
|
+
- Writing concurrent code with goroutines, channels, and `sync` primitives
|
|
9
|
+
- Error handling, wrapping, and sentinel errors
|
|
10
|
+
- Writing table-driven tests
|
|
11
|
+
|
|
12
|
+
## When NOT to use
|
|
13
|
+
- Node.js, Python, or other language services — different ecosystems
|
|
14
|
+
- Protobuf/gRPC services without prior gRPC setup context
|
|
15
|
+
- Pure scripting tasks — shell or Python are better fits
|
|
16
|
+
|
|
17
|
+
## Instructions
|
|
18
|
+
|
|
19
|
+
### Project layout
|
|
20
|
+
```
|
|
21
|
+
myapp/
|
|
22
|
+
├── cmd/
|
|
23
|
+
│ └── server/
|
|
24
|
+
│ └── main.go # Entry point — thin, just wires and starts
|
|
25
|
+
├── internal/ # Private packages — not importable externally
|
|
26
|
+
│ ├── config/
|
|
27
|
+
│ ├── handler/
|
|
28
|
+
│ ├── service/
|
|
29
|
+
│ └── store/
|
|
30
|
+
├── pkg/ # Public packages — importable by other modules
|
|
31
|
+
│ └── apierr/
|
|
32
|
+
├── go.mod
|
|
33
|
+
└── go.sum
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### main.go — wiring only
|
|
37
|
+
```go
|
|
38
|
+
// cmd/server/main.go
|
|
39
|
+
package main
|
|
40
|
+
|
|
41
|
+
import (
|
|
42
|
+
"context"
|
|
43
|
+
"log/slog"
|
|
44
|
+
"net/http"
|
|
45
|
+
"os"
|
|
46
|
+
"os/signal"
|
|
47
|
+
"syscall"
|
|
48
|
+
"time"
|
|
49
|
+
|
|
50
|
+
"myapp/internal/config"
|
|
51
|
+
"myapp/internal/handler"
|
|
52
|
+
"myapp/internal/store"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
func main() {
|
|
56
|
+
cfg := config.MustLoad()
|
|
57
|
+
db := store.MustConnect(cfg.DatabaseURL)
|
|
58
|
+
defer db.Close()
|
|
59
|
+
|
|
60
|
+
mux := handler.NewMux(db, cfg)
|
|
61
|
+
|
|
62
|
+
srv := &http.Server{
|
|
63
|
+
Addr: ":" + cfg.Port,
|
|
64
|
+
Handler: mux,
|
|
65
|
+
ReadTimeout: 10 * time.Second,
|
|
66
|
+
WriteTimeout: 30 * time.Second,
|
|
67
|
+
IdleTimeout: 60 * time.Second,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
go func() {
|
|
71
|
+
slog.Info("server starting", "addr", srv.Addr)
|
|
72
|
+
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
|
|
73
|
+
slog.Error("server error", "err", err)
|
|
74
|
+
os.Exit(1)
|
|
75
|
+
}
|
|
76
|
+
}()
|
|
77
|
+
|
|
78
|
+
quit := make(chan os.Signal, 1)
|
|
79
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
80
|
+
<-quit
|
|
81
|
+
|
|
82
|
+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
83
|
+
defer cancel()
|
|
84
|
+
srv.Shutdown(ctx)
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Error handling
|
|
89
|
+
```go
|
|
90
|
+
// Define sentinel errors for expected conditions
|
|
91
|
+
var (
|
|
92
|
+
ErrNotFound = errors.New("not found")
|
|
93
|
+
ErrDuplicate = errors.New("duplicate entry")
|
|
94
|
+
ErrForbidden = errors.New("forbidden")
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Wrap errors with context — use %w to preserve unwrapping
|
|
98
|
+
func (s *UserStore) GetByID(ctx context.Context, id string) (*User, error) {
|
|
99
|
+
var u User
|
|
100
|
+
err := s.db.QueryRowContext(ctx, `SELECT id, email FROM users WHERE id = $1`, id).
|
|
101
|
+
Scan(&u.ID, &u.Email)
|
|
102
|
+
if errors.Is(err, sql.ErrNoRows) {
|
|
103
|
+
return nil, fmt.Errorf("user %s: %w", id, ErrNotFound)
|
|
104
|
+
}
|
|
105
|
+
if err != nil {
|
|
106
|
+
return nil, fmt.Errorf("user store get: %w", err)
|
|
107
|
+
}
|
|
108
|
+
return &u, nil
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Caller checks behavior, not message strings
|
|
112
|
+
user, err := store.GetByID(ctx, id)
|
|
113
|
+
if errors.Is(err, ErrNotFound) {
|
|
114
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### HTTP handlers with chi
|
|
120
|
+
```go
|
|
121
|
+
// internal/handler/users.go
|
|
122
|
+
type UserHandler struct {
|
|
123
|
+
svc UserService
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func (h *UserHandler) Register(r chi.Router) {
|
|
127
|
+
r.Get("/users/{id}", h.GetUser)
|
|
128
|
+
r.Post("/users", h.CreateUser)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
|
|
132
|
+
id := chi.URLParam(r, "id")
|
|
133
|
+
user, err := h.svc.GetUser(r.Context(), id)
|
|
134
|
+
if err != nil {
|
|
135
|
+
renderError(w, err)
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
renderJSON(w, http.StatusOK, user)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func renderJSON(w http.ResponseWriter, code int, v any) {
|
|
142
|
+
w.Header().Set("Content-Type", "application/json")
|
|
143
|
+
w.WriteHeader(code)
|
|
144
|
+
json.NewEncoder(w).Encode(v)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
func renderError(w http.ResponseWriter, err error) {
|
|
148
|
+
switch {
|
|
149
|
+
case errors.Is(err, ErrNotFound):
|
|
150
|
+
http.Error(w, "not found", http.StatusNotFound)
|
|
151
|
+
case errors.Is(err, ErrForbidden):
|
|
152
|
+
http.Error(w, "forbidden", http.StatusForbidden)
|
|
153
|
+
default:
|
|
154
|
+
slog.Error("internal error", "err", err)
|
|
155
|
+
http.Error(w, "internal server error", http.StatusInternalServerError)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Interfaces
|
|
161
|
+
```go
|
|
162
|
+
// Define interfaces where they're used (consumer side), not where implemented
|
|
163
|
+
// internal/handler/users.go
|
|
164
|
+
type UserService interface {
|
|
165
|
+
GetUser(ctx context.Context, id string) (*User, error)
|
|
166
|
+
CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Keep interfaces small — 1-3 methods preferred
|
|
170
|
+
// io.Reader, io.Writer are the canonical examples
|
|
171
|
+
|
|
172
|
+
// Embedding for composition
|
|
173
|
+
type ReadWriter interface {
|
|
174
|
+
io.Reader
|
|
175
|
+
io.Writer
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Concurrency patterns
|
|
180
|
+
```go
|
|
181
|
+
// Fan-out with errgroup
|
|
182
|
+
import "golang.org/x/sync/errgroup"
|
|
183
|
+
|
|
184
|
+
func fetchAll(ctx context.Context, ids []string) ([]*User, error) {
|
|
185
|
+
g, ctx := errgroup.WithContext(ctx)
|
|
186
|
+
results := make([]*User, len(ids))
|
|
187
|
+
|
|
188
|
+
for i, id := range ids {
|
|
189
|
+
i, id := i, id // capture loop vars
|
|
190
|
+
g.Go(func() error {
|
|
191
|
+
u, err := store.GetByID(ctx, id)
|
|
192
|
+
if err != nil {
|
|
193
|
+
return err
|
|
194
|
+
}
|
|
195
|
+
results[i] = u
|
|
196
|
+
return nil
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if err := g.Wait(); err != nil {
|
|
201
|
+
return nil, err
|
|
202
|
+
}
|
|
203
|
+
return results, nil
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Worker pool
|
|
207
|
+
func processJobs(ctx context.Context, jobs <-chan Job) {
|
|
208
|
+
const workers = 10
|
|
209
|
+
var wg sync.WaitGroup
|
|
210
|
+
for range workers {
|
|
211
|
+
wg.Add(1)
|
|
212
|
+
go func() {
|
|
213
|
+
defer wg.Done()
|
|
214
|
+
for job := range jobs {
|
|
215
|
+
if err := process(ctx, job); err != nil {
|
|
216
|
+
slog.Error("job failed", "id", job.ID, "err", err)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}()
|
|
220
|
+
}
|
|
221
|
+
wg.Wait()
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Context propagation
|
|
226
|
+
```go
|
|
227
|
+
// Always propagate context as first argument
|
|
228
|
+
func (s *Service) DoWork(ctx context.Context, id string) error {
|
|
229
|
+
// Pass ctx to all downstream calls
|
|
230
|
+
user, err := s.store.GetByID(ctx, id)
|
|
231
|
+
if err != nil {
|
|
232
|
+
return err
|
|
233
|
+
}
|
|
234
|
+
return s.notifier.Send(ctx, user.Email)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Store request-scoped values in context only for cross-cutting concerns
|
|
238
|
+
// (tracing IDs, auth info) — never for optional function parameters
|
|
239
|
+
type contextKey string
|
|
240
|
+
const requestIDKey contextKey = "request_id"
|
|
241
|
+
|
|
242
|
+
func WithRequestID(ctx context.Context, id string) context.Context {
|
|
243
|
+
return context.WithValue(ctx, requestIDKey, id)
|
|
244
|
+
}
|
|
245
|
+
func GetRequestID(ctx context.Context) string {
|
|
246
|
+
id, _ := ctx.Value(requestIDKey).(string)
|
|
247
|
+
return id
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Table-driven tests
|
|
252
|
+
```go
|
|
253
|
+
func TestGetUser(t *testing.T) {
|
|
254
|
+
tests := []struct {
|
|
255
|
+
name string
|
|
256
|
+
id string
|
|
257
|
+
want *User
|
|
258
|
+
wantErr error
|
|
259
|
+
}{
|
|
260
|
+
{"found", "123", &User{ID: "123", Email: "a@b.com"}, nil},
|
|
261
|
+
{"not found", "999", nil, ErrNotFound},
|
|
262
|
+
{"empty id", "", nil, ErrNotFound},
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for _, tt := range tests {
|
|
266
|
+
t.Run(tt.name, func(t *testing.T) {
|
|
267
|
+
svc := newTestService(t)
|
|
268
|
+
got, err := svc.GetUser(context.Background(), tt.id)
|
|
269
|
+
if !errors.Is(err, tt.wantErr) {
|
|
270
|
+
t.Fatalf("err = %v, want %v", err, tt.wantErr)
|
|
271
|
+
}
|
|
272
|
+
if diff := cmp.Diff(tt.want, got); diff != "" {
|
|
273
|
+
t.Errorf("mismatch (-want +got):\n%s", diff)
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Logging with slog
|
|
281
|
+
```go
|
|
282
|
+
// Structured logging — never fmt.Println in production code
|
|
283
|
+
slog.Info("request completed", "method", r.Method, "path", r.URL.Path, "status", 200, "duration_ms", elapsed.Milliseconds())
|
|
284
|
+
slog.Error("db query failed", "query", "getUserByID", "err", err)
|
|
285
|
+
|
|
286
|
+
// Set up JSON logger in main
|
|
287
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
288
|
+
Level: slog.LevelInfo,
|
|
289
|
+
}))
|
|
290
|
+
slog.SetDefault(logger)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Example
|
|
294
|
+
|
|
295
|
+
**User:** Build a Go HTTP API for a to-do list: create, list, and complete tasks. PostgreSQL backend, chi router, structured logging, graceful shutdown.
|
|
296
|
+
|
|
297
|
+
**Expected output:**
|
|
298
|
+
- `cmd/server/main.go` — wires store, handler, starts server with graceful shutdown
|
|
299
|
+
- `internal/store/task.go` — `TaskStore` with `Create`, `List`, `Complete` methods using `database/sql`
|
|
300
|
+
- `internal/handler/tasks.go` — `TaskHandler` with `Register(r chi.Router)`, JSON responses
|
|
301
|
+
- `internal/handler/tasks_test.go` — table-driven tests with a mock store
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
> **Work with us:** Claudient is backed by [Uitbreiden](https://uitbreiden.com/) — we build AI products and B2B solutions with developer communities. [uitbreiden.com](https://uitbreiden.com/)
|