这是indexloc提供的服务,不要输入任何密码
Skip to content
Closed
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
110 changes: 110 additions & 0 deletions jsonrpc/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package jsonrpc

import (
"reflect"

"github.com/danielgtaylor/huma/v2"
)

type JSONRPCErrorCode int

const (
// ParseError defines invalid JSON was received by the server.
// An error occurred on the server while parsing the JSON text.
ParseError JSONRPCErrorCode = -32700

// InvalidRequestError defines the JSON sent is not a valid Request object.
InvalidRequestError JSONRPCErrorCode = -32600

// MethodNotFoundError defines the method does not exist / is not available.
MethodNotFoundError JSONRPCErrorCode = -32601

// InvalidParamsError defines invalid method parameter(s).
InvalidParamsError JSONRPCErrorCode = -32602

// InternalError defines a server error
InternalError JSONRPCErrorCode = -32603
)

var errorMessage = map[JSONRPCErrorCode]string{
ParseError: "An error occurred on the server while parsing JSON object.",
InvalidRequestError: "The JSON sent is not a valid Request object.",
MethodNotFoundError: "The method does not exist / is not available.",
InvalidParamsError: "Invalid method parameter(s).",
InternalError: "Internal JSON-RPC error.",
}

func GetDefaultErrorMessage(code JSONRPCErrorCode) string {
return errorMessage[code]
}

// Error defines a JSON RPC error that can be returned in a Response from the spec
// http://www.jsonrpc.org/specification#error_object
type JSONRPCError struct {
// The error type that occurred.
Code JSONRPCErrorCode `json:"code"`

// A short description of the error. The message SHOULD be limited to a concise
// single sentence.
Message string `json:"message"`

// Additional information about the error. The value of this member is defined by
// the sender (e.g. detailed error information, nested errors etc.).
Data interface{} `json:"data,omitempty"`
}

// Error implements error.
func (e JSONRPCError) Error() string {
if e.Message != "" {
return e.Message
}
return errorMessage[e.Code]
}

// ErrorCode returns the JSON RPC error code associated with the error.
func (e JSONRPCError) ErrorCode() JSONRPCErrorCode {
return e.Code
}

type ResponseStatusError struct {
Response[any]
status int `json:"-"`
}

func (e *ResponseStatusError) Error() string {
if e.Response.Error != nil {
return e.Response.Error.Message
}
return ""
}

func (e *ResponseStatusError) GetStatus() int {
return e.status
}

func (e ResponseStatusError) Schema(r huma.Registry) *huma.Schema {

errorObjectSchema := r.Schema(reflect.TypeOf(e.Response.Error), true, "")

responseObjectSchema := &huma.Schema{
Type: huma.TypeObject,
Required: []string{"jsonrpc"},
Properties: map[string]*huma.Schema{
"jsonrpc": {
Type: huma.TypeString,
Enum: []any{"2.0"},
Description: "JSON-RPC version, must be '2.0'",
},
"id": {
Description: "Request identifier. Compulsory for method responses. This MUST be null to the client in case of parse errors etc.",
OneOf: []*huma.Schema{
{Type: huma.TypeInteger},
{Type: huma.TypeString},
},
},
"error": errorObjectSchema,
},
}

return responseObjectSchema
}
115 changes: 115 additions & 0 deletions jsonrpc/example/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package example

import (
"context"

"github.com/danielgtaylor/huma/v2/jsonrpc"
)

// /////////////// Handlers /////////////////

// AddParams defines the parameters for the "add" method
type AddParams struct {
A int `json:"a"`
B int `json:"b"`
}

type AddResult struct {
Sum int `json:"sum"`
}

type NotifyParams struct {
Message string `json:"message"`
}

// ConcatParams defines the parameters for the "concat" method
type ConcatParams struct {
S1 string `json:"s1"`
S2 string `json:"s2"`
}

// PingParams defines the parameters for the "ping" notification
type PingParams struct {
Message string `json:"message"`
}

// AddEndpoint is the handler for the "add" method
func AddEndpoint(ctx context.Context, params AddParams) (AddResult, error) {
res := params.A + params.B
return AddResult{Sum: res}, nil
}

// ConcatEndpoint is the handler for the "concat" method
func ConcatEndpoint(ctx context.Context, params ConcatParams) (string, error) {
return params.S1 + params.S2, nil
}

// PingEndpoint is the handler for the "ping" notification
func PingEndpoint(ctx context.Context, params PingParams) error {
return nil
}

