2025-08-10 15:21:10 -04:00
"""
Validation Manager
This module provides centralized validation logic for Google Docs operations,
extracting validation patterns from individual tool functions.
"""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
import logging
2025-08-12 09:37:20 -04:00
from typing import Dict , Any , List , Tuple , Optional
2026-02-17 14:49:49 -05:00
from urllib.parse import urlparse
2025-08-10 15:21:10 -04:00
2025-12-13 11:11:04 -08:00
from gdocs.docs_helpers import validate_operation
2025-08-10 15:21:10 -04:00
logger = logging . getLogger ( __name__ )
class ValidationManager :
"""
Centralized validation manager for Google Docs operations.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Provides consistent validation patterns and error messages across
all document operations, reducing code duplication and improving
error message quality.
"""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def __init__ ( self ):
"""Initialize the validation manager."""
self . validation_rules = self . _setup_validation_rules ()
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def _setup_validation_rules ( self ) -> Dict [ str , Any ]:
"""Setup validation rules and constraints."""
return {
2025-12-13 13:49:28 -08:00
"table_max_rows" : 1000 ,
"table_max_columns" : 20 ,
"document_id_pattern" : r "^[a-zA-Z0-9-_]+$" ,
"max_text_length" : 1000000 , # 1MB text limit
"font_size_range" : ( 1 , 400 ), # Google Docs font size limits
"valid_header_footer_types" : [ "DEFAULT" , "FIRST_PAGE_ONLY" , "EVEN_PAGE" ],
"valid_section_types" : [ "header" , "footer" ],
"valid_list_types" : [ "UNORDERED" , "ORDERED" ],
"valid_element_types" : [ "table" , "list" , "page_break" ],
2026-02-13 14:29:43 -08:00
"valid_alignments" : [ "START" , "CENTER" , "END" , "JUSTIFIED" ],
"heading_level_range" : ( 0 , 6 ),
2025-08-10 15:21:10 -04:00
}
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_document_id ( self , document_id : str ) -> Tuple [ bool , str ]:
"""
Validate Google Docs document ID format.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
document_id: Document ID to validate
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
if not document_id :
return False , "Document ID cannot be empty"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
if not isinstance ( document_id , str ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "Document ID must be a string, got { type ( document_id ) . __name__ } " ,
)
2025-08-10 15:21:10 -04:00
# Basic length check (Google Docs IDs are typically 40+ characters)
if len ( document_id ) < 20 :
return False , "Document ID appears too short to be valid"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_table_data ( self , table_data : List [ List [ str ]]) -> Tuple [ bool , str ]:
"""
Comprehensive validation for table data format.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
This extracts and centralizes table validation logic from multiple functions.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
table_data: 2D array of data to validate
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, detailed_error_message)
"""
if not table_data :
2025-12-13 13:49:28 -08:00
return (
False ,
"Table data cannot be empty. Required format: [['col1', 'col2'], ['row1col1', 'row1col2']]" ,
)
2025-08-10 15:21:10 -04:00
if not isinstance ( table_data , list ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "Table data must be a list, got { type ( table_data ) . __name__ } . Required format: [['col1', 'col2'], ['row1col1', 'row1col2']]" ,
)
2025-08-10 15:21:10 -04:00
# Check if it's a 2D list
if not all ( isinstance ( row , list ) for row in table_data ):
2025-12-13 13:49:28 -08:00
non_list_rows = [
i for i , row in enumerate ( table_data ) if not isinstance ( row , list )
]
return (
False ,
f "All rows must be lists. Rows { non_list_rows } are not lists. Required format: [['col1', 'col2'], ['row1col1', 'row1col2']]" ,
)
2025-08-10 15:21:10 -04:00
# Check for empty rows
if any ( len ( row ) == 0 for row in table_data ):
empty_rows = [ i for i , row in enumerate ( table_data ) if len ( row ) == 0 ]
2025-12-13 13:49:28 -08:00
return (
False ,
f "Rows cannot be empty. Empty rows found at indices: { empty_rows } " ,
)
2025-08-10 15:21:10 -04:00
# Check column consistency
col_counts = [ len ( row ) for row in table_data ]
if len ( set ( col_counts )) > 1 :
2025-12-13 13:49:28 -08:00
return (
False ,
f "All rows must have the same number of columns. Found column counts: { col_counts } . Fix your data structure." ,
)
2025-08-10 15:21:10 -04:00
rows = len ( table_data )
cols = col_counts [ 0 ]
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
# Check dimension limits
2025-12-13 13:49:28 -08:00
if rows > self . validation_rules [ "table_max_rows" ]:
return (
False ,
f "Too many rows ( { rows } ). Maximum allowed: { self . validation_rules [ 'table_max_rows' ] } " ,
)
if cols > self . validation_rules [ "table_max_columns" ]:
return (
False ,
f "Too many columns ( { cols } ). Maximum allowed: { self . validation_rules [ 'table_max_columns' ] } " ,
)
2025-08-10 15:21:10 -04:00
# Check cell content types
for row_idx , row in enumerate ( table_data ):
for col_idx , cell in enumerate ( row ):
if cell is None :
2025-12-13 13:49:28 -08:00
return (
False ,
f "Cell ( { row_idx } , { col_idx } ) is None. All cells must be strings, use empty string '' for empty cells." ,
)
2025-08-10 15:21:10 -04:00
if not isinstance ( cell , str ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "Cell ( { row_idx } , { col_idx } ) is { type ( cell ) . __name__ } , not string. All cells must be strings. Value: { repr ( cell ) } " ,
)
2025-08-10 15:21:10 -04:00
return True , f "Valid table data: { rows } × { cols } table format"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_text_formatting_params (
self ,
bold : Optional [ bool ] = None ,
italic : Optional [ bool ] = None ,
underline : Optional [ bool ] = None ,
font_size : Optional [ int ] = None ,
2025-11-27 11:29:25 +01:00
font_family : Optional [ str ] = None ,
2025-12-20 22:39:41 +01:00
text_color : Optional [ str ] = None ,
background_color : Optional [ str ] = None ,
2026-02-07 13:44:09 -08:00
link_url : Optional [ str ] = None ,
2025-08-10 15:21:10 -04:00
) -> Tuple [ bool , str ]:
"""
Validate text formatting parameters.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
bold: Bold setting
italic: Italic setting
underline: Underline setting
font_size: Font size in points
font_family: Font family name
2025-12-20 22:39:41 +01:00
text_color: Text color in "#RRGGBB" format
background_color: Background color in "#RRGGBB" format
2026-02-07 13:44:09 -08:00
link_url: Hyperlink URL (http/https)
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
# Check if at least one formatting option is provided
2025-11-27 11:29:25 +01:00
formatting_params = [
2025-12-13 13:49:28 -08:00
bold ,
italic ,
underline ,
font_size ,
font_family ,
text_color ,
background_color ,
2026-02-07 13:44:09 -08:00
link_url ,
2025-11-27 11:29:25 +01:00
]
2025-08-10 15:21:10 -04:00
if all ( param is None for param in formatting_params ):
2025-12-13 13:49:28 -08:00
return (
False ,
2026-02-07 13:44:09 -08:00
"At least one formatting parameter must be provided (bold, italic, underline, font_size, font_family, text_color, background_color, or link_url)" ,
2025-12-13 13:49:28 -08:00
)
2025-08-10 15:21:10 -04:00
# Validate boolean parameters
2025-12-13 13:49:28 -08:00
for param , name in [
( bold , "bold" ),
( italic , "italic" ),
( underline , "underline" ),
]:
2025-08-10 15:21:10 -04:00
if param is not None and not isinstance ( param , bool ):
2025-12-13 13:49:28 -08:00
return (
False ,
f " { name } parameter must be boolean (True/False), got { type ( param ) . __name__ } " ,
)
2025-08-10 15:21:10 -04:00
# Validate font size
if font_size is not None :
if not isinstance ( font_size , int ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "font_size must be an integer, got { type ( font_size ) . __name__ } " ,
)
min_size , max_size = self . validation_rules [ "font_size_range" ]
2025-08-10 15:21:10 -04:00
if not ( min_size <= font_size <= max_size ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "font_size must be between { min_size } and { max_size } points, got { font_size } " ,
)
2025-08-10 15:21:10 -04:00
# Validate font family
if font_family is not None :
if not isinstance ( font_family , str ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "font_family must be a string, got { type ( font_family ) . __name__ } " ,
)
2025-08-10 15:21:10 -04:00
if not font_family . strip ():
return False , "font_family cannot be empty"
2025-11-27 11:29:25 +01:00
# Validate colors
is_valid , error_msg = self . validate_color_param ( text_color , "text_color" )
if not is_valid :
return False , error_msg
2025-12-13 13:49:28 -08:00
is_valid , error_msg = self . validate_color_param (
background_color , "background_color"
)
2025-11-27 11:29:25 +01:00
if not is_valid :
return False , error_msg
2025-12-13 13:49:28 -08:00
2026-02-07 13:44:09 -08:00
is_valid , error_msg = self . validate_link_url ( link_url )
if not is_valid :
return False , error_msg
2025-08-10 15:21:10 -04:00
return True , ""
2025-11-27 11:29:25 +01:00
2026-02-07 13:44:09 -08:00
def validate_link_url ( self , link_url : Optional [ str ]) -> Tuple [ bool , str ]:
"""Validate hyperlink URL parameters."""
if link_url is None :
return True , ""
if not isinstance ( link_url , str ):
return False , f "link_url must be a string, got { type ( link_url ) . __name__ } "
if not link_url . strip ():
return False , "link_url cannot be empty"
2026-02-17 14:49:49 -05:00
parsed = urlparse ( link_url )
if parsed . scheme not in ( "http" , "https" ):
2026-02-07 13:44:09 -08:00
return False , "link_url must start with http:// or https://"
2026-02-17 14:49:49 -05:00
if not parsed . netloc :
return False , "link_url must include a valid host"
2026-02-07 13:44:09 -08:00
return True , ""
2025-11-27 11:29:25 +01:00
2026-02-13 14:29:43 -08:00
def validate_paragraph_style_params (
self ,
heading_level : Optional [ int ] = None ,
alignment : Optional [ str ] = None ,
line_spacing : Optional [ float ] = None ,
indent_first_line : Optional [ float ] = None ,
indent_start : Optional [ float ] = None ,
indent_end : Optional [ float ] = None ,
space_above : Optional [ float ] = None ,
space_below : Optional [ float ] = None ,
2026-03-12 20:34:36 +01:00
named_style_type : Optional [ str ] = None ,
2026-02-13 14:29:43 -08:00
) -> Tuple [ bool , str ]:
"""
Validate paragraph style parameters.
Args:
heading_level: Heading level 0-6 (0 = NORMAL_TEXT, 1-6 = HEADING_N)
alignment: Text alignment - 'START', 'CENTER', 'END', or 'JUSTIFIED'
line_spacing: Line spacing multiplier (must be positive)
indent_first_line: First line indent in points
indent_start: Left/start indent in points
indent_end: Right/end indent in points
space_above: Space above paragraph in points
space_below: Space below paragraph in points
2026-03-12 20:34:36 +01:00
named_style_type: Direct named style (TITLE, SUBTITLE, HEADING_1..6, NORMAL_TEXT)
2026-02-13 14:29:43 -08:00
Returns:
Tuple of (is_valid, error_message)
"""
style_params = [
heading_level ,
alignment ,
line_spacing ,
indent_first_line ,
indent_start ,
indent_end ,
space_above ,
space_below ,
2026-03-12 20:34:36 +01:00
named_style_type ,
2026-02-13 14:29:43 -08:00
]
if all ( param is None for param in style_params ):
return (
False ,
2026-03-12 20:34:36 +01:00
"At least one paragraph style parameter must be provided (heading_level, alignment, line_spacing, indent_first_line, indent_start, indent_end, space_above, space_below, or named_style_type)" ,
2026-02-13 14:29:43 -08:00
)
2026-03-12 20:34:36 +01:00
if named_style_type is not None :
valid_styles = [
2026-03-15 17:29:20 -04:00
"NORMAL_TEXT" ,
"TITLE" ,
"SUBTITLE" ,
"HEADING_1" ,
"HEADING_2" ,
"HEADING_3" ,
"HEADING_4" ,
"HEADING_5" ,
"HEADING_6" ,
2026-03-12 20:34:36 +01:00
]
if named_style_type not in valid_styles :
return (
False ,
f "Invalid named_style_type ' { named_style_type } '. Must be one of: { ', ' . join ( valid_styles ) } " ,
)
2026-02-13 14:29:43 -08:00
if heading_level is not None :
if not isinstance ( heading_level , int ):
return (
False ,
f "heading_level must be an integer, got { type ( heading_level ) . __name__ } " ,
)
min_level , max_level = self . validation_rules [ "heading_level_range" ]
if not ( min_level <= heading_level <= max_level ):
return (
False ,
f "heading_level must be between { min_level } and { max_level } , got { heading_level } " ,
)
if alignment is not None :
if not isinstance ( alignment , str ):
return (
False ,
f "alignment must be a string, got { type ( alignment ) . __name__ } " ,
)
valid = self . validation_rules [ "valid_alignments" ]
if alignment . upper () not in valid :
return (
False ,
f "alignment must be one of: { ', ' . join ( valid ) } , got ' { alignment } '" ,
)
if line_spacing is not None :
if not isinstance ( line_spacing , ( int , float )):
return (
False ,
f "line_spacing must be a number, got { type ( line_spacing ) . __name__ } " ,
)
if line_spacing <= 0 :
return False , "line_spacing must be positive"
for param , name in [
( indent_first_line , "indent_first_line" ),
( indent_start , "indent_start" ),
( indent_end , "indent_end" ),
( space_above , "space_above" ),
( space_below , "space_below" ),
]:
if param is not None :
if not isinstance ( param , ( int , float )):
return (
False ,
f " { name } must be a number, got { type ( param ) . __name__ } " ,
)
2026-02-14 12:15:43 -05:00
# indent_first_line may be negative (hanging indent)
2026-02-13 15:11:31 -08:00
if name != "indent_first_line" and param < 0 :
2026-02-13 14:29:43 -08:00
return False , f " { name } must be non-negative, got { param } "
return True , ""
2025-12-20 22:39:41 +01:00
def validate_color_param (
self , color : Optional [ str ], param_name : str
) -> Tuple [ bool , str ]:
"""Validate color parameters (hex string "#RRGGBB")."""
2025-11-27 11:29:25 +01:00
if color is None :
return True , ""
2025-12-20 22:39:41 +01:00
if not isinstance ( color , str ):
return False , f " { param_name } must be a hex string like '#RRGGBB'"
2025-11-27 11:29:25 +01:00
2025-12-20 22:39:41 +01:00
if len ( color ) != 7 or not color . startswith ( "#" ):
return False , f " { param_name } must be a hex string like '#RRGGBB'"
2025-11-27 11:29:25 +01:00
2025-12-20 22:39:41 +01:00
hex_color = color [ 1 :]
if any ( c not in "0123456789abcdefABCDEF" for c in hex_color ):
return False , f " { param_name } must be a hex string like '#RRGGBB'"
2025-11-27 11:29:25 +01:00
2025-12-20 22:39:41 +01:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:56:18 -04:00
def validate_index ( self , index : int , context : str = "Index" ) -> Tuple [ bool , str ]:
"""
Validate a single document index.
2025-12-13 13:49:28 -08:00
2025-08-10 15:56:18 -04:00
Args:
index: Index to validate
context: Context description for error messages
2025-12-13 13:49:28 -08:00
2025-08-10 15:56:18 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance ( index , int ):
return False , f " { context } must be an integer, got { type ( index ) . __name__ } "
2025-12-13 13:49:28 -08:00
2025-08-10 15:56:18 -04:00
if index < 0 :
2025-12-13 13:49:28 -08:00
return (
False ,
f " { context } { index } is negative. You MUST call inspect_doc_structure first to get the proper insertion index." ,
)
2025-08-10 15:56:18 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_index_range (
self ,
start_index : int ,
end_index : Optional [ int ] = None ,
2025-12-13 13:49:28 -08:00
document_length : Optional [ int ] = None ,
2025-08-10 15:21:10 -04:00
) -> Tuple [ bool , str ]:
"""
Validate document index ranges.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
start_index: Starting index
end_index: Ending index (optional)
document_length: Total document length for bounds checking
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
# Validate start_index
if not isinstance ( start_index , int ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "start_index must be an integer, got { type ( start_index ) . __name__ } " ,
)
2025-08-10 15:21:10 -04:00
if start_index < 0 :
return False , f "start_index cannot be negative, got { start_index } "
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
# Validate end_index if provided
if end_index is not None :
if not isinstance ( end_index , int ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "end_index must be an integer, got { type ( end_index ) . __name__ } " ,
)
2025-08-10 15:21:10 -04:00
if end_index <= start_index :
2025-12-13 13:49:28 -08:00
return (
False ,
f "end_index ( { end_index } ) must be greater than start_index ( { start_index } )" ,
)
2025-08-10 15:21:10 -04:00
# Validate against document length if provided
if document_length is not None :
if start_index >= document_length :
2025-12-13 13:49:28 -08:00
return (
False ,
f "start_index ( { start_index } ) exceeds document length ( { document_length } )" ,
)
2025-08-10 15:21:10 -04:00
if end_index is not None and end_index > document_length :
2025-12-13 13:49:28 -08:00
return (
False ,
f "end_index ( { end_index } ) exceeds document length ( { document_length } )" ,
)
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_element_insertion_params (
2025-12-13 13:49:28 -08:00
self , element_type : str , index : int , ** kwargs
2025-08-10 15:21:10 -04:00
) -> Tuple [ bool , str ]:
"""
Validate parameters for element insertion.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
element_type: Type of element to insert
index: Insertion index
**kwargs: Additional parameters specific to element type
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
# Validate element type
2025-12-13 13:49:28 -08:00
if element_type not in self . validation_rules [ "valid_element_types" ]:
valid_types = ", " . join ( self . validation_rules [ "valid_element_types" ])
return (
False ,
f "Invalid element_type ' { element_type } '. Must be one of: { valid_types } " ,
)
2025-08-10 15:21:10 -04:00
# Validate index
if not isinstance ( index , int ) or index < 0 :
return False , f "index must be a non-negative integer, got { index } "
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
# Validate element-specific parameters
if element_type == "table" :
2025-12-13 13:49:28 -08:00
rows = kwargs . get ( "rows" )
columns = kwargs . get ( "columns" )
2025-08-10 15:21:10 -04:00
if not rows or not columns :
return False , "Table insertion requires 'rows' and 'columns' parameters"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
if not isinstance ( rows , int ) or not isinstance ( columns , int ):
return False , "Table rows and columns must be integers"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
if rows <= 0 or columns <= 0 :
return False , "Table rows and columns must be positive integers"
2025-12-13 13:49:28 -08:00
if rows > self . validation_rules [ "table_max_rows" ]:
return (
False ,
f "Too many rows ( { rows } ). Maximum: { self . validation_rules [ 'table_max_rows' ] } " ,
)
if columns > self . validation_rules [ "table_max_columns" ]:
return (
False ,
f "Too many columns ( { columns } ). Maximum: { self . validation_rules [ 'table_max_columns' ] } " ,
)
2025-08-10 15:21:10 -04:00
elif element_type == "list" :
2025-12-13 13:49:28 -08:00
list_type = kwargs . get ( "list_type" )
2025-08-10 15:21:10 -04:00
if not list_type :
return False , "List insertion requires 'list_type' parameter"
2025-12-13 13:49:28 -08:00
if list_type not in self . validation_rules [ "valid_list_types" ]:
valid_types = ", " . join ( self . validation_rules [ "valid_list_types" ])
return (
False ,
f "Invalid list_type ' { list_type } '. Must be one of: { valid_types } " ,
)
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def validate_header_footer_params (
2025-12-13 13:49:28 -08:00
self , section_type : str , header_footer_type : str = "DEFAULT"
2025-08-10 15:21:10 -04:00
) -> Tuple [ bool , str ]:
"""
Validate header/footer operation parameters.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
section_type: Type of section ("header" or "footer")
header_footer_type: Specific header/footer type
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
2025-12-13 13:49:28 -08:00
if section_type not in self . validation_rules [ "valid_section_types" ]:
valid_types = ", " . join ( self . validation_rules [ "valid_section_types" ])
return (
False ,
f "section_type must be one of: { valid_types } , got ' { section_type } '" ,
)
if header_footer_type not in self . validation_rules [ "valid_header_footer_types" ]:
valid_types = ", " . join ( self . validation_rules [ "valid_header_footer_types" ])
return (
False ,
f "header_footer_type must be one of: { valid_types } , got ' { header_footer_type } '" ,
)
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
def validate_batch_operations (
self , operations : List [ Dict [ str , Any ]]
) -> Tuple [ bool , str ]:
2025-08-10 15:21:10 -04:00
"""
Validate a list of batch operations.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
operations: List of operation dictionaries
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
if not operations :
return False , "Operations list cannot be empty"
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
if not isinstance ( operations , list ):
return False , f "Operations must be a list, got { type ( operations ) . __name__ } "
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
# Validate each operation
for i , op in enumerate ( operations ):
if not isinstance ( op , dict ):
2025-12-13 13:49:28 -08:00
return (
False ,
f "Operation { i + 1 } must be a dictionary, got { type ( op ) . __name__ } " ,
)
if "type" not in op :
return False , f "Operation { i + 1 } missing required 'type' field"
2025-12-13 11:11:04 -08:00
# Validate required fields for the operation type
is_valid , error_msg = validate_operation ( op )
if not is_valid :
2025-12-13 13:49:28 -08:00
return False , f "Operation { i + 1 } : { error_msg } "
2025-12-13 11:11:04 -08:00
2025-12-13 13:49:28 -08:00
op_type = op [ "type" ]
2025-12-13 11:11:04 -08:00
2025-12-13 13:49:28 -08:00
if op_type == "format_text" :
2025-12-13 11:11:04 -08:00
is_valid , error_msg = self . validate_text_formatting_params (
2025-12-13 13:49:28 -08:00
op . get ( "bold" ),
op . get ( "italic" ),
op . get ( "underline" ),
op . get ( "font_size" ),
op . get ( "font_family" ),
op . get ( "text_color" ),
op . get ( "background_color" ),
2026-02-07 13:44:09 -08:00
op . get ( "link_url" ),
2025-12-13 11:11:04 -08:00
)
if not is_valid :
2025-12-13 13:49:28 -08:00
return False , f "Operation { i + 1 } (format_text): { error_msg } "
2025-12-13 11:11:04 -08:00
is_valid , error_msg = self . validate_index_range (
2025-12-13 13:49:28 -08:00
op [ "start_index" ], op [ "end_index" ]
2025-12-13 11:11:04 -08:00
)
if not is_valid :
2025-12-13 13:49:28 -08:00
return False , f "Operation { i + 1 } (format_text): { error_msg } "
2026-02-13 14:29:43 -08:00
elif op_type == "update_paragraph_style" :
is_valid , error_msg = self . validate_paragraph_style_params (
op . get ( "heading_level" ),
op . get ( "alignment" ),
op . get ( "line_spacing" ),
op . get ( "indent_first_line" ),
op . get ( "indent_start" ),
op . get ( "indent_end" ),
op . get ( "space_above" ),
op . get ( "space_below" ),
2026-03-12 20:34:36 +01:00
op . get ( "named_style_type" ),
2026-02-13 14:29:43 -08:00
)
if not is_valid :
return (
False ,
f "Operation { i + 1 } (update_paragraph_style): { error_msg } " ,
)
is_valid , error_msg = self . validate_index_range (
op [ "start_index" ], op [ "end_index" ]
)
if not is_valid :
return (
False ,
f "Operation { i + 1 } (update_paragraph_style): { error_msg } " ,
)
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
def validate_text_content (
self , text : str , max_length : Optional [ int ] = None
) -> Tuple [ bool , str ]:
2025-08-10 15:21:10 -04:00
"""
Validate text content for insertion.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Args:
text: Text to validate
max_length: Maximum allowed length
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Tuple of (is_valid, error_message)
"""
if not isinstance ( text , str ):
return False , f "Text must be a string, got { type ( text ) . __name__ } "
2025-12-13 13:49:28 -08:00
max_len = max_length or self . validation_rules [ "max_text_length" ]
2025-08-10 15:21:10 -04:00
if len ( text ) > max_len :
return False , f "Text too long ( { len ( text ) } characters). Maximum: { max_len } "
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
return True , ""
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
def get_validation_summary ( self ) -> Dict [ str , Any ]:
"""
Get a summary of all validation rules and constraints.
2025-12-13 13:49:28 -08:00
2025-08-10 15:21:10 -04:00
Returns:
Dictionary containing validation rules
"""
return {
2025-12-13 13:49:28 -08:00
"constraints" : self . validation_rules . copy (),
"supported_operations" : {
"table_operations" : [ "create_table" , "populate_table" ],
2026-02-13 15:11:31 -08:00
"text_operations" : [
"insert_text" ,
"format_text" ,
"find_replace" ,
"update_paragraph_style" ,
],
2025-12-13 13:49:28 -08:00
"element_operations" : [
"insert_table" ,
"insert_list" ,
"insert_page_break" ,
],
"header_footer_operations" : [ "update_header" , "update_footer" ],
},
"data_formats" : {
"table_data" : "2D list of strings: [['col1', 'col2'], ['row1col1', 'row1col2']]" ,
"text_formatting" : "Optional boolean/integer parameters for styling" ,
"document_indices" : "Non-negative integers for position specification" ,
2025-08-10 15:21:10 -04:00
},
2025-11-27 11:29:25 +01:00
}