From 695013be92c74a9e9af871d6e87ab2edc2bcf270 Mon Sep 17 00:00:00 2001 From: nutcas3 Date: Sat, 2 May 2026 19:03:24 +0300 Subject: [PATCH] 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 --- .../internal/mvno/validator.go | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 apps/carrier-connector/internal/mvno/validator.go diff --git a/apps/carrier-connector/internal/mvno/validator.go b/apps/carrier-connector/internal/mvno/validator.go new file mode 100644 index 0000000..a06d4e6 --- /dev/null +++ b/apps/carrier-connector/internal/mvno/validator.go @@ -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)) +}