Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
952 changes: 476 additions & 476 deletions _examples/golang-basics/example.gen.go

Large diffs are not rendered by default.

361 changes: 171 additions & 190 deletions _examples/golang-imports/api.gen.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion _examples/golang-imports/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package main

//go:generate go run github.com/webrpc/webrpc/cmd/webrpc-gen -schema=./proto/api.ridl -target=../../../gen-golang -out=./api.gen.go -pkg=main -server -client -fmt=false
//go:generate go run github.com/webrpc/webrpc/cmd/webrpc-gen -schema=./proto/api.ridl -target=../../../gen-golang -out=./api.gen.go -pkg=main -server -client

import (
"context"
Expand Down
120 changes: 0 additions & 120 deletions client.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -187,125 +187,5 @@ func (r *streamReader) handleReadError(err error) error {

{{- end }}

// HTTPClient is the interface used by generated clients to send HTTP requests.
// It is fulfilled by *(net/http).Client, which is sufficient for most users.
// Users can provide their own implementation for special retry policies.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

// urlBase helps ensure that addr specifies a scheme. If it is unparsable
// as a URL, it returns addr unchanged.
func urlBase(addr string) string {
// If the addr specifies a scheme, use it. If not, default to
// http. If url.Parse fails on it, return it unchanged.
url, err := url.Parse(addr)
if err != nil {
return addr
}
if url.Scheme == "" {
url.Scheme = "http"
}
return url.String()
}

// newRequest makes an http.Request from a client, adding common headers.
func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Accept", contentType)
req.Header.Set("Content-Type", contentType)
{{- if eq $opts.webrpcHeader true }}
req.Header.Set(WebrpcHeader, WebrpcHeaderValue)
{{- end }}
if headers, ok := HTTPRequestHeaders(ctx); ok {
for k := range headers {
for _, v := range headers[k] {
req.Header.Add(k, v)
}
}
}
return req, nil
}

// doHTTPRequest is common code to make a request to the remote service.
func doHTTPRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) (*http.Response, error) {
reqBody, err := {{$json}}.Marshal(in)
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("failed to marshal JSON body: %w", err)
}
if err = ctx.Err(); err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("aborted because context was done: %w", err)
}

req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json")
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("could not build request: %w", err)
}

resp, err := client.Do(req)
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCause(err)
}

if resp.StatusCode != 200 {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to read server error response body: %w", err)
}

var rpcErr WebRPCError
if err := {{$json}}.Unmarshal(respBody, &rpcErr); err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal server error: %w", err)
}
if rpcErr.Cause != "" {
rpcErr.cause = errors.New(rpcErr.Cause)
}
return nil, rpcErr
}

if out != nil {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to read response body: %w", err)
}

err = {{$json}}.Unmarshal(respBody, &out)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal JSON response body: %w", err)
}
}

return resp, nil
}

func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) {
if _, ok := h["Accept"]; ok {
return nil, errors.New("provided header cannot set Accept")
}
if _, ok := h["Content-Type"]; ok {
return nil, errors.New("provided header cannot set Content-Type")
}

copied := make(http.Header, len(h))
for k, vv := range h {
if vv == nil {
copied[k] = nil
continue
}
copied[k] = make([]string, len(vv))
copy(copied[k], vv)
}

return context.WithValue(ctx, HTTPClientRequestHeadersCtxKey, copied), nil
}

func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) {
h, ok := ctx.Value(HTTPClientRequestHeadersCtxKey).(http.Header)
return h, ok
}

{{- end -}}
{{- end -}}
129 changes: 129 additions & 0 deletions clientHelpers.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{{- define "clientHelpers"}}
{{- $json := .Json -}}
{{- $opts := .Opts -}}

//
// Client helpers
//

// HTTPClient is the interface used by generated clients to send HTTP requests.
// It is fulfilled by *(net/http).Client, which is sufficient for most users.
// Users can provide their own implementation for special retry policies.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}

// urlBase helps ensure that addr specifies a scheme. If it is unparsable
// as a URL, it returns addr unchanged.
func urlBase(addr string) string {
// If the addr specifies a scheme, use it. If not, default to
// http. If url.Parse fails on it, return it unchanged.
url, err := url.Parse(addr)
if err != nil {
return addr
}
if url.Scheme == "" {
url.Scheme = "http"
}
return url.String()
}

