Files
komari/pkg/rpc/request.go
T
2025-11-14 18:21:23 +08:00

228 lines
6.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package rpc
import (
"encoding/json"
"errors"
"reflect"
"strings"
)
// JsonRpcRequest 表示 JSON-RPC 2.0 请求(单个或批量中的一个元素)
// 说明:为了能区分 Notification(无 id),这里将 ID 定义为 any 并打上 omitempty。
// 你可以通过 req.HasID() 判断是否为普通请求(需要返回)还是通知。
type JsonRpcRequest struct {
Version string `json:"jsonrpc"` // 必须 === "2.0"
Method string `json:"method"` // 方法名;以 "rpc." 前缀的为保留内部方法
Params any `json:"params,omitempty"` // 参数(位置数组或命名对象)
ID any `json:"id,omitempty"` // 字符串 / 数值 / nullNotification 时省略
}
// NewRequest 创建一个普通请求(带 id)
func NewRequest(id any, method string, params any) *JsonRpcRequest {
return &JsonRpcRequest{Version: RPC_VERSION, Method: method, Params: params, ID: id}
}
// NewNotification 创建一个 Notification(无 id,不会收到返回)
func NewNotification(method string, params any) *JsonRpcRequest {
return &JsonRpcRequest{Version: RPC_VERSION, Method: method, Params: params}
}
// HasID 判断是否包含 idNotification 没有 id,不需要返回)
func (r *JsonRpcRequest) HasID() bool { return r != nil && r.ID != nil }
// Validate 校验请求格式合法性(不校验方法是否存在)
func (r *JsonRpcRequest) Validate() *JsonRpcError {
if r == nil {
return &JsonRpcError{Code: InvalidRequest, Message: "invalid request: null"}
}
if r.Version != RPC_VERSION {
return &JsonRpcError{Code: InvalidRequest, Message: "invalid jsonrpc version"}
}
if strings.TrimSpace(r.Method) == "" {
return &JsonRpcError{Code: InvalidRequest, Message: "method required"}
}
return nil
}
// GetParams(兼容旧接口):按名称获取参数(仅 map Object 情况),没有则 target 不变
func (r *JsonRpcRequest) GetParams(name string, target *any) {
if r == nil || r.Params == nil || target == nil {
return
}
if m, ok := r.Params.(map[string]any); ok {
*target = m[name]
}
}
// GetParamAs 获取具名参数并尝试转换为类型 T
func GetParamAs[T any](req *JsonRpcRequest, name string) (val T, ok bool) {
if req == nil || req.Params == nil {
return
}
if m, isMap := req.Params.(map[string]any); isMap {
raw, exists := m[name]
if !exists {
return
}
if v, good := raw.(T); good {
return v, true
}
b, err := json.Marshal(raw)
if err != nil {
return
}
var t T
if err = json.Unmarshal(b, &t); err != nil {
return
}
return t, true
}
return
}
// GetPositionalParamAs 获取位置参数 idx 的值为 T
func GetPositionalParamAs[T any](req *JsonRpcRequest, idx int) (val T, ok bool) {
if req == nil || req.Params == nil {
return
}
if arr, isArr := req.Params.([]any); isArr {
if idx < 0 || idx >= len(arr) {
return
}
raw := arr[idx]
if v, good := raw.(T); good {
return v, true
}
b, err := json.Marshal(raw)
if err != nil {
return
}
var t T
if err = json.Unmarshal(b, &t); err != nil {
return
}
return t, true
}
return
}
// BindParams 将 Params 绑定到给定结构体指针。
// 支持:
// 1. object(map) -> 按字段名反序列化(标准 encoding/json 行为,大小写不敏感)
// 2. array([]any) -> 若 target 是结构体指针,按导出字段声明顺序依次填充;
// 数组更短: 剩余字段保持零值;数组更长: 忽略多余元素。
// 非结构体指针则退回原逻辑整体反序列化。
// 3. 单一标量 -> 若 target 是结构体指针,则赋值给第一个导出字段;否则整体反序列化。
// 4. 其它类型 -> 直接整体反序列化。
func (r *JsonRpcRequest) BindParams(target any) error {
if r == nil {
return errors.New("nil request")
}
if target == nil || reflect.ValueOf(target).Kind() != reflect.Ptr {
return errors.New("target must be pointer")
}
if r.Params == nil {
return nil
}
switch p := r.Params.(type) {
case map[string]any:
b, err := json.Marshal(p)
if err != nil {
return err
}
return json.Unmarshal(b, target)
case []any:
// 特殊处理:struct 指针时按字段顺序映射
rv := reflect.ValueOf(target).Elem()
if rv.Kind() == reflect.Struct {
rt := rv.Type()
ai := 0
for i := 0; i < rt.NumField() && ai < len(p); i++ {
f := rt.Field(i)
if f.PkgPath != "" { // 非导出字段跳过(不占位置)
continue
}
fv := rv.Field(i)
raw := p[ai]
ai++
// 快速路径:可直接赋值
if raw != nil {
val := reflect.ValueOf(raw)
if val.IsValid() {
if val.Type().AssignableTo(fv.Type()) {
fv.Set(val)
continue
}
if val.Type().ConvertibleTo(fv.Type()) {
fv.Set(val.Convert(fv.Type()))
continue
}
}
}
// 回退:通过 JSON 做一次精确转换(处理数字 float64 -> int 等)
b, err := json.Marshal(raw)
if err != nil {
return err
}
tmp := reflect.New(fv.Type())
if err = json.Unmarshal(b, tmp.Interface()); err != nil {
return err
}
fv.Set(tmp.Elem())
}
return nil
}
// 非 struct 情况,退回整体解码
b, err := json.Marshal(p)
if err != nil {
return err
}
return json.Unmarshal(b, target)
default:
// 单一标量到结构体首个导出字段
rv := reflect.ValueOf(target).Elem()
if rv.Kind() == reflect.Struct {
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i)
if f.PkgPath != "" { // 非导出
continue
}
fv := rv.Field(i)
raw := p
if raw != nil {
val := reflect.ValueOf(raw)
if val.IsValid() {
if val.Type().AssignableTo(fv.Type()) {
fv.Set(val)
return nil
}
if val.Type().ConvertibleTo(fv.Type()) {
fv.Set(val.Convert(fv.Type()))
return nil
}
}
}
// fallback json 转换
b, err := json.Marshal(raw)
if err != nil {
return err
}
tmp := reflect.New(fv.Type())
if err = json.Unmarshal(b, tmp.Interface()); err != nil {
return err
}
fv.Set(tmp.Elem())
return nil
}
// 无导出字段
return nil
}
b, err := json.Marshal(p)
if err != nil {
return err
}
return json.Unmarshal(b, target)
}
}