package docgenopenrpc import ( "encoding/json" "go/ast" "net" "reflect" "github.com/alecthomas/jsonschema" go_openrpc_reflect "github.com/etclabscore/go-openrpc-reflect" "github.com/filecoin-project/lotus/api/docgen" "github.com/filecoin-project/lotus/build" "github.com/ipfs/go-cid" meta_schema "github.com/open-rpc/meta-schema" ) // 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 }