111 lines
3.6 KiB
Go
111 lines
3.6 KiB
Go
package apigen
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
|
|
)
|
|
|
|
const generatorConfigFile = "omni.gen.json"
|
|
|
|
// GeneratorConfig contains developer-owned scaffolding options only.
|
|
type GeneratorConfig struct {
|
|
Repository string `json:"repository,omitempty"`
|
|
Middleware string `json:"middleware,omitempty"`
|
|
inputDigest string
|
|
}
|
|
|
|
func readGeneratorConfig(root string) (GeneratorConfig, bool, error) {
|
|
cfg := GeneratorConfig{Repository: "none", Middleware: "factories"}
|
|
if err := safePath(root, generatorConfigFile); err != nil {
|
|
return cfg, false, err
|
|
}
|
|
stat, err := os.Lstat(filepath.Join(root, generatorConfigFile))
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
// Existing projects keep their handwritten middleware signature until they
|
|
// explicitly select factories; do not rewrite or adopt developer files.
|
|
if raw, e := os.ReadFile(filepath.Join(root, manifestPath)); e == nil {
|
|
var previous manifest
|
|
if err := json.Unmarshal(raw, &previous); err != nil {
|
|
return cfg, false, err
|
|
}
|
|
if previous.GeneratorConfig {
|
|
return cfg, false, errors.New("omni.gen.json is missing; restore the project-owned generation options before regenerating")
|
|
}
|
|
cfg.Middleware = "handlers"
|
|
} else if !errors.Is(e, os.ErrNotExist) {
|
|
return cfg, false, e
|
|
}
|
|
return cfg, false, nil
|
|
}
|
|
if err != nil {
|
|
return cfg, false, err
|
|
}
|
|
if !stat.Mode().IsRegular() || stat.Size() > 1<<20 {
|
|
return cfg, false, errors.New("omni.gen.json must be a regular file of at most 1 MiB")
|
|
}
|
|
raw, err := readGeneratorConfigFile(root, generatorConfigFile)
|
|
if err != nil {
|
|
return cfg, false, err
|
|
}
|
|
var keys map[string]json.RawMessage
|
|
if err = apierrors.DecodeCatalogJSON(raw, &keys); err != nil {
|
|
return cfg, false, fmt.Errorf("omni.gen.json: %w", err)
|
|
}
|
|
for _, key := range []string{"mappings", "mappingFiles"} {
|
|
if _, exists := keys[key]; exists {
|
|
return cfg, false, errors.New("mapping generation was removed: migrate callers to copyx or ordinary Go, remove mappings/mappingFiles from omni.gen.json, then remove internal/mapping/mappingsGen.go; see COPY.md")
|
|
}
|
|
}
|
|
if err = apierrors.DecodeCatalogJSON(raw, &cfg); err != nil {
|
|
return cfg, false, fmt.Errorf("omni.gen.json: %w", err)
|
|
}
|
|
digest := sha256.Sum256(raw)
|
|
cfg.inputDigest = fmt.Sprintf("%x", digest)
|
|
if cfg.Repository != "none" && cfg.Repository != "group" {
|
|
return cfg, false, errors.New("omni.gen.json: repository must be none or group")
|
|
}
|
|
if cfg.Middleware != "factories" && cfg.Middleware != "handlers" {
|
|
return cfg, false, errors.New("omni.gen.json: middleware must be factories or handlers")
|
|
}
|
|
return cfg, true, nil
|
|
}
|
|
|
|
// Read through a bounded reader as well as checking stat: a growing file cannot
|
|
// make configuration loading unbounded. safePath also rejects symlink escapes.
|
|
func readGeneratorConfigFile(root, name string) ([]byte, error) {
|
|
if err := safePath(root, name); err != nil {
|
|
return nil, err
|
|
}
|
|
f, err := os.Open(filepath.Join(root, filepath.FromSlash(name)))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", name, err)
|
|
}
|
|
defer f.Close()
|
|
stat, err := f.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !stat.Mode().IsRegular() || stat.Size() > 1<<20 {
|
|
return nil, fmt.Errorf("%s must be a regular file of at most 1 MiB", name)
|
|
}
|
|
raw, err := io.ReadAll(io.LimitReader(f, (1<<20)+1))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(raw) > 1<<20 {
|
|
return nil, fmt.Errorf("%s exceeds 1 MiB", name)
|
|
}
|
|
if trimmed := strings.TrimSpace(string(raw)); !strings.HasPrefix(trimmed, "{") {
|
|
return nil, fmt.Errorf("%s must contain a JSON object", name)
|
|
}
|
|
return raw, nil
|
|
}
|