mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
189 lines
4.5 KiB
Go
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
|
||
|
}
|