@@ -212,7 +212,7 @@ func validateWithSchemaAndLocation(frontmatter map[string]any, schemaJSON, conte
212212
213213 // Rewrite "additional properties not allowed" errors to be more friendly
214214 message := rewriteAdditionalPropertiesError (primaryPath .Message )
215-
215+
216216 // Add schema-based suggestions
217217 suggestions := generateSchemaBasedSuggestions (schemaJSON , primaryPath .Message , primaryPath .Path )
218218 if suggestions != "" {
@@ -240,7 +240,7 @@ func validateWithSchemaAndLocation(frontmatter map[string]any, schemaJSON, conte
240240
241241 // Rewrite "additional properties not allowed" errors to be more friendly
242242 message := rewriteAdditionalPropertiesError (errorMsg )
243-
243+
244244 // Add schema-based suggestions for fallback case
245245 suggestions := generateSchemaBasedSuggestions (schemaJSON , errorMsg , "" )
246246 if suggestions != "" {
@@ -393,6 +393,14 @@ func findFrontmatterBounds(lines []string) (startIdx int, endIdx int, frontmatte
393393 return startIdx , endIdx , frontmatterContent
394394}
395395
396+ // Constants for suggestion limits and field generation
397+ const (
398+ maxClosestMatches = 3 // Maximum number of closest matches to find
399+ maxSuggestions = 5 // Maximum number of suggestions to show
400+ maxAcceptedFields = 10 // Maximum number of accepted fields to display
401+ maxExampleFields = 3 // Maximum number of fields to include in example JSON
402+ )
403+
396404// generateSchemaBasedSuggestions generates helpful suggestions based on the schema and error type
397405func generateSchemaBasedSuggestions (schemaJSON , errorMessage , jsonPath string ) string {
398406 // Parse the schema to extract information for suggestions
@@ -405,7 +413,7 @@ func generateSchemaBasedSuggestions(schemaJSON, errorMessage, jsonPath string) s
405413 if strings .Contains (strings .ToLower (errorMessage ), "additional propert" ) && strings .Contains (strings .ToLower (errorMessage ), "not allowed" ) {
406414 invalidProps := extractAdditionalPropertyNames (errorMessage )
407415 acceptedFields := extractAcceptedFieldsFromSchema (schemaDoc , jsonPath )
408-
416+
409417 if len (acceptedFields ) > 0 {
410418 return generateFieldSuggestions (invalidProps , acceptedFields )
411419 }
@@ -510,12 +518,12 @@ func resolveSchemaWithOneOf(schema map[string]any) map[string]any {
510518
511519// generateFieldSuggestions creates a helpful suggestion message for invalid field names
512520func generateFieldSuggestions (invalidProps , acceptedFields []string ) string {
513- if len (acceptedFields ) == 0 {
521+ if len (acceptedFields ) == 0 || len ( invalidProps ) == 0 {
514522 return ""
515523 }
516524
517525 var suggestion strings.Builder
518-
526+
519527 if len (invalidProps ) == 1 {
520528 suggestion .WriteString ("Did you mean one of: " )
521529 } else {
@@ -525,26 +533,26 @@ func generateFieldSuggestions(invalidProps, acceptedFields []string) string {
525533 // Find closest matches using simple string distance
526534 var suggestions []string
527535 for _ , invalidProp := range invalidProps {
528- closest := findClosestMatches (invalidProp , acceptedFields , 3 )
536+ closest := findClosestMatches (invalidProp , acceptedFields , maxClosestMatches )
529537 suggestions = append (suggestions , closest ... )
530538 }
531539
532540 // If we have specific suggestions, show them first
533541 if len (suggestions ) > 0 {
534542 // Remove duplicates
535543 uniqueSuggestions := removeDuplicates (suggestions )
536- if len (uniqueSuggestions ) <= 5 {
544+ if len (uniqueSuggestions ) <= maxSuggestions {
537545 suggestion .WriteString (strings .Join (uniqueSuggestions , ", " ))
538546 } else {
539- suggestion .WriteString (strings .Join (uniqueSuggestions [:5 ], ", " ))
547+ suggestion .WriteString (strings .Join (uniqueSuggestions [:maxSuggestions ], ", " ))
540548 suggestion .WriteString (", ..." )
541549 }
542550 } else {
543551 // Show all accepted fields if no close matches
544- if len (acceptedFields ) <= 10 {
552+ if len (acceptedFields ) <= maxAcceptedFields {
545553 suggestion .WriteString (strings .Join (acceptedFields , ", " ))
546554 } else {
547- suggestion .WriteString (strings .Join (acceptedFields [:10 ], ", " ))
555+ suggestion .WriteString (strings .Join (acceptedFields [:maxAcceptedFields ], ", " ))
548556 suggestion .WriteString (", ..." )
549557 }
550558 }
@@ -565,7 +573,7 @@ func findClosestMatches(target string, candidates []string, maxResults int) []st
565573 for _ , candidate := range candidates {
566574 candidateLower := strings .ToLower (candidate )
567575 score := calculateSimilarityScore (targetLower , candidateLower )
568-
576+
569577 // Only include if there's some similarity
570578 if score > 0 {
571579 matches = append (matches , match {value : candidate , score : score })
@@ -588,32 +596,41 @@ func findClosestMatches(target string, candidates []string, maxResults int) []st
588596
589597// calculateSimilarityScore calculates a simple similarity score between two strings
590598func calculateSimilarityScore (a , b string ) int {
599+ // Early exit for obviously poor matches (length difference > 2x shorter string length)
600+ minLen := len (a )
601+ if len (b ) < minLen {
602+ minLen = len (b )
603+ }
604+ lengthDiff := abs (len (a ) - len (b ))
605+ if lengthDiff > minLen * 2 && minLen > 0 {
606+ return 0
607+ }
608+
591609 // Simple heuristics for string similarity
592610 score := 0
593-
611+
594612 // Bonus for substring matches
595613 if strings .Contains (b , a ) || strings .Contains (a , b ) {
596614 score += 10
597615 }
598-
616+
599617 // Bonus for common prefixes
600618 commonPrefix := 0
601619 for i := 0 ; i < len (a ) && i < len (b ) && a [i ] == b [i ]; i ++ {
602620 commonPrefix ++
603621 }
604622 score += commonPrefix * 2
605-
623+
606624 // Bonus for common suffixes
607625 commonSuffix := 0
608626 for i := 0 ; i < len (a ) && i < len (b ) && a [len (a )- 1 - i ] == b [len (b )- 1 - i ]; i ++ {
609627 commonSuffix ++
610628 }
611629 score += commonSuffix * 2
612-
630+
613631 // Penalty for length difference
614- lengthDiff := abs (len (a ) - len (b ))
615632 score -= lengthDiff
616-
633+
617634 return score
618635}
619636
@@ -694,11 +711,10 @@ func generateExampleFromSchema(schema map[string]any) any {
694711
695712 // Add a few example properties (prioritize required ones)
696713 count := 0
697- maxFields := 3
698714
699715 // First, add required fields
700716 for propName , propSchema := range properties {
701- if requiredFields [propName ] && count < maxFields {
717+ if requiredFields [propName ] && count < maxExampleFields {
702718 if propSchemaMap , ok := propSchema .(map [string ]any ); ok {
703719 result [propName ] = generateExampleFromSchema (propSchemaMap )
704720 count ++
@@ -708,7 +724,7 @@ func generateExampleFromSchema(schema map[string]any) any {
708724
709725 // Then add some optional fields if we have room
710726 for propName , propSchema := range properties {
711- if ! requiredFields [propName ] && count < maxFields {
727+ if ! requiredFields [propName ] && count < maxExampleFields {
712728 if propSchemaMap , ok := propSchema .(map [string ]any ); ok {
713729 result [propName ] = generateExampleFromSchema (propSchemaMap )
714730 count ++
@@ -726,14 +742,14 @@ func generateExampleFromSchema(schema map[string]any) any {
726742func removeDuplicates (strings []string ) []string {
727743 seen := make (map [string ]bool )
728744 var result []string
729-
745+
730746 for _ , str := range strings {
731747 if ! seen [str ] {
732748 seen [str ] = true
733749 result = append (result , str )
734750 }
735751 }
736-
752+
737753 return result
738754}
739755
0 commit comments