163 lines
4.9 KiB
Go
163 lines
4.9 KiB
Go
package docgenopenrpc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"go/ast"
|
|
"net"
|
|
"reflect"
|
|
|
|
"github.com/alecthomas/jsonschema"
|
|
go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect"
|
|
"github.com/ipfs/go-cid"
|
|
meta_schema "github.com/open-rpc/meta-schema"
|
|
|
|
"github.com/filecoin-project/lotus/api/docgen"
|
|
"github.com/filecoin-project/lotus/build"
|
|
)
|
|
|
|
// schemaDictEntry represents a type association passed to the jsonschema reflector.
|
|
type schemaDictEntry struct {
|
|
example interface{}
|
|
rawJson string
|
|
}
|
|
|
|
const integerD = `{
|
|
"title": "number",
|
|
"type": "number",
|
|
"description": "Number is a number"
|
|
}`
|
|
|
|
const cidCidD = `{"title": "Content Identifier", "type": "string", "description": "Cid represents a self-describing content addressed identifier. It is formed by a Version, a Codec (which indicates a multicodec-packed content type) and a Multihash."}`
|
|
|
|
func OpenRPCSchemaTypeMapper(ty reflect.Type) *jsonschema.Type {
|
|
unmarshalJSONToJSONSchemaType := func(input string) *jsonschema.Type {
|
|
var js jsonschema.Type
|
|
err := json.Unmarshal([]byte(input), &js)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return &js
|
|
}
|
|
|
|
if ty.Kind() == reflect.Ptr {
|
|
ty = ty.Elem()
|
|
}
|
|
|
|
if ty == reflect.TypeOf((*interface{})(nil)).Elem() {
|
|
return &jsonschema.Type{Type: "object", AdditionalProperties: []byte("true")}
|
|
}
|
|
|
|
// Second, handle other types.
|
|
// Use a slice instead of a map because it preserves order, as a logic safeguard/fallback.
|
|
dict := []schemaDictEntry{
|
|
{cid.Cid{}, cidCidD},
|
|
}
|
|
|
|
for _, d := range dict {
|
|
if reflect.TypeOf(d.example) == ty {
|
|
tt := unmarshalJSONToJSONSchemaType(d.rawJson)
|
|
|
|
return tt
|
|
}
|
|
}
|
|
|
|
// Handle primitive types in case there are generic cases
|
|
// specific to our services.
|
|
switch ty.Kind() {
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
// Return all integer types as the hex representation integer schemea.
|
|
ret := unmarshalJSONToJSONSchemaType(integerD)
|
|
return ret
|
|
case reflect.Uintptr:
|
|
return &jsonschema.Type{Type: "number", Title: "uintptr-title"}
|
|
case reflect.Struct:
|
|
case reflect.Map:
|
|
case reflect.Slice, reflect.Array:
|
|
case reflect.Float32, reflect.Float64:
|
|
case reflect.Bool:
|
|
case reflect.String:
|
|
case reflect.Ptr, reflect.Interface:
|
|
default:
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewLotusOpenRPCDocument defines application-specific documentation and configuration for its OpenRPC document.
|
|
func NewLotusOpenRPCDocument(Comments, GroupDocs map[string]string) *go_openrpc_reflect.Document {
|
|
d := &go_openrpc_reflect.Document{}
|
|
|
|
// Register "Meta" document fields.
|
|
// These include getters for
|
|
// - Servers object
|
|
// - Info object
|
|
// - ExternalDocs object
|
|
//
|
|
// These objects represent server-specific data that cannot be
|
|
// reflected.
|
|
d.WithMeta(&go_openrpc_reflect.MetaT{
|
|
GetServersFn: func() func(listeners []net.Listener) (*meta_schema.Servers, error) {
|
|
return func(listeners []net.Listener) (*meta_schema.Servers, error) {
|
|
return nil, nil
|
|
}
|
|
},
|
|
GetInfoFn: func() (info *meta_schema.InfoObject) {
|
|
info = &meta_schema.InfoObject{}
|
|
title := "Lotus RPC API"
|
|
info.Title = (*meta_schema.InfoObjectProperties)(&title)
|
|
|
|
version := build.BuildVersion
|
|
info.Version = (*meta_schema.InfoObjectVersion)(&version)
|
|
return info
|
|
},
|
|
GetExternalDocsFn: func() (exdocs *meta_schema.ExternalDocumentationObject) {
|
|
return nil // FIXME
|
|
},
|
|
})
|
|
|
|
// Use a provided Ethereum default configuration as a base.
|
|
appReflector := &go_openrpc_reflect.EthereumReflectorT{}
|
|
|
|
// Install overrides for the json schema->type map fn used by the jsonschema reflect package.
|
|
appReflector.FnSchemaTypeMap = func() func(ty reflect.Type) *jsonschema.Type {
|
|
return OpenRPCSchemaTypeMapper
|
|
}
|
|
|
|
appReflector.FnIsMethodEligible = func(m reflect.Method) bool {
|
|
for i := 0; i < m.Func.Type().NumOut(); i++ {
|
|
if m.Func.Type().Out(i).Kind() == reflect.Chan {
|
|
return false
|
|
}
|
|
}
|
|
return go_openrpc_reflect.EthereumReflector.IsMethodEligible(m)
|
|
}
|
|
appReflector.FnGetMethodName = func(moduleName string, r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) {
|
|
if m.Name == "ID" {
|
|
return moduleName + "_ID", nil
|
|
}
|
|
if moduleName == "rpc" && m.Name == "Discover" {
|
|
return "rpc.discover", nil
|
|
}
|
|
|
|
return moduleName + "." + m.Name, nil
|
|
}
|
|
|
|
appReflector.FnGetMethodSummary = func(r reflect.Value, m reflect.Method, funcDecl *ast.FuncDecl) (string, error) {
|
|
if v, ok := Comments[m.Name]; ok {
|
|
return v, nil
|
|
}
|
|
return "", nil // noComment
|
|
}
|
|
|
|
appReflector.FnSchemaExamples = func(ty reflect.Type) (examples *meta_schema.Examples, err error) {
|
|
v := docgen.ExampleValue("unknown", ty, ty) // This isn't ideal, but seems to work well enough.
|
|
return &meta_schema.Examples{
|
|
meta_schema.AlwaysTrue(v),
|
|
}, nil
|
|
}
|
|
|
|
// Finally, register the configured reflector to the document.
|
|
d.WithReflector(appReflector)
|
|
return d
|
|
}
|