From 10705b10efbd8ea5d0b00115ebc741a95e15c3cb Mon Sep 17 00:00:00 2001 From: Lukas Jenicek Date: Mon, 30 Sep 2024 10:38:31 +0200 Subject: [PATCH] add client and server versions of generators (webrpc, schema and ridl schema) --- _examples/golang-basics/example.gen.go | 88 +++++++++++++++++++++++-- _examples/golang-basics/example.ridl | 9 ++- _examples/golang-basics/example_test.go | 8 +++ _examples/golang-basics/main.go | 24 +++++++ _examples/golang-imports/api.gen.go | 69 ++++++++++++++++++- client.go.tmpl | 1 + main.go.tmpl | 64 ++++++++++++++++++ server.go.tmpl | 2 + 8 files changed, 257 insertions(+), 8 deletions(-) diff --git a/_examples/golang-basics/example.gen.go b/_examples/golang-basics/example.gen.go index 204d609..9441e1a 100644 --- a/_examples/golang-basics/example.gen.go +++ b/_examples/golang-basics/example.gen.go @@ -1,6 +1,6 @@ -// example v0.0.1 05b7a5c86b98738f4fe6ce9bb1fccd4af064847a +// example v0.0.1 1d8e79836f13f0f32db0beaa2c049263409d4c6b // -- -// Code generated by webrpc-gen@v0.19.3-1-g8d197ff with ../../../gen-golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.20.0-6-g209939c 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 @@ -21,6 +21,10 @@ import ( "github.com/google/uuid" ) +const WebRPCHeader = "webrpc@v0.20.0-6-g209939c;gen-golang@unknown;example@v0.0.1" + +const WebRPCHeaderName = "Webrpc" + // WebRPC description and code-gen version func WebRPCVersion() string { return "v1" @@ -33,7 +37,67 @@ func WebRPCSchemaVersion() string { // Schema hash generated from your RIDL schema func WebRPCSchemaHash() string { - return "05b7a5c86b98738f4fe6ce9bb1fccd4af064847a" + return "1d8e79836f13f0f32db0beaa2c049263409d4c6b" +} + +type WebRPCGenVersions struct { + WebRPCGenVersion string + CodeGenName string + CodeGenVersion string + SchemaName string + SchemaVersion string +} + +func ServerVersion() (*WebRPCGenVersions, error) { + versions, err := parseWebRPCGenVersions(WebRPCHeader) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func VersionFromHeader(h http.Header) (*WebRPCGenVersions, error) { + if h.Get(WebRPCHeaderName) == "" { + return nil, fmt.Errorf("header is empty or missing") + } + + versions, err := parseWebRPCGenVersions(h.Get(WebRPCHeaderName)) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func parseWebRPCGenVersions(header string) (*WebRPCGenVersions, error) { + versions := strings.Split(header, ";") + if len(versions) < 3 { + return nil, fmt.Errorf("expected at least 3 parts while parsing webrpc header: %v", header) + } + + _, webrpcGenVersion, ok := strings.Cut(versions[0], "@") + if !ok { + return nil, fmt.Errorf("webrpc gen version could not be parsed from: %s", versions[0]) + } + + tmplTarget, tmplVersion, ok := strings.Cut(versions[1], "@") + if !ok { + return nil, fmt.Errorf("tmplTarget and tmplVersion could not be parsed from: %s", versions[1]) + } + + schemaName, schemaVersion, ok := strings.Cut(versions[2], "@") + if !ok { + return nil, fmt.Errorf("schema name and schema version could not be parsed from: %s", versions[2]) + } + + return &WebRPCGenVersions{ + WebRPCGenVersion: webrpcGenVersion, + CodeGenName: tmplTarget, + CodeGenVersion: tmplVersion, + SchemaName: schemaName, + SchemaVersion: schemaVersion, + }, nil } // @@ -143,9 +207,11 @@ type SearchFilter struct { } type Version struct { - WebrpcVersion string `json:"webrpcVersion"` - SchemaVersion string `json:"schemaVersion"` - SchemaHash string `json:"schemaHash"` + WebrpcVersion string `json:"webrpcVersion"` + SchemaVersion string `json:"schemaVersion"` + SchemaHash string `json:"schemaHash"` + ClientGenVersion *GenVersions `json:"clientGenVersion"` + ServerGenVersion *GenVersions `json:"serverGenVersion"` } type ComplexType struct { @@ -160,6 +226,13 @@ type ComplexType struct { User *User `json:"user"` } +type GenVersions struct { + WebRPCGenVersion string `json:"WebRPCGenVersion"` + TmplTarget string `json:"TmplTarget"` + TmplVersion string `json:"TmplVersion"` + SchemaVersion string `json:"SchemaVersion"` +} + var ( methods = map[string]method{ "/rpc/ExampleService/Ping": { @@ -267,6 +340,8 @@ func (s *exampleServiceServer) ServeHTTP(w http.ResponseWriter, r *http.Request) } }() + w.Header().Set(WebRPCHeaderName, WebRPCHeader) + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) @@ -707,6 +782,7 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType } req.Header.Set("Accept", contentType) req.Header.Set("Content-Type", contentType) + req.Header.Set(WebRPCHeaderName, WebRPCHeader) if headers, ok := HTTPRequestHeaders(ctx); ok { for k := range headers { for _, v := range headers[k] { diff --git a/_examples/golang-basics/example.ridl b/_examples/golang-basics/example.ridl index d60169f..66c6730 100644 --- a/_examples/golang-basics/example.ridl +++ b/_examples/golang-basics/example.ridl @@ -2,7 +2,6 @@ webrpc = v1 # version of webrpc schema format (ridl or json) name = example # name of your backend app version = v0.0.1 # version of your schema - # user role # which defines which type of operations user can do @@ -69,6 +68,8 @@ struct Version - webrpcVersion: string - schemaVersion: string - schemaHash: string + - clientGenVersion: GenVersions + - serverGenVersion: GenVersions struct ComplexType - meta: map @@ -81,6 +82,12 @@ struct ComplexType - mapOfUsers: map - user: User +struct GenVersions + - WebRPCGenVersion: string + - TmplTarget: string + - TmplVersion: string + - SchemaVersion: string + error 500100 MissingArgument "missing argument" error 500101 InvalidUsername "invalid username" error 400100 MemoryFull "system memory is full" diff --git a/_examples/golang-basics/example_test.go b/_examples/golang-basics/example_test.go index 1da0f52..7340a19 100644 --- a/_examples/golang-basics/example_test.go +++ b/_examples/golang-basics/example_test.go @@ -38,6 +38,14 @@ func TestStatus(t *testing.T) { assert.NoError(t, err) } +func TestVersion(t *testing.T) { + version, err := client.Version(context.Background()) + + assert.NoError(t, err) + assert.NotNil(t, version.ClientGenVersion) + assert.NotNil(t, version.ServerGenVersion) +} + func TestGetUser(t *testing.T) { { arg1 := map[string]string{"a": "1"} diff --git a/_examples/golang-basics/main.go b/_examples/golang-basics/main.go index 61b5a50..6af3b7e 100644 --- a/_examples/golang-basics/main.go +++ b/_examples/golang-basics/main.go @@ -48,10 +48,34 @@ func (rpc *ExampleServiceRPC) Status(ctx context.Context) (bool, error) { } func (rpc *ExampleServiceRPC) Version(ctx context.Context) (*Version, error) { + serverVersions, err := ServerVersion() + if err != nil { + return nil, fmt.Errorf("parse server webrpc gen versions: %w", err) + } + + req := RequestFromContext(ctx) + + clientVersions, err := VersionFromHeader(req.Header) + if err != nil { + return nil, fmt.Errorf("parse client webrpc gen versions: %w", err) + } + return &Version{ WebrpcVersion: WebRPCVersion(), SchemaVersion: WebRPCSchemaVersion(), SchemaHash: WebRPCSchemaHash(), + ClientGenVersion: &GenVersions{ + WebRPCGenVersion: clientVersions.WebRPCGenVersion, + TmplTarget: clientVersions.CodeGenName, + TmplVersion: clientVersions.CodeGenVersion, + SchemaVersion: clientVersions.CodeGenVersion, + }, + ServerGenVersion: &GenVersions{ + WebRPCGenVersion: serverVersions.WebRPCGenVersion, + TmplTarget: serverVersions.CodeGenName, + TmplVersion: serverVersions.CodeGenVersion, + SchemaVersion: serverVersions.CodeGenVersion, + }, }, nil } diff --git a/_examples/golang-imports/api.gen.go b/_examples/golang-imports/api.gen.go index ef953a8..2231b18 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 cae4e128f4fb4c938bfe1ea312deeea3dfd6b6af // -- -// Code generated by webrpc-gen@v0.19.3-1-g8d197ff with ../../../gen-golang generator. DO NOT EDIT. +// Code generated by webrpc-gen@v0.20.0-6-g209939c 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 @@ -18,6 +18,10 @@ import ( ) +const WebRPCHeader = "webrpc@v0.20.0-6-g209939c;gen-golang@unknown;example-api-service@v1.0.0" + +const WebRPCHeaderName = "Webrpc" + // WebRPC description and code-gen version func WebRPCVersion() string { return "v1" @@ -33,6 +37,66 @@ func WebRPCSchemaHash() string { return "cae4e128f4fb4c938bfe1ea312deeea3dfd6b6af" } +type WebRPCGenVersions struct { + WebRPCGenVersion string + CodeGenName string + CodeGenVersion string + SchemaName string + SchemaVersion string +} + +func ServerVersion() (*WebRPCGenVersions, error) { + versions, err := parseWebRPCGenVersions(WebRPCHeader) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func VersionFromHeader(h http.Header) (*WebRPCGenVersions, error) { + if h.Get(WebRPCHeaderName) == "" { + return nil, fmt.Errorf("header is empty or missing") + } + + versions, err := parseWebRPCGenVersions(h.Get(WebRPCHeaderName)) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func parseWebRPCGenVersions(header string) (*WebRPCGenVersions, error) { + versions := strings.Split(header, ";") + if len(versions) < 3 { + return nil, fmt.Errorf("expected at least 3 parts while parsing webrpc header: %v", header) + } + + _, webrpcGenVersion, ok := strings.Cut(versions[0], "@") + if !ok { + return nil, fmt.Errorf("webrpc gen version could not be parsed from: %s", versions[0]) + } + + tmplTarget, tmplVersion, ok := strings.Cut(versions[1], "@") + if !ok { + return nil, fmt.Errorf("tmplTarget and tmplVersion could not be parsed from: %s", versions[1]) + } + + schemaName, schemaVersion, ok := strings.Cut(versions[2], "@") + if !ok { + return nil, fmt.Errorf("schema name and schema version could not be parsed from: %s", versions[2]) + } + + return &WebRPCGenVersions{ + WebRPCGenVersion: webrpcGenVersion, + CodeGenName: tmplTarget, + CodeGenVersion: tmplVersion, + SchemaName: schemaName, + SchemaVersion: schemaVersion, + }, nil +} + // // Common types // @@ -167,6 +231,8 @@ func (s *exampleAPIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() + w.Header().Set(WebRPCHeaderName, WebRPCHeader) + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r) @@ -433,6 +499,7 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType } req.Header.Set("Accept", contentType) req.Header.Set("Content-Type", contentType) + req.Header.Set(WebRPCHeaderName, WebRPCHeader) if headers, ok := HTTPRequestHeaders(ctx); ok { for k := range headers { for _, v := range headers[k] { diff --git a/client.go.tmpl b/client.go.tmpl index 23f14d9..b7ee378 100644 --- a/client.go.tmpl +++ b/client.go.tmpl @@ -208,6 +208,7 @@ func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType } req.Header.Set("Accept", contentType) req.Header.Set("Content-Type", contentType) + req.Header.Set(WebRPCHeaderName, WebRPCHeader) if headers, ok := HTTPRequestHeaders(ctx); ok { for k := range headers { for _, v := range headers[k] { diff --git a/main.go.tmpl b/main.go.tmpl index e298dd4..2614eb0 100644 --- a/main.go.tmpl +++ b/main.go.tmpl @@ -81,6 +81,10 @@ package {{get $opts "pkg"}} {{template "imports" dict "Types" .Types "Opts" $opts }} +const WebRPCHeader = "{{ .WebrpcHeader }}" + +const WebRPCHeaderName = "Webrpc" + // WebRPC description and code-gen version func WebRPCVersion() string { return "{{.WebrpcVersion}}" @@ -96,6 +100,66 @@ func WebRPCSchemaHash() string { return "{{.SchemaHash}}" } +type WebRPCGenVersions struct { + WebRPCGenVersion string + CodeGenName string + CodeGenVersion string + SchemaName string + SchemaVersion string +} + +func ServerVersion() (*WebRPCGenVersions, error) { + versions, err := parseWebRPCGenVersions(WebRPCHeader) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func VersionFromHeader(h http.Header) (*WebRPCGenVersions, error) { + if h.Get(WebRPCHeaderName) == "" { + return nil, fmt.Errorf("header is empty or missing") + } + + versions, err := parseWebRPCGenVersions(h.Get(WebRPCHeaderName)) + if err != nil { + return nil, fmt.Errorf("webrpc header is invalid: %w", err) + } + + return versions, nil +} + +func parseWebRPCGenVersions(header string) (*WebRPCGenVersions, error) { + versions := strings.Split(header, ";") + if len(versions) < 3 { + return nil, fmt.Errorf("expected at least 3 parts while parsing webrpc header: %v", header) + } + + _, webrpcGenVersion, ok := strings.Cut(versions[0], "@") + if !ok { + return nil, fmt.Errorf("webrpc gen version could not be parsed from: %s", versions[0]) + } + + tmplTarget, tmplVersion, ok := strings.Cut(versions[1], "@") + if !ok { + return nil, fmt.Errorf("tmplTarget and tmplVersion could not be parsed from: %s", versions[1]) + } + + schemaName, schemaVersion, ok := strings.Cut(versions[2], "@") + if !ok { + return nil, fmt.Errorf("schema name and schema version could not be parsed from: %s", versions[2]) + } + + return &WebRPCGenVersions{ + WebRPCGenVersion: webrpcGenVersion, + CodeGenName: tmplTarget, + CodeGenVersion: tmplVersion, + SchemaName: schemaName, + SchemaVersion: schemaVersion, + }, nil +} + {{- printf "\n" -}} {{- if eq $opts.importTypesFrom "" }} diff --git a/server.go.tmpl b/server.go.tmpl index 4bb6a89..5c20846 100644 --- a/server.go.tmpl +++ b/server.go.tmpl @@ -38,6 +38,8 @@ func (s *{{$serviceName}}) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() + w.Header().Set(WebRPCHeaderName, WebRPCHeader) + ctx := r.Context() ctx = context.WithValue(ctx, HTTPResponseWriterCtxKey, w) ctx = context.WithValue(ctx, HTTPRequestCtxKey, r)