// newRequest makes an http.Request from a client, adding common headers.
func newRequest(ctx context.Context, url string, reqBody io.Reader, contentType string) (*http.Request, error) {
req, err := http.NewRequestWithContext(ctx, "POST", url, reqBody)
if err != nil {
return nil, err
}
req.Header.Set("Accept", contentType)
req.Header.Set("Content-Type", contentType)
{{- if eq $opts.webrpcHeader true }}
req.Header.Set(WebrpcHeader, WebrpcHeaderValue)
{{- end }}
if headers, ok := HTTPRequestHeaders(ctx); ok {
for k := range headers {
for _, v := range headers[k] {
req.Header.Add(k, v)
}
}
}
return req, nil
}

// doHTTPRequest is common code to make a request to the remote service.
func doHTTPRequest(ctx context.Context, client HTTPClient, url string, in, out interface{}) (*http.Response, error) {
reqBody, err := {{$json}}.Marshal(in)
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("failed to marshal JSON body: %w", err)
}
if err = ctx.Err(); err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("aborted because context was done: %w", err)
}

req, err := newRequest(ctx, url, bytes.NewBuffer(reqBody), "application/json")
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCausef("could not build request: %w", err)
}

resp, err := client.Do(req)
if err != nil {
return nil, ErrWebrpcRequestFailed.WithCause(err)
}

if resp.StatusCode != 200 {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to read server error response body: %w", err)
}

var rpcErr WebRPCError
if err := {{$json}}.Unmarshal(respBody, &rpcErr); err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal server error: %w", err)
}
if rpcErr.Cause != "" {
rpcErr.cause = errors.New(rpcErr.Cause)
}
return nil, rpcErr
}

if out != nil {
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to read response body: %w", err)
}

err = {{$json}}.Unmarshal(respBody, &out)
if err != nil {
return nil, ErrWebrpcBadResponse.WithCausef("failed to unmarshal JSON response body: %w", err)
}
}

return resp, nil
}

func WithHTTPRequestHeaders(ctx context.Context, h http.Header) (context.Context, error) {
if _, ok := h["Accept"]; ok {
return nil, errors.New("provided header cannot set Accept")
}
if _, ok := h["Content-Type"]; ok {
return nil, errors.New("provided header cannot set Content-Type")
}

copied := make(http.Header, len(h))
for k, vv := range h {
if vv == nil {
copied[k] = nil
continue
}
copied[k] = make([]string, len(vv))
copy(copied[k], vv)
}

return context.WithValue(ctx, HTTPClientRequestHeadersCtxKey, copied), nil
}

func HTTPRequestHeaders(ctx context.Context) (http.Header, bool) {
h, ok := ctx.Value(HTTPClientRequestHeadersCtxKey).(http.Header)
return h, ok
}

{{- end -}}
50 changes: 50 additions & 0 deletions clientInterface.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{{- define "clientInterface"}}
{{- $services := .Services -}}
{{- $typeMap := .TypeMap -}}
{{- $typePrefix := .TypePrefix -}}
{{- $json := .Json -}}
{{- $opts := .Opts -}}

{{- if $services -}}
//
// Client interface
//

{{ if and $services $opts.types -}}
{{ range $_, $service := $services -}}
type {{$service.Name}}Client interface {
{{- range $_, $method := $service.Methods}}
{{- $deprecated := index $method.Annotations "deprecated" -}}
{{- if gt (len $method.Comments) 0 }}
{{- range $_, $comment := $method.Comments }}
// {{ replaceAll $comment "\"" "'" }}
{{- end }}
{{- if $deprecated }}
//
{{- end }}
{{- end }}
{{- if $deprecated }}
// Deprecated: {{ $deprecated.Value }}
{{- end }}
{{ if eq $method.StreamOutput true -}}
{{$method.Name}}(ctx context.Context{{range $_, $input := $method.Inputs}}, {{$input.Name}} {{template "field" dict "Name" $input.Name "Type" $input.Type "TypeMap" $typeMap "TypePrefix" $typePrefix "Optional" $input.Optional "TypeMeta" $input.Meta "Succinct" $method.Succinct}}{{end}}) ({{$method.Name}}StreamReader, error)
{{- else -}}
{{$method.Name}}(ctx context.Context{{range $_, $input := $method.Inputs}}, {{$input.Name}} {{template "field" dict "Name" $input.Name "Type" $input.Type "TypeMap" $typeMap "TypePrefix" $typePrefix "Optional" $input.Optional "TypeMeta" $input.Meta "Succinct" $method.Succinct}}{{end}}) {{if len .Outputs}}({{end}}{{range $i, $output := .Outputs}}{{template "field" dict "Name" $output.Name "Type" $output.Type "TypeMap" $typeMap "TypePrefix" $typePrefix "Optional" $output.Optional "TypeMeta" $output.Meta}}{{if lt $i (len $method.Outputs)}}, {{end}}{{end}}error{{if len $method.Outputs}}){{end}}
{{- end -}}

{{- end}}
}

