handler

package
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 1, 2024 License: BSD-3-Clause Imports: 9 Imported by: 37

Documentation

Overview

Package handler provides implementations of the jrpc2.Assigner interface, and support for adapting functions to jrpc2.Handler signature.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(fn any) jrpc2.Handler

New adapts a function to a jrpc2.Handler. The concrete value of fn must be function accepted by Check. The resulting jrpc2.Handler will handle JSON encoding and decoding, call fn, and report appropriate errors.

New is intended for use during program initialization, and will panic if the type of fn does not have one of the accepted forms. Programs that need to check for possible errors should call handler.Check directly, and use the Wrap method of the resulting FuncInfo to obtain the wrapper.

func NewPos added in v0.28.4

func NewPos(fn any, names ...string) jrpc2.Handler

NewPos adapts a function to a jrpc2.Handler. The concrete value of fn must be a function accepted by Positional. The resulting handler will handle JSON encoding and decoding, call fn, and report appropriate errors.

NewPos is intended for use during program initialization, and will panic if the type of fn does not have one of the accepted forms. Programs that need to check for possible errors should call handler.Positional directly, and use the Wrap method of the resulting FuncInfo to obtain the wrapper.

Types

type Args

type Args []any

Args is a wrapper that decodes an array of positional parameters into concrete locations.

Unmarshaling a JSON value into an Args value v succeeds if the JSON encodes an array with length len(v), and unmarshaling each subvalue i into the corresponding v[i] succeeds. As a special case, if v[i] == nil the corresponding value is discarded.

Marshaling an Args value v into JSON succeeds if each element of the slice is JSON marshalable, and yields a JSON array of length len(v) containing the JSON values corresponding to the elements of v.

Usage example:

func Handler(ctx context.Context, req *jrpc2.Request) (any, error) {
   var x, y int
   var s string

   if err := req.UnmarshalParams(&handler.Args{&x, &y, &s}); err != nil {
      return nil, err
   }
   // do useful work with x, y, and s
}
Example (Marshal)
package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2/handler"
)

func main() {
	bits, err := json.Marshal(handler.Args{1, "foo", false, nil})
	if err != nil {
		log.Fatalf("Encoding failed: %v", err)
	}
	fmt.Println(string(bits))
}
Output:

[1,"foo",false,null]
Example (Unmarshal)
package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2/handler"
)

func main() {
	const input = `[25, false, "apple"]`

	var count int
	var item string

	if err := json.Unmarshal([]byte(input), &handler.Args{&count, nil, &item}); err != nil {
		log.Fatalf("Decoding failed: %v", err)
	}
	fmt.Printf("count=%d, item=%q\n", count, item)
}
Output:

count=25, item="apple"

func (Args) MarshalJSON

func (a Args) MarshalJSON() ([]byte, error)

MarshalJSON supports JSON marshaling for a.

func (Args) UnmarshalJSON

func (a Args) UnmarshalJSON(data []byte) error

UnmarshalJSON supports JSON unmarshaling for a.

type Func

type Func = jrpc2.Handler

Func is a convenience alias for jrpc2.Handler.

type FuncInfo added in v0.21.2

type FuncInfo struct {
	Type         reflect.Type // the complete function type
	Argument     reflect.Type // the non-context argument type, or nil
	Result       reflect.Type // the non-error result type, or nil
	ReportsError bool         // true if the function reports an error
	// contains filtered or unexported fields
}

FuncInfo captures type signature information from a valid handler function.

func Check added in v0.21.2

func Check(fn any) (*FuncInfo, error)

Check checks whether fn can serve as a jrpc2.Handler. The concrete value of fn must be a function with one of the following type signature schemes, for JSON-marshalable types X and Y:

func(context.Context) error
func(context.Context) Y
func(context.Context) (Y, error)
func(context.Context, X) error
func(context.Context, X) Y
func(context.Context, X) (Y, error)
func(context.Context, *jrpc2.Request) error
func(context.Context, *jrpc2.Request) Y
func(context.Context, *jrpc2.Request) (Y, error)
func(context.Context, *jrpc2.Request) (any, error)

If fn does not have one of these forms, Check reports an error.

