mattermost/api/server/main.go
Jesse Hallam 885802eae7
Updated API Code Samples (#24141)
* api: remove PHP code samples

* api: remove Curl code samples

* api: remove Go code samples

* link out to marketplace exclusively for community-built drivers

* absolute path to V4_SRC

* programmatically extract x-codeSamples

* initial batch of examples

* Update api/server/main.go

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Update api/server/main.go

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* Update api/server/main.go

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>

* updated examples

---------

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
2023-08-03 12:45:10 -03:00

189 lines
4.5 KiB
Go

package main
import (
"bytes"
"log"
"os"
"text/template"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"github.com/pb33f/libopenapi"
v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
"golang.org/x/tools/imports"
)
// exampleText defines the template in which the corresponding ExampleClient4_* body is wrapped.
const exampleText = `
package main
import (
{{- range .Imports -}}
{{- if .}}
{{"\t"}}{{.}}
{{- else}}
{{"\t"}}{{end -}}
{{- end}}
)
func main() {
{{.Body -}}
}`
func main() {
var exampleTmpl = template.Must(template.New("example").Parse(exampleText))
if len(os.Args) <= 1 {
log.Fatal("Expected filename to APIv4 spec as argument")
}
filename := os.Args[1]
data, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("failed to read %s: %s", filename, err)
}
// Parse the Open APIv4 Spec
document, err := libopenapi.NewDocument(data)
if err != nil {
log.Fatalf("Failed to parse OpenAPI spec: %s", err)
}
v3Model, errors := document.BuildV3Model()
if len(errors) > 0 {
for i := range errors {
log.Printf("error: %s\n", errors[i])
}
log.Fatalf("cannot create v3 model from document: %d errors reported", len(errors))
}
applyExamples(v3Model, exampleTmpl)
// Re-render the file with the injected examples.
newDocument, _, _, errors := document.RenderAndReload()
if len(errors) > 0 {
for _, err := range errors {
log.Printf("error: %s\n", err)
}
log.Fatalf("cannot render document: %d errors reported", len(errors))
}
err = os.WriteFile(filename, newDocument, 0644)
if err != nil {
log.Fatal(err)
}
}
func applyExamples(v3Model *libopenapi.DocumentModel[v3high.Document], tmpl *template.Template) {
fileSet, modelFuncs, err := getModelFuncs()
if err != nil {
log.Fatalf("Failed to parse example funcs: %s", err)
}
for _, path := range v3Model.Model.Paths.PathItems {
applyExample(tmpl, fileSet, modelFuncs, path.Get)
applyExample(tmpl, fileSet, modelFuncs, path.Post)
applyExample(tmpl, fileSet, modelFuncs, path.Delete)
applyExample(tmpl, fileSet, modelFuncs, path.Options)
applyExample(tmpl, fileSet, modelFuncs, path.Head)
applyExample(tmpl, fileSet, modelFuncs, path.Patch)
applyExample(tmpl, fileSet, modelFuncs, path.Trace)
}
}
// applyExample looks through the functions in model_test to find an ExampleClient4_* matching the
// operation's unique identifier.
func applyExample(tmpl *template.Template, fileSet *token.FileSet, exampleFuncs []modelFunc, operation *v3high.Operation) {
// Not all of GET, POST, OPTIONS, etc. are defined for each operation.
if operation == nil {
return
}
var exampleFunction modelFunc
var found = false
for _, e := range exampleFuncs {
if e.FuncDecl.Name.Name == "ExampleClient4_"+operation.OperationId {
exampleFunction = e
found = true
break
}
}
if !found {
return
}
// Find all the imports used by the function so we can re-create a minimal example.
var fileImports []string
for _, i := range exampleFunction.File.Imports {
fileImports = append(fileImports, i.Path.Value)
}
// Render the example body using the template.
var body bytes.Buffer
err := printer.Fprint(&body, fileSet, exampleFunction.FuncDecl.Body.List)
if err != nil {
log.Fatal(err)
}
data := struct {
Imports []string
Body string
}{
fileImports,
body.String(),
}
// Process the resulting Go file to get the right indention, minimal set of imports, etc.
var unformattedExample bytes.Buffer
if err := tmpl.Execute(&unformattedExample, data); err != nil {
log.Fatalf("failed to render template: %v", err)
}
ignoredFilePath := "path"
example, err := imports.Process(ignoredFilePath, unformattedExample.Bytes(), nil)
if err != nil {
log.Fatal(err)
}
// Inject the resulting code sample
operation.Extensions["x-codeSamples"] = []struct {
Lang string
Source string
}{
{
Lang: "Go",
Source: string(example),
},
}
}
type modelFunc struct {
File *ast.File
FuncDecl *ast.FuncDecl
}
// getModelFuncs builds a fileset and function declaration set for the model/model_test packages.
func getModelFuncs() (*token.FileSet, []modelFunc, error) {
fileSet := token.NewFileSet()
packs, err := parser.ParseDir(fileSet, "../../server/public/model", nil, 0)
if err != nil {
return nil, nil, err
}
var examples []modelFunc
for _, pack := range packs {
for _, f := range pack.Files {
for _, d := range f.Decls {
if fn, isFn := d.(*ast.FuncDecl); isFn {
examples = append(examples, modelFunc{f, fn})
}
}
}
}
return fileSet, examples, nil
}