Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
192 lines
4.8 KiB
Go
192 lines
4.8 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"
|
|
"github.com/pb33f/libopenapi/orderedmap"
|
|
"go.yaml.in/yaml/v4"
|
|
"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, err := document.BuildV3Model()
|
|
if err != nil {
|
|
log.Fatalf("cannot create v3 model from document: %s", err)
|
|
}
|
|
|
|
applyExamples(v3Model, exampleTmpl)
|
|
|
|
// Re-render the file with the injected examples.
|
|
newDocument, _, _, err := document.RenderAndReload()
|
|
if err != nil {
|
|
log.Fatalf("cannot render document: %s", err)
|
|
}
|
|
|
|
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.ValuesFromOldest() {
|
|
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
|
|
type codeSample struct {
|
|
Lang string `yaml:"lang"`
|
|
Source string `yaml:"source"`
|
|
}
|
|
yamlBytes, err := yaml.Marshal([]codeSample{{Lang: "Go", Source: string(example)}})
|
|
if err != nil {
|
|
log.Fatalf("failed to marshal x-codeSamples: %v", err)
|
|
}
|
|
var samplesNode yaml.Node
|
|
if err := yaml.Unmarshal(yamlBytes, &samplesNode); err != nil {
|
|
log.Fatalf("failed to create yaml node for x-codeSamples: %v", err)
|
|
}
|
|
if operation.Extensions == nil {
|
|
operation.Extensions = orderedmap.New[string, *yaml.Node]()
|
|
}
|
|
operation.Extensions.Set("x-codeSamples", samplesNode.Content[0])
|
|
}
|
|
|
|
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
|
|
}
|