Skip to content

Commit

Permalink
Add OpenAPI documentation page
Browse files Browse the repository at this point in the history
  • Loading branch information
sudorandom committed Aug 20, 2024
1 parent fc4cded commit 5ef7da2
Show file tree
Hide file tree
Showing 6 changed files with 1,703 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
.DS_Store
tmp
116 changes: 112 additions & 4 deletions cmd/fauxrpc/cmd_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import (
"errors"
"fmt"
"log"
"log/slog"
"net/http"
"os"
"strings"

"connectrpc.com/grpcreflect"
"connectrpc.com/vanguard"
plugin_go "github.com/golang/protobuf/protoc-gen-go/plugin"
"github.com/sudorandom/fauxrpc/private/protobuf"
"github.com/sudorandom/protoc-gen-connect-openapi/converter"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/pluginpb"
"gopkg.in/yaml.v3"
)

type RunCmd struct {
Expand All @@ -34,8 +41,9 @@ func (c *RunCmd) Run(globals *Globals) error {
}

// TODO: Add --no-reflection option
// TODO: Add --no-openapi option
// TODO: Load descriptors from stdin (assume protocol descriptors in binary format)
// TODO: way more options for data generator, including a stub service for registering stubs
// TODO: add a stub service for registering stubs

serviceNames := []string{}
vgservices := []*vanguard.Service{}
Expand All @@ -58,12 +66,112 @@ func (c *RunCmd) Run(globals *Globals) error {
mux.Handle("/", transcoder)
mux.Handle(grpcreflect.NewHandlerV1(reflector))
mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector))

// OpenAPI Stuff
resp, err := convertToOpenAPISpec(registry)
if err != nil {
return err
}
mux.Handle("GET /fauxrpc.openapi.html", singleFileHandler(openapiHTML))
mux.Handle("GET /fauxrpc.openapi.yaml", singleFileHandler(resp.File[0].GetContent()))

server := &http.Server{
Addr: c.Addr,
Handler: h2c.NewHandler(mux, &http2.Server{}),
}

slog.Info(fmt.Sprintf("Listening on http://%s", c.Addr))
slog.Info(fmt.Sprintf("See available methods: buf curl --http2-prior-knowledge http://%s --list-methods", c.Addr))
fmt.Printf("FauxRPC (%s)", fullVersion())
fmt.Printf("Listening on http://%s\n", c.Addr)
fmt.Printf("OpenAPI documentation: http://%s/fauxrpc.openapi.html\n", c.Addr)
fmt.Println()
fmt.Println("Example Commands:")
fmt.Printf("$ buf curl --http2-prior-knowledge http://%s --list-methods\n", c.Addr)
fmt.Printf("$ buf curl --http2-prior-knowledge http://%s/[METHOD_NAME]\n", c.Addr)
return server.ListenAndServe()
}

func convertToOpenAPISpec(registry *protobuf.ServiceRegistry) (*pluginpb.CodeGeneratorResponse, error) {
req := new(plugin_go.CodeGeneratorRequest)
files := registry.Files()
files.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
if fd.Services().Len() > 0 {
req.FileToGenerate = append(req.FileToGenerate, string(fd.Path()))
}
req.ProtoFile = append(req.ProtoFile, protodesc.ToFileDescriptorProto(fd.ParentFile()))
return true
})
openapiBaseFile, err := os.CreateTemp("", "base.*.openapi.yaml")
if err != nil {
return nil, err
}
defer openapiBaseFile.Close()

descBuilder := strings.Builder{}
descBuilder.WriteString("This is a [FauxRPC](https://fauxrpc.com/) server that is currently hosting the following services:\n")
registry.ForEachService(func(sd protoreflect.ServiceDescriptor) {
descBuilder.WriteString("- ")
descBuilder.WriteString(string(sd.FullName()))
descBuilder.WriteByte('\n')
})
descBuilder.WriteByte('\n')
descBuilder.WriteString("FauxRPC is a mock server that supports gRPC, gRPC-Web, Connect and HTTP/JSON transcoding.")

base := openAPIBase{
OpenAPI: "3.1.0",
Info: openAPIBaseInfo{
Title: "FauxRPC Generated Documentation",
Description: descBuilder.String(),
Version: strings.TrimPrefix(version, "v"),
},
}
b, err := yaml.Marshal(base)
if err != nil {
return nil, err
}
if _, err := openapiBaseFile.Write(b); err != nil {
return nil, err
}

req.Parameter = proto.String(fmt.Sprintf("path=all.openapi.yaml,base=%s", openapiBaseFile.Name()))

return converter.Convert(req)
}

func singleFileHandler(content string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, content)
}
}

const openapiHTML = `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Elements in HTML</title>
<!-- Embed elements Elements via Web Component -->
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
</head>
<body>
<elements-api
apiDescriptionUrl="/fauxrpc.openapi.yaml"
router="hash"
layout="sidebar"
/>
</body>
</html>
`

type openAPIBaseInfo struct {
Description string `yaml:"description"`
Title string `yaml:"title"`
Version string `yaml:"version"`
}
type openAPIBase struct {
OpenAPI string `yaml:"openapi"`
Info openAPIBaseInfo `yaml:"info"`
}
2 changes: 1 addition & 1 deletion cmd/fauxrpc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ func (n *staticNames) Names() []string {
}

func fullVersion() string {
return fmt.Sprintf("fauxrpc: %s (%s) @ %s; %s", version, commit, date, runtime.Version())
return fmt.Sprintf("%s (%s) @ %s; %s", version, commit, date, runtime.Version())
}
18 changes: 14 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,34 @@ require (
github.com/alecthomas/kong v0.9.0
github.com/brianvoe/gofakeit/v7 v7.0.4
github.com/bufbuild/protocompile v0.14.0
github.com/bufbuild/protovalidate-go v0.6.3
github.com/bufbuild/protovalidate-go v0.6.4
github.com/bufbuild/protoyaml-go v0.1.11
github.com/golang/protobuf v1.5.4
github.com/stretchr/testify v1.9.0
golang.org/x/net v0.26.0
github.com/sudorandom/protoc-gen-connect-openapi v0.8.10
golang.org/x/net v0.28.0
google.golang.org/protobuf v1.34.2
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 // indirect
github.com/google/cel-go v0.21.0 // indirect
github.com/google/gnostic v0.7.0 // indirect
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 // indirect
github.com/lmittmann/tint v1.0.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/pb33f/libopenapi v0.16.14 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 5ef7da2

Please sign in to comment.