@@ -26,21 +26,95 @@ def exists(value: Any) -> Any:
2626 return value
2727
2828
29+ def validate_field_schema (trigger_schema : dict [str , Any ]) -> dict [str , Any ]:
30+ """Validate a field schema including context references."""
31+
32+ for field_name , field_schema in trigger_schema .get ("fields" , {}).items ():
33+ # Validate context if present
34+ if "context" in field_schema :
35+ if CONF_SELECTOR not in field_schema :
36+ raise vol .Invalid (
37+ f"Context defined without a selector in '{ field_name } '"
38+ )
39+
40+ context = field_schema ["context" ]
41+ if not isinstance (context , dict ):
42+ raise vol .Invalid (f"Context must be a dictionary in '{ field_name } '" )
43+
44+ # Determine which selector type is being used
45+ selector_config = field_schema [CONF_SELECTOR ]
46+ selector_class = selector .selector (selector_config )
47+
48+ for context_key , field_ref in context .items ():
49+ # Check if context key is allowed for this selector type
50+ allowed_keys = selector_class .allowed_context_keys
51+ if context_key not in allowed_keys :
52+ raise vol .Invalid (
53+ f"Invalid context key '{ context_key } ' for selector type '{ selector_class .selector_type } '. "
54+ f"Allowed keys: { ', ' .join (sorted (allowed_keys )) if allowed_keys else 'none' } "
55+ )
56+
57+ # Check if the referenced field exists in trigger schema or target
58+ if not isinstance (field_ref , str ):
59+ raise vol .Invalid (
60+ f"Context value for '{ context_key } ' must be a string field reference"
61+ )
62+
63+ # Check if field exists in trigger schema fields or target
64+ trigger_fields = trigger_schema ["fields" ]
65+ field_exists = field_ref in trigger_fields
66+ if field_exists and "selector" in trigger_fields [field_ref ]:
67+ # Check if the selector type is allowed for this context key
68+ field_selector_config = trigger_fields [field_ref ][CONF_SELECTOR ]
69+ field_selector_class = selector .selector (field_selector_config )
70+ if field_selector_class .selector_type not in allowed_keys .get (
71+ context_key , set ()
72+ ):
73+ raise vol .Invalid (
74+ f"The context '{ context_key } ' for '{ field_name } ' references '{ field_ref } ', but '{ context_key } ' "
75+ f"does not allow selectors of type '{ field_selector_class .selector_type } '. Allowed selector types: { ', ' .join (allowed_keys .get (context_key , set ()))} "
76+ )
77+ if not field_exists and "target" in trigger_schema :
78+ # Target is a special field that always exists when defined
79+ field_exists = field_ref == "target"
80+ if field_exists and "target" not in allowed_keys .get (
81+ context_key , set ()
82+ ):
83+ raise vol .Invalid (
84+ f"The context '{ context_key } ' for '{ field_name } ' references 'target', but '{ context_key } ' "
85+ f"does not allow 'target'. Allowed selector types: { ', ' .join (allowed_keys .get (context_key , set ()))} "
86+ )
87+
88+ if not field_exists :
89+ raise vol .Invalid (
90+ f"Context reference '{ field_ref } ' for key '{ context_key } ' does not exist "
91+ f"in trigger schema fields or target"
92+ )
93+
94+ return trigger_schema
95+
96+
2997FIELD_SCHEMA = vol .Schema (
3098 {
3199 vol .Optional ("example" ): exists ,
32100 vol .Optional ("default" ): exists ,
33101 vol .Optional ("required" ): bool ,
34102 vol .Optional (CONF_SELECTOR ): selector .validate_selector ,
103+ vol .Optional ("context" ): {
104+ str : str # key is context key, value is field name in the schema which value should be used
105+ }, # Will be validated in validate_field_schema
35106 }
36107)
37108
38109TRIGGER_SCHEMA = vol .Any (
39- vol .Schema (
40- {
41- vol .Optional ("target" ): selector .TargetSelector .CONFIG_SCHEMA ,
42- vol .Optional ("fields" ): vol .Schema ({str : FIELD_SCHEMA }),
43- }
110+ vol .All (
111+ vol .Schema (
112+ {
113+ vol .Optional ("target" ): selector .TargetSelector .CONFIG_SCHEMA ,
114+ vol .Optional ("fields" ): vol .Schema ({str : FIELD_SCHEMA }),
115+ }
116+ ),
117+ validate_field_schema ,
44118 ),
45119 None ,
46120)
0 commit comments