Skip to content

Instantly share code, notes, and snippets.

@Splizard
Created May 27, 2024 09:13
Show Gist options
  • Save Splizard/1d75d2efa397dcf9d0e2389afc805f3d to your computer and use it in GitHub Desktop.
Save Splizard/1d75d2efa397dcf9d0e2389afc805f3d to your computer and use it in GitHub Desktop.
Self-hosted runtime API documentation in Go.
package main
import (
"reflect"
"runtime"
"strings"
"runtime.link/api"
"runtime.link/api/rest"
)
type API struct {
api.Specification
Echo func(Message) Message `rest:"POST /echo"
echos back the input string.`
GOOS func() string `rest:"GET /goos"
returns the operating system of the server.`
Docs func() []Endpoint `rest:"GET /docs"
returns self-hosted documentation for the API`
}
type Message struct {
Text string `json:"text"
content of the message, should be human readable.`
}
type Endpoint struct {
Method string `json:"method"
is the HTTP method of the endpoint`
Path string `json:"path"
is the URL path of the endpoint`
Docs string `json:"docs"
is the human readable description of the endpoint`
Request any `json:"request"
that each endpoint accepts, including their data type and whether they are required or optional`
Response any `json:"response"
that represents the data type of the response`
}
func makeDocumentationFor(rtype reflect.Type, docs string) any {
switch rtype.Kind() {
case reflect.Struct:
var fields = make(map[string]any)
for i := 0; i < rtype.NumField(); i++ {
field := rtype.Field(i)
jsonName, _, _ := strings.Cut(string(field.Tag.Get("json")), ",")
_, docs, _ := strings.Cut(string(field.Tag), "\n")
docs = strings.Replace(docs, "\t", "", -1)
fields[jsonName] = makeDocumentationFor(field.Type, docs)
}
return fields
case reflect.Slice:
return []any{makeDocumentationFor(rtype.Elem(), docs)}
default:
return rtype.Kind().String() + " - " + docs
}
}
func main() {
rest.ListenAndServe(":8080", nil, API{
Echo: func(msg Message) Message { return msg },
GOOS: func() string { return runtime.GOOS },
Docs: func() []Endpoint {
spec := api.StructureOf(API{})
docs := make([]Endpoint, 0, len(spec.Functions))
for _, fn := range spec.Functions {
tag := fn.Tags.Get("rest")
method, path, _ := strings.Cut(tag, " ")
var endpoint = Endpoint{
Method: method,
Path: path,
Docs: fn.Docs,
}
if fn.Type.NumIn() > 0 {
endpoint.Request = makeDocumentationFor(fn.Type.In(0), "request")
}
if fn.Type.NumOut() > 0 {
endpoint.Response = makeDocumentationFor(fn.Type.Out(0), "response")
}
docs = append(docs, endpoint)
}
return docs
},
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment