@plazmodium/odin 0.3.3-beta → 0.3.4-beta
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 +16 -10
- package/builtin/ODIN.md +1045 -0
- package/builtin/agent-definitions/README.md +170 -0
- package/builtin/agent-definitions/_shared-context.md +377 -0
- package/builtin/agent-definitions/architect.md +627 -0
- package/builtin/agent-definitions/builder.md +716 -0
- package/builtin/agent-definitions/discovery.md +293 -0
- package/builtin/agent-definitions/documenter.md +238 -0
- package/builtin/agent-definitions/guardian.md +1049 -0
- package/builtin/agent-definitions/integrator.md +363 -0
- package/builtin/agent-definitions/planning.md +236 -0
- package/builtin/agent-definitions/product.md +405 -0
- package/builtin/agent-definitions/release.md +430 -0
- package/builtin/agent-definitions/reviewer.md +447 -0
- package/builtin/agent-definitions/watcher.md +402 -0
- package/builtin/skills/api/graphql/SKILL.md +548 -0
- package/builtin/skills/api/grpc/SKILL.md +554 -0
- package/builtin/skills/api/rest-api/SKILL.md +469 -0
- package/builtin/skills/api/trpc/SKILL.md +503 -0
- package/builtin/skills/architecture/clean-architecture/SKILL.md +141 -0
- package/builtin/skills/architecture/domain-driven-design/SKILL.md +129 -0
- package/builtin/skills/architecture/event-driven/SKILL.md +145 -0
- package/builtin/skills/architecture/microservices/SKILL.md +143 -0
- package/builtin/skills/architecture/tla-precheck/SKILL.md +171 -0
- package/builtin/skills/backend/golang-gin/SKILL.md +141 -0
- package/builtin/skills/backend/nodejs-express/SKILL.md +277 -0
- package/builtin/skills/backend/nodejs-fastify/SKILL.md +152 -0
- package/builtin/skills/backend/python-django/SKILL.md +128 -0
- package/builtin/skills/backend/python-fastapi/SKILL.md +140 -0
- package/builtin/skills/database/mongodb/SKILL.md +132 -0
- package/builtin/skills/database/postgresql/SKILL.md +120 -0
- package/builtin/skills/database/prisma-orm/SKILL.md +366 -0
- package/builtin/skills/database/redis/SKILL.md +140 -0
- package/builtin/skills/database/supabase/SKILL.md +416 -0
- package/builtin/skills/devops/aws/SKILL.md +382 -0
- package/builtin/skills/devops/docker/SKILL.md +359 -0
- package/builtin/skills/devops/github-actions/SKILL.md +435 -0
- package/builtin/skills/devops/kubernetes/SKILL.md +459 -0
- package/builtin/skills/devops/terraform/SKILL.md +453 -0
- package/builtin/skills/frontend/alpine-dev/SKILL.md +27 -0
- package/builtin/skills/frontend/angular-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/astro-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/htmx-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/nextjs-dev/SKILL.md +470 -0
- package/builtin/skills/frontend/react-patterns/SKILL.md +166 -0
- package/builtin/skills/frontend/svelte-dev/SKILL.md +28 -0
- package/builtin/skills/frontend/tailwindcss/SKILL.md +131 -0
- package/builtin/skills/frontend/vuejs-dev/SKILL.md +28 -0
- package/builtin/skills/generic-dev/SKILL.md +307 -0
- package/builtin/skills/testing/cypress/SKILL.md +372 -0
- package/builtin/skills/testing/jest/SKILL.md +176 -0
- package/builtin/skills/testing/playwright/SKILL.md +341 -0
- package/builtin/skills/testing/unit-tests-eval-sdd/SKILL.md +73 -0
- package/builtin/skills/testing/unit-tests-sdd/SKILL.md +83 -0
- package/builtin/skills/testing/vitest/SKILL.md +249 -0
- package/dist/adapters/skills/filesystem.d.ts.map +1 -1
- package/dist/adapters/skills/filesystem.js +2 -18
- package/dist/adapters/skills/filesystem.js.map +1 -1
- package/dist/builtin-assets.d.ts +8 -0
- package/dist/builtin-assets.d.ts.map +1 -0
- package/dist/builtin-assets.js +90 -0
- package/dist/builtin-assets.js.map +1 -0
- package/dist/init.js +69 -11
- package/dist/init.js.map +1 -1
- package/dist/schemas.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/prepare-phase-context.d.ts.map +1 -1
- package/dist/tools/prepare-phase-context.js +5 -0
- package/dist/tools/prepare-phase-context.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: grpc
|
|
3
|
+
description: gRPC expertise for building high-performance RPC services. Covers Protocol Buffers, service definitions, streaming, and client/server implementation in multiple languages.
|
|
4
|
+
category: api
|
|
5
|
+
compatible_with:
|
|
6
|
+
- golang-gin
|
|
7
|
+
- python-fastapi
|
|
8
|
+
- microservices
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# gRPC High-Performance RPC
|
|
12
|
+
|
|
13
|
+
## Instructions
|
|
14
|
+
|
|
15
|
+
1. **Assess the use case**: gRPC excels at microservices, real-time streaming, and polyglot environments.
|
|
16
|
+
2. **Follow gRPC conventions**:
|
|
17
|
+
- Define services in .proto files
|
|
18
|
+
- Use Protocol Buffers for serialization
|
|
19
|
+
- Implement proper error handling with status codes
|
|
20
|
+
- Consider streaming patterns
|
|
21
|
+
3. **Provide complete examples**: Include proto definitions, server, and client code.
|
|
22
|
+
4. **Guide on best practices**: Error handling, deadlines, load balancing.
|
|
23
|
+
|
|
24
|
+
## Protocol Buffers
|
|
25
|
+
|
|
26
|
+
### Basic Service Definition
|
|
27
|
+
|
|
28
|
+
```protobuf
|
|
29
|
+
// user.proto
|
|
30
|
+
syntax = "proto3";
|
|
31
|
+
|
|
32
|
+
package user.v1;
|
|
33
|
+
|
|
34
|
+
option go_package = "github.com/myorg/myapp/gen/user/v1";
|
|
35
|
+
|
|
36
|
+
import "google/protobuf/timestamp.proto";
|
|
37
|
+
import "google/protobuf/empty.proto";
|
|
38
|
+
|
|
39
|
+
// User service definition
|
|
40
|
+
service UserService {
|
|
41
|
+
// Unary RPC
|
|
42
|
+
rpc GetUser(GetUserRequest) returns (User);
|
|
43
|
+
rpc CreateUser(CreateUserRequest) returns (User);
|
|
44
|
+
rpc UpdateUser(UpdateUserRequest) returns (User);
|
|
45
|
+
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
|
|
46
|
+
|
|
47
|
+
// Server streaming
|
|
48
|
+
rpc ListUsers(ListUsersRequest) returns (stream User);
|
|
49
|
+
|
|
50
|
+
// Client streaming
|
|
51
|
+
rpc BatchCreateUsers(stream CreateUserRequest) returns (BatchCreateResponse);
|
|
52
|
+
|
|
53
|
+
// Bidirectional streaming
|
|
54
|
+
rpc Chat(stream ChatMessage) returns (stream ChatMessage);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Messages
|
|
58
|
+
message User {
|
|
59
|
+
string id = 1;
|
|
60
|
+
string email = 2;
|
|
61
|
+
string name = 3;
|
|
62
|
+
Role role = 4;
|
|
63
|
+
google.protobuf.Timestamp created_at = 5;
|
|
64
|
+
google.protobuf.Timestamp updated_at = 6;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
message GetUserRequest {
|
|
68
|
+
string id = 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
message CreateUserRequest {
|
|
72
|
+
string email = 1;
|
|
73
|
+
string name = 2;
|
|
74
|
+
string password = 3;
|
|
75
|
+
Role role = 4;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
message UpdateUserRequest {
|
|
79
|
+
string id = 1;
|
|
80
|
+
optional string email = 2;
|
|
81
|
+
optional string name = 3;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
message DeleteUserRequest {
|
|
85
|
+
string id = 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
message ListUsersRequest {
|
|
89
|
+
int32 page_size = 1;
|
|
90
|
+
string page_token = 2;
|
|
91
|
+
UserFilter filter = 3;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
message UserFilter {
|
|
95
|
+
optional Role role = 1;
|
|
96
|
+
optional string search = 2;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
message BatchCreateResponse {
|
|
100
|
+
int32 created_count = 1;
|
|
101
|
+
repeated string user_ids = 2;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
message ChatMessage {
|
|
105
|
+
string user_id = 1;
|
|
106
|
+
string content = 2;
|
|
107
|
+
google.protobuf.Timestamp timestamp = 3;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
enum Role {
|
|
111
|
+
ROLE_UNSPECIFIED = 0;
|
|
112
|
+
ROLE_USER = 1;
|
|
113
|
+
ROLE_ADMIN = 2;
|
|
114
|
+
ROLE_MODERATOR = 3;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Field Rules
|
|
119
|
+
|
|
120
|
+
```protobuf
|
|
121
|
+
message Example {
|
|
122
|
+
// Scalar types
|
|
123
|
+
string name = 1;
|
|
124
|
+
int32 age = 2;
|
|
125
|
+
int64 id = 3;
|
|
126
|
+
bool active = 4;
|
|
127
|
+
double price = 5;
|
|
128
|
+
bytes data = 6;
|
|
129
|
+
|
|
130
|
+
// Optional (explicit presence)
|
|
131
|
+
optional string nickname = 7;
|
|
132
|
+
|
|
133
|
+
// Repeated (arrays)
|
|
134
|
+
repeated string tags = 8;
|
|
135
|
+
|
|
136
|
+
// Maps
|
|
137
|
+
map<string, string> metadata = 9;
|
|
138
|
+
|
|
139
|
+
// Nested message
|
|
140
|
+
Address address = 10;
|
|
141
|
+
|
|
142
|
+
// Oneof (union type)
|
|
143
|
+
oneof contact {
|
|
144
|
+
string phone = 11;
|
|
145
|
+
string email = 12;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
message Address {
|
|
150
|
+
string street = 1;
|
|
151
|
+
string city = 2;
|
|
152
|
+
string country = 3;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Server Implementation
|
|
157
|
+
|
|
158
|
+
### Go Server
|
|
159
|
+
|
|
160
|
+
```go
|
|
161
|
+
// server/main.go
|
|
162
|
+
package main
|
|
163
|
+
|
|
164
|
+
import (
|
|
165
|
+
"context"
|
|
166
|
+
"log"
|
|
167
|
+
"net"
|
|
168
|
+
|
|
169
|
+
"google.golang.org/grpc"
|
|
170
|
+
"google.golang.org/grpc/codes"
|
|
171
|
+
"google.golang.org/grpc/status"
|
|
172
|
+
|
|
173
|
+
pb "github.com/myorg/myapp/gen/user/v1"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
type userServer struct {
|
|
177
|
+
pb.UnimplementedUserServiceServer
|
|
178
|
+
db *database
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
|
|
182
|
+
user, err := s.db.FindUser(req.Id)
|
|
183
|
+
if err != nil {
|
|
184
|
+
return nil, status.Errorf(codes.NotFound, "user not found: %v", err)
|
|
185
|
+
}
|
|
186
|
+
return user, nil
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
|
|
190
|
+
// Validation
|
|
191
|
+
if req.Email == "" {
|
|
192
|
+
return nil, status.Error(codes.InvalidArgument, "email is required")
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
user, err := s.db.CreateUser(req)
|
|
196
|
+
if err != nil {
|
|
197
|
+
return nil, status.Errorf(codes.Internal, "failed to create user: %v", err)
|
|
198
|
+
}
|
|
199
|
+
return user, nil
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Server streaming
|
|
203
|
+
func (s *userServer) ListUsers(req *pb.ListUsersRequest, stream pb.UserService_ListUsersServer) error {
|
|
204
|
+
users, err := s.db.ListUsers(req.Filter)
|
|
205
|
+
if err != nil {
|
|
206
|
+
return status.Errorf(codes.Internal, "failed to list users: %v", err)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for _, user := range users {
|
|
210
|
+
if err := stream.Send(user); err != nil {
|
|
211
|
+
return err
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return nil
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Bidirectional streaming
|
|
218
|
+
func (s *userServer) Chat(stream pb.UserService_ChatServer) error {
|
|
219
|
+
for {
|
|
220
|
+
msg, err := stream.Recv()
|
|
221
|
+
if err == io.EOF {
|
|
222
|
+
return nil
|
|
223
|
+
}
|
|
224
|
+
if err != nil {
|
|
225
|
+
return err
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Echo back
|
|
229
|
+
response := &pb.ChatMessage{
|
|
230
|
+
UserId: "server",
|
|
231
|
+
Content: "Received: " + msg.Content,
|
|
232
|
+
Timestamp: timestamppb.Now(),
|
|
233
|
+
}
|
|
234
|
+
if err := stream.Send(response); err != nil {
|
|
235
|
+
return err
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
func main() {
|
|
241
|
+
lis, err := net.Listen("tcp", ":50051")
|
|
242
|
+
if err != nil {
|
|
243
|
+
log.Fatalf("failed to listen: %v", err)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
grpcServer := grpc.NewServer(
|
|
247
|
+
grpc.UnaryInterceptor(loggingInterceptor),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
pb.RegisterUserServiceServer(grpcServer, &userServer{db: newDatabase()})
|
|
251
|
+
|
|
252
|
+
log.Println("Server listening on :50051")
|
|
253
|
+
if err := grpcServer.Serve(lis); err != nil {
|
|
254
|
+
log.Fatalf("failed to serve: %v", err)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Node.js Server
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// server.ts
|
|
263
|
+
import * as grpc from '@grpc/grpc-js';
|
|
264
|
+
import * as protoLoader from '@grpc/proto-loader';
|
|
265
|
+
|
|
266
|
+
const packageDefinition = protoLoader.loadSync('./user.proto', {
|
|
267
|
+
keepCase: true,
|
|
268
|
+
longs: String,
|
|
269
|
+
enums: String,
|
|
270
|
+
defaults: true,
|
|
271
|
+
oneofs: true,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const proto = grpc.loadPackageDefinition(packageDefinition) as any;
|
|
275
|
+
|
|
276
|
+
const userService: grpc.UntypedServiceImplementation = {
|
|
277
|
+
getUser: async (call, callback) => {
|
|
278
|
+
try {
|
|
279
|
+
const user = await db.findUser(call.request.id);
|
|
280
|
+
if (!user) {
|
|
281
|
+
callback({
|
|
282
|
+
code: grpc.status.NOT_FOUND,
|
|
283
|
+
message: 'User not found',
|
|
284
|
+
});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
callback(null, user);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
callback({
|
|
290
|
+
code: grpc.status.INTERNAL,
|
|
291
|
+
message: 'Internal error',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
|
|
296
|
+
listUsers: async (call) => {
|
|
297
|
+
const users = await db.listUsers(call.request.filter);
|
|
298
|
+
for (const user of users) {
|
|
299
|
+
call.write(user);
|
|
300
|
+
}
|
|
301
|
+
call.end();
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const server = new grpc.Server();
|
|
306
|
+
server.addService(proto.user.v1.UserService.service, userService);
|
|
307
|
+
|
|
308
|
+
server.bindAsync(
|
|
309
|
+
'0.0.0.0:50051',
|
|
310
|
+
grpc.ServerCredentials.createInsecure(),
|
|
311
|
+
(err, port) => {
|
|
312
|
+
if (err) {
|
|
313
|
+
console.error(err);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
console.log(`Server running on port ${port}`);
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Client Implementation
|
|
322
|
+
|
|
323
|
+
### Go Client
|
|
324
|
+
|
|
325
|
+
```go
|
|
326
|
+
// client/main.go
|
|
327
|
+
package main
|
|
328
|
+
|
|
329
|
+
import (
|
|
330
|
+
"context"
|
|
331
|
+
"io"
|
|
332
|
+
"log"
|
|
333
|
+
"time"
|
|
334
|
+
|
|
335
|
+
"google.golang.org/grpc"
|
|
336
|
+
"google.golang.org/grpc/credentials/insecure"
|
|
337
|
+
|
|
338
|
+
pb "github.com/myorg/myapp/gen/user/v1"
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
func main() {
|
|
342
|
+
conn, err := grpc.Dial(
|
|
343
|
+
"localhost:50051",
|
|
344
|
+
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
345
|
+
)
|
|
346
|
+
if err != nil {
|
|
347
|
+
log.Fatalf("failed to connect: %v", err)
|
|
348
|
+
}
|
|
349
|
+
defer conn.Close()
|
|
350
|
+
|
|
351
|
+
client := pb.NewUserServiceClient(conn)
|
|
352
|
+
|
|
353
|
+
// Unary call with deadline
|
|
354
|
+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
355
|
+
defer cancel()
|
|
356
|
+
|
|
357
|
+
user, err := client.GetUser(ctx, &pb.GetUserRequest{Id: "123"})
|
|
358
|
+
if err != nil {
|
|
359
|
+
log.Fatalf("GetUser failed: %v", err)
|
|
360
|
+
}
|
|
361
|
+
log.Printf("User: %v", user)
|
|
362
|
+
|
|
363
|
+
// Server streaming
|
|
364
|
+
stream, err := client.ListUsers(ctx, &pb.ListUsersRequest{PageSize: 10})
|
|
365
|
+
if err != nil {
|
|
366
|
+
log.Fatalf("ListUsers failed: %v", err)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for {
|
|
370
|
+
user, err := stream.Recv()
|
|
371
|
+
if err == io.EOF {
|
|
372
|
+
break
|
|
373
|
+
}
|
|
374
|
+
if err != nil {
|
|
375
|
+
log.Fatalf("stream error: %v", err)
|
|
376
|
+
}
|
|
377
|
+
log.Printf("Received user: %v", user)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### TypeScript Client
|
|
383
|
+
|
|
384
|
+
```typescript
|
|
385
|
+
// client.ts
|
|
386
|
+
import * as grpc from '@grpc/grpc-js';
|
|
387
|
+
import * as protoLoader from '@grpc/proto-loader';
|
|
388
|
+
|
|
389
|
+
const packageDefinition = protoLoader.loadSync('./user.proto');
|
|
390
|
+
const proto = grpc.loadPackageDefinition(packageDefinition) as any;
|
|
391
|
+
|
|
392
|
+
const client = new proto.user.v1.UserService(
|
|
393
|
+
'localhost:50051',
|
|
394
|
+
grpc.credentials.createInsecure()
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Unary call
|
|
398
|
+
client.getUser({ id: '123' }, (err: any, response: any) => {
|
|
399
|
+
if (err) {
|
|
400
|
+
console.error('Error:', err);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
console.log('User:', response);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Server streaming
|
|
407
|
+
const stream = client.listUsers({ page_size: 10 });
|
|
408
|
+
stream.on('data', (user: any) => {
|
|
409
|
+
console.log('Received user:', user);
|
|
410
|
+
});
|
|
411
|
+
stream.on('end', () => {
|
|
412
|
+
console.log('Stream ended');
|
|
413
|
+
});
|
|
414
|
+
stream.on('error', (err: any) => {
|
|
415
|
+
console.error('Stream error:', err);
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Promisified client
|
|
419
|
+
function getUser(id: string): Promise<User> {
|
|
420
|
+
return new Promise((resolve, reject) => {
|
|
421
|
+
client.getUser({ id }, (err: any, response: any) => {
|
|
422
|
+
if (err) reject(err);
|
|
423
|
+
else resolve(response);
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Error Handling
|
|
430
|
+
|
|
431
|
+
### Status Codes
|
|
432
|
+
|
|
433
|
+
| Code | Name | Use Case |
|
|
434
|
+
|------|------|----------|
|
|
435
|
+
| 0 | OK | Success |
|
|
436
|
+
| 1 | CANCELLED | Client cancelled |
|
|
437
|
+
| 2 | UNKNOWN | Unknown error |
|
|
438
|
+
| 3 | INVALID_ARGUMENT | Bad request |
|
|
439
|
+
| 4 | DEADLINE_EXCEEDED | Timeout |
|
|
440
|
+
| 5 | NOT_FOUND | Resource not found |
|
|
441
|
+
| 6 | ALREADY_EXISTS | Resource exists |
|
|
442
|
+
| 7 | PERMISSION_DENIED | Not authorized |
|
|
443
|
+
| 8 | RESOURCE_EXHAUSTED | Rate limited |
|
|
444
|
+
| 9 | FAILED_PRECONDITION | Invalid state |
|
|
445
|
+
| 10 | ABORTED | Conflict |
|
|
446
|
+
| 11 | OUT_OF_RANGE | Invalid range |
|
|
447
|
+
| 12 | UNIMPLEMENTED | Not implemented |
|
|
448
|
+
| 13 | INTERNAL | Server error |
|
|
449
|
+
| 14 | UNAVAILABLE | Service unavailable |
|
|
450
|
+
| 16 | UNAUTHENTICATED | Not authenticated |
|
|
451
|
+
|
|
452
|
+
### Error Details
|
|
453
|
+
|
|
454
|
+
```go
|
|
455
|
+
import (
|
|
456
|
+
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
457
|
+
"google.golang.org/grpc/status"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
func validateRequest(req *pb.CreateUserRequest) error {
|
|
461
|
+
var violations []*errdetails.BadRequest_FieldViolation
|
|
462
|
+
|
|
463
|
+
if req.Email == "" {
|
|
464
|
+
violations = append(violations, &errdetails.BadRequest_FieldViolation{
|
|
465
|
+
Field: "email",
|
|
466
|
+
Description: "Email is required",
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if len(violations) > 0 {
|
|
471
|
+
st := status.New(codes.InvalidArgument, "validation failed")
|
|
472
|
+
br := &errdetails.BadRequest{FieldViolations: violations}
|
|
473
|
+
st, _ = st.WithDetails(br)
|
|
474
|
+
return st.Err()
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return nil
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
## Interceptors (Middleware)
|
|
482
|
+
|
|
483
|
+
```go
|
|
484
|
+
// Unary interceptor
|
|
485
|
+
func loggingInterceptor(
|
|
486
|
+
ctx context.Context,
|
|
487
|
+
req interface{},
|
|
488
|
+
info *grpc.UnaryServerInfo,
|
|
489
|
+
handler grpc.UnaryHandler,
|
|
490
|
+
) (interface{}, error) {
|
|
491
|
+
start := time.Now()
|
|
492
|
+
|
|
493
|
+
resp, err := handler(ctx, req)
|
|
494
|
+
|
|
495
|
+
log.Printf("method=%s duration=%s error=%v",
|
|
496
|
+
info.FullMethod,
|
|
497
|
+
time.Since(start),
|
|
498
|
+
err,
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
return resp, err
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Auth interceptor
|
|
505
|
+
func authInterceptor(
|
|
506
|
+
ctx context.Context,
|
|
507
|
+
req interface{},
|
|
508
|
+
info *grpc.UnaryServerInfo,
|
|
509
|
+
handler grpc.UnaryHandler,
|
|
510
|
+
) (interface{}, error) {
|
|
511
|
+
md, ok := metadata.FromIncomingContext(ctx)
|
|
512
|
+
if !ok {
|
|
513
|
+
return nil, status.Error(codes.Unauthenticated, "no metadata")
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
tokens := md.Get("authorization")
|
|
517
|
+
if len(tokens) == 0 {
|
|
518
|
+
return nil, status.Error(codes.Unauthenticated, "no token")
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
user, err := validateToken(tokens[0])
|
|
522
|
+
if err != nil {
|
|
523
|
+
return nil, status.Error(codes.Unauthenticated, "invalid token")
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
ctx = context.WithValue(ctx, userKey, user)
|
|
527
|
+
return handler(ctx, req)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Chain interceptors
|
|
531
|
+
server := grpc.NewServer(
|
|
532
|
+
grpc.ChainUnaryInterceptor(
|
|
533
|
+
loggingInterceptor,
|
|
534
|
+
authInterceptor,
|
|
535
|
+
),
|
|
536
|
+
)
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Best Practices
|
|
540
|
+
|
|
541
|
+
- **Use deadlines** - Always set timeouts on client calls
|
|
542
|
+
- **Handle streaming errors** - Check for EOF and errors in loops
|
|
543
|
+
- **Use interceptors** - For cross-cutting concerns (logging, auth, tracing)
|
|
544
|
+
- **Version your APIs** - Include version in package name
|
|
545
|
+
- **Document with comments** - Proto comments become documentation
|
|
546
|
+
- **Use well-known types** - google.protobuf.Timestamp, Empty, etc.
|
|
547
|
+
- **Implement health checks** - Use grpc-health-probe
|
|
548
|
+
- **Enable reflection** - For debugging with grpcurl
|
|
549
|
+
|
|
550
|
+
## References
|
|
551
|
+
|
|
552
|
+
- gRPC Documentation: https://grpc.io/docs/
|
|
553
|
+
- Protocol Buffers: https://protobuf.dev/
|
|
554
|
+
- gRPC Status Codes: https://grpc.io/docs/guides/status-codes/
|