212 lines
5.4 KiB
Go
212 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"git.yuntuo.hk/k2safe/go-omni/cmd/go-omni/apigen"
|
|
)
|
|
|
|
func runAPI(args []string) error {
|
|
if len(args) == 0 || args[0] == "help" || args[0] == "--help" || args[0] == "-h" {
|
|
usage()
|
|
return nil
|
|
}
|
|
command := args[0]
|
|
if command == "upgrade" {
|
|
return runUpgrade(args[1:])
|
|
}
|
|
if command == "go" {
|
|
command = "generate"
|
|
}
|
|
if command == "check" {
|
|
command = "validate"
|
|
}
|
|
if command != "new" && command != "generate" && command != "validate" && command != "inspect" && command != "export" && command != "format" {
|
|
return fmt.Errorf("unknown api command %q", command)
|
|
}
|
|
readOnly := command == "validate" || command == "inspect" || command == "export" || command == "format"
|
|
o := apigen.Options{}
|
|
for i := 1; i < len(args); i++ {
|
|
arg := args[i]
|
|
if arg == "--help" || arg == "-h" {
|
|
usage()
|
|
return nil
|
|
}
|
|
if arg == "--check" || arg == "--dry-run" {
|
|
if command != "generate" && !(command == "format" && arg == "--check") {
|
|
return fmt.Errorf("%s belongs to api generate", arg)
|
|
}
|
|
if arg == "--check" {
|
|
o.Check = true
|
|
} else {
|
|
o.DryRun = true
|
|
}
|
|
continue
|
|
}
|
|
if !strings.HasPrefix(arg, "-") {
|
|
if command == "new" && o.Dir == "" {
|
|
o.Dir = arg
|
|
continue
|
|
}
|
|
return fmt.Errorf("unexpected argument %q", arg)
|
|
}
|
|
name, value, inline := strings.Cut(arg, "=")
|
|
// goctl-style -api/-dir aliases ease migration; long forms are canonical.
|
|
if name == "-api" {
|
|
name = "--api"
|
|
}
|
|
if name == "-dir" {
|
|
name = "--dir"
|
|
}
|
|
switch name {
|
|
case "--api", "--dir", "--module", "--framework-dir", "--framework-version", "--log-output", "--log-format", "--database", "--storage":
|
|
default:
|
|
return fmt.Errorf("unknown api option %q", name)
|
|
}
|
|
if !inline {
|
|
if i+1 >= len(args) || strings.HasPrefix(args[i+1], "-") {
|
|
return fmt.Errorf("%s requires a value", name)
|
|
}
|
|
i++
|
|
value = args[i]
|
|
}
|
|
if strings.TrimSpace(value) == "" {
|
|
return fmt.Errorf("%s requires a value", name)
|
|
}
|
|
if readOnly && name != "--api" {
|
|
return fmt.Errorf("api %s does not accept %s", command, name)
|
|
}
|
|
switch name {
|
|
case "--api":
|
|
o.API = value
|
|
case "--dir":
|
|
o.Dir = value
|
|
case "--module":
|
|
o.Module = value
|
|
case "--framework-dir":
|
|
o.FrameworkDir = value
|
|
case "--framework-version":
|
|
o.FrameworkVersion = value
|
|
case "--database":
|
|
o.Database = value
|
|
case "--storage":
|
|
o.Storage = value
|
|
case "--log-output":
|
|
o.LogOutput = value
|
|
case "--log-format":
|
|
o.LogFormat = value
|
|
}
|
|
}
|
|
if command != "new" && o.API == "" {
|
|
dir := o.Dir
|
|
if dir == "" {
|
|
dir = "."
|
|
}
|
|
var err error
|
|
o.API, err = apigen.ProjectEntry(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if command == "format" {
|
|
changes, err := apigen.FormatProject(o.API, o.Check)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("API format: %d files updated\n", len(changes))
|
|
return nil
|
|
}
|
|
if readOnly {
|
|
spec, err := apigen.Parse(o.API)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
compiled, err := apigen.Compile(spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch command {
|
|
case "validate":
|
|
fmt.Printf("valid API: %d types, %d routes, %d errors, %d auth schemes\n", len(spec.Types), len(spec.Routes), len(spec.Errors), len(spec.Auth))
|
|
case "export":
|
|
fmt.Println(string(compiled.Document))
|
|
case "inspect":
|
|
writer := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
|
|
fmt.Fprintln(writer, "METHOD\tPATH\tHANDLER\tAUTH\tRESPONSE\tERRORS")
|
|
definitions := map[string]apigen.ErrorDefinition{}
|
|
for _, e := range spec.Errors {
|
|
definitions[e.Name] = e
|
|
}
|
|
for _, r := range compiled.Spec.Routes {
|
|
auth := r.Auth
|
|
if auth == "" {
|
|
auth = "(undeclared)"
|
|
}
|
|
var errs []string
|
|
for _, name := range r.Errors {
|
|
e := definitions[name]
|
|
errs = append(errs, fmt.Sprintf("%d:%s", e.Status, e.Code))
|
|
}
|
|
format := r.ResponseFormat
|
|
if format == "" {
|
|
format = "default"
|
|
}
|
|
fmt.Fprintf(writer, "%s\t%s\t%s/%s\t%s\t%s\t%s\n", strings.ToUpper(r.Method), r.Path, r.Group, r.Handler, auth, format, strings.Join(errs, ", "))
|
|
}
|
|
return writer.Flush()
|
|
}
|
|
return nil
|
|
}
|
|
var out apigen.Output
|
|
var err error
|
|
if command == "new" {
|
|
out, err = apigen.New(o)
|
|
} else {
|
|
if o.Dir == "" {
|
|
o.Dir = "."
|
|
}
|
|
out, err = apigen.Generate(o)
|
|
}
|
|
printAPIDiagnostics(out.Diagnostics)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if o.DryRun {
|
|
for _, change := range out.Changes {
|
|
fmt.Printf("%-7s %s\n", change.Action, change.Path)
|
|
}
|
|
fmt.Printf("Preview: %d changes; %d existing developer-owned files preserved; no files written\n", len(out.Changes), len(out.Preserved))
|
|
} else if o.Check {
|
|
fmt.Println("API generated files are current; no definite Logic signature errors found (run go test ./... for full verification)")
|
|
} else {
|
|
fmt.Printf("API project generated: %d generated, %d created, %d preserved, %d removed\n", len(out.Generated), len(out.Created), len(out.Preserved), len(out.Removed))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func printAPIDiagnostics(diagnostics []apigen.Diagnostic) {
|
|
for _, d := range diagnostics {
|
|
location := d.Path
|
|
if d.Line > 0 {
|
|
location += fmt.Sprintf(":%d", d.Line)
|
|
}
|
|
fmt.Printf("%s [%s] %s", strings.ToUpper(d.Severity), d.Code, location)
|
|
if d.Operation != "" {
|
|
fmt.Printf(" (%s)", d.Operation)
|
|
}
|
|
fmt.Printf(": %s\n", d.Message)
|
|
if d.Before != "" {
|
|
fmt.Printf(" before: %s\n", d.Before)
|
|
}
|
|
if d.After != "" {
|
|
fmt.Printf(" after: %s\n", d.After)
|
|
}
|
|
for _, detail := range d.Details {
|
|
fmt.Printf(" - %s\n", detail)
|
|
}
|
|
}
|
|
}
|