139 lines
3.5 KiB
Go
139 lines
3.5 KiB
Go
package persona
|
|
|
|
import "fmt"
|
|
|
|
// Validate performs comprehensive validation on a character.
|
|
// Validation is species-aware and applies appropriate rules:
|
|
// - Human: Ethnicity plausibility + consistency checks
|
|
// - Humanoid: Morphology validation + consistency checks
|
|
// - Android: Minimal validation (synthetic beings)
|
|
func Validate(c *Character, opts ...ValidateOption) error {
|
|
if c == nil {
|
|
return ErrNilCharacter
|
|
}
|
|
|
|
cfg := defaultValidateConfig()
|
|
for _, opt := range opts {
|
|
opt(cfg)
|
|
}
|
|
|
|
var errs ValidationErrors
|
|
|
|
// Basic validation
|
|
if err := validateBasics(c); err != nil {
|
|
errs = append(errs, ValidationError{
|
|
Message: err.Error(),
|
|
Err: err,
|
|
})
|
|
}
|
|
|
|
// Species-specific validation
|
|
switch c.Species {
|
|
case SpeciesHuman:
|
|
if !cfg.skipPlausibility {
|
|
if violations := ValidateHumanPlausibility(c.DNA); violations != nil && !violations.Valid {
|
|
for _, v := range violations.Violations {
|
|
if v.Level == PlausibilityImplausible && !cfg.allowRareFeatures {
|
|
errs.AddValue(v.Field, v.Value, v.Message, ErrImplausibleFeature)
|
|
} else if cfg.strictMode && v.Level == PlausibilityRare {
|
|
errs.AddValue(v.Field, v.Value, v.Message, ErrImplausibleFeature)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
case SpeciesHumanoid:
|
|
if !cfg.skipMorphology {
|
|
if morphErrs := ValidateHumanoidMorphology(c); morphErrs.HasErrors() {
|
|
errs = append(errs, morphErrs...)
|
|
}
|
|
}
|
|
|
|
case SpeciesAndroid:
|
|
// Minimal validation for synthetic beings
|
|
// Just ensure basic structure is valid
|
|
|
|
default:
|
|
errs.Add("species", fmt.Sprintf("unknown species type: %s", c.Species), ErrInvalidSpecies)
|
|
}
|
|
|
|
// Cross-attribute consistency checks
|
|
if !cfg.skipConsistency && c.DNA != nil {
|
|
if result := ValidateConsistency(c.DNA); !result.Valid {
|
|
for _, issue := range result.Issues {
|
|
if issue.Severity == "error" || cfg.strictMode {
|
|
errs.Add(issue.Field1, issue.Description, ErrInconsistentAttributes)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if errs.HasErrors() {
|
|
return errs
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// validateBasics performs basic structural validation.
|
|
func validateBasics(c *Character) error {
|
|
if c.ID == "" {
|
|
return fmt.Errorf("character ID is required")
|
|
}
|
|
|
|
if !c.Species.IsValid() {
|
|
return ErrInvalidSpecies
|
|
}
|
|
|
|
if c.DNA == nil {
|
|
return ErrNilDNA
|
|
}
|
|
|
|
// Validate identity
|
|
if c.DNA.Identity.Age < 0 || c.DNA.Identity.Age > 150 {
|
|
return ErrInvalidAge
|
|
}
|
|
|
|
if c.DNA.Identity.Ethnicity != "" && !c.DNA.Identity.Ethnicity.IsValid() {
|
|
return ErrInvalidEthnicity
|
|
}
|
|
|
|
if c.DNA.Identity.Gender != "" && !c.DNA.Identity.Gender.IsValid() {
|
|
return ErrInvalidGender
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateHuman is a convenience function for validating human characters.
|
|
func ValidateHuman(c *Character, opts ...ValidateOption) error {
|
|
if c == nil {
|
|
return ErrNilCharacter
|
|
}
|
|
if c.Species != SpeciesHuman {
|
|
return fmt.Errorf("expected human species, got %s", c.Species)
|
|
}
|
|
return Validate(c, opts...)
|
|
}
|
|
|
|
// ValidateHumanoid is a convenience function for validating humanoid characters.
|
|
func ValidateHumanoid(c *Character, opts ...ValidateOption) error {
|
|
if c == nil {
|
|
return ErrNilCharacter
|
|
}
|
|
if c.Species != SpeciesHumanoid {
|
|
return fmt.Errorf("expected humanoid species, got %s", c.Species)
|
|
}
|
|
return Validate(c, opts...)
|
|
}
|
|
|
|
// ValidateAndroid is a convenience function for validating android characters.
|
|
func ValidateAndroid(c *Character, opts ...ValidateOption) error {
|
|
if c == nil {
|
|
return ErrNilCharacter
|
|
}
|
|
if c.Species != SpeciesAndroid {
|
|
return fmt.Errorf("expected android species, got %s", c.Species)
|
|
}
|
|
return Validate(c, opts...)
|
|
}
|