feat: Add MVNO onboarding validator with business, plan, and technical validation

- Add OnboardingValidator struct with ValidateRequest and ValidateMVNO methods in validator.go
- Add business info validation with name length, ID format, email regex, and phone length checks
- Add plan validation with subscriber limits for Starter (1000) and Growth (10000) plans
- Add technical requirements validation with country code format and use case length checks
- Add compliance validation with country-specific regulatory checks and restricted country filtering
- Add technical feasibility validation
This commit is contained in:
nutcas3
2026-05-02 19:03:24 +03:00
parent 77ca25a453
commit 695013be92
@@ -0,0 +1,190 @@
package mvno
import (
"context"
"fmt"
"regexp"
"slices"
"strings"
"github.com/sirupsen/logrus"
)
// OnboardingValidator validates MVNO onboarding requests and configurations
type OnboardingValidator struct {
logger *logrus.Logger
}
// NewOnboardingValidator creates a new validator instance
func NewOnboardingValidator(logger *logrus.Logger) *OnboardingValidator {
return &OnboardingValidator{logger: logger}
}
// ValidateRequest validates the initial onboarding request
func (v *OnboardingValidator) ValidateRequest(req *OnboardingRequest) error {
if err := v.validateBusinessInfo(req); err != nil {
return fmt.Errorf("business validation failed: %w", err)
}
if err := v.validatePlan(req); err != nil {
return fmt.Errorf("plan validation failed: %w", err)
}
if err := v.validateTechnicalRequirements(req); err != nil {
return fmt.Errorf("technical validation failed: %w", err)
}
return nil
}
// ValidateMVNO performs comprehensive MVNO validation
func (v *OnboardingValidator) ValidateMVNO(ctx context.Context, mvno *MVNO) error {
// Validate business registration
if err := v.validateBusinessRegistration(mvno.BusinessID); err != nil {
return fmt.Errorf("business registration validation failed: %w", err)
}
// Validate compliance requirements
if err := v.validateCompliance(ctx, mvno); err != nil {
return fmt.Errorf("compliance validation failed: %w", err)
}
// Validate technical feasibility
if err := v.validateTechnicalFeasibility(mvno); err != nil {
return fmt.Errorf("technical feasibility validation failed: %w", err)
}
return nil
}
// validateBusinessInfo validates business information
func (v *OnboardingValidator) validateBusinessInfo(req *OnboardingRequest) error {
if len(req.BusinessName) < 3 {
return fmt.Errorf("business name must be at least 3 characters")
}
if !v.isValidBusinessID(req.BusinessID) {
return fmt.Errorf("invalid business ID format")
}
if !v.isValidEmail(req.ContactEmail) {
return fmt.Errorf("invalid contact email format")
}
if len(req.ContactPhone) < 10 {
return fmt.Errorf("contact phone must be at least 10 digits")
}
return nil
}
// validatePlan validates the selected plan
func (v *OnboardingValidator) validatePlan(req *OnboardingRequest) error {
validPlans := []MVNOPlan{PlanStarter, PlanGrowth, PlanScale, PlanEnterprise}
isValid := slices.Contains(validPlans, req.Plan)
if !isValid {
return fmt.Errorf("invalid plan selected")
}
if req.EstimatedSubs < 1 {
return fmt.Errorf("estimated subscribers must be at least 1")
}
// Validate plan-specific requirements
switch req.Plan {
case PlanStarter:
if req.EstimatedSubs > 1000 {
return fmt.Errorf("starter plan limited to 1000 subscribers")
}
case PlanGrowth:
if req.EstimatedSubs > 10000 {
return fmt.Errorf("growth plan limited to 10000 subscribers")
}
}
return nil
}
// validateTechnicalRequirements validates technical requirements
func (v *OnboardingValidator) validateTechnicalRequirements(req *OnboardingRequest) error {
if len(req.TargetCountries) == 0 {
return fmt.Errorf("at least one target country must be specified")
}
if len(req.UseCase) < 10 {
return fmt.Errorf("use case description too short")
}
// Validate country codes
for _, country := range req.TargetCountries {
if len(country) != 2 {
return fmt.Errorf("invalid country code format: %s", country)
}
}
return nil
}
// validateBusinessRegistration validates business registration
func (v *OnboardingValidator) validateBusinessRegistration(businessID string) error {
// In production, this would validate against business registry
// For now, basic format validation
if len(businessID) < 8 {
return fmt.Errorf("business ID too short")
}
return nil
}
// validateCompliance validates regulatory compliance
func (v *OnboardingValidator) validateCompliance(ctx context.Context, mvno *MVNO) error {
// Check regulatory compliance for target countries
for _, country := range mvno.Config.AllowedCountries {
if err := v.checkCountryCompliance(country); err != nil {
return fmt.Errorf("compliance check failed for %s: %w", country, err)
}
}
return nil
}
// validateTechnicalFeasibility validates technical implementation feasibility
func (v *OnboardingValidator) validateTechnicalFeasibility(mvno *MVNO) error {
// Validate carrier availability for target countries
for _, country := range mvno.Config.AllowedCountries {
if !v.hasCarrierCoverage(country) {
return fmt.Errorf("no carrier coverage available for %s", country)
}
}
return nil
}
// isValidBusinessID validates business ID format
func (v *OnboardingValidator) isValidBusinessID(id string) bool {
// Basic validation - alphanumeric with possible hyphens
matched, _ := regexp.MatchString(`^[A-Za-z0-9\-]{8,}$`, id)
return matched
}
// isValidEmail validates email format
func (v *OnboardingValidator) isValidEmail(email string) bool {
matched, _ := regexp.MatchString(`^[^\s@]+@[^\s@]+\.[^\s@]+$`, email)
return matched
}
// checkCountryCompliance checks if country is compliant
func (v *OnboardingValidator) checkCountryCompliance(country string) error {
// In production, this would check regulatory requirements
// For now, basic validation
restricted := []string{"XX", "YY"} // Example restricted countries
if slices.Contains(restricted, strings.ToUpper(country)) {
return fmt.Errorf("country not supported")
}
return nil
}
// hasCarrierCoverage checks if carrier coverage exists
func (v *OnboardingValidator) hasCarrierCoverage(country string) bool {
// In production, this would check carrier availability
// For now, assume most countries have coverage
supported := []string{"US", "GB", "DE", "FR", "JP", "AU", "CA"}
return slices.Contains(supported, strings.ToUpper(country))
}