func NotifyEndpoint(ctx context.Context, params NotifyParams) error {
// Process notification
return nil
}

func GetMethodHandlers() map[string]jsonrpc.IMethodHandler {
// Define method maps
methodMap := map[string]jsonrpc.IMethodHandler{
"add": &jsonrpc.MethodHandler[AddParams, AddResult]{Endpoint: AddEndpoint},
"addpositional": &jsonrpc.MethodHandler[[]int, AddResult]{
Endpoint: func(ctx context.Context, params []int) (AddResult, error) {
res := 0
for _, v := range params {
res += v
}
return AddResult{Sum: res}, nil
},
},
"concat": &jsonrpc.MethodHandler[ConcatParams, string]{Endpoint: ConcatEndpoint},
"concatOptionalIn": &jsonrpc.MethodHandler[*ConcatParams, string]{
Endpoint: func(ctx context.Context, params *ConcatParams) (string, error) {
if params != nil {
return params.S1 + params.S2, nil
}
return "", nil
},
},
"concatOptionalInOut": &jsonrpc.MethodHandler[*ConcatParams, *string]{
Endpoint: func(ctx context.Context, params *ConcatParams) (*string, error) {
if params != nil {
r := params.S1 + params.S2
return &r, nil
}
return nil, nil
},
},
"echo": &jsonrpc.MethodHandler[any, any]{
Endpoint: func(ctx context.Context, _ any) (any, error) {
return nil, nil
},
},
"echooptional": &jsonrpc.MethodHandler[*string, *string]{
Endpoint: func(ctx context.Context, e *string) (*string, error) {
return e, nil
},
},
}

return methodMap

}

func GetNotificationHandlers() map[string]jsonrpc.INotificationHandler {

notificationMap := map[string]jsonrpc.INotificationHandler{
"ping": &jsonrpc.NotificationHandler[PingParams]{Endpoint: PingEndpoint},
"notify": &jsonrpc.NotificationHandler[NotifyParams]{
Endpoint: NotifyEndpoint,
},
}

return notificationMap

}
103 changes: 103 additions & 0 deletions jsonrpc/example/httpsse_cli.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package example

import (
"context"
"fmt"
"log"
"net/http"
"runtime/debug"
"time"

"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
"github.com/danielgtaylor/huma/v2/humacli"
"github.com/danielgtaylor/huma/v2/jsonrpc"
)

// CLI options can be added as needed
type Options struct {
Host string `doc:"Host to listen on" default:"localhost"`
Port int `doc:"Port to listen on" default:"8080"`
Debug bool `doc:"Enable debug logs" default:"false"`
}

// This is a huma middleware.
// Either a huma middleware can be added or a http handler middleware can be added
func loggingMiddleware(ctx huma.Context, next func(huma.Context)) {
// log.Printf("Received request: %v %v", ctx.URL().RawPath, ctx.Operation().Path)
next(ctx)
// log.Printf("Responded to request: %v %v", ctx.URL().RawPath, ctx.Operation().Path)
}

// This is a http handler middleware.
// PanicRecoveryMiddleware recovers from panics in handlers
func PanicRecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// Log the panic to stderr
log.Printf("Recovered from panic: %+v", err)

// Optionally, log the stack trace
log.Printf("%s", debug.Stack())

// Return a 500 Internal Server Error
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}

func SetupSSETransport() http.Handler {
// Use default go router
router := http.NewServeMux()

api := humago.New(router, huma.DefaultConfig("Example JSONRPC API", "1.0.0"))
// Add any middlewares
api.UseMiddleware(loggingMiddleware)
handler := PanicRecoveryMiddleware(router)

// Init the servers method and notifications handlers
methodMap := GetMethodHandlers()
notificationMap := GetNotificationHandlers()
op := jsonrpc.GetDefaultOperation()
// Register the methods
jsonrpc.Register(api, op, methodMap, notificationMap)

return handler
}

func GetHTTPServerCLI() humacli.CLI {

cli := humacli.New(func(hooks humacli.Hooks, opts *Options) {
log.Printf("Options are %+v\n", opts)
handler := SetupSSETransport()
// Initialize the http server
server := http.Server{
Addr: fmt.Sprintf("%s:%d", opts.Host, opts.Port),
Handler: handler,
}

// Hook the HTTP server.
hooks.OnStart(func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
})

hooks.OnStop(func() {
// Gracefully shutdown your server here
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = server.Shutdown(ctx)
})
})

return cli
}

func StartHTTPServer() {
cli := GetHTTPServerCLI()
cli.Run()
}
Loading