145 lines
3.7 KiB
Go
145 lines
3.7 KiB
Go
package apigen
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
type WorkspaceService struct {
|
|
Dir string `json:"dir"`
|
|
API string `json:"api,omitempty"`
|
|
Module string `json:"module,omitempty"`
|
|
}
|
|
type WorkspaceConfig struct {
|
|
Services []WorkspaceService `json:"services"`
|
|
ErrorCatalogs []WorkspaceCatalog `json:"errorCatalogs,omitempty"`
|
|
}
|
|
|
|
// Workspace operates independent Go modules listed in an explicit local file.
|
|
// All projects are preflighted before generate/format writes. Each generator
|
|
// transaction is atomic within its project; no cross-module transaction is promised.
|
|
func Workspace(path, command string, w io.Writer) error {
|
|
if command != "generate" && command != "check" && command != "format" {
|
|
return errors.New("workspace command must be generate, check or format")
|
|
}
|
|
if w == nil {
|
|
w = io.Discard
|
|
}
|
|
real, err := filepath.Abs(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
stat, err := os.Lstat(real)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !stat.Mode().IsRegular() || stat.Size() > 65536 {
|
|
return errors.New("workspace configuration must be a regular file of at most 64 KiB")
|
|
}
|
|
raw, err := os.ReadFile(real)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var cfg WorkspaceConfig
|
|
if err = apierrors.DecodeCatalogJSON(raw, &cfg); err != nil {
|
|
return err
|
|
}
|
|
if len(cfg.Services) == 0 || len(cfg.Services) > 64 {
|
|
return errors.New("workspace must list 1..64 services")
|
|
}
|
|
root := canonical(filepath.Dir(real))
|
|
var options []Options
|
|
seen := map[string]bool{}
|
|
for _, service := range cfg.Services {
|
|
if service.Dir == "" || service.Dir == "." {
|
|
return errors.New("workspace requires distinct relative service directories")
|
|
}
|
|
if err = safePath(root, service.Dir+"/.goOmniWorkspaceCheck"); err != nil {
|
|
return err
|
|
}
|
|
dir := filepath.Join(root, filepath.FromSlash(service.Dir))
|
|
key := strings.ToLower(dir)
|
|
for prior := range seen {
|
|
if prior == key || strings.HasPrefix(key, prior+string(filepath.Separator)) || strings.HasPrefix(prior, key+string(filepath.Separator)) {
|
|
return errors.New("workspace service directories overlap")
|
|
}
|
|
}
|
|
seen[key] = true
|
|
entry := service.API
|
|
if entry == "" {
|
|
entry, err = ProjectEntry(dir)
|
|
} else {
|
|
if err = safePath(dir, entry); err != nil {
|
|
return err
|
|
}
|
|
entry = filepath.Join(dir, entry)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
options = append(options, Options{API: entry, Dir: dir, Module: service.Module, DryRun: true})
|
|
}
|
|
var catalogs map[string][]byte
|
|
if command != "format" {
|
|
catalogs, err = prepareWorkspaceCatalogs(root, cfg.ErrorCatalogs, command == "check")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := range options {
|
|
options[i].documentCatalogs = catalogs
|
|
}
|
|
}
|
|
var failures error
|
|
for _, o := range options {
|
|
if command == "format" {
|
|
spec, e := Parse(o.API)
|
|
if e == nil {
|
|
_, e = Compile(spec)
|
|
}
|
|
err = e
|
|
} else {
|
|
_, err = Generate(o)
|
|
}
|
|
if err != nil {
|
|
failures = errors.Join(failures, fmt.Errorf("%s: %w", o.Dir, err))
|
|
}
|
|
}
|
|
if failures != nil {
|
|
return failures
|
|
}
|
|
if command == "generate" {
|
|
for path, data := range catalogs {
|
|
if err = writeWorkspaceCatalog(path, data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for _, o := range options {
|
|
switch command {
|
|
case "format":
|
|
_, err = FormatProject(o.API, false)
|
|
case "generate":
|
|
o.DryRun = false
|
|
_, err = Generate(o)
|
|
case "check":
|
|
_, fmtErr := FormatProject(o.API, true)
|
|
o.DryRun = false
|
|
o.Check = true
|
|
_, genErr := Generate(o)
|
|
err = errors.Join(fmtErr, genErr)
|
|
}
|
|
if err != nil {
|
|
failures = errors.Join(failures, fmt.Errorf("%s: %w", o.Dir, err))
|
|
fmt.Fprintf(w, "FAIL %s\n", o.Dir)
|
|
} else {
|
|
fmt.Fprintf(w, "OK %s\n", o.Dir)
|
|
}
|
|
}
|
|
return failures
|
|
}
|