{{- range $_, $method := $service.Methods }}
{{ if eq $method.StreamOutput true -}}
type {{$method.Name}}StreamReader interface {
Read() ({{range $i, $output := $method.Outputs}}{{if gt $i 0}}, {{end}}{{$output.Name}} {{template "field" dict "Name" $output.Name "Type" $output.Type "Optional" $output.Optional "TypeMap" $typeMap "TypePrefix" $typePrefix "TypeMeta" $output.Meta}}{{end}}, err error)
}
{{ end }}
{{- end }}

{{- end -}}
{{- end -}}

{{- end -}}
{{- end -}}
2 changes: 1 addition & 1 deletion enum_string.go.tmpl → enumString.go.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{{- define "enum_string" -}}
{{- define "enumString" -}}
{{- $name := .Name -}}
{{- $type := .Type -}}
{{- $fields := .Fields -}}
Expand Down
5 changes: 0 additions & 5 deletions errors.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ func (e WebRPCError) WithCausef(format string, args ...interface{}) WebRPCError
return err
}

// Deprecated: Use .WithCause() method on WebRPCError.
func ErrorWithCause(rpcErr WebRPCError, cause error) WebRPCError {
return rpcErr.WithCause(cause)
}

{{- if $opts.errorStackTrace }}

func (e WebRPCError) StackFrames() []uintptr {
Expand Down
16 changes: 7 additions & 9 deletions helpers.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{{ $opts := .Opts -}}

//
// Helpers
// Webrpc helpers
//

type method struct {
Expand Down Expand Up @@ -38,17 +38,15 @@ func (k *contextKey) String() string {

var (
{{- if $opts.client}}
HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"}
HTTPClientRequestHeadersCtxKey = &contextKey{"HTTPClientRequestHeaders"} // client
{{- end}}

{{- if $opts.server}}
HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"}
{{ end}}
HTTPRequestCtxKey = &contextKey{"HTTPRequest"}

ServiceNameCtxKey = &contextKey{"ServiceName"}

MethodNameCtxKey = &contextKey{"MethodName"}
HTTPResponseWriterCtxKey = &contextKey{"HTTPResponseWriter"} // server
{{- end }}
HTTPRequestCtxKey = &contextKey{"HTTPRequest"} // server
ServiceNameCtxKey = &contextKey{"ServiceName"} // server
MethodNameCtxKey = &contextKey{"MethodName"} // server
)

func ServiceNameFromContext(ctx context.Context) string {
Expand Down
20 changes: 0 additions & 20 deletions imports.go.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,4 @@ import (
{{- end }}
)

{{- if eq $opts.json "jsoniter" }}

// Opinionated configuration for -json=jsoniter encoding.
// Reference: https://github.com/json-iterator/go/blob/master/config.go
var jsonCfg = jsoniter.Config{
ValidateJsonRawMessage: true,
}.Froze()

{{- else if eq $opts.json "sonic" }}

// Opinionated configuration for -json=sonic encoding.
// Reference: https://github.com/bytedance/sonic/blob/main/api.go
var jsonCfg = sonic.Config{
NoNullSliceOrMap: true, // Encode nil slices/maps as '[]'/'{}' instead of 'null' to prevent runtime issues in JavaScript.
NoValidateJSONMarshaler: true, // Skip validation of JSON output from types implementing json.Marshaler to enhance performance.
NoValidateJSONSkip: true, // Bypass validation when skipping over JSON values during decoding, improving efficiency.
}.Froze()

{{- end -}}

{{- end -}}
Loading