From 0ab843138535ce330371b1535266d388d86fbc19 Mon Sep 17 00:00:00 2001 From: Lukas Jenicek Date: Wed, 2 Oct 2024 15:09:28 +0200 Subject: [PATCH] render annotations map for request paths and call onRequest hook if defined (#71) * call onDeprecate hook when method is deprecated * add request to deprecate hook * regenerate examples with latest webrpc * return error from OnRequest hook * rename to MethodAnnotationsFromContext * move annotations render to common (client, server) tmpl * move annotations above WebRPC services * rename to methodAnotations * rename to MethodAnnotationsFromCtx * call OnRequest before handler is called and handle err * use tabs * generate annotations for multiple services in the same map * prevent nil panics * render map[string]MethodCtx in template * get method ctx using request * dont export method * set updated ctx to Request * check if req != nil --- _examples/golang-basics/example.gen.go | 78 ++++++++++++++++++++++++-- _examples/golang-basics/example.ridl | 1 + _examples/golang-imports/api.gen.go | 62 +++++++++++++++++++- helpers.go.tmpl | 22 +++++++- server.go.tmpl | 14 +++++ types.go.tmpl | 18 ++++++ 6 files changed, 187 insertions(+), 8 deletions(-) diff --git a/_examples/golang-basics/example.gen.go b/_examples/golang-basics/example.gen.go index a59409b..204d609 100644 --- a/_examples/golang-basics/example.gen.go +++ b/_examples/golang-basics/example.gen.go @@ -1,6 +1,6 @@ -// example v0.0.1 3a3a4087149f1118f0b9494fa254afab46564a1b +// example v0.0.1 05b7a5c86b98738f4fe6ce9bb1fccd4af064847a // -- -// Code generated by webrpc-gen@v0.19.3 with ../../../gen-golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.19.3-1-g8d197ff with ../../../gen-golang generator. DO NOT EDIT. // // webrpc-gen -schema=example.ridl -target=../../../gen-golang -pkg=main -server -client -legacyErrors -fixEmptyArrays -out=./example.gen.go package main @@ -33,7 +33,7 @@ func WebRPCSchemaVersion() string { // Schema hash generated from your RIDL schema func WebRPCSchemaHash() string { - return "3a3a4087149f1118f0b9494fa254afab46564a1b" + return "05b7a5c86b98738f4fe6ce9bb1fccd4af064847a" } // @@ -160,6 +160,41 @@ type ComplexType struct { User *User `json:"user"` } +var ( + methods = map[string]method{ + "/rpc/ExampleService/Ping": { + Name: "Ping", + Service: "ExampleService", + Annotations: map[string]string{}, + }, + "/rpc/ExampleService/Status": { + Name: "Status", + Service: "ExampleService", + Annotations: map[string]string{"internal": ""}, + }, + "/rpc/ExampleService/Version": { + Name: "Version", + Service: "ExampleService", + Annotations: map[string]string{}, + }, + "/rpc/ExampleService/GetUser": { + Name: "GetUser", + Service: "ExampleService", + Annotations: map[string]string{}, + }, + "/rpc/ExampleService/FindUser": { + Name: "FindUser", + Service: "ExampleService", + Annotations: map[string]string{}, + }, + "/rpc/ExampleService/LogEvent": { + Name: "LogEvent", + Service: "ExampleService", + Annotations: map[string]string{}, + }, + } +) + var WebRPCServices = map[string][]string{ "ExampleService": { "Ping", @@ -213,7 +248,8 @@ type WebRPCServer interface { type exampleServiceServer struct { ExampleService - OnError func(r *http.Request, rpcErr *WebRPCError) + OnError func(r *http.Request, rpcErr *WebRPCError) + OnRequest func(w http.ResponseWriter, r *http.Request) error } func NewExampleServiceServer(svc ExampleService) *exampleServiceServer { @@ -236,6 +272,8 @@ func (s *exampleServiceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) ctx = context.WithValue(ctx, ServiceNameCtxKey, "ExampleService") + r = r.WithContext(ctx) + var handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) switch r.URL.Path { case "/rpc/ExampleService/Ping": @@ -271,6 +309,17 @@ func (s *exampleServiceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) switch contentType { case "application/json": + if s.OnRequest != nil { + if err := s.OnRequest(w, r); err != nil { + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) + return + } + } + handler(ctx, w, r) default: err := ErrWebrpcBadRequest.WithCause(fmt.Errorf("unsupported Content-Type %q (only application/json is allowed)", r.Header.Get("Content-Type"))) @@ -749,6 +798,12 @@ func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { // Helpers // +type method struct { + Name string + Service string + Annotations map[string]string +} + type contextKey struct { name string } @@ -782,6 +837,21 @@ func RequestFromContext(ctx context.Context) *http.Request { r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) return r } + +func MethodCtx(ctx context.Context) (method, bool) { + req := RequestFromContext(ctx) + if req == nil { + return method{}, false + } + + m, ok := methods[req.URL.Path] + if !ok { + return method{}, false + } + + return m, true +} + func ResponseWriterFromContext(ctx context.Context) http.ResponseWriter { w, _ := ctx.Value(HTTPResponseWriterCtxKey).(http.ResponseWriter) return w diff --git a/_examples/golang-basics/example.ridl b/_examples/golang-basics/example.ridl index 9b9f739..d60169f 100644 --- a/_examples/golang-basics/example.ridl +++ b/_examples/golang-basics/example.ridl @@ -92,6 +92,7 @@ service ExampleService # Status endpoint # # gives you current status of running application + @internal - Status() => (status: bool) - Version() => (version: Version) - GetUser(header: map, userID: uint64) => (user: User) diff --git a/_examples/golang-imports/api.gen.go b/_examples/golang-imports/api.gen.go index e454f74..ef953a8 100644 --- a/_examples/golang-imports/api.gen.go +++ b/_examples/golang-imports/api.gen.go @@ -1,6 +1,6 @@ -// example-api-service v1.0.0 eb9a5d4082a36a8cb84eaa4b3e1091fc4f1f6f3d +// example-api-service v1.0.0 cae4e128f4fb4c938bfe1ea312deeea3dfd6b6af // -- -// Code generated by webrpc-gen@v0.19.3 with ../../../gen-golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.19.3-1-g8d197ff with ../../../gen-golang generator. DO NOT EDIT. // // webrpc-gen -schema=./proto/api.ridl -target=../../../gen-golang -out=./api.gen.go -pkg=main -server -client -legacyErrors=true -fmt=false package main @@ -30,7 +30,7 @@ func WebRPCSchemaVersion() string { // Schema hash generated from your RIDL schema func WebRPCSchemaHash() string { - return "eb9a5d4082a36a8cb84eaa4b3e1091fc4f1f6f3d" + return "cae4e128f4fb4c938bfe1ea312deeea3dfd6b6af" } // @@ -84,6 +84,26 @@ func (x *Location) Is(values ...Location) bool { return false } +var ( + methods = map[string]method{ + "/rpc/ExampleAPI/Ping": { + Name: "Ping", + Service: "ExampleAPI", + Annotations: map[string]string{}, + }, + "/rpc/ExampleAPI/Status": { + Name: "Status", + Service: "ExampleAPI", + Annotations: map[string]string{}, + }, + "/rpc/ExampleAPI/GetUsers": { + Name: "GetUsers", + Service: "ExampleAPI", + Annotations: map[string]string{}, + }, + } +) + var WebRPCServices = map[string][]string{ "ExampleAPI": { "Ping", @@ -129,6 +149,7 @@ type WebRPCServer interface { type exampleAPIServer struct { ExampleAPI OnError func(r *http.Request, rpcErr *WebRPCError) + OnRequest func(w http.ResponseWriter, r *http.Request) error } func NewExampleAPIServer(svc ExampleAPI) *exampleAPIServer { @@ -151,6 +172,8 @@ func (s *exampleAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) ctx = context.WithValue(ctx, ServiceNameCtxKey, "ExampleAPI") + r = r.WithContext(ctx) + var handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) switch r.URL.Path { case "/rpc/ExampleAPI/Ping": @@ -180,6 +203,17 @@ func (s *exampleAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch contentType { case "application/json": + if s.OnRequest != nil { + if err := s.OnRequest(w, r); err != nil { + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) + return + } + } + handler(ctx, w, r) default: err := ErrWebrpcBadRequest.WithCause(fmt.Errorf("unsupported Content-Type %q (only application/json is allowed)", r.Header.Get("Content-Type"))) @@ -490,6 +524,12 @@ func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) { // Helpers // +type method struct { + Name string + Service string + Annotations map[string]string +} + type contextKey struct { name string } @@ -523,6 +563,22 @@ func RequestFromContext(ctx context.Context) *http.Request { r, _ := ctx.Value(HTTPRequestCtxKey).(*http.Request) return r } + +func MethodCtx(ctx context.Context) (method, bool) { + req := RequestFromContext(ctx) + if req == nil { + return method{}, false + } + + m, ok := methods[req.URL.Path] + if !ok { + return method{}, false + } + + return m, true +} + + func ResponseWriterFromContext(ctx context.Context) http.ResponseWriter { w, _ := ctx.Value(HTTPResponseWriterCtxKey).(http.ResponseWriter) return w diff --git a/helpers.go.tmpl b/helpers.go.tmpl index c743309..ae0c538 100644 --- a/helpers.go.tmpl +++ b/helpers.go.tmpl @@ -5,6 +5,12 @@ // Helpers // +type method struct { + Name string + Service string + Annotations map[string]string +} + type contextKey struct { name string } @@ -43,7 +49,21 @@ func RequestFromContext(ctx context.Context) *http.Request { return r } -{{- if $opts.server}} +func MethodCtx(ctx context.Context) (method, bool) { + req := RequestFromContext(ctx) + if req == nil { + return method{}, false + } + + m, ok := methods[req.URL.Path] + if !ok { + return method{}, false + } + + return m, true +} + +{{ if $opts.server}} func ResponseWriterFromContext(ctx context.Context) http.ResponseWriter { w, _ := ctx.Value(HTTPResponseWriterCtxKey).(http.ResponseWriter) return w diff --git a/server.go.tmpl b/server.go.tmpl index 67dc7f3..4bb6a89 100644 --- a/server.go.tmpl +++ b/server.go.tmpl @@ -20,6 +20,7 @@ type WebRPCServer interface { type {{$serviceName}} struct { {{$typePrefix}}{{$service.Name}} OnError func(r *http.Request, rpcErr *WebRPCError) + OnRequest func(w http.ResponseWriter, r *http.Request) error } func New{{firstLetterToUpper $service.Name}}Server(svc {{$typePrefix}}{{.Name}}) *{{$serviceName}} { @@ -42,6 +43,8 @@ func (s *{{$serviceName}}) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) ctx = context.WithValue(ctx, ServiceNameCtxKey, "{{.Name}}") + r = r.WithContext(ctx) + var handler func(ctx context.Context, w http.ResponseWriter, r *http.Request) switch r.URL.Path { {{- range $_, $method := $service.Methods}} @@ -69,6 +72,17 @@ func (s *{{$serviceName}}) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch contentType { case "application/json": + if s.OnRequest != nil { + if err := s.OnRequest(w, r); err != nil { + rpcErr, ok := err.(WebRPCError) + if !ok { + rpcErr = ErrWebrpcEndpoint.WithCause(err) + } + s.sendErrorJSON(w, r, rpcErr) + return + } + } + handler(ctx, w, r) default: err := ErrWebrpcBadRequest.WithCause(fmt.Errorf("unsupported Content-Type %q (only application/json is allowed)", r.Header.Get("Content-Type"))) diff --git a/types.go.tmpl b/types.go.tmpl index 9b88d38..a9cefeb 100644 --- a/types.go.tmpl +++ b/types.go.tmpl @@ -29,6 +29,24 @@ {{- end }} +var ( + methods = map[string]method{ + {{- range $_, $service := $services -}} + {{- range $_, $method := $service.Methods }} + "/rpc/{{$service.Name}}/{{$method.Name}}": { + Name: "{{$method.Name}}", + Service: "{{$service.Name}}", + Annotations: map[string]string{ + {{- range $_, $annotation := $method.Annotations -}} + "{{$annotation.AnnotationType}}": "{{$annotation.Value}}", + {{- end -}} + }, + }, + {{- end -}} + {{ end }} + } +) + var WebRPCServices = map[string][]string{ {{- range $_, $service := $services}} "{{$service.Name}}": {