@mytechtoday/augment-extensions 1.3.1 → 1.5.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/README.md +105 -6
- package/augment-extensions/coding-standards/c/CHANGELOG.md +55 -0
- package/augment-extensions/coding-standards/c/LICENSE +22 -0
- package/augment-extensions/coding-standards/c/README.md +167 -0
- package/augment-extensions/coding-standards/c/config/defaults.json +26 -0
- package/augment-extensions/coding-standards/c/config/examples/embedded.yaml +25 -0
- package/augment-extensions/coding-standards/c/config/examples/systems.json +31 -0
- package/augment-extensions/coding-standards/c/config/schema.json +244 -0
- package/augment-extensions/coding-standards/c/docs/API.md +613 -0
- package/augment-extensions/coding-standards/c/docs/CONFIGURATION.md +259 -0
- package/augment-extensions/coding-standards/c/docs/USER_GUIDE.md +567 -0
- package/augment-extensions/coding-standards/c/examples/drivers/Makefile +33 -0
- package/augment-extensions/coding-standards/c/examples/drivers/README.md +192 -0
- package/augment-extensions/coding-standards/c/examples/drivers/dma-example.c +224 -0
- package/augment-extensions/coding-standards/c/examples/drivers/example.dts +64 -0
- package/augment-extensions/coding-standards/c/examples/drivers/platform-driver.c +174 -0
- package/augment-extensions/coding-standards/c/examples/embedded/README.md +167 -0
- package/augment-extensions/coding-standards/c/examples/embedded/gpio-control.c +172 -0
- package/augment-extensions/coding-standards/c/examples/embedded/timer-isr.c +198 -0
- package/augment-extensions/coding-standards/c/examples/embedded/uart-communication.c +212 -0
- package/augment-extensions/coding-standards/c/examples/kernel/Makefile +82 -0
- package/augment-extensions/coding-standards/c/examples/kernel/README.md +168 -0
- package/augment-extensions/coding-standards/c/examples/kernel/char-device.c +198 -0
- package/augment-extensions/coding-standards/c/examples/kernel/proc-file.c +131 -0
- package/augment-extensions/coding-standards/c/examples/kernel/simple-module.c +111 -0
- package/augment-extensions/coding-standards/c/examples/legacy/Makefile +62 -0
- package/augment-extensions/coding-standards/c/examples/legacy/README.md +255 -0
- package/augment-extensions/coding-standards/c/examples/legacy/c89-to-c11-migration.c +268 -0
- package/augment-extensions/coding-standards/c/examples/legacy/compatibility-layer.c +239 -0
- package/augment-extensions/coding-standards/c/examples/networking/Makefile +35 -0
- package/augment-extensions/coding-standards/c/examples/networking/README.md +207 -0
- package/augment-extensions/coding-standards/c/examples/networking/protocol-parser.c +270 -0
- package/augment-extensions/coding-standards/c/examples/networking/tcp-server.c +197 -0
- package/augment-extensions/coding-standards/c/examples/networking/udp-multicast.c +220 -0
- package/augment-extensions/coding-standards/c/examples/realtime/Makefile +53 -0
- package/augment-extensions/coding-standards/c/examples/realtime/README.md +199 -0
- package/augment-extensions/coding-standards/c/examples/realtime/deadline-monitoring.c +260 -0
- package/augment-extensions/coding-standards/c/examples/realtime/priority-scheduling.c +258 -0
- package/augment-extensions/coding-standards/c/examples/systems/Makefile +34 -0
- package/augment-extensions/coding-standards/c/examples/systems/README.md +123 -0
- package/augment-extensions/coding-standards/c/examples/systems/ipc-pipes.c +181 -0
- package/augment-extensions/coding-standards/c/examples/systems/process-management.c +153 -0
- package/augment-extensions/coding-standards/c/examples/systems/signal-handling.c +162 -0
- package/augment-extensions/coding-standards/c/module.json +149 -0
- package/augment-extensions/coding-standards/c/rules/categories/drivers.md +635 -0
- package/augment-extensions/coding-standards/c/rules/categories/embedded.md +510 -0
- package/augment-extensions/coding-standards/c/rules/categories/kernel.md +653 -0
- package/augment-extensions/coding-standards/c/rules/categories/legacy.md +526 -0
- package/augment-extensions/coding-standards/c/rules/categories/networking.md +735 -0
- package/augment-extensions/coding-standards/c/rules/categories/realtime.md +631 -0
- package/augment-extensions/coding-standards/c/rules/categories/systems.md +586 -0
- package/augment-extensions/coding-standards/c/rules/universal/const-correctness.md +275 -0
- package/augment-extensions/coding-standards/c/rules/universal/documentation.md +251 -0
- package/augment-extensions/coding-standards/c/rules/universal/error-handling.md +250 -0
- package/augment-extensions/coding-standards/c/rules/universal/header-guards.md +254 -0
- package/augment-extensions/coding-standards/c/rules/universal/memory-safety.md +233 -0
- package/augment-extensions/coding-standards/c/rules/universal/naming.md +146 -0
- package/augment-extensions/coding-standards/c/src/conflict-detector.ts +461 -0
- package/augment-extensions/coding-standards/c/src/prompt-generator.ts +307 -0
- package/augment-extensions/coding-standards/c/src/rule-evaluator.ts +307 -0
- package/augment-extensions/coding-standards/c/src/rule-override.ts +427 -0
- package/augment-extensions/coding-standards/c/src/template-engine.ts +217 -0
- package/augment-extensions/coding-standards/c/templates/prompts/drivers.txt +191 -0
- package/augment-extensions/coding-standards/c/templates/prompts/embedded.txt +164 -0
- package/augment-extensions/coding-standards/c/templates/prompts/kernel.txt +175 -0
- package/augment-extensions/coding-standards/c/templates/prompts/legacy.txt +280 -0
- package/augment-extensions/coding-standards/c/templates/prompts/networking.txt +259 -0
- package/augment-extensions/coding-standards/c/templates/prompts/realtime.txt +219 -0
- package/augment-extensions/coding-standards/c/templates/prompts/systems.txt +147 -0
- package/augment-extensions/coding-standards/c/tests/integration/category-specific.test.ts +356 -0
- package/augment-extensions/coding-standards/c/tests/integration/end-to-end-workflow.test.ts +377 -0
- package/augment-extensions/coding-standards/c/tests/performance/benchmarks.test.ts +407 -0
- package/augment-extensions/coding-standards/c/tests/unit/config-manager.test.ts +345 -0
- package/augment-extensions/coding-standards/c/tests/unit/conflict-detector.test.ts +294 -0
- package/augment-extensions/coding-standards/c/tests/unit/prompt-generator.test.ts +174 -0
- package/augment-extensions/coding-standards/c/tests/unit/registry.test.ts +313 -0
- package/augment-extensions/coding-standards/c/tests/unit/rule-evaluator.test.ts +318 -0
- package/augment-extensions/coding-standards/c/tests/unit/rule-override.test.ts +326 -0
- package/augment-extensions/coding-standards/c/tests/unit/template-engine.test.ts +314 -0
- package/augment-extensions/coding-standards/go/CHARACTER-COUNT-REPORT.md +135 -0
- package/augment-extensions/coding-standards/go/PHASE1-COMPLETION.md +146 -0
- package/augment-extensions/coding-standards/go/PHASE4-COMPLETION.md +184 -0
- package/augment-extensions/coding-standards/go/README.md +200 -0
- package/augment-extensions/coding-standards/go/VALIDATION-CHECKLIST.md +154 -0
- package/augment-extensions/coding-standards/go/config/examples/example-cli.json +15 -0
- package/augment-extensions/coding-standards/go/config/examples/example-microservices.json +21 -0
- package/augment-extensions/coding-standards/go/config/examples/example-multi-category.yaml +24 -0
- package/augment-extensions/coding-standards/go/config/examples/example-web.json +15 -0
- package/augment-extensions/coding-standards/go/config/schema.json +110 -0
- package/augment-extensions/coding-standards/go/docs/CATEGORIES.md +221 -0
- package/augment-extensions/coding-standards/go/docs/CONFIGURATION.md +198 -0
- package/augment-extensions/coding-standards/go/docs/TROUBLESHOOTING.md +285 -0
- package/augment-extensions/coding-standards/go/examples/cli/cobra-app.go +287 -0
- package/augment-extensions/coding-standards/go/examples/cloud-native-app.go +217 -0
- package/augment-extensions/coding-standards/go/examples/devops-tool.go +250 -0
- package/augment-extensions/coding-standards/go/examples/distributed-system.go +247 -0
- package/augment-extensions/coding-standards/go/examples/microservices/grpc-service.go +253 -0
- package/augment-extensions/coding-standards/go/examples/rest-api.go +270 -0
- package/augment-extensions/coding-standards/go/examples/web/http-server.go +224 -0
- package/augment-extensions/coding-standards/go/module.json +139 -0
- package/augment-extensions/coding-standards/go/rules/categories/api-development/api-versioning.md +149 -0
- package/augment-extensions/coding-standards/go/rules/categories/api-development/rate-limiting.md +209 -0
- package/augment-extensions/coding-standards/go/rules/categories/api-development/rest-api-design.md +183 -0
- package/augment-extensions/coding-standards/go/rules/categories/cloud-native/cloud-config.md +193 -0
- package/augment-extensions/coding-standards/go/rules/categories/cloud-native/health-checks.md +231 -0
- package/augment-extensions/coding-standards/go/rules/categories/cloud-native/kubernetes.md +180 -0
- package/augment-extensions/coding-standards/go/rules/categories/devops-tooling/automation.md +179 -0
- package/augment-extensions/coding-standards/go/rules/categories/devops-tooling/ci-cd-integration.md +147 -0
- package/augment-extensions/coding-standards/go/rules/categories/devops-tooling/infrastructure-as-code.md +231 -0
- package/augment-extensions/coding-standards/go/rules/categories/distributed-systems/caching.md +150 -0
- package/augment-extensions/coding-standards/go/rules/categories/distributed-systems/consensus.md +187 -0
- package/augment-extensions/coding-standards/go/rules/categories/distributed-systems/event-sourcing.md +246 -0
- package/augment-extensions/coding-standards/go/rules/cli/command-parsing.md +264 -0
- package/augment-extensions/coding-standards/go/rules/cli/configuration.md +268 -0
- package/augment-extensions/coding-standards/go/rules/cli/cross-platform.md +324 -0
- package/augment-extensions/coding-standards/go/rules/microservices/distributed-tracing.md +253 -0
- package/augment-extensions/coding-standards/go/rules/microservices/grpc.md +257 -0
- package/augment-extensions/coding-standards/go/rules/microservices/metrics.md +278 -0
- package/augment-extensions/coding-standards/go/rules/microservices/service-discovery.md +249 -0
- package/augment-extensions/coding-standards/go/rules/universal/code-organization.md +221 -0
- package/augment-extensions/coding-standards/go/rules/universal/documentation.md +269 -0
- package/augment-extensions/coding-standards/go/rules/universal/performance.md +323 -0
- package/augment-extensions/coding-standards/go/rules/universal/testing.md +162 -0
- package/augment-extensions/coding-standards/go/rules/web/graceful-shutdown.md +249 -0
- package/augment-extensions/coding-standards/go/rules/web/http-handlers.md +164 -0
- package/augment-extensions/coding-standards/go/rules/web/middleware.md +234 -0
- package/augment-extensions/coding-standards/go/rules/web/routing.md +251 -0
- package/augment-extensions/coding-standards/go/templates/prompts/api.md +160 -0
- package/augment-extensions/coding-standards/go/templates/prompts/cli.md +225 -0
- package/augment-extensions/coding-standards/go/templates/prompts/cloud-native.md +121 -0
- package/augment-extensions/coding-standards/go/templates/prompts/devops.md +146 -0
- package/augment-extensions/coding-standards/go/templates/prompts/distributed.md +133 -0
- package/augment-extensions/coding-standards/go/templates/prompts/microservices.md +225 -0
- package/augment-extensions/coding-standards/go/templates/prompts/web.md +181 -0
- package/augment-extensions/coding-standards/go/tests/integration/module-integration.test.ts +164 -0
- package/augment-extensions/coding-standards/go/tests/unit/category-selection.test.ts +147 -0
- package/augment-extensions/coding-standards/go/tests/unit/module-structure.test.ts +154 -0
- package/augment-extensions/coding-standards/go/tests/validate-character-count.ps1 +13 -0
- package/augment-extensions/coding-standards/go/tests/validate-examples.ps1 +148 -0
- package/augment-extensions/coding-standards/go/tests/validate-examples.sh +135 -0
- package/cli/dist/analysis/ast-parser.d.ts +47 -0
- package/cli/dist/analysis/ast-parser.d.ts.map +1 -0
- package/cli/dist/analysis/ast-parser.js +161 -0
- package/cli/dist/analysis/ast-parser.js.map +1 -0
- package/cli/dist/analysis/complexity-analyzer.d.ts +27 -0
- package/cli/dist/analysis/complexity-analyzer.d.ts.map +1 -0
- package/cli/dist/analysis/complexity-analyzer.js +189 -0
- package/cli/dist/analysis/complexity-analyzer.js.map +1 -0
- package/cli/dist/analysis/dependency-analyzer.d.ts +23 -0
- package/cli/dist/analysis/dependency-analyzer.d.ts.map +1 -0
- package/cli/dist/analysis/dependency-analyzer.js +237 -0
- package/cli/dist/analysis/dependency-analyzer.js.map +1 -0
- package/cli/dist/analysis/index.d.ts +9 -0
- package/cli/dist/analysis/index.d.ts.map +1 -0
- package/cli/dist/analysis/index.js +25 -0
- package/cli/dist/analysis/index.js.map +1 -0
- package/cli/dist/analysis/security-scanner.d.ts +11 -0
- package/cli/dist/analysis/security-scanner.d.ts.map +1 -0
- package/cli/dist/analysis/security-scanner.js +294 -0
- package/cli/dist/analysis/security-scanner.js.map +1 -0
- package/cli/dist/analysis/types.d.ts +151 -0
- package/cli/dist/analysis/types.d.ts.map +1 -0
- package/cli/dist/analysis/types.js +6 -0
- package/cli/dist/analysis/types.js.map +1 -0
- package/cli/dist/cli.js +24 -0
- package/cli/dist/cli.js.map +1 -1
- package/cli/dist/commands/code-analysis.d.ts +11 -0
- package/cli/dist/commands/code-analysis.d.ts.map +1 -0
- package/cli/dist/commands/code-analysis.js +412 -0
- package/cli/dist/commands/code-analysis.js.map +1 -0
- package/modules.md +99 -3
- package/package.json +21 -2
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Package main demonstrates a production-ready gRPC microservice
|
|
2
|
+
package main
|
|
3
|
+
|
|
4
|
+
import (
|
|
5
|
+
"context"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"log"
|
|
9
|
+
"log/slog"
|
|
10
|
+
"net"
|
|
11
|
+
"os"
|
|
12
|
+
"os/signal"
|
|
13
|
+
"syscall"
|
|
14
|
+
"time"
|
|
15
|
+
|
|
16
|
+
"google.golang.org/grpc"
|
|
17
|
+
"google.golang.org/grpc/codes"
|
|
18
|
+
"google.golang.org/grpc/status"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
// User represents a user in the system
|
|
22
|
+
type User struct {
|
|
23
|
+
ID int64
|
|
24
|
+
Name string
|
|
25
|
+
Email string
|
|
26
|
+
CreatedAt time.Time
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
var ErrNotFound = errors.New("user not found")
|
|
30
|
+
|
|
31
|
+
// UserRepository handles user data operations
|
|
32
|
+
type UserRepository struct {
|
|
33
|
+
users map[int64]*User
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func NewUserRepository() *UserRepository {
|
|
37
|
+
return &UserRepository{
|
|
38
|
+
users: make(map[int64]*User),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func (r *UserRepository) GetUser(ctx context.Context, id int64) (*User, error) {
|
|
43
|
+
user, ok := r.users[id]
|
|
44
|
+
if !ok {
|
|
45
|
+
return nil, ErrNotFound
|
|
46
|
+
}
|
|
47
|
+
return user, nil
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func (r *UserRepository) CreateUser(ctx context.Context, name, email string) (*User, error) {
|
|
51
|
+
id := int64(len(r.users) + 1)
|
|
52
|
+
user := &User{
|
|
53
|
+
ID: id,
|
|
54
|
+
Name: name,
|
|
55
|
+
Email: email,
|
|
56
|
+
CreatedAt: time.Now(),
|
|
57
|
+
}
|
|
58
|
+
r.users[id] = user
|
|
59
|
+
return user, nil
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// UserServiceServer implements the gRPC UserService
|
|
63
|
+
type UserServiceServer struct {
|
|
64
|
+
repo *UserRepository
|
|
65
|
+
logger *slog.Logger
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func NewUserServiceServer(logger *slog.Logger) *UserServiceServer {
|
|
69
|
+
return &UserServiceServer{
|
|
70
|
+
repo: NewUserRepository(),
|
|
71
|
+
logger: logger,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// GetUser retrieves a user by ID
|
|
76
|
+
func (s *UserServiceServer) GetUser(ctx context.Context, req *GetUserRequest) (*GetUserResponse, error) {
|
|
77
|
+
if req.Id <= 0 {
|
|
78
|
+
return nil, status.Error(codes.InvalidArgument, "user ID must be positive")
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
user, err := s.repo.GetUser(ctx, req.Id)
|
|
82
|
+
if err != nil {
|
|
83
|
+
if errors.Is(err, ErrNotFound) {
|
|
84
|
+
return nil, status.Error(codes.NotFound, "user not found")
|
|
85
|
+
}
|
|
86
|
+
s.logger.Error("failed to get user", "error", err)
|
|
87
|
+
return nil, status.Error(codes.Internal, "internal error")
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return &GetUserResponse{
|
|
91
|
+
User: &UserProto{
|
|
92
|
+
Id: user.ID,
|
|
93
|
+
Name: user.Name,
|
|
94
|
+
Email: user.Email,
|
|
95
|
+
CreatedAt: user.CreatedAt.Unix(),
|
|
96
|
+
},
|
|
97
|
+
}, nil
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// CreateUser creates a new user
|
|
101
|
+
func (s *UserServiceServer) CreateUser(ctx context.Context, req *CreateUserRequest) (*CreateUserResponse, error) {
|
|
102
|
+
if req.Name == "" {
|
|
103
|
+
return nil, status.Error(codes.InvalidArgument, "name is required")
|
|
104
|
+
}
|
|
105
|
+
if req.Email == "" {
|
|
106
|
+
return nil, status.Error(codes.InvalidArgument, "email is required")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
user, err := s.repo.CreateUser(ctx, req.Name, req.Email)
|
|
110
|
+
if err != nil {
|
|
111
|
+
s.logger.Error("failed to create user", "error", err)
|
|
112
|
+
return nil, status.Error(codes.Internal, "internal error")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
s.logger.Info("user created", "id", user.ID, "name", user.Name)
|
|
116
|
+
|
|
117
|
+
return &CreateUserResponse{
|
|
118
|
+
User: &UserProto{
|
|
119
|
+
Id: user.ID,
|
|
120
|
+
Name: user.Name,
|
|
121
|
+
Email: user.Email,
|
|
122
|
+
CreatedAt: user.CreatedAt.Unix(),
|
|
123
|
+
},
|
|
124
|
+
}, nil
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Logging interceptor
|
|
128
|
+
func loggingUnaryInterceptor(logger *slog.Logger) grpc.UnaryServerInterceptor {
|
|
129
|
+
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
130
|
+
start := time.Now()
|
|
131
|
+
|
|
132
|
+
resp, err := handler(ctx, req)
|
|
133
|
+
|
|
134
|
+
logger.Info("gRPC call",
|
|
135
|
+
"method", info.FullMethod,
|
|
136
|
+
"duration_ms", time.Since(start).Milliseconds(),
|
|
137
|
+
"error", err,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return resp, err
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Recovery interceptor
|
|
145
|
+
func recoveryUnaryInterceptor(logger *slog.Logger) grpc.UnaryServerInterceptor {
|
|
146
|
+
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
|
|
147
|
+
defer func() {
|
|
148
|
+
if r := recover(); r != nil {
|
|
149
|
+
logger.Error("panic recovered", "panic", r, "method", info.FullMethod)
|
|
150
|
+
err = status.Error(codes.Internal, "internal error")
|
|
151
|
+
}
|
|
152
|
+
}()
|
|
153
|
+
return handler(ctx, req)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Server manages the gRPC server lifecycle
|
|
158
|
+
type Server struct {
|
|
159
|
+
grpcServer *grpc.Server
|
|
160
|
+
listener net.Listener
|
|
161
|
+
logger *slog.Logger
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func NewServer(port int, logger *slog.Logger) (*Server, error) {
|
|
165
|
+
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
|
166
|
+
if err != nil {
|
|
167
|
+
return nil, err
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
grpcServer := grpc.NewServer(
|
|
171
|
+
grpc.ChainUnaryInterceptor(
|
|
172
|
+
recoveryUnaryInterceptor(logger),
|
|
173
|
+
loggingUnaryInterceptor(logger),
|
|
174
|
+
),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
// Register service
|
|
178
|
+
userService := NewUserServiceServer(logger)
|
|
179
|
+
RegisterUserServiceServer(grpcServer, userService)
|
|
180
|
+
|
|
181
|
+
return &Server{
|
|
182
|
+
grpcServer: grpcServer,
|
|
183
|
+
listener: listener,
|
|
184
|
+
logger: logger,
|
|
185
|
+
}, nil
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
func (s *Server) Start() error {
|
|
189
|
+
s.logger.Info("gRPC server starting", "addr", s.listener.Addr())
|
|
190
|
+
return s.grpcServer.Serve(s.listener)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
func (s *Server) Stop() {
|
|
194
|
+
s.logger.Info("gRPC server stopping")
|
|
195
|
+
s.grpcServer.GracefulStop()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
func main() {
|
|
199
|
+
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
|
200
|
+
|
|
201
|
+
srv, err := NewServer(50051, logger)
|
|
202
|
+
if err != nil {
|
|
203
|
+
log.Fatal(err)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Start server in goroutine
|
|
207
|
+
go func() {
|
|
208
|
+
if err := srv.Start(); err != nil {
|
|
209
|
+
logger.Error("server failed", "error", err)
|
|
210
|
+
os.Exit(1)
|
|
211
|
+
}
|
|
212
|
+
}()
|
|
213
|
+
|
|
214
|
+
// Wait for interrupt signal
|
|
215
|
+
quit := make(chan os.Signal, 1)
|
|
216
|
+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
217
|
+
<-quit
|
|
218
|
+
|
|
219
|
+
logger.Info("shutdown signal received")
|
|
220
|
+
srv.Stop()
|
|
221
|
+
logger.Info("server stopped")
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Proto message definitions (normally generated from .proto files)
|
|
225
|
+
type GetUserRequest struct {
|
|
226
|
+
Id int64
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
type GetUserResponse struct {
|
|
230
|
+
User *UserProto
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type CreateUserRequest struct {
|
|
234
|
+
Name string
|
|
235
|
+
Email string
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
type CreateUserResponse struct {
|
|
239
|
+
User *UserProto
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
type UserProto struct {
|
|
243
|
+
Id int64
|
|
244
|
+
Name string
|
|
245
|
+
Email string
|
|
246
|
+
CreatedAt int64
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Service registration (normally generated)
|
|
250
|
+
func RegisterUserServiceServer(s *grpc.Server, srv *UserServiceServer) {
|
|
251
|
+
// Registration logic would be generated by protoc
|
|
252
|
+
}
|
|
253
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// Package main demonstrates a REST API in Go with versioning, rate limiting,
|
|
2
|
+
// pagination, and proper error handling.
|
|
3
|
+
package main
|
|
4
|
+
|
|
5
|
+
import (
|
|
6
|
+
"context"
|
|
7
|
+
"encoding/json"
|
|
8
|
+
"fmt"
|
|
9
|
+
"log"
|
|
10
|
+
"net/http"
|
|
11
|
+
"strconv"
|
|
12
|
+
"time"
|
|
13
|
+
|
|
14
|
+
"github.com/gorilla/mux"
|
|
15
|
+
"golang.org/x/time/rate"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// User represents a user entity
|
|
19
|
+
type User struct {
|
|
20
|
+
ID string `json:"id"`
|
|
21
|
+
FirstName string `json:"first_name"`
|
|
22
|
+
LastName string `json:"last_name"`
|
|
23
|
+
Email string `json:"email"`
|
|
24
|
+
CreatedAt time.Time `json:"created_at"`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ErrorResponse represents an API error
|
|
28
|
+
type ErrorResponse struct {
|
|
29
|
+
Error string `json:"error"`
|
|
30
|
+
Message string `json:"message"`
|
|
31
|
+
Code string `json:"code,omitempty"`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// PaginatedResponse represents a paginated API response
|
|
35
|
+
type PaginatedResponse struct {
|
|
36
|
+
Data interface{} `json:"data"`
|
|
37
|
+
Page int `json:"page"`
|
|
38
|
+
PageSize int `json:"page_size"`
|
|
39
|
+
TotalItems int `json:"total_items"`
|
|
40
|
+
TotalPages int `json:"total_pages"`
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// RateLimiter manages rate limiting
|
|
44
|
+
type RateLimiter struct {
|
|
45
|
+
limiters map[string]*rate.Limiter
|
|
46
|
+
rate rate.Limit
|
|
47
|
+
burst int
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// NewRateLimiter creates a new rate limiter
|
|
51
|
+
func NewRateLimiter(r rate.Limit, b int) *RateLimiter {
|
|
52
|
+
return &RateLimiter{
|
|
53
|
+
limiters: make(map[string]*rate.Limiter),
|
|
54
|
+
rate: r,
|
|
55
|
+
burst: b,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// GetLimiter returns a limiter for the given key
|
|
60
|
+
func (rl *RateLimiter) GetLimiter(key string) *rate.Limiter {
|
|
61
|
+
limiter, exists := rl.limiters[key]
|
|
62
|
+
if !exists {
|
|
63
|
+
limiter = rate.NewLimiter(rl.rate, rl.burst)
|
|
64
|
+
rl.limiters[key] = limiter
|
|
65
|
+
}
|
|
66
|
+
return limiter
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// API represents the REST API server
|
|
70
|
+
type API struct {
|
|
71
|
+
router *mux.Router
|
|
72
|
+
rateLimiter *RateLimiter
|
|
73
|
+
users map[string]*User // In-memory store for demo
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// NewAPI creates a new API instance
|
|
77
|
+
func NewAPI() *API {
|
|
78
|
+
api := &API{
|
|
79
|
+
router: mux.NewRouter(),
|
|
80
|
+
rateLimiter: NewRateLimiter(rate.Limit(10), 20),
|
|
81
|
+
users: make(map[string]*User),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
api.setupRoutes()
|
|
85
|
+
return api
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// setupRoutes configures API routes
|
|
89
|
+
func (api *API) setupRoutes() {
|
|
90
|
+
// Apply middleware
|
|
91
|
+
api.router.Use(api.rateLimitMiddleware)
|
|
92
|
+
api.router.Use(api.loggingMiddleware)
|
|
93
|
+
|
|
94
|
+
// V1 routes
|
|
95
|
+
v1 := api.router.PathPrefix("/api/v1").Subrouter()
|
|
96
|
+
v1.HandleFunc("/users", api.listUsersV1).Methods("GET")
|
|
97
|
+
v1.HandleFunc("/users", api.createUserV1).Methods("POST")
|
|
98
|
+
v1.HandleFunc("/users/{id}", api.getUserV1).Methods("GET")
|
|
99
|
+
v1.HandleFunc("/users/{id}", api.updateUserV1).Methods("PUT")
|
|
100
|
+
v1.HandleFunc("/users/{id}", api.deleteUserV1).Methods("DELETE")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// rateLimitMiddleware implements rate limiting
|
|
104
|
+
func (api *API) rateLimitMiddleware(next http.Handler) http.Handler {
|
|
105
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
106
|
+
key := r.RemoteAddr
|
|
107
|
+
limiter := api.rateLimiter.GetLimiter(key)
|
|
108
|
+
|
|
109
|
+
if !limiter.Allow() {
|
|
110
|
+
w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", api.rateLimiter.burst))
|
|
111
|
+
w.Header().Set("X-RateLimit-Remaining", "0")
|
|
112
|
+
w.Header().Set("Retry-After", "60")
|
|
113
|
+
|
|
114
|
+
api.writeError(w, http.StatusTooManyRequests, "Rate limit exceeded")
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
w.Header().Set("X-RateLimit-Limit", fmt.Sprintf("%d", api.rateLimiter.burst))
|
|
119
|
+
next.ServeHTTP(w, r)
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// loggingMiddleware logs requests
|
|
124
|
+
func (api *API) loggingMiddleware(next http.Handler) http.Handler {
|
|
125
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
126
|
+
start := time.Now()
|
|
127
|
+
log.Printf("%s %s", r.Method, r.URL.Path)
|
|
128
|
+
next.ServeHTTP(w, r)
|
|
129
|
+
log.Printf("Completed in %v", time.Since(start))
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// listUsersV1 handles GET /api/v1/users
|
|
134
|
+
func (api *API) listUsersV1(w http.ResponseWriter, r *http.Request) {
|
|
135
|
+
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
136
|
+
if page < 1 {
|
|
137
|
+
page = 1
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size"))
|
|
141
|
+
if pageSize < 1 || pageSize > 100 {
|
|
142
|
+
pageSize = 20
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Convert map to slice
|
|
146
|
+
users := make([]*User, 0, len(api.users))
|
|
147
|
+
for _, user := range api.users {
|
|
148
|
+
users = append(users, user)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Simple pagination
|
|
152
|
+
start := (page - 1) * pageSize
|
|
153
|
+
end := start + pageSize
|
|
154
|
+
if start > len(users) {
|
|
155
|
+
start = len(users)
|
|
156
|
+
}
|
|
157
|
+
if end > len(users) {
|
|
158
|
+
end = len(users)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
response := PaginatedResponse{
|
|
162
|
+
Data: users[start:end],
|
|
163
|
+
Page: page,
|
|
164
|
+
PageSize: pageSize,
|
|
165
|
+
TotalItems: len(users),
|
|
166
|
+
TotalPages: (len(users) + pageSize - 1) / pageSize,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
api.writeJSON(w, http.StatusOK, response)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// createUserV1 handles POST /api/v1/users
|
|
173
|
+
func (api *API) createUserV1(w http.ResponseWriter, r *http.Request) {
|
|
174
|
+
var user User
|
|
175
|
+
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
|
176
|
+
api.writeError(w, http.StatusBadRequest, "Invalid request body")
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
user.ID = fmt.Sprintf("user-%d", len(api.users)+1)
|
|
181
|
+
user.CreatedAt = time.Now()
|
|
182
|
+
|
|
183
|
+
api.users[user.ID] = &user
|
|
184
|
+
|
|
185
|
+
api.writeJSON(w, http.StatusCreated, user)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// getUserV1 handles GET /api/v1/users/{id}
|
|
189
|
+
func (api *API) getUserV1(w http.ResponseWriter, r *http.Request) {
|
|
190
|
+
vars := mux.Vars(r)
|
|
191
|
+
id := vars["id"]
|
|
192
|
+
|
|
193
|
+
user, exists := api.users[id]
|
|
194
|
+
if !exists {
|
|
195
|
+
api.writeError(w, http.StatusNotFound, "User not found")
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
api.writeJSON(w, http.StatusOK, user)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// updateUserV1 handles PUT /api/v1/users/{id}
|
|
203
|
+
func (api *API) updateUserV1(w http.ResponseWriter, r *http.Request) {
|
|
204
|
+
vars := mux.Vars(r)
|
|
205
|
+
id := vars["id"]
|
|
206
|
+
|
|
207
|
+
if _, exists := api.users[id]; !exists {
|
|
208
|
+
api.writeError(w, http.StatusNotFound, "User not found")
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var user User
|
|
213
|
+
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
|
|
214
|
+
api.writeError(w, http.StatusBadRequest, "Invalid request body")
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
user.ID = id
|
|
219
|
+
api.users[id] = &user
|
|
220
|
+
|
|
221
|
+
api.writeJSON(w, http.StatusOK, user)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// deleteUserV1 handles DELETE /api/v1/users/{id}
|
|
225
|
+
func (api *API) deleteUserV1(w http.ResponseWriter, r *http.Request) {
|
|
226
|
+
vars := mux.Vars(r)
|
|
227
|
+
id := vars["id"]
|
|
228
|
+
|
|
229
|
+
if _, exists := api.users[id]; !exists {
|
|
230
|
+
api.writeError(w, http.StatusNotFound, "User not found")
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
delete(api.users, id)
|
|
235
|
+
w.WriteHeader(http.StatusNoContent)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// writeJSON writes a JSON response
|
|
239
|
+
func (api *API) writeJSON(w http.ResponseWriter, status int, data interface{}) {
|
|
240
|
+
w.Header().Set("Content-Type", "application/json")
|
|
241
|
+
w.WriteHeader(status)
|
|
242
|
+
json.NewEncoder(w).Encode(data)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// writeError writes an error response
|
|
246
|
+
func (api *API) writeError(w http.ResponseWriter, status int, message string) {
|
|
247
|
+
response := ErrorResponse{
|
|
248
|
+
Error: http.StatusText(status),
|
|
249
|
+
Message: message,
|
|
250
|
+
}
|
|
251
|
+
api.writeJSON(w, status, response)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
func main() {
|
|
255
|
+
api := NewAPI()
|
|
256
|
+
|
|
257
|
+
server := &http.Server{
|
|
258
|
+
Addr: ":8080",
|
|
259
|
+
Handler: api.router,
|
|
260
|
+
ReadTimeout: 15 * time.Second,
|
|
261
|
+
WriteTimeout: 15 * time.Second,
|
|
262
|
+
IdleTimeout: 60 * time.Second,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
log.Println("Starting REST API server on :8080")
|
|
266
|
+
if err := server.ListenAndServe(); err != nil {
|
|
267
|
+
log.Fatalf("Server failed: %v", err)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|