If the type of X is a struct or a pointer to a struct, the generated wrapper accepts JSON parameters as either an object or an array. The caller may disable array support by calling AllowArray(false). When enabled, array parameters are mapped to the fields of X in the order of field declaration, save that unexported fields are skipped. If a field has a `json:"-"` tag, it is also skipped. Anonymous fields are skipped unless they are tagged.

For other (non-struct) argument types, the accepted format is whatever the json.Unmarshal function can decode into the value. Note, however, that the JSON-RPC standard restricts encoded parameter values to arrays and objects. Check will accept argument types that cannot accept arrays or objects, but the wrapper will report an error when decoding the request. The recommended solution is to define a struct type for your parameters.

For a single arbitrary type, another approach is to use a 1-element array:

func(ctx context.Context, sp [1]string) error {
   s := sp[0] // pull the actual argument out of the array
   // ...
}

For more complex positional signatures, see also handler.Positional.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2/handler"
)

func main() {
	fi, err := handler.Check(func(_ context.Context, ss []string) int { return len(ss) })
	if err != nil {
		log.Fatalf("Check failed: %v", err)
	}
	fmt.Printf("Argument type: %v\n", fi.Argument)
	fmt.Printf("Result type:   %v\n", fi.Result)
	fmt.Printf("Reports error? %v\n", fi.ReportsError)
	fmt.Printf("Wrapped type:  %T\n", fi.Wrap())
}
Output:

Argument type: []string
Result type:   int
Reports error? false
Wrapped type:  func(context.Context, *jrpc2.Request) (interface {}, error)

func Positional added in v0.28.3

func Positional(fn any, names ...string) (*FuncInfo, error)

Positional checks whether fn can serve as a jrpc2.Handler. The concrete value of fn must be a function with one of the following type signature schemes:

func(context.Context, X1, X2, ..., Xn) (Y, error)
func(context.Context, X1, X2, ..., Xn) Y
func(context.Context, X1, X2, ..., Xn) error

for JSON-marshalable types X_i and Y. If fn does not have one of these forms, Positional reports an error. The given names must match the number of non-context arguments exactly. Variadic functions are not supported.

In contrast to Check, this function allows any number of arguments, but the caller must provide names for them. Positional creates an anonymous struct type whose fields correspond to the non-context arguments of fn. The names are used as the JSON field keys for the corresponding parameters.

When converted into a jrpc2.Handler, the wrapped function accepts either a JSON array with exactly n members, or a JSON object with the field keys named. For example, given:

func add(ctx context.Context, x, y int) int { return x + y }

fi, err := handler.Positional(add, "first", "second")
// ...
call := fi.Wrap()

the resulting handler by default accepts a JSON array with with (exactly) the same number of elements as the positional parameters:

[17, 23]

No arguments can be omitted in this format, but the caller can use a JSON "null" in place of any argument. The caller may also disable array support by setting AllowArray(false) on the resulting FuncInfo.

The handler will also accept a parameter object like:

{"first": 17, "second": 23}

where "first" is mapped to argument x and "second" to argument y. In this form, fields may be omitted, but unknown field keys generate an error. The object keys are taken from the arguments to Positional, not the parameter names declared on the function.

Example (Array)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2"
	"github.com/creachadair/jrpc2/handler"
	"github.com/creachadair/jrpc2/internal/testutil"
)

func describe(_ context.Context, name string, age int, isOld bool) error {
	fmt.Printf("%s is %d (old: %v)\n", name, age, isOld)
	return nil
}

func main() {
	call := handler.NewPos(describe, "name", "age", "isOld")

	req := mustParseReq(`{
	  "jsonrpc": "2.0",
	  "id": 1,
	  "method": "foo",
	  "params": ["Marvin", 973000, true]
	}`)
	if _, err := call(context.Background(), req); err != nil {
		log.Fatalf("Call: %v", err)
	}
}

func mustParseReq(s string) *jrpc2.Request {
	req, err := testutil.ParseRequest(s)
	if err != nil {
		log.Fatalf("ParseRequest: %v", err)
	}
	return req
}
Output:

Marvin is 973000 (old: true)
Example (Object)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2"
	"github.com/creachadair/jrpc2/handler"
	"github.com/creachadair/jrpc2/internal/testutil"
)

