installerbuilder/internal/config/validate.go

249 lines
6.1 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"strings"
)
// ValidationError 表示配置验证错误
type ValidationError struct {
Field string
Message string
}
// ValidationResult 表示配置验证结果
type ValidationResult struct {
Valid bool
Errors []ValidationError
}
// String 返回验证结果的字符串表示
func (vr ValidationResult) String() string {
if vr.Valid {
return "配置验证通过"
}
var sb strings.Builder
sb.WriteString(fmt.Sprintf("配置验证失败,共有 %d 个错误:\n", len(vr.Errors)))
for i, err := range vr.Errors {
sb.WriteString(fmt.Sprintf("%d. %s: %s\n", i+1, err.Field, err.Message))
}
return sb.String()
}
// Validate 验证配置是否有效
func (c *Config) Validate(strict bool) ValidationResult {
result := ValidationResult{
Valid: true,
Errors: []ValidationError{},
}
// 验证基本信息
if c.Name == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "name",
Message: "安装包名称不能为空",
})
}
if c.Version == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "version",
Message: "安装包版本不能为空",
})
}
// 验证构建设置
if len(c.Build.Targets) == 0 {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "build.targets",
Message: "至少需要指定一个目标平台",
})
}
if len(c.Build.Formats) == 0 {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "build.formats",
Message: "至少需要指定一个输出格式",
})
}
// 验证文件和目录
for i, file := range c.Contents.Files {
if file.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].source", i),
Message: "源文件路径不能为空",
})
} else if strict {
// 在严格模式下,验证源文件是否存在
if _, err := os.Stat(file.Source); os.IsNotExist(err) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].source", i),
Message: fmt.Sprintf("源文件 '%s' 不存在", file.Source),
})
}
}
if file.Destination == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.files[%d].destination", i),
Message: "目标路径不能为空",
})
}
}
// 验证脚本
for i, script := range c.Contents.Scripts {
if script.Type == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].type", i),
Message: "脚本类型不能为空",
})
} else if !isValidScriptType(script.Type) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].type", i),
Message: fmt.Sprintf("无效的脚本类型: %s", script.Type),
})
}
if script.Content == "" && script.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d]", i),
Message: "脚本内容和源文件路径不能同时为空",
})
}
if script.Source != "" && strict {
// 在严格模式下,验证脚本源文件是否存在
if _, err := os.Stat(script.Source); os.IsNotExist(err) {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.scripts[%d].source", i),
Message: fmt.Sprintf("脚本源文件 '%s' 不存在", script.Source),
})
}
}
}
// 验证符号链接
for i, symlink := range c.Contents.Symlinks {
if symlink.Source == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.symlinks[%d].source", i),
Message: "符号链接源路径不能为空",
})
}
if symlink.Destination == "" {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: fmt.Sprintf("contents.symlinks[%d].destination", i),
Message: "符号链接目标路径不能为空",
})
}
}
// 验证平台特定配置
for _, target := range c.Build.Targets {
switch target {
case "windows":
// 验证Windows MSI配置
if containsFormat(c.Build.Formats, "msi") {
if c.Platforms.Windows.Msi.UpgradeCode == "" && strict {
result.Valid = false
result.Errors = append(result.Errors, ValidationError{
Field: "platforms.windows.msi.upgradeCode",
Message: "MSI升级代码不能为空",
})
}
}
case "linux":
// 验证Linux DEB配置
if containsFormat(c.Build.Formats, "deb") {
// 可以添加更多DEB特定的验证
}
// 验证Linux RPM配置
if containsFormat(c.Build.Formats, "rpm") {
// 可以添加更多RPM特定的验证
}
}
}
return result
}
// 检查是否为有效的脚本类型
func isValidScriptType(scriptType string) bool {
validTypes := []string{
"preinstall",
"postinstall",
"preuninstall",
"postuninstall",
}
for _, validType := range validTypes {
if scriptType == validType {
return true
}
}
return false
}
// 检查格式列表是否包含指定格式
func containsFormat(formats []string, format string) bool {
for _, f := range formats {
if f == format {
return true
}
}
return false
}
// ValidateConfigFile 验证指定路径的配置文件
func ValidateConfigFile(path string, strict bool) (ValidationResult, error) {
config, err := LoadConfig(path)
if err != nil {
return ValidationResult{
Valid: false,
Errors: []ValidationError{
{
Field: "file",
Message: err.Error(),
},
},
}, err
}
// 设置工作目录为配置文件所在目录,以便相对路径的验证
if strict {
oldWd, err := os.Getwd()
if err != nil {
return ValidationResult{}, err
}
defer os.Chdir(oldWd)
configDir := filepath.Dir(path)
if err := os.Chdir(configDir); err != nil {
return ValidationResult{}, err
}
}
return config.Validate(strict), nil
}