func describe(_ context.Context, name string, age int, isOld bool) error {
	fmt.Printf("%s is %d (old: %v)\n", name, age, isOld)
	return nil
}

func main() {
	call := handler.NewPos(describe, "name", "age", "isOld")

	req := mustParseReq(`{
	  "jsonrpc": "2.0",
	  "id": 1,
	  "method": "foo",
	  "params": {
	    "name":  "Dennis",
	    "age":   37,
	    "isOld": false
	  }
	}`)
	if _, err := call(context.Background(), req); err != nil {
		log.Fatalf("Call: %v", err)
	}
}

func mustParseReq(s string) *jrpc2.Request {
	req, err := testutil.ParseRequest(s)
	if err != nil {
		log.Fatalf("ParseRequest: %v", err)
	}
	return req
}
Output:

Dennis is 37 (old: false)

func (*FuncInfo) AllowArray added in v1.2.0

func (fi *FuncInfo) AllowArray(ok bool) *FuncInfo

AllowArray sets the flag on fi that determines whether the wrapper it generates allows struct arguments to be sent in array notation. If true, a parameter array is decoded into corresponding fields of the struct argument in declaration order; if false, array arguments report an error. The default value is currently true. This option has no effect for non-struct arguments.

func (*FuncInfo) SetStrict added in v0.39.0

func (fi *FuncInfo) SetStrict(strict bool) *FuncInfo

SetStrict sets the flag on fi that determines whether the wrapper it generates will enforce strict field checking. If set true, the wrapper will report an error when unmarshaling an object into a struct if the object contains fields unknown by the struct. Strict field checking has no effect for non-struct arguments.

func (*FuncInfo) Wrap added in v0.21.2

func (fi *FuncInfo) Wrap() jrpc2.Handler

Wrap adapts the function represented by fi to a jrpc2.Handler. The wrapped function can obtain the *jrpc2.Request value from its context argument using the jrpc2.InboundRequest helper.

This method panics if fi == nil or if it does not represent a valid function type. A FuncInfo returned by a successful call to Check is always valid.

type Map

type Map map[string]jrpc2.Handler

A Map is a trivial implementation of the jrpc2.Assigner interface that looks up method names in a static map of function values.

func (Map) Assign

func (m Map) Assign(_ context.Context, method string) jrpc2.Handler

Assign implements part of the jrpc2.Assigner interface.

func (Map) Names

func (m Map) Names() []string

Names implements the optional jrpc2.Namer extension interface.

type Obj added in v0.6.2

type Obj map[string]any

Obj is a wrapper that maps object fields into concrete locations.

Unmarshaling a JSON text into an Obj value v succeeds if the JSON encodes an object, and unmarshaling the value for each key k of the object into v[k] succeeds. If k does not exist in v, it is ignored.

Marshaling an Obj into JSON works as for an ordinary map.

Example (Unmarshal)
package main

import (
	"encoding/json"
	"fmt"
	"log"

	"github.com/creachadair/jrpc2/handler"
)

func main() {
	const input = `{"uid": 501, "name": "P. T. Barnum", "tags": [1, 3]}`

	var uid int
	var name string

	if err := json.Unmarshal([]byte(input), &handler.Obj{
		"uid":  &uid,
		"name": &name,
	}); err != nil {
		log.Fatalf("Decoding failed: %v", err)
	}
	fmt.Printf("uid=%d, name=%q\n", uid, name)
}
Output:

uid=501, name="P. T. Barnum"

func (Obj) UnmarshalJSON added in v0.6.2

func (o Obj) UnmarshalJSON(data []byte) error

UnmarshalJSON supports JSON unmarshaling into o.

type ServiceMap

type ServiceMap map[string]jrpc2.Assigner

A ServiceMap combines multiple assigners into one, permitting a server to export multiple services under different names.

func (ServiceMap) Assign

func (m ServiceMap) Assign(ctx context.Context, method string) jrpc2.Handler

Assign splits the inbound method name as Service.Method, and passes the Method portion to the corresponding Service assigner. If method does not have the form Service.Method, or if Service is not set in m, the lookup fails and returns nil.

func (ServiceMap) Names

func (m ServiceMap) Names() []string

Names reports the composed names of all the methods in the service, each having the form Service.Method.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL