ultrafast_mcp_core/schema/
validation.rs

1use crate::error::{MCPResult, ToolError};
2use serde_json::{Value, json};
3use std::borrow::Cow;
4use std::collections::HashSet;
5
6/// Enhanced schema validation with comprehensive JSON Schema support
7pub fn validate_against_schema(data: &Value, schema: &Value) -> MCPResult<()> {
8    // Handle allOf first: validate all subschemas, then parent schema (without allOf)
9    if let Some(all_of) = schema.get("allOf") {
10        if let Some(schemas) = all_of.as_array() {
11            for schema_item in schemas {
12                if let Err(e) = validate_against_schema(data, schema_item) {
13                    return Err(ToolError::SchemaValidation(format!(
14                        "allOf validation failed: {e}"
15                    ))
16                    .into());
17                }
18            }
19        }
20        // Validate parent schema with allOf removed
21        if let Some(mut parent) = schema.as_object().cloned() {
22            parent.remove("allOf");
23            let parent_schema = Value::Object(parent);
24            return validate_against_schema(data, &parent_schema);
25        }
26    }
27    // Handle anyOf/oneOf: validate and return immediately
28    if schema.get("anyOf").is_some() || schema.get("oneOf").is_some() {
29        validate_combined_schemas(data, schema)?;
30        return Ok(());
31    }
32
33    // Handle null values
34    if data.is_null() {
35        if let Some(nullable) = schema.get("nullable") {
36            if nullable.as_bool().unwrap_or(false) {
37                return Ok(());
38            }
39        }
40        if let Some(schema_type) = schema.get("type").and_then(|t| t.as_str()) {
41            if schema_type == "null" {
42                return Ok(());
43            }
44        }
45        return Err(ToolError::SchemaValidation("Value cannot be null".to_string()).into());
46    }
47
48    // Handle type validation
49    if let Some(schema_type) = schema.get("type").and_then(|t| t.as_str()) {
50        match schema_type {
51            "string" => validate_string(data, schema)?,
52            "number" => validate_number(data, schema)?,
53            "integer" => validate_integer(data, schema)?,
54            "boolean" => validate_boolean(data, schema)?,
55            "array" => validate_array(data, schema)?,
56            "object" => validate_object(data, schema)?,
57            "null" => {
58                if !data.is_null() {
59                    return Err(ToolError::SchemaValidation(format!(
60                        "Expected null, got {}",
61                        type_name(data)
62                    ))
63                    .into());
64                }
65            }
66            _ => {
67                // Unknown type, skip validation
68            }
69        }
70    }
71
72    // Type-specific constraints (enforced even if type is not present)
73    // String constraints
74    if data.is_string() {
75        let string_value = data.as_str().unwrap();
76        if let Some(min_length) = schema.get("minLength").and_then(|v| v.as_u64()) {
77            if string_value.len() < min_length as usize {
78                return Err(ToolError::SchemaValidation(format!(
79                    "String length {} is less than minimum {}",
80                    string_value.len(),
81                    min_length
82                ))
83                .into());
84            }
85        }
86        if let Some(max_length) = schema.get("maxLength").and_then(|v| v.as_u64()) {
87            if string_value.len() > max_length as usize {
88                return Err(ToolError::SchemaValidation(format!(
89                    "String length {} is greater than maximum {}",
90                    string_value.len(),
91                    max_length
92                ))
93                .into());
94            }
95        }
96        if let Some(pattern) = schema.get("pattern").and_then(|v| v.as_str()) {
97            if let Ok(regex) = regex::Regex::new(pattern) {
98                if !regex.is_match(string_value) {
99                    return Err(ToolError::SchemaValidation(format!(
100                        "String '{string_value}' does not match pattern '{pattern}'"
101                    ))
102                    .into());
103                }
104            }
105        }
106        if let Some(format) = schema.get("format").and_then(|v| v.as_str()) {
107            validate_string_format(string_value, format)?;
108        }
109    }
110    // Number constraints
111    if data.is_number() {
112        let number_value = data.as_f64().unwrap();
113        if let Some(minimum) = schema.get("minimum").and_then(|v| v.as_f64()) {
114            let exclusive = schema
115                .get("exclusiveMinimum")
116                .and_then(|v| v.as_bool())
117                .unwrap_or(false);
118            if exclusive {
119                if number_value <= minimum {
120                    return Err(ToolError::SchemaValidation(format!(
121                        "Number {number_value} must be greater than {minimum}"
122                    ))
123                    .into());
124                }
125            } else if number_value < minimum {
126                return Err(ToolError::SchemaValidation(format!(
127                    "Number {number_value} must be greater than or equal to {minimum}"
128                ))
129                .into());
130            }
131        }
132        if let Some(maximum) = schema.get("maximum").and_then(|v| v.as_f64()) {
133            let exclusive = schema
134                .get("exclusiveMaximum")
135                .and_then(|v| v.as_bool())
136                .unwrap_or(false);
137            if exclusive {
138                if number_value >= maximum {
139                    return Err(ToolError::SchemaValidation(format!(
140                        "Number {number_value} must be less than {maximum}"
141                    ))
142                    .into());
143                }
144            } else if number_value > maximum {
145                return Err(ToolError::SchemaValidation(format!(
146                    "Number {number_value} must be less than or equal to {maximum}"
147                ))
148                .into());
149            }
150        }
151        if let Some(multiple_of) = schema.get("multipleOf").and_then(|v| v.as_f64()) {
152            if multiple_of != 0.0 && (number_value % multiple_of).abs() > f64::EPSILON {
153                return Err(ToolError::SchemaValidation(format!(
154                    "Number {number_value} must be a multiple of {multiple_of}"
155                ))
156                .into());
157            }
158        }
159    }
160    // Integer constraints
161    if data.is_i64() || data.is_u64() {
162        let integer_value = data
163            .as_i64()
164            .unwrap_or_else(|| data.as_u64().unwrap() as i64);
165        if let Some(minimum) = schema.get("minimum").and_then(|v| v.as_i64()) {
166            let exclusive = schema
167                .get("exclusiveMinimum")
168                .and_then(|v| v.as_bool())
169                .unwrap_or(false);
170            if exclusive {
171                if integer_value <= minimum {
172                    return Err(ToolError::SchemaValidation(format!(
173                        "Integer {integer_value} must be greater than {minimum}"
174                    ))
175                    .into());
176                }
177            } else if integer_value < minimum {
178                return Err(ToolError::SchemaValidation(format!(
179                    "Integer {integer_value} must be greater than or equal to {minimum}"
180                ))
181                .into());
182            }
183        }
184        if let Some(maximum) = schema.get("maximum").and_then(|v| v.as_i64()) {
185            let exclusive = schema
186                .get("exclusiveMaximum")
187                .and_then(|v| v.as_bool())
188                .unwrap_or(false);
189            if exclusive {
190                if integer_value >= maximum {
191                    return Err(ToolError::SchemaValidation(format!(
192                        "Integer {integer_value} must be less than {maximum}"
193                    ))
194                    .into());
195                }
196            } else if integer_value > maximum {
197                return Err(ToolError::SchemaValidation(format!(
198                    "Integer {integer_value} must be less than or equal to {maximum}"
199                ))
200                .into());
201            }
202        }
203        if let Some(multiple_of) = schema.get("multipleOf").and_then(|v| v.as_i64()) {
204            if multiple_of != 0 && integer_value % multiple_of != 0 {
205                return Err(ToolError::SchemaValidation(format!(
206                    "Integer {integer_value} must be a multiple of {multiple_of}"
207                ))
208                .into());
209            }
210        }
211    }
212
213    // Validate enum values
214    if let Some(enum_values) = schema.get("enum") {
215        if let Some(enum_array) = enum_values.as_array() {
216            if !enum_array.contains(data) {
217                return Err(ToolError::SchemaValidation(format!(
218                    "Value must be one of: {enum_array:?}"
219                ))
220                .into());
221            }
222        }
223    }
224
225    // Validate const value
226    if let Some(const_value) = schema.get("const") {
227        if data != const_value {
228            return Err(ToolError::SchemaValidation(format!(
229                "Value must be exactly: {const_value:?}"
230            ))
231            .into());
232        }
233    }
234
235    Ok(())
236}
237
238fn validate_string(data: &Value, schema: &Value) -> MCPResult<()> {
239    if !data.is_string() {
240        return Err(ToolError::SchemaValidation(format!(
241            "Expected string, got {}",
242            type_name(data)
243        ))
244        .into());
245    }
246
247    let string_value = data.as_str().unwrap();
248
249    // Validate min/max length
250    if let Some(min_length) = schema.get("minLength").and_then(|v| v.as_u64()) {
251        if string_value.len() < min_length as usize {
252            return Err(ToolError::SchemaValidation(format!(
253                "String length {} is less than minimum {}",
254                string_value.len(),
255                min_length
256            ))
257            .into());
258        }
259    }
260
261    if let Some(max_length) = schema.get("maxLength").and_then(|v| v.as_u64()) {
262        if string_value.len() > max_length as usize {
263            return Err(ToolError::SchemaValidation(format!(
264                "String length {} is greater than maximum {}",
265                string_value.len(),
266                max_length
267            ))
268            .into());
269        }
270    }
271
272    // Validate pattern
273    if let Some(pattern) = schema.get("pattern").and_then(|v| v.as_str()) {
274        if let Ok(regex) = regex::Regex::new(pattern) {
275            if !regex.is_match(string_value) {
276                return Err(ToolError::SchemaValidation(format!(
277                    "String '{string_value}' does not match pattern '{pattern}'"
278                ))
279                .into());
280            }
281        }
282    }
283
284    // Validate format
285    if let Some(format) = schema.get("format").and_then(|v| v.as_str()) {
286        validate_string_format(string_value, format)?;
287    }
288
289    Ok(())
290}
291
292fn validate_number(data: &Value, schema: &Value) -> MCPResult<()> {
293    if !data.is_number() {
294        return Err(ToolError::SchemaValidation(format!(
295            "Expected number, got {}",
296            type_name(data)
297        ))
298        .into());
299    }
300
301    let number_value = data.as_f64().unwrap();
302
303    // Validate minimum/maximum
304    if let Some(minimum) = schema.get("minimum").and_then(|v| v.as_f64()) {
305        let exclusive = schema
306            .get("exclusiveMinimum")
307            .and_then(|v| v.as_bool())
308            .unwrap_or(false);
309        if exclusive {
310            if number_value <= minimum {
311                return Err(ToolError::SchemaValidation(format!(
312                    "Number {number_value} must be greater than {minimum}"
313                ))
314                .into());
315            }
316        } else if number_value < minimum {
317            return Err(ToolError::SchemaValidation(format!(
318                "Number {number_value} must be greater than or equal to {minimum}"
319            ))
320            .into());
321        }
322    }
323
324    if let Some(maximum) = schema.get("maximum").and_then(|v| v.as_f64()) {
325        let exclusive = schema
326            .get("exclusiveMaximum")
327            .and_then(|v| v.as_bool())
328            .unwrap_or(false);
329        if exclusive {
330            if number_value >= maximum {
331                return Err(ToolError::SchemaValidation(format!(
332                    "Number {number_value} must be less than {maximum}"
333                ))
334                .into());
335            }
336        } else if number_value > maximum {
337            return Err(ToolError::SchemaValidation(format!(
338                "Number {number_value} must be less than or equal to {maximum}"
339            ))
340            .into());
341        }
342    }
343
344    // Validate multipleOf
345    if let Some(multiple_of) = schema.get("multipleOf").and_then(|v| v.as_f64()) {
346        if multiple_of != 0.0 && (number_value % multiple_of).abs() > f64::EPSILON {
347            return Err(ToolError::SchemaValidation(format!(
348                "Number {number_value} must be a multiple of {multiple_of}"
349            ))
350            .into());
351        }
352    }
353
354    Ok(())
355}
356
357fn validate_integer(data: &Value, schema: &Value) -> MCPResult<()> {
358    if !data.is_i64() && !data.is_u64() {
359        return Err(ToolError::SchemaValidation(format!(
360            "Expected integer, got {}",
361            type_name(data)
362        ))
363        .into());
364    }
365
366    let integer_value = data
367        .as_i64()
368        .unwrap_or_else(|| data.as_u64().unwrap() as i64);
369
370    // Validate minimum/maximum
371    if let Some(minimum) = schema.get("minimum").and_then(|v| v.as_i64()) {
372        let exclusive = schema
373            .get("exclusiveMinimum")
374            .and_then(|v| v.as_bool())
375            .unwrap_or(false);
376        if exclusive {
377            if integer_value <= minimum {
378                return Err(ToolError::SchemaValidation(format!(
379                    "Integer {integer_value} must be greater than {minimum}"
380                ))
381                .into());
382            }
383        } else if integer_value < minimum {
384            return Err(ToolError::SchemaValidation(format!(
385                "Integer {integer_value} must be greater than or equal to {minimum}"
386            ))
387            .into());
388        }
389    }
390
391    if let Some(maximum) = schema.get("maximum").and_then(|v| v.as_i64()) {
392        let exclusive = schema
393            .get("exclusiveMaximum")
394            .and_then(|v| v.as_bool())
395            .unwrap_or(false);
396        if exclusive {
397            if integer_value >= maximum {
398                return Err(ToolError::SchemaValidation(format!(
399                    "Integer {integer_value} must be less than {maximum}"
400                ))
401                .into());
402            }
403        } else if integer_value > maximum {
404            return Err(ToolError::SchemaValidation(format!(
405                "Integer {integer_value} must be less than or equal to {maximum}"
406            ))
407            .into());
408        }
409    }
410
411    // Validate multipleOf
412    if let Some(multiple_of) = schema.get("multipleOf").and_then(|v| v.as_i64()) {
413        if multiple_of != 0 && integer_value % multiple_of != 0 {
414            return Err(ToolError::SchemaValidation(format!(
415                "Integer {integer_value} must be a multiple of {multiple_of}"
416            ))
417            .into());
418        }
419    }
420
421    Ok(())
422}
423
424fn validate_boolean(data: &Value, _schema: &Value) -> MCPResult<()> {
425    if !data.is_boolean() {
426        return Err(ToolError::SchemaValidation(format!(
427            "Expected boolean, got {}",
428            type_name(data)
429        ))
430        .into());
431    }
432    Ok(())
433}
434
435fn validate_array(data: &Value, schema: &Value) -> MCPResult<()> {
436    if !data.is_array() {
437        return Err(ToolError::SchemaValidation(format!(
438            "Expected array, got {}",
439            type_name(data)
440        ))
441        .into());
442    }
443
444    let array = data.as_array().unwrap();
445
446    // Validate min/max items
447    if let Some(min_items) = schema.get("minItems").and_then(|v| v.as_u64()) {
448        if array.len() < min_items as usize {
449            return Err(ToolError::SchemaValidation(format!(
450                "Array has {} items, minimum required is {}",
451                array.len(),
452                min_items
453            ))
454            .into());
455        }
456    }
457
458    if let Some(max_items) = schema.get("maxItems").and_then(|v| v.as_u64()) {
459        if array.len() > max_items as usize {
460            return Err(ToolError::SchemaValidation(format!(
461                "Array has {} items, maximum allowed is {}",
462                array.len(),
463                max_items
464            ))
465            .into());
466        }
467    }
468
469    // Validate unique items
470    if let Some(unique_items) = schema.get("uniqueItems").and_then(|v| v.as_bool()) {
471        if unique_items {
472            let mut seen = HashSet::new();
473            for item in array {
474                if !seen.insert(item) {
475                    return Err(ToolError::SchemaValidation(
476                        "Array items must be unique".to_string(),
477                    )
478                    .into());
479                }
480            }
481        }
482    }
483
484    // Validate array items if schema is provided
485    if let Some(items_schema) = schema.get("items") {
486        for (i, item) in array.iter().enumerate() {
487            validate_against_schema(item, items_schema).map_err(|e| {
488                ToolError::SchemaValidation(format!("Array item {i} validation failed: {e}"))
489            })?;
490        }
491    }
492
493    // Validate additional items
494    if let Some(additional_items) = schema.get("additionalItems") {
495        if let Some(items_schema) = schema.get("items") {
496            if let Some(items_array) = items_schema.as_array() {
497                let max_defined_items = items_array.len();
498                if array.len() > max_defined_items {
499                    if let Some(additional_schema) = additional_items.as_object() {
500                        if additional_schema.is_empty() {
501                            return Err(ToolError::SchemaValidation(
502                                "Additional items not allowed".to_string(),
503                            )
504                            .into());
505                        }
506                    } else if !additional_items.as_bool().unwrap_or(true) {
507                        return Err(ToolError::SchemaValidation(
508                            "Additional items not allowed".to_string(),
509                        )
510                        .into());
511                    } else {
512                        for item in &array[max_defined_items..] {
513                            validate_against_schema(item, additional_items)?;
514                        }
515                    }
516                }
517            }
518        }
519    }
520
521    Ok(())
522}
523
524fn validate_object(data: &Value, schema: &Value) -> MCPResult<()> {
525    if !data.is_object() {
526        return Err(ToolError::SchemaValidation(format!(
527            "Expected object, got {}",
528            type_name(data)
529        ))
530        .into());
531    }
532
533    let obj = data.as_object().unwrap();
534
535    // Validate min/max properties
536    if let Some(min_properties) = schema.get("minProperties").and_then(|v| v.as_u64()) {
537        if obj.len() < min_properties as usize {
538            return Err(ToolError::SchemaValidation(format!(
539                "Object has {} properties, minimum required is {}",
540                obj.len(),
541                min_properties
542            ))
543            .into());
544        }
545    }
546
547    if let Some(max_properties) = schema.get("maxProperties").and_then(|v| v.as_u64()) {
548        if obj.len() > max_properties as usize {
549            return Err(ToolError::SchemaValidation(format!(
550                "Object has {} properties, maximum allowed is {}",
551                obj.len(),
552                max_properties
553            ))
554            .into());
555        }
556    }
557
558    // Validate required properties
559    if let Some(required) = schema.get("required") {
560        if let Some(required_array) = required.as_array() {
561            for req in required_array {
562                if let Some(prop_name) = req.as_str() {
563                    if !obj.contains_key(prop_name) {
564                        return Err(ToolError::SchemaValidation(format!(
565                            "Missing required property: {prop_name}"
566                        ))
567                        .into());
568                    }
569                }
570            }
571        }
572    }
573
574    // Validate properties if schema is provided
575    if let Some(properties_schema) = schema.get("properties") {
576        if let Some(props) = properties_schema.as_object() {
577            for (key, value) in obj {
578                if let Some(prop_schema) = props.get(key) {
579                    validate_against_schema(value, prop_schema).map_err(|e| {
580                        ToolError::SchemaValidation(format!(
581                            "Property '{key}' validation failed: {e}"
582                        ))
583                    })?;
584                }
585            }
586        }
587    }
588
589    // Validate additional properties
590    if let Some(additional_properties) = schema.get("additionalProperties") {
591        if let Some(properties_schema) = schema.get("properties") {
592            if let Some(props) = properties_schema.as_object() {
593                for (key, value) in obj {
594                    if !props.contains_key(key) {
595                        if let Some(additional_schema) = additional_properties.as_object() {
596                            if additional_schema.is_empty() {
597                                return Err(ToolError::SchemaValidation(format!(
598                                    "Additional property '{key}' not allowed"
599                                ))
600                                .into());
601                            }
602                        } else if !additional_properties.as_bool().unwrap_or(true) {
603                            return Err(ToolError::SchemaValidation(format!(
604                                "Additional property '{key}' not allowed"
605                            ))
606                            .into());
607                        } else {
608                            validate_against_schema(value, additional_properties)?;
609                        }
610                    }
611                }
612            }
613        }
614    }
615
616    // Validate property names
617    if let Some(property_names) = schema.get("propertyNames") {
618        for key in obj.keys() {
619            let key_value = Value::String(key.clone());
620            validate_against_schema(&key_value, property_names).map_err(|e| {
621                ToolError::SchemaValidation(format!("Property name '{key}' validation failed: {e}"))
622            })?;
623        }
624    }
625
626    // Validate dependencies
627    if let Some(dependencies) = schema.get("dependencies") {
628        if let Some(deps) = dependencies.as_object() {
629            for (property, dependency) in deps {
630                if obj.contains_key(property) {
631                    match dependency {
632                        Value::Array(required_props) => {
633                            for req_prop in required_props {
634                                if let Some(prop_name) = req_prop.as_str() {
635                                    if !obj.contains_key(prop_name) {
636                                        return Err(ToolError::SchemaValidation(format!(
637                                            "Property '{property}' requires property '{prop_name}'"
638                                        ))
639                                        .into());
640                                    }
641                                }
642                            }
643                        }
644                        Value::Object(schema_dep) => {
645                            let schema_value = Value::Object(schema_dep.clone());
646                            validate_against_schema(data, &schema_value)?;
647                        }
648                        _ => {}
649                    }
650                }
651            }
652        }
653    }
654
655    Ok(())
656}
657
658fn validate_combined_schemas(data: &Value, schema: &Value) -> MCPResult<()> {
659    // Validate oneOf (exactly one schema must match)
660    if let Some(one_of) = schema.get("oneOf") {
661        if let Some(schemas) = one_of.as_array() {
662            let mut matches = 0;
663            for schema_item in schemas {
664                if validate_against_schema(data, schema_item).is_ok() {
665                    matches += 1;
666                }
667            }
668            if matches != 1 {
669                return Err(ToolError::SchemaValidation(
670                    "Value must match exactly one schema from oneOf".to_string(),
671                )
672                .into());
673            }
674        }
675    }
676
677    // Validate anyOf (at least one schema must match)
678    if let Some(any_of) = schema.get("anyOf") {
679        if let Some(schemas) = any_of.as_array() {
680            let mut has_match = false;
681            for schema_item in schemas {
682                if validate_against_schema(data, schema_item).is_ok() {
683                    has_match = true;
684                    break;
685                }
686            }
687            if !has_match {
688                return Err(ToolError::SchemaValidation(
689                    "Value must match at least one schema from anyOf".to_string(),
690                )
691                .into());
692            }
693        }
694    }
695
696    // Validate allOf (all schemas must match)
697    if let Some(all_of) = schema.get("allOf") {
698        if let Some(schemas) = all_of.as_array() {
699            for schema_item in schemas {
700                if let Err(e) = validate_against_schema(data, schema_item) {
701                    return Err(ToolError::SchemaValidation(format!(
702                        "allOf validation failed: {e}"
703                    ))
704                    .into());
705                }
706            }
707        }
708    }
709
710    Ok(())
711}
712
713fn validate_string_format(value: &str, format: &str) -> MCPResult<()> {
714    match format {
715        "date-time" => {
716            // Basic ISO 8601 date-time validation
717            if !value.contains('T') && !value.contains(' ') {
718                return Err(
719                    ToolError::SchemaValidation("Invalid date-time format".to_string()).into(),
720                );
721            }
722        }
723        "date" => {
724            // Basic date validation (YYYY-MM-DD)
725            if value.matches(r"^\d{4}-\d{2}-\d{2}$").next().is_none() {
726                return Err(ToolError::SchemaValidation("Invalid date format".to_string()).into());
727            }
728        }
729        "time" => {
730            // Basic time validation (HH:MM:SS)
731            if value.matches(r"^\d{2}:\d{2}:\d{2}").next().is_none() {
732                return Err(ToolError::SchemaValidation("Invalid time format".to_string()).into());
733            }
734        }
735        "email" => {
736            // Basic email validation
737            if !value.contains('@') || !value.contains('.') {
738                return Err(ToolError::SchemaValidation("Invalid email format".to_string()).into());
739            }
740        }
741        "uri" => {
742            // Basic URI validation
743            if !value.contains("://") {
744                return Err(ToolError::SchemaValidation("Invalid URI format".to_string()).into());
745            }
746        }
747        "uuid" => {
748            // Basic UUID validation
749            if value
750                .matches(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$")
751                .next()
752                .is_none()
753            {
754                return Err(ToolError::SchemaValidation("Invalid UUID format".to_string()).into());
755            }
756        }
757        _ => {
758            // Unknown format, skip validation
759        }
760    }
761    Ok(())
762}
763
764/// Get the type name of a JSON value for error messages
765fn type_name(value: &Value) -> &'static str {
766    match value {
767        Value::Null => "null",
768        Value::Bool(_) => "boolean",
769        Value::Number(_) => "number",
770        Value::String(_) => "string",
771        Value::Array(_) => "array",
772        Value::Object(_) => "object",
773    }
774}
775
776/// Validate that data conforms to expected input schema for a tool
777pub fn validate_tool_input(data: &Value, schema: &Value) -> MCPResult<()> {
778    validate_against_schema(data, schema)
779}
780
781/// Validate that data conforms to expected output schema for a tool
782pub fn validate_tool_output(data: &Value, schema: &Value) -> MCPResult<()> {
783    validate_against_schema(data, schema)
784}
785
786/// Validate a complete tool schema definition
787pub fn validate_tool_schema(schema: &Value) -> MCPResult<()> {
788    // Check if schema is an object
789    if !schema.is_object() {
790        return Err(
791            ToolError::SchemaValidation("Tool schema must be an object".to_string()).into(),
792        );
793    }
794
795    // Validate required top-level properties for tool schemas
796    if let Some(obj) = schema.as_object() {
797        // Check for required properties
798        if !obj.contains_key("type") {
799            return Err(ToolError::SchemaValidation(
800                "Tool schema must have 'type' property".to_string(),
801            )
802            .into());
803        }
804
805        // Validate type
806        if let Some(schema_type) = obj.get("type").and_then(|t| t.as_str()) {
807            if schema_type != "object" {
808                return Err(ToolError::SchemaValidation(
809                    "Tool schema type must be 'object'".to_string(),
810                )
811                .into());
812            }
813        }
814
815        // Validate properties if present
816        if let Some(properties) = obj.get("properties") {
817            validate_against_schema(properties, &json!({"type": "object"}))?;
818        }
819
820        // Validate required properties array if present
821        if let Some(required) = obj.get("required") {
822            if let Some(required_array) = required.as_array() {
823                for req in required_array {
824                    if !req.is_string() {
825                        return Err(ToolError::SchemaValidation(
826                            "Required properties must be strings".to_string(),
827                        )
828                        .into());
829                    }
830                }
831            } else {
832                return Err(ToolError::SchemaValidation(
833                    "Required property must be an array".to_string(),
834                )
835                .into());
836            }
837        }
838    }
839
840    Ok(())
841}
842
843/// Validate tool input with detailed error context and path information
844pub fn validate_tool_input_with_context(
845    data: &Value,
846    schema: &Value,
847    tool_name: &str,
848) -> MCPResult<ValidationContext> {
849    let mut context = ValidationContext::new(tool_name.to_string());
850
851    // First validate the schema itself
852    validate_tool_schema(schema)?;
853
854    // Then validate the data against the schema with context tracking
855    validate_with_context(data, schema, &mut context, "".to_string())?;
856
857    Ok(context)
858}
859
860/// Validate tool output with detailed error context and path information  
861pub fn validate_tool_output_with_context(
862    data: &Value,
863    schema: &Value,
864    tool_name: &str,
865) -> MCPResult<ValidationContext> {
866    let mut context = ValidationContext::new(tool_name.to_string());
867
868    // Validate output schema if present
869    if schema.as_object().is_some() {
870        validate_tool_schema(schema)?;
871    }
872
873    // Validate the output data
874    validate_with_context(data, schema, &mut context, "".to_string())?;
875
876    Ok(context)
877}
878
879/// Comprehensive tool definition validation with security checks
880pub fn validate_tool_definition_comprehensive(
881    tool: &crate::types::tools::Tool,
882) -> MCPResult<ValidationReport> {
883    let mut report = ValidationReport::new(tool.name.clone());
884
885    // Basic validation
886    if let Err(e) = tool.validate() {
887        report.add_error(ValidationError::new(
888            "basic_validation".to_string(),
889            format!("Basic tool validation failed: {e}"),
890            ErrorSeverity::High,
891        ));
892        return Ok(report);
893    }
894
895    // Schema complexity validation
896    validate_schema_complexity(&tool.input_schema, &mut report, "input_schema")?;
897
898    if let Some(ref output_schema) = tool.output_schema {
899        validate_schema_complexity(output_schema, &mut report, "output_schema")?;
900    }
901
902    // Security validation
903    validate_tool_security(tool, &mut report)?;
904
905    // Performance validation
906    validate_tool_performance(tool, &mut report)?;
907
908    Ok(report)
909}
910
911/// Validate schema complexity to prevent DoS attacks
912fn validate_schema_complexity(
913    schema: &Value,
914    report: &mut ValidationReport,
915    context: &str,
916) -> MCPResult<()> {
917    let complexity = calculate_schema_complexity(schema, 0);
918
919    // Check maximum complexity
920    if complexity > MAX_SCHEMA_COMPLEXITY {
921        report.add_error(ValidationError::new(
922            format!("{context}_complexity"),
923            format!("Schema complexity {complexity} exceeds maximum {MAX_SCHEMA_COMPLEXITY}"),
924            ErrorSeverity::High,
925        ));
926    } else if complexity > WARN_SCHEMA_COMPLEXITY {
927        report.add_warning(ValidationWarning::new(
928            format!("{context}_complexity"),
929            format!("Schema complexity {complexity} is high, consider simplifying"),
930        ));
931    }
932
933    // Check nesting depth
934    let depth = calculate_schema_depth(schema, 0);
935    if depth > MAX_SCHEMA_DEPTH {
936        report.add_error(ValidationError::new(
937            format!("{context}_depth"),
938            format!("Schema nesting depth {depth} exceeds maximum {MAX_SCHEMA_DEPTH}"),
939            ErrorSeverity::High,
940        ));
941    }
942
943    Ok(())
944}
945
946/// Calculate schema complexity score
947fn calculate_schema_complexity(schema: &Value, current_depth: usize) -> usize {
948    if current_depth > 20 {
949        // Prevent infinite recursion
950        return 1000; // High penalty for excessive depth
951    }
952
953    match schema {
954        Value::Object(obj) => {
955            let mut complexity = 1;
956
957            // Add complexity for each property
958            if let Some(properties) = obj.get("properties").and_then(|p| p.as_object()) {
959                complexity += properties.len();
960                for prop_schema in properties.values() {
961                    complexity += calculate_schema_complexity(prop_schema, current_depth + 1);
962                }
963            }
964
965            // Add complexity for array items
966            if let Some(items) = obj.get("items") {
967                complexity += calculate_schema_complexity(items, current_depth + 1);
968            }
969
970            // Add complexity for combined schemas
971            for key in &["allOf", "anyOf", "oneOf"] {
972                if let Some(schemas) = obj.get(*key).and_then(|s| s.as_array()) {
973                    complexity += schemas.len() * 2; // Higher penalty for complex combinations
974                    for sub_schema in schemas {
975                        complexity += calculate_schema_complexity(sub_schema, current_depth + 1);
976                    }
977                }
978            }
979
980            complexity
981        }
982        Value::Array(arr) => arr
983            .iter()
984            .map(|item| calculate_schema_complexity(item, current_depth + 1))
985            .sum(),
986        _ => 1,
987    }
988}
989
990/// Calculate schema nesting depth
991fn calculate_schema_depth(schema: &Value, current_depth: usize) -> usize {
992    match schema {
993        Value::Object(obj) => {
994            let mut max_depth = current_depth;
995
996            // Check properties
997            if let Some(properties) = obj.get("properties").and_then(|p| p.as_object()) {
998                for prop_schema in properties.values() {
999                    let prop_depth = calculate_schema_depth(prop_schema, current_depth + 1);
1000                    max_depth = max_depth.max(prop_depth);
1001                }
1002            }
1003
1004            // Check array items
1005            if let Some(items) = obj.get("items") {
1006                let items_depth = calculate_schema_depth(items, current_depth + 1);
1007                max_depth = max_depth.max(items_depth);
1008            }
1009
1010            // Check combined schemas
1011            for key in &["allOf", "anyOf", "oneOf"] {
1012                if let Some(schemas) = obj.get(*key).and_then(|s| s.as_array()) {
1013                    for sub_schema in schemas {
1014                        let sub_depth = calculate_schema_depth(sub_schema, current_depth + 1);
1015                        max_depth = max_depth.max(sub_depth);
1016                    }
1017                }
1018            }
1019
1020            max_depth
1021        }
1022        _ => current_depth,
1023    }
1024}
1025
1026/// Validate tool security considerations
1027fn validate_tool_security(
1028    tool: &crate::types::tools::Tool,
1029    report: &mut ValidationReport,
1030) -> MCPResult<()> {
1031    // Check for potential security risks in tool name
1032    if tool.name.contains("..") || tool.name.contains("/") || tool.name.contains("\\") {
1033        report.add_error(ValidationError::new(
1034            "tool_name_security".to_string(),
1035            "Tool name contains potentially unsafe characters".to_string(),
1036            ErrorSeverity::High,
1037        ));
1038    }
1039
1040    // Check for sensitive parameter names
1041    if let Some(properties) = tool
1042        .input_schema
1043        .get("properties")
1044        .and_then(|p| p.as_object())
1045    {
1046        for prop_name in properties.keys() {
1047            if is_sensitive_parameter_name(prop_name) {
1048                report.add_warning(ValidationWarning::new(
1049                    "sensitive_parameter".to_string(),
1050                    format!(
1051                        "Parameter '{prop_name}' may contain sensitive data, ensure proper handling"
1052                    ),
1053                ));
1054            }
1055        }
1056    }
1057
1058    // Check for overly permissive schemas
1059    if is_overly_permissive_schema(&tool.input_schema) {
1060        report.add_warning(ValidationWarning::new(
1061            "permissive_schema".to_string(),
1062            "Input schema is very permissive, consider adding more constraints".to_string(),
1063        ));
1064    }
1065
1066    Ok(())
1067}
1068
1069/// Validate tool performance characteristics
1070fn validate_tool_performance(
1071    tool: &crate::types::tools::Tool,
1072    report: &mut ValidationReport,
1073) -> MCPResult<()> {
1074    // Check for performance anti-patterns in schema
1075    if has_performance_antipatterns(&tool.input_schema) {
1076        report.add_warning(ValidationWarning::new(
1077            "performance_concern".to_string(),
1078            "Schema contains patterns that may impact validation performance".to_string(),
1079        ));
1080    }
1081
1082    // Check description length (very long descriptions may impact UI performance)
1083    if tool.description.len() > 1000 {
1084        report.add_warning(ValidationWarning::new(
1085            "long_description".to_string(),
1086            format!(
1087                "Tool description is {} characters, consider shortening for better UX",
1088                tool.description.len()
1089            ),
1090        ));
1091    }
1092
1093    Ok(())
1094}
1095
1096/// Check if parameter name suggests sensitive data
1097fn is_sensitive_parameter_name(name: &str) -> bool {
1098    let sensitive_patterns = [
1099        "password",
1100        "passwd",
1101        "secret",
1102        "key",
1103        "token",
1104        "credential",
1105        "auth",
1106        "api_key",
1107        "private",
1108        "confidential",
1109        "sensitive",
1110    ];
1111
1112    let name_lower = name.to_lowercase();
1113    sensitive_patterns
1114        .iter()
1115        .any(|pattern| name_lower.contains(pattern))
1116}
1117
1118/// Check if schema is overly permissive
1119fn is_overly_permissive_schema(schema: &Value) -> bool {
1120    // Check for schemas that accept any type without constraints
1121    if let Some(obj) = schema.as_object() {
1122        // No type specified and no constraints
1123        if !obj.contains_key("type")
1124            && !obj.contains_key("properties")
1125            && !obj.contains_key("enum")
1126            && !obj.contains_key("const")
1127            && !obj.contains_key("pattern")
1128        {
1129            return true;
1130        }
1131
1132        // Object type with additionalProperties: true and no defined properties
1133        if obj.get("type").and_then(|t| t.as_str()) == Some("object")
1134            && obj.get("additionalProperties").and_then(|ap| ap.as_bool()) == Some(true)
1135            && !obj.contains_key("properties")
1136        {
1137            return true;
1138        }
1139
1140        // Check for properties with empty schema objects (no constraints)
1141        if let Some(properties) = obj.get("properties").and_then(|p| p.as_object()) {
1142            for (_, prop_schema) in properties {
1143                if let Some(prop_obj) = prop_schema.as_object() {
1144                    if prop_obj.is_empty() {
1145                        return true; // Empty schema object is overly permissive
1146                    }
1147                }
1148            }
1149        }
1150    } else if schema.as_object().is_some_and(|o| o.is_empty()) {
1151        return true; // Empty schema object
1152    }
1153
1154    false
1155}
1156
1157/// Check for performance anti-patterns in schema
1158fn has_performance_antipatterns(schema: &Value) -> bool {
1159    // Very complex regex patterns
1160    if let Some(pattern) = schema.get("pattern").and_then(|p| p.as_str()) {
1161        if pattern.len() > 200 || pattern.contains(".*.*") || pattern.contains("(.+)+") {
1162            return true;
1163        }
1164    }
1165
1166    // Very large enum lists
1167    if let Some(enum_values) = schema.get("enum").and_then(|e| e.as_array()) {
1168        if enum_values.len() > 100 {
1169            return true;
1170        }
1171    }
1172
1173    false
1174}
1175
1176/// Validate data against schema with detailed context tracking
1177fn validate_with_context(
1178    data: &Value,
1179    schema: &Value,
1180    context: &mut ValidationContext,
1181    path: String,
1182) -> MCPResult<()> {
1183    // Track validation path
1184    context.push_path(path.clone());
1185
1186    // Perform validation with enhanced error reporting
1187    if let Err(e) = validate_against_schema(data, schema) {
1188        context.add_error(ValidationError::new(
1189            path.clone(),
1190            format!("{e}"),
1191            ErrorSeverity::High,
1192        ));
1193        return Err(e);
1194    }
1195
1196    // Additional context-specific validations
1197    validate_data_size_limits(data, context, &path)?;
1198    validate_data_security(data, context, &path)?;
1199
1200    context.pop_path();
1201    Ok(())
1202}
1203
1204/// Validate data size limits to prevent DoS
1205fn validate_data_size_limits(
1206    data: &Value,
1207    context: &mut ValidationContext,
1208    path: &str,
1209) -> MCPResult<()> {
1210    match data {
1211        Value::String(s) => {
1212            if s.len() > MAX_STRING_LENGTH {
1213                context.add_warning(ValidationWarning::new(
1214                    path.to_string(),
1215                    format!(
1216                        "String length {} exceeds recommended maximum {}",
1217                        s.len(),
1218                        MAX_STRING_LENGTH
1219                    ),
1220                ));
1221            }
1222        }
1223        Value::Array(arr) => {
1224            if arr.len() > MAX_ARRAY_LENGTH {
1225                context.add_warning(ValidationWarning::new(
1226                    path.to_string(),
1227                    format!(
1228                        "Array length {} exceeds recommended maximum {}",
1229                        arr.len(),
1230                        MAX_ARRAY_LENGTH
1231                    ),
1232                ));
1233            }
1234        }
1235        Value::Object(obj) => {
1236            if obj.len() > MAX_OBJECT_PROPERTIES {
1237                context.add_warning(ValidationWarning::new(
1238                    path.to_string(),
1239                    format!(
1240                        "Object has {} properties, exceeds recommended maximum {}",
1241                        obj.len(),
1242                        MAX_OBJECT_PROPERTIES
1243                    ),
1244                ));
1245            }
1246        }
1247        _ => {}
1248    }
1249    Ok(())
1250}
1251
1252/// Validate data for potential security issues
1253fn validate_data_security(
1254    data: &Value,
1255    context: &mut ValidationContext,
1256    path: &str,
1257) -> MCPResult<()> {
1258    if let Value::String(s) = data {
1259        // Check for potential script injection
1260        if contains_script_patterns(s) {
1261            context.add_warning(ValidationWarning::new(
1262                path.to_string(),
1263                "String contains potential script injection patterns".to_string(),
1264            ));
1265        }
1266
1267        // Check for path traversal attempts
1268        if s.contains("../") || s.contains("..\\") {
1269            context.add_warning(ValidationWarning::new(
1270                path.to_string(),
1271                "String contains potential path traversal patterns".to_string(),
1272            ));
1273        }
1274
1275        // Check for very long strings that might be malicious
1276        if s.len() > SECURITY_STRING_LENGTH_LIMIT {
1277            context.add_warning(ValidationWarning::new(
1278                path.to_string(),
1279                format!("String length {} may indicate malicious input", s.len()),
1280            ));
1281        }
1282    }
1283    Ok(())
1284}
1285
1286/// Check if string contains script injection patterns
1287fn contains_script_patterns(s: &str) -> bool {
1288    let patterns = [
1289        "<script",
1290        "javascript:",
1291        "data:text/html",
1292        "eval(",
1293        "setTimeout(",
1294        "setInterval(",
1295    ];
1296    let s_lower = s.to_lowercase();
1297    patterns.iter().any(|pattern| s_lower.contains(pattern))
1298}
1299
1300// Constants for validation limits
1301const MAX_SCHEMA_COMPLEXITY: usize = 1000;
1302const WARN_SCHEMA_COMPLEXITY: usize = 500;
1303const MAX_SCHEMA_DEPTH: usize = 20;
1304const MAX_STRING_LENGTH: usize = 100_000;
1305const MAX_ARRAY_LENGTH: usize = 10_000;
1306const MAX_OBJECT_PROPERTIES: usize = 1_000;
1307const SECURITY_STRING_LENGTH_LIMIT: usize = 1_000_000;
1308
1309/// Validation context for detailed error reporting
1310#[derive(Debug, Clone)]
1311pub struct ValidationContext {
1312    pub tool_name: String,
1313    pub path_stack: Vec<String>,
1314    pub errors: Vec<ValidationError>,
1315    pub warnings: Vec<ValidationWarning>,
1316}
1317
1318impl ValidationContext {
1319    pub fn new(tool_name: String) -> Self {
1320        Self {
1321            tool_name,
1322            path_stack: Vec::new(),
1323            errors: Vec::new(),
1324            warnings: Vec::new(),
1325        }
1326    }
1327
1328    pub fn push_path(&mut self, path: String) {
1329        self.path_stack.push(path);
1330    }
1331
1332    pub fn pop_path(&mut self) {
1333        self.path_stack.pop();
1334    }
1335
1336    pub fn add_error(&mut self, error: ValidationError) {
1337        self.errors.push(error);
1338    }
1339
1340    pub fn add_warning(&mut self, warning: ValidationWarning) {
1341        self.warnings.push(warning);
1342    }
1343
1344    pub fn has_errors(&self) -> bool {
1345        !self.errors.is_empty()
1346    }
1347
1348    pub fn has_warnings(&self) -> bool {
1349        !self.warnings.is_empty()
1350    }
1351}
1352
1353/// Validation report for comprehensive tool validation
1354#[derive(Debug, Clone)]
1355pub struct ValidationReport {
1356    pub tool_name: String,
1357    pub errors: Vec<ValidationError>,
1358    pub warnings: Vec<ValidationWarning>,
1359    pub performance_metrics: PerformanceMetrics,
1360}
1361
1362impl ValidationReport {
1363    pub fn new(tool_name: String) -> Self {
1364        Self {
1365            tool_name,
1366            errors: Vec::new(),
1367            warnings: Vec::new(),
1368            performance_metrics: PerformanceMetrics::default(),
1369        }
1370    }
1371
1372    pub fn add_error(&mut self, error: ValidationError) {
1373        self.errors.push(error);
1374    }
1375
1376    pub fn add_warning(&mut self, warning: ValidationWarning) {
1377        self.warnings.push(warning);
1378    }
1379
1380    pub fn is_valid(&self) -> bool {
1381        self.errors.is_empty()
1382    }
1383
1384    pub fn severity_level(&self) -> ErrorSeverity {
1385        self.errors
1386            .iter()
1387            .map(|e| &e.severity)
1388            .max()
1389            .cloned()
1390            .unwrap_or(ErrorSeverity::Low)
1391    }
1392}
1393
1394/// Validation error with detailed context
1395#[derive(Debug, Clone)]
1396pub struct ValidationError {
1397    pub path: String,
1398    pub message: String,
1399    pub severity: ErrorSeverity,
1400}
1401
1402impl ValidationError {
1403    pub fn new(path: String, message: String, severity: ErrorSeverity) -> Self {
1404        Self {
1405            path,
1406            message,
1407            severity,
1408        }
1409    }
1410}
1411
1412/// Validation warning
1413#[derive(Debug, Clone)]
1414pub struct ValidationWarning {
1415    pub path: String,
1416    pub message: String,
1417}
1418
1419impl ValidationWarning {
1420    pub fn new(path: String, message: String) -> Self {
1421        Self { path, message }
1422    }
1423}
1424
1425/// Error severity levels
1426#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
1427pub enum ErrorSeverity {
1428    Low,
1429    Medium,
1430    High,
1431    Critical,
1432}
1433
1434/// Performance metrics for validation
1435#[derive(Debug, Clone, Default)]
1436pub struct PerformanceMetrics {
1437    pub schema_complexity: usize,
1438    pub validation_time_ms: Option<u64>,
1439    pub memory_usage_bytes: Option<usize>,
1440}
1441
1442/// Comprehensive MCP message validation
1443pub struct MCPMessageValidator {
1444    /// Maximum allowed string length in messages
1445    max_string_length: usize,
1446    /// Maximum allowed array length
1447    max_array_length: usize,
1448    /// Maximum allowed object properties
1449    max_object_properties: usize,
1450    /// Maximum allowed message depth
1451    max_depth: usize,
1452    /// Whether to allow potentially dangerous content
1453    allow_dangerous_content: bool,
1454    /// Custom validation patterns
1455    blocked_patterns: Vec<regex::Regex>,
1456}
1457
1458impl Default for MCPMessageValidator {
1459    fn default() -> Self {
1460        let blocked_patterns = vec![
1461            // Script injection patterns
1462            regex::Regex::new(r"(?i)<script[^>]*>").unwrap(),
1463            regex::Regex::new(r"(?i)javascript:").unwrap(),
1464            regex::Regex::new(r"(?i)data:text/html").unwrap(),
1465            regex::Regex::new(r"(?i)eval\s*\(").unwrap(),
1466            // SQL injection patterns
1467            regex::Regex::new(r"(?i)(union|select|insert|update|delete|drop|create|alter)\s+")
1468                .unwrap(),
1469            regex::Regex::new(r"(?i)(or|and)\s+\d+\s*=\s*\d+").unwrap(),
1470            // Command injection patterns
1471            regex::Regex::new(r"[;&|`$(){}\[\]\\]").unwrap(),
1472            // Path traversal patterns
1473            regex::Regex::new(r"\.\.[\\/]").unwrap(),
1474            // XML/XXE patterns
1475            regex::Regex::new(r"(?i)<!entity").unwrap(),
1476            regex::Regex::new(r"(?i)<!doctype").unwrap(),
1477        ];
1478
1479        Self {
1480            max_string_length: 1_000_000, // 1MB
1481            max_array_length: 50_000,
1482            max_object_properties: 5_000,
1483            max_depth: 50,
1484            allow_dangerous_content: false,
1485            blocked_patterns,
1486        }
1487    }
1488}
1489
1490impl MCPMessageValidator {
1491    /// Create a new validator with custom limits
1492    pub fn new(
1493        max_string_length: usize,
1494        max_array_length: usize,
1495        max_object_properties: usize,
1496        max_depth: usize,
1497    ) -> Self {
1498        Self {
1499            max_string_length,
1500            max_array_length,
1501            max_object_properties,
1502            max_depth,
1503            ..Default::default()
1504        }
1505    }
1506
1507    /// Enable/disable dangerous content
1508    pub fn with_dangerous_content(mut self, allow: bool) -> Self {
1509        self.allow_dangerous_content = allow;
1510        self
1511    }
1512
1513    /// Add custom blocked pattern
1514    pub fn add_blocked_pattern(&mut self, pattern: &str) -> Result<(), regex::Error> {
1515        let regex = regex::Regex::new(pattern)?;
1516        self.blocked_patterns.push(regex);
1517        Ok(())
1518    }
1519
1520    /// Validate complete JSON-RPC message
1521    pub fn validate_message(
1522        &self,
1523        message: &crate::protocol::jsonrpc::JsonRpcMessage,
1524    ) -> MCPResult<ValidationReport> {
1525        let mut report = ValidationReport::new("message".to_string());
1526
1527        match message {
1528            crate::protocol::jsonrpc::JsonRpcMessage::Request(req) => {
1529                self.validate_request(req, &mut report)?;
1530            }
1531            crate::protocol::jsonrpc::JsonRpcMessage::Response(resp) => {
1532                self.validate_response(resp, &mut report)?;
1533            }
1534            crate::protocol::jsonrpc::JsonRpcMessage::Notification(notif) => {
1535                self.validate_notification(notif, &mut report)?;
1536            }
1537        }
1538
1539        Ok(report)
1540    }
1541
1542    /// Validate JSON-RPC request
1543    fn validate_request(
1544        &self,
1545        request: &crate::protocol::jsonrpc::JsonRpcRequest,
1546        report: &mut ValidationReport,
1547    ) -> MCPResult<()> {
1548        // Validate JSON-RPC version
1549        if request.jsonrpc != Cow::Borrowed("2.0") {
1550            report.add_error(ValidationError::new(
1551                "jsonrpc".to_string(),
1552                "Invalid JSON-RPC version, must be '2.0'".to_string(),
1553                ErrorSeverity::High,
1554            ));
1555        }
1556
1557        // Validate method name
1558        self.validate_method_name(&request.method, report)?;
1559
1560        // Validate request ID
1561        if let Some(ref id) = request.id {
1562            self.validate_request_id(id, report)?;
1563        }
1564
1565        // Validate parameters
1566        if let Some(ref params) = request.params {
1567            self.validate_value(params, report, "params".to_string(), 0)?;
1568            self.validate_method_specific_params(&request.method, params, report)?;
1569        }
1570
1571        Ok(())
1572    }
1573
1574    /// Validate JSON-RPC response
1575    fn validate_response(
1576        &self,
1577        response: &crate::protocol::jsonrpc::JsonRpcResponse,
1578        report: &mut ValidationReport,
1579    ) -> MCPResult<()> {
1580        // Validate JSON-RPC version
1581        if response.jsonrpc != Cow::Borrowed("2.0") {
1582            report.add_error(ValidationError::new(
1583                "jsonrpc".to_string(),
1584                "Invalid JSON-RPC version, must be '2.0'".to_string(),
1585                ErrorSeverity::High,
1586            ));
1587        }
1588
1589        // Validate response ID
1590        if let Some(ref id) = response.id {
1591            self.validate_request_id(id, report)?;
1592        }
1593
1594        // Validate that response has either result or error, but not both
1595        match (&response.result, &response.error) {
1596            (Some(_), Some(_)) => {
1597                report.add_error(ValidationError::new(
1598                    "response".to_string(),
1599                    "Response cannot have both result and error".to_string(),
1600                    ErrorSeverity::High,
1601                ));
1602            }
1603            (None, None) => {
1604                report.add_error(ValidationError::new(
1605                    "response".to_string(),
1606                    "Response must have either result or error".to_string(),
1607                    ErrorSeverity::High,
1608                ));
1609            }
1610            _ => {}
1611        }
1612
1613        // Validate result if present
1614        if let Some(ref result) = response.result {
1615            self.validate_value(result, report, "result".to_string(), 0)?;
1616        }
1617
1618        // Validate error if present
1619        if let Some(ref error) = response.error {
1620            self.validate_error(error, report)?;
1621        }
1622
1623        Ok(())
1624    }
1625
1626    /// Validate JSON-RPC notification (request without ID)
1627    fn validate_notification(
1628        &self,
1629        notification: &crate::protocol::jsonrpc::JsonRpcRequest,
1630        report: &mut ValidationReport,
1631    ) -> MCPResult<()> {
1632        // Validate JSON-RPC version
1633        if notification.jsonrpc != Cow::Borrowed("2.0") {
1634            report.add_error(ValidationError::new(
1635                "jsonrpc".to_string(),
1636                "Invalid JSON-RPC version, must be '2.0'".to_string(),
1637                ErrorSeverity::High,
1638            ));
1639        }
1640
1641        // Validate method name
1642        self.validate_method_name(&notification.method, report)?;
1643
1644        // Validate parameters
1645        if let Some(ref params) = notification.params {
1646            self.validate_value(params, report, "params".to_string(), 0)?;
1647            self.validate_method_specific_params(&notification.method, params, report)?;
1648        }
1649
1650        Ok(())
1651    }
1652
1653    /// Validate method name format and security
1654    fn validate_method_name(&self, method: &str, report: &mut ValidationReport) -> MCPResult<()> {
1655        // Check for empty method
1656        if method.is_empty() {
1657            report.add_error(ValidationError::new(
1658                "method".to_string(),
1659                "Method name cannot be empty".to_string(),
1660                ErrorSeverity::High,
1661            ));
1662            return Ok(());
1663        }
1664
1665        // Check method name length
1666        if method.len() > 100 {
1667            report.add_error(ValidationError::new(
1668                "method".to_string(),
1669                format!(
1670                    "Method name too long: {} characters (max 100)",
1671                    method.len()
1672                ),
1673                ErrorSeverity::High,
1674            ));
1675        }
1676
1677        // Check for invalid characters
1678        if !method
1679            .chars()
1680            .all(|c| c.is_alphanumeric() || c == '/' || c == '_' || c == '-' || c == '.')
1681        {
1682            report.add_error(ValidationError::new(
1683                "method".to_string(),
1684                "Method name contains invalid characters".to_string(),
1685                ErrorSeverity::High,
1686            ));
1687        }
1688
1689        // Check for security patterns
1690        if method.contains("..") || method.starts_with('/') || method.ends_with('/') {
1691            report.add_error(ValidationError::new(
1692                "method".to_string(),
1693                "Method name contains potentially unsafe patterns".to_string(),
1694                ErrorSeverity::High,
1695            ));
1696        }
1697
1698        // Check for private/internal methods
1699        if method.starts_with('_') || method.contains("internal") || method.contains("private") {
1700            report.add_warning(ValidationWarning::new(
1701                "method".to_string(),
1702                "Method name suggests internal/private usage".to_string(),
1703            ));
1704        }
1705
1706        Ok(())
1707    }
1708
1709    /// Validate request ID format and security
1710    fn validate_request_id(
1711        &self,
1712        id: &crate::protocol::jsonrpc::RequestId,
1713        report: &mut ValidationReport,
1714    ) -> MCPResult<()> {
1715        match id {
1716            crate::protocol::jsonrpc::RequestId::String(s) => {
1717                if s.is_empty() {
1718                    report.add_error(ValidationError::new(
1719                        "id".to_string(),
1720                        "Request ID string cannot be empty".to_string(),
1721                        ErrorSeverity::Medium,
1722                    ));
1723                }
1724
1725                if s.len() > 200 {
1726                    report.add_error(ValidationError::new(
1727                        "id".to_string(),
1728                        format!(
1729                            "Request ID string too long: {} characters (max 200)",
1730                            s.len()
1731                        ),
1732                        ErrorSeverity::Medium,
1733                    ));
1734                }
1735
1736                // Check for dangerous characters
1737                if s.contains('\0') || s.contains('\n') || s.contains('\r') || s.contains('\t') {
1738                    report.add_error(ValidationError::new(
1739                        "id".to_string(),
1740                        "Request ID contains control characters".to_string(),
1741                        ErrorSeverity::Medium,
1742                    ));
1743                }
1744
1745                // Check for potential injection
1746                self.validate_string_security(s, report, "id".to_string())?;
1747            }
1748            crate::protocol::jsonrpc::RequestId::Number(n) => {
1749                // Check for reasonable numeric range
1750                if *n < -9_223_372_036_854_775_807 {
1751                    report.add_error(ValidationError::new(
1752                        "id".to_string(),
1753                        "Request ID number out of reasonable range".to_string(),
1754                        ErrorSeverity::Low,
1755                    ));
1756                }
1757            }
1758        }
1759
1760        Ok(())
1761    }
1762
1763    /// Validate JSON-RPC error object
1764    fn validate_error(
1765        &self,
1766        error: &crate::protocol::jsonrpc::JsonRpcError,
1767        report: &mut ValidationReport,
1768    ) -> MCPResult<()> {
1769        // Validate error code is in valid range
1770        if (error.code < -32999 || error.code > -32000)
1771            && (error.code < -32700 || error.code > -32600)
1772        {
1773            report.add_warning(ValidationWarning::new(
1774                "error.code".to_string(),
1775                format!(
1776                    "Error code {} is outside standard JSON-RPC ranges",
1777                    error.code
1778                ),
1779            ));
1780        }
1781
1782        // Validate error message
1783        if error.message.is_empty() {
1784            report.add_error(ValidationError::new(
1785                "error.message".to_string(),
1786                "Error message cannot be empty".to_string(),
1787                ErrorSeverity::Medium,
1788            ));
1789        }
1790
1791        if error.message.len() > 1000 {
1792            report.add_warning(ValidationWarning::new(
1793                "error.message".to_string(),
1794                format!(
1795                    "Error message very long: {} characters",
1796                    error.message.len()
1797                ),
1798            ));
1799        }
1800
1801        self.validate_string_security(&error.message, report, "error.message".to_string())?;
1802
1803        // Validate error data if present
1804        if let Some(ref data) = error.data {
1805            self.validate_value(data, report, "error.data".to_string(), 0)?;
1806        }
1807
1808        Ok(())
1809    }
1810
1811    /// Validate method-specific parameters
1812    fn validate_method_specific_params(
1813        &self,
1814        method: &str,
1815        params: &serde_json::Value,
1816        report: &mut ValidationReport,
1817    ) -> MCPResult<()> {
1818        match method {
1819            "initialize" => self.validate_initialize_params(params, report)?,
1820            "tools/call" => self.validate_tool_call_params(params, report)?,
1821            "resources/read" => self.validate_resource_read_params(params, report)?,
1822            "resources/subscribe" => self.validate_resource_subscribe_params(params, report)?,
1823            "resources/unsubscribe" => self.validate_resource_unsubscribe_params(params, report)?,
1824            "prompts/get" => self.validate_prompt_get_params(params, report)?,
1825            "sampling/createMessage" => self.validate_sampling_params(params, report)?,
1826            "elicitation/request" => self.validate_elicitation_params(params, report)?,
1827            "logging/log" => self.validate_logging_params(params, report)?,
1828            _ => {
1829                // Generic validation for unknown methods
1830                if let Some(obj) = params.as_object() {
1831                    if obj.len() > 50 {
1832                        report.add_warning(ValidationWarning::new(
1833                            "params".to_string(),
1834                            format!(
1835                                "Large parameter object for method '{}': {} properties",
1836                                method,
1837                                obj.len()
1838                            ),
1839                        ));
1840                    }
1841                }
1842            }
1843        }
1844
1845        Ok(())
1846    }
1847
1848    /// Validate initialize method parameters
1849    fn validate_initialize_params(
1850        &self,
1851        params: &serde_json::Value,
1852        report: &mut ValidationReport,
1853    ) -> MCPResult<()> {
1854        let obj = match params.as_object() {
1855            Some(obj) => obj,
1856            None => {
1857                report.add_error(ValidationError::new(
1858                    "initialize.params".to_string(),
1859                    "Initialize parameters must be an object".to_string(),
1860                    ErrorSeverity::High,
1861                ));
1862                return Ok(());
1863            }
1864        };
1865
1866        // Validate protocol version
1867        if let Some(version) = obj.get("protocolVersion") {
1868            if let Some(version_str) = version.as_str() {
1869                if version_str != "2025-06-18"
1870                    && version_str != "2025-03-26"
1871                    && version_str != "2024-11-05"
1872                {
1873                    report.add_error(ValidationError::new(
1874                        "initialize.protocolVersion".to_string(),
1875                        format!("Unsupported protocol version: {version_str}"),
1876                        ErrorSeverity::High,
1877                    ));
1878                }
1879            } else {
1880                report.add_error(ValidationError::new(
1881                    "initialize.protocolVersion".to_string(),
1882                    "Protocol version must be a string".to_string(),
1883                    ErrorSeverity::High,
1884                ));
1885            }
1886        }
1887
1888        // Validate client info
1889        if let Some(client_info) = obj.get("clientInfo") {
1890            self.validate_client_info(client_info, report)?;
1891        }
1892
1893        // Validate capabilities
1894        if let Some(capabilities) = obj.get("capabilities") {
1895            self.validate_capabilities(
1896                capabilities,
1897                report,
1898                "initialize.capabilities".to_string(),
1899            )?;
1900        }
1901
1902        Ok(())
1903    }
1904
1905    /// Validate tool call parameters
1906    fn validate_tool_call_params(
1907        &self,
1908        params: &serde_json::Value,
1909        report: &mut ValidationReport,
1910    ) -> MCPResult<()> {
1911        let obj = match params.as_object() {
1912            Some(obj) => obj,
1913            None => {
1914                report.add_error(ValidationError::new(
1915                    "tools/call.params".to_string(),
1916                    "Tool call parameters must be an object".to_string(),
1917                    ErrorSeverity::High,
1918                ));
1919                return Ok(());
1920            }
1921        };
1922
1923        // Validate tool name
1924        if let Some(name) = obj.get("name") {
1925            if let Some(name_str) = name.as_str() {
1926                if name_str.is_empty() {
1927                    report.add_error(ValidationError::new(
1928                        "tools/call.name".to_string(),
1929                        "Tool name cannot be empty".to_string(),
1930                        ErrorSeverity::High,
1931                    ));
1932                }
1933
1934                if name_str.len() > 200 {
1935                    report.add_error(ValidationError::new(
1936                        "tools/call.name".to_string(),
1937                        format!(
1938                            "Tool name too long: {} characters (max 200)",
1939                            name_str.len()
1940                        ),
1941                        ErrorSeverity::High,
1942                    ));
1943                }
1944
1945                // Check for dangerous tool names
1946                if name_str.starts_with('_') || name_str.contains("..") || name_str.contains('/') {
1947                    report.add_error(ValidationError::new(
1948                        "tools/call.name".to_string(),
1949                        "Tool name contains potentially unsafe characters".to_string(),
1950                        ErrorSeverity::High,
1951                    ));
1952                }
1953
1954                self.validate_string_security(name_str, report, "tools/call.name".to_string())?;
1955            } else {
1956                report.add_error(ValidationError::new(
1957                    "tools/call.name".to_string(),
1958                    "Tool name must be a string".to_string(),
1959                    ErrorSeverity::High,
1960                ));
1961            }
1962        } else {
1963            report.add_error(ValidationError::new(
1964                "tools/call.name".to_string(),
1965                "Tool name is required".to_string(),
1966                ErrorSeverity::High,
1967            ));
1968        }
1969
1970        // Validate arguments if present
1971        if let Some(arguments) = obj.get("arguments") {
1972            self.validate_value(arguments, report, "tools/call.arguments".to_string(), 0)?;
1973        }
1974
1975        Ok(())
1976    }
1977
1978    /// Validate resource read parameters
1979    fn validate_resource_read_params(
1980        &self,
1981        params: &serde_json::Value,
1982        report: &mut ValidationReport,
1983    ) -> MCPResult<()> {
1984        let obj = match params.as_object() {
1985            Some(obj) => obj,
1986            None => {
1987                report.add_error(ValidationError::new(
1988                    "resources/read.params".to_string(),
1989                    "Resource read parameters must be an object".to_string(),
1990                    ErrorSeverity::High,
1991                ));
1992                return Ok(());
1993            }
1994        };
1995
1996        // Validate URI
1997        if let Some(uri) = obj.get("uri") {
1998            if let Some(uri_str) = uri.as_str() {
1999                self.validate_uri(uri_str, report)?;
2000            } else {
2001                report.add_error(ValidationError::new(
2002                    "resources/read.uri".to_string(),
2003                    "URI must be a string".to_string(),
2004                    ErrorSeverity::High,
2005                ));
2006            }
2007        } else {
2008            report.add_error(ValidationError::new(
2009                "resources/read.uri".to_string(),
2010                "URI is required".to_string(),
2011                ErrorSeverity::High,
2012            ));
2013        }
2014
2015        Ok(())
2016    }
2017
2018    /// Validate resource subscribe parameters
2019    fn validate_resource_subscribe_params(
2020        &self,
2021        params: &serde_json::Value,
2022        report: &mut ValidationReport,
2023    ) -> MCPResult<()> {
2024        let obj = match params.as_object() {
2025            Some(obj) => obj,
2026            None => {
2027                report.add_error(ValidationError::new(
2028                    "resources/subscribe.params".to_string(),
2029                    "Resource subscribe parameters must be an object".to_string(),
2030                    ErrorSeverity::High,
2031                ));
2032                return Ok(());
2033            }
2034        };
2035
2036        // Validate URI
2037        if let Some(uri) = obj.get("uri") {
2038            if let Some(uri_str) = uri.as_str() {
2039                self.validate_uri(uri_str, report)?;
2040            } else {
2041                report.add_error(ValidationError::new(
2042                    "resources/subscribe.uri".to_string(),
2043                    "URI must be a string".to_string(),
2044                    ErrorSeverity::High,
2045                ));
2046            }
2047        } else {
2048            report.add_error(ValidationError::new(
2049                "resources/subscribe.uri".to_string(),
2050                "URI is required".to_string(),
2051                ErrorSeverity::High,
2052            ));
2053        }
2054
2055        Ok(())
2056    }
2057
2058    /// Validate resource unsubscribe parameters
2059    fn validate_resource_unsubscribe_params(
2060        &self,
2061        params: &serde_json::Value,
2062        report: &mut ValidationReport,
2063    ) -> MCPResult<()> {
2064        let obj = match params.as_object() {
2065            Some(obj) => obj,
2066            None => {
2067                report.add_error(ValidationError::new(
2068                    "resources/unsubscribe.params".to_string(),
2069                    "Resource unsubscribe parameters must be an object".to_string(),
2070                    ErrorSeverity::High,
2071                ));
2072                return Ok(());
2073            }
2074        };
2075
2076        // Validate URI
2077        if let Some(uri) = obj.get("uri") {
2078            if let Some(uri_str) = uri.as_str() {
2079                self.validate_uri(uri_str, report)?;
2080            } else {
2081                report.add_error(ValidationError::new(
2082                    "resources/unsubscribe.uri".to_string(),
2083                    "URI must be a string".to_string(),
2084                    ErrorSeverity::High,
2085                ));
2086            }
2087        } else {
2088            report.add_error(ValidationError::new(
2089                "resources/unsubscribe.uri".to_string(),
2090                "URI is required".to_string(),
2091                ErrorSeverity::High,
2092            ));
2093        }
2094
2095        Ok(())
2096    }
2097
2098    /// Validate prompt get parameters
2099    fn validate_prompt_get_params(
2100        &self,
2101        params: &serde_json::Value,
2102        report: &mut ValidationReport,
2103    ) -> MCPResult<()> {
2104        let obj = match params.as_object() {
2105            Some(obj) => obj,
2106            None => {
2107                report.add_error(ValidationError::new(
2108                    "prompts/get.params".to_string(),
2109                    "Prompt get parameters must be an object".to_string(),
2110                    ErrorSeverity::High,
2111                ));
2112                return Ok(());
2113            }
2114        };
2115
2116        // Validate prompt name
2117        if let Some(name) = obj.get("name") {
2118            if let Some(name_str) = name.as_str() {
2119                if name_str.is_empty() {
2120                    report.add_error(ValidationError::new(
2121                        "prompts/get.name".to_string(),
2122                        "Prompt name cannot be empty".to_string(),
2123                        ErrorSeverity::High,
2124                    ));
2125                }
2126
2127                if name_str.len() > 200 {
2128                    report.add_error(ValidationError::new(
2129                        "prompts/get.name".to_string(),
2130                        format!(
2131                            "Prompt name too long: {} characters (max 200)",
2132                            name_str.len()
2133                        ),
2134                        ErrorSeverity::High,
2135                    ));
2136                }
2137
2138                self.validate_string_security(name_str, report, "prompts/get.name".to_string())?;
2139            } else {
2140                report.add_error(ValidationError::new(
2141                    "prompts/get.name".to_string(),
2142                    "Prompt name must be a string".to_string(),
2143                    ErrorSeverity::High,
2144                ));
2145            }
2146        } else {
2147            report.add_error(ValidationError::new(
2148                "prompts/get.name".to_string(),
2149                "Prompt name is required".to_string(),
2150                ErrorSeverity::High,
2151            ));
2152        }
2153
2154        // Validate arguments if present
2155        if let Some(arguments) = obj.get("arguments") {
2156            self.validate_value(arguments, report, "prompts/get.arguments".to_string(), 0)?;
2157        }
2158
2159        Ok(())
2160    }
2161
2162    /// Validate sampling parameters
2163    fn validate_sampling_params(
2164        &self,
2165        params: &serde_json::Value,
2166        report: &mut ValidationReport,
2167    ) -> MCPResult<()> {
2168        let obj = match params.as_object() {
2169            Some(obj) => obj,
2170            None => {
2171                report.add_error(ValidationError::new(
2172                    "sampling/createMessage.params".to_string(),
2173                    "Sampling parameters must be an object".to_string(),
2174                    ErrorSeverity::High,
2175                ));
2176                return Ok(());
2177            }
2178        };
2179
2180        // Validate messages array if present
2181        if let Some(messages) = obj.get("messages") {
2182            if let Some(messages_array) = messages.as_array() {
2183                if messages_array.len() > 1000 {
2184                    report.add_error(ValidationError::new(
2185                        "sampling/createMessage.messages".to_string(),
2186                        format!("Too many messages: {} (max 1000)", messages_array.len()),
2187                        ErrorSeverity::High,
2188                    ));
2189                }
2190
2191                for (i, message) in messages_array.iter().enumerate() {
2192                    self.validate_value(
2193                        message,
2194                        report,
2195                        format!("sampling/createMessage.messages[{i}]"),
2196                        0,
2197                    )?;
2198                }
2199            } else {
2200                report.add_error(ValidationError::new(
2201                    "sampling/createMessage.messages".to_string(),
2202                    "Messages must be an array".to_string(),
2203                    ErrorSeverity::High,
2204                ));
2205            }
2206        }
2207
2208        // Validate model preferences if present
2209        if let Some(model_prefs) = obj.get("modelPreferences") {
2210            self.validate_value(
2211                model_prefs,
2212                report,
2213                "sampling/createMessage.modelPreferences".to_string(),
2214                0,
2215            )?;
2216        }
2217
2218        Ok(())
2219    }
2220
2221    /// Validate elicitation parameters
2222    fn validate_elicitation_params(
2223        &self,
2224        params: &serde_json::Value,
2225        report: &mut ValidationReport,
2226    ) -> MCPResult<()> {
2227        let obj = match params.as_object() {
2228            Some(obj) => obj,
2229            None => {
2230                report.add_error(ValidationError::new(
2231                    "elicitation/request.params".to_string(),
2232                    "Elicitation parameters must be an object".to_string(),
2233                    ErrorSeverity::High,
2234                ));
2235                return Ok(());
2236            }
2237        };
2238
2239        // Validate prompt if present
2240        if let Some(prompt) = obj.get("prompt") {
2241            if let Some(prompt_str) = prompt.as_str() {
2242                if prompt_str.len() > 10000 {
2243                    report.add_warning(ValidationWarning::new(
2244                        "elicitation/request.prompt".to_string(),
2245                        format!("Very long prompt: {} characters", prompt_str.len()),
2246                    ));
2247                }
2248
2249                self.validate_string_security(
2250                    prompt_str,
2251                    report,
2252                    "elicitation/request.prompt".to_string(),
2253                )?;
2254            }
2255        }
2256
2257        Ok(())
2258    }
2259
2260    /// Validate logging parameters
2261    fn validate_logging_params(
2262        &self,
2263        params: &serde_json::Value,
2264        report: &mut ValidationReport,
2265    ) -> MCPResult<()> {
2266        let obj = match params.as_object() {
2267            Some(obj) => obj,
2268            None => {
2269                report.add_error(ValidationError::new(
2270                    "logging/log.params".to_string(),
2271                    "Logging parameters must be an object".to_string(),
2272                    ErrorSeverity::High,
2273                ));
2274                return Ok(());
2275            }
2276        };
2277
2278        // Validate log level if present
2279        if let Some(level) = obj.get("level") {
2280            if let Some(level_str) = level.as_str() {
2281                let valid_levels = ["error", "warning", "info", "debug"];
2282                if !valid_levels.contains(&level_str) {
2283                    report.add_error(ValidationError::new(
2284                        "logging/log.level".to_string(),
2285                        format!(
2286                            "Invalid log level: {level_str} (must be one of: {valid_levels:?})"
2287                        ),
2288                        ErrorSeverity::Medium,
2289                    ));
2290                }
2291            } else {
2292                report.add_error(ValidationError::new(
2293                    "logging/log.level".to_string(),
2294                    "Log level must be a string".to_string(),
2295                    ErrorSeverity::Medium,
2296                ));
2297            }
2298        }
2299
2300        // Validate message if present
2301        if let Some(message) = obj.get("message") {
2302            if let Some(message_str) = message.as_str() {
2303                if message_str.len() > 10000 {
2304                    report.add_warning(ValidationWarning::new(
2305                        "logging/log.message".to_string(),
2306                        format!("Very long log message: {} characters", message_str.len()),
2307                    ));
2308                }
2309
2310                self.validate_string_security(
2311                    message_str,
2312                    report,
2313                    "logging/log.message".to_string(),
2314                )?;
2315            }
2316        }
2317
2318        Ok(())
2319    }
2320
2321    /// Validate client info object
2322    fn validate_client_info(
2323        &self,
2324        client_info: &serde_json::Value,
2325        report: &mut ValidationReport,
2326    ) -> MCPResult<()> {
2327        if let Some(obj) = client_info.as_object() {
2328            // Validate name
2329            if let Some(name) = obj.get("name") {
2330                if let Some(name_str) = name.as_str() {
2331                    if name_str.is_empty() {
2332                        report.add_error(ValidationError::new(
2333                            "clientInfo.name".to_string(),
2334                            "Client name cannot be empty".to_string(),
2335                            ErrorSeverity::Medium,
2336                        ));
2337                    }
2338
2339                    if name_str.len() > 200 {
2340                        report.add_error(ValidationError::new(
2341                            "clientInfo.name".to_string(),
2342                            format!(
2343                                "Client name too long: {} characters (max 200)",
2344                                name_str.len()
2345                            ),
2346                            ErrorSeverity::Medium,
2347                        ));
2348                    }
2349
2350                    self.validate_string_security(name_str, report, "clientInfo.name".to_string())?;
2351                }
2352            }
2353
2354            // Validate version
2355            if let Some(version) = obj.get("version") {
2356                if let Some(version_str) = version.as_str() {
2357                    if version_str.len() > 100 {
2358                        report.add_warning(ValidationWarning::new(
2359                            "clientInfo.version".to_string(),
2360                            format!(
2361                                "Client version string very long: {} characters",
2362                                version_str.len()
2363                            ),
2364                        ));
2365                    }
2366                }
2367            }
2368        } else {
2369            report.add_error(ValidationError::new(
2370                "clientInfo".to_string(),
2371                "Client info must be an object".to_string(),
2372                ErrorSeverity::Medium,
2373            ));
2374        }
2375
2376        Ok(())
2377    }
2378
2379    /// Validate capabilities object
2380    fn validate_capabilities(
2381        &self,
2382        capabilities: &serde_json::Value,
2383        report: &mut ValidationReport,
2384        path: String,
2385    ) -> MCPResult<()> {
2386        if let Some(obj) = capabilities.as_object() {
2387            // Check for excessively large capabilities
2388            if obj.len() > 100 {
2389                report.add_warning(ValidationWarning::new(
2390                    path.clone(),
2391                    format!("Large capabilities object: {} properties", obj.len()),
2392                ));
2393            }
2394
2395            // Validate each capability
2396            for (key, value) in obj {
2397                self.validate_value(value, report, format!("{path}.{key}"), 0)?;
2398            }
2399        } else {
2400            report.add_error(ValidationError::new(
2401                path,
2402                "Capabilities must be an object".to_string(),
2403                ErrorSeverity::Medium,
2404            ));
2405        }
2406
2407        Ok(())
2408    }
2409
2410    /// Validate URI format and security
2411    fn validate_uri(&self, uri: &str, report: &mut ValidationReport) -> MCPResult<()> {
2412        // Check URI length
2413        if uri.len() > 2048 {
2414            report.add_error(ValidationError::new(
2415                "uri".to_string(),
2416                format!("URI too long: {} characters (max 2048)", uri.len()),
2417                ErrorSeverity::High,
2418            ));
2419        }
2420
2421        // Check for basic URI format
2422        if !uri.contains("://") && !uri.starts_with("file://") && !uri.starts_with("data:") {
2423            report.add_error(ValidationError::new(
2424                "uri".to_string(),
2425                "URI must contain a valid scheme".to_string(),
2426                ErrorSeverity::High,
2427            ));
2428        }
2429
2430        // Check for dangerous URI schemes
2431        let dangerous_schemes = [
2432            "javascript:",
2433            "data:text/html",
2434            "vbscript:",
2435            "file:///proc",
2436            "file:///sys",
2437        ];
2438        let uri_lower = uri.to_lowercase();
2439
2440        for scheme in &dangerous_schemes {
2441            if uri_lower.starts_with(scheme) {
2442                if !self.allow_dangerous_content {
2443                    report.add_error(ValidationError::new(
2444                        "uri".to_string(),
2445                        format!("Potentially dangerous URI scheme: {scheme}"),
2446                        ErrorSeverity::High,
2447                    ));
2448                } else {
2449                    report.add_warning(ValidationWarning::new(
2450                        "uri".to_string(),
2451                        format!("Dangerous URI scheme detected: {scheme}"),
2452                    ));
2453                }
2454            }
2455        }
2456
2457        // Check for path traversal in URI
2458        if uri.contains("../") || uri.contains("..\\") {
2459            report.add_error(ValidationError::new(
2460                "uri".to_string(),
2461                "URI contains path traversal patterns".to_string(),
2462                ErrorSeverity::High,
2463            ));
2464        }
2465
2466        // Additional security validation
2467        self.validate_string_security(uri, report, "uri".to_string())?;
2468
2469        Ok(())
2470    }
2471
2472    /// Validate any JSON value recursively
2473    fn validate_value(
2474        &self,
2475        value: &serde_json::Value,
2476        report: &mut ValidationReport,
2477        path: String,
2478        depth: usize,
2479    ) -> MCPResult<()> {
2480        // Check depth limit
2481        if depth > self.max_depth {
2482            report.add_error(ValidationError::new(
2483                path.clone(),
2484                format!("Value depth {} exceeds maximum {}", depth, self.max_depth),
2485                ErrorSeverity::High,
2486            ));
2487            return Ok(());
2488        }
2489
2490        match value {
2491            serde_json::Value::String(s) => {
2492                self.validate_string_value(s, report, path)?;
2493            }
2494            serde_json::Value::Array(arr) => {
2495                self.validate_array_value(arr, report, path, depth)?;
2496            }
2497            serde_json::Value::Object(obj) => {
2498                self.validate_object_value(obj, report, path, depth)?;
2499            }
2500            serde_json::Value::Number(n) => {
2501                self.validate_number_value(n, report, path)?;
2502            }
2503            // Bool and Null are generally safe
2504            _ => {}
2505        }
2506
2507        Ok(())
2508    }
2509
2510    /// Validate string value for size and security
2511    fn validate_string_value(
2512        &self,
2513        s: &str,
2514        report: &mut ValidationReport,
2515        path: String,
2516    ) -> MCPResult<()> {
2517        // Check string length
2518        if s.len() > self.max_string_length {
2519            report.add_error(ValidationError::new(
2520                path.clone(),
2521                format!(
2522                    "String too long: {} characters (max {})",
2523                    s.len(),
2524                    self.max_string_length
2525                ),
2526                ErrorSeverity::High,
2527            ));
2528        }
2529
2530        // Check for extremely large strings that might be DoS attempts
2531        if s.len() > 100_000 {
2532            report.add_warning(ValidationWarning::new(
2533                path.clone(),
2534                format!("Very large string: {} characters", s.len()),
2535            ));
2536        }
2537
2538        // Security validation
2539        self.validate_string_security(s, report, path)?;
2540
2541        Ok(())
2542    }
2543
2544    /// Validate string for security issues
2545    fn validate_string_security(
2546        &self,
2547        s: &str,
2548        report: &mut ValidationReport,
2549        path: String,
2550    ) -> MCPResult<()> {
2551        // Check for null bytes
2552        if s.contains('\0') {
2553            report.add_error(ValidationError::new(
2554                path.clone(),
2555                "String contains null bytes".to_string(),
2556                ErrorSeverity::High,
2557            ));
2558        }
2559
2560        // Check against blocked patterns
2561        for pattern in &self.blocked_patterns {
2562            if pattern.is_match(s) {
2563                if !self.allow_dangerous_content {
2564                    report.add_error(ValidationError::new(
2565                        path.clone(),
2566                        format!("String matches blocked pattern: {}", pattern.as_str()),
2567                        ErrorSeverity::High,
2568                    ));
2569                } else {
2570                    report.add_warning(ValidationWarning::new(
2571                        path.clone(),
2572                        format!(
2573                            "String matches potentially dangerous pattern: {}",
2574                            pattern.as_str()
2575                        ),
2576                    ));
2577                }
2578            }
2579        }
2580
2581        // Check for suspicious character sequences
2582        let suspicious_patterns = [
2583            ("\r\n\r\n", "HTTP header injection"),
2584            ("\n\n", "Potential header injection"),
2585            ("%%", "URL encoding attack"),
2586            ("%00", "Null byte encoding"),
2587            ("${", "Variable substitution attack"),
2588            ("#{", "Expression language injection"),
2589        ];
2590
2591        for (pattern, description) in &suspicious_patterns {
2592            if s.contains(pattern) {
2593                report.add_warning(ValidationWarning::new(
2594                    path.clone(),
2595                    format!("String contains suspicious pattern '{pattern}': {description}"),
2596                ));
2597            }
2598        }
2599
2600        Ok(())
2601    }
2602
2603    /// Validate array value
2604    fn validate_array_value(
2605        &self,
2606        arr: &[serde_json::Value],
2607        report: &mut ValidationReport,
2608        path: String,
2609        depth: usize,
2610    ) -> MCPResult<()> {
2611        // Check array length
2612        if arr.len() > self.max_array_length {
2613            report.add_error(ValidationError::new(
2614                path.clone(),
2615                format!(
2616                    "Array too large: {} elements (max {})",
2617                    arr.len(),
2618                    self.max_array_length
2619                ),
2620                ErrorSeverity::High,
2621            ));
2622        }
2623
2624        // Validate each element
2625        for (i, item) in arr.iter().enumerate() {
2626            self.validate_value(item, report, format!("{path}[{i}]"), depth + 1)?;
2627        }
2628
2629        Ok(())
2630    }
2631
2632    /// Validate object value
2633    fn validate_object_value(
2634        &self,
2635        obj: &serde_json::Map<String, serde_json::Value>,
2636        report: &mut ValidationReport,
2637        path: String,
2638        depth: usize,
2639    ) -> MCPResult<()> {
2640        // Check object size
2641        if obj.len() > self.max_object_properties {
2642            report.add_error(ValidationError::new(
2643                path.clone(),
2644                format!(
2645                    "Object too large: {} properties (max {})",
2646                    obj.len(),
2647                    self.max_object_properties
2648                ),
2649                ErrorSeverity::High,
2650            ));
2651        }
2652
2653        // Validate each property
2654        for (key, value) in obj {
2655            // Validate key
2656            if key.is_empty() {
2657                report.add_error(ValidationError::new(
2658                    path.clone(),
2659                    "Object key cannot be empty".to_string(),
2660                    ErrorSeverity::Medium,
2661                ));
2662            }
2663
2664            if key.len() > 200 {
2665                report.add_error(ValidationError::new(
2666                    path.clone(),
2667                    format!("Object key too long: {} characters (max 200)", key.len()),
2668                    ErrorSeverity::Medium,
2669                ));
2670            }
2671
2672            // Check for potentially dangerous keys
2673            if key.starts_with('_') && key != "_meta" {
2674                report.add_warning(ValidationWarning::new(
2675                    path.clone(),
2676                    format!("Private key detected: {key}"),
2677                ));
2678            }
2679
2680            // Validate key security
2681            self.validate_string_security(key, report, format!("{path}.{key}"))?;
2682
2683            // Validate value
2684            self.validate_value(value, report, format!("{path}.{key}"), depth + 1)?;
2685        }
2686
2687        Ok(())
2688    }
2689
2690    /// Validate number value
2691    fn validate_number_value(
2692        &self,
2693        n: &serde_json::Number,
2694        report: &mut ValidationReport,
2695        path: String,
2696    ) -> MCPResult<()> {
2697        // Check for special float values
2698        if let Some(f) = n.as_f64() {
2699            if f.is_infinite() || f.is_nan() {
2700                report.add_error(ValidationError::new(
2701                    path,
2702                    "Number cannot be infinite or NaN".to_string(),
2703                    ErrorSeverity::Medium,
2704                ));
2705            }
2706        }
2707
2708        Ok(())
2709    }
2710}
2711
2712#[cfg(test)]
2713mod mcp_message_validator_tests {
2714    use super::*;
2715    use crate::protocol::jsonrpc::{
2716        JsonRpcError, JsonRpcMessage, JsonRpcRequest, JsonRpcResponse, RequestId,
2717    };
2718    use serde_json::json;
2719
2720    fn create_test_validator() -> MCPMessageValidator {
2721        MCPMessageValidator::default()
2722    }
2723
2724    #[test]
2725    fn test_validate_valid_request() {
2726        let validator = create_test_validator();
2727        let request = JsonRpcRequest {
2728            jsonrpc: Cow::Borrowed("2.0"),
2729            method: "tools/call".to_string(),
2730            params: Some(json!({
2731                "name": "test_tool",
2732                "arguments": {"input": "hello"}
2733            })),
2734            id: Some(RequestId::String("1".to_string())),
2735            meta: std::collections::HashMap::new(),
2736        };
2737        let message = JsonRpcMessage::Request(request);
2738
2739        let result = validator.validate_message(&message);
2740        assert!(result.is_ok());
2741        let report = result.unwrap();
2742        assert!(
2743            report.is_valid(),
2744            "Expected no validation errors: {:?}",
2745            report.errors
2746        );
2747    }
2748
2749    #[test]
2750    fn test_validate_invalid_jsonrpc_version() {
2751        let validator = create_test_validator();
2752        let request = JsonRpcRequest {
2753            jsonrpc: Cow::Borrowed("1.0"),
2754            method: "test".to_string(),
2755            params: None,
2756            id: Some(RequestId::String("1".to_string())),
2757            meta: std::collections::HashMap::new(),
2758        };
2759        let message = JsonRpcMessage::Request(request);
2760
2761        let result = validator.validate_message(&message);
2762        assert!(result.is_ok());
2763        let report = result.unwrap();
2764        assert!(!report.is_valid());
2765        assert_eq!(report.errors.len(), 1);
2766        assert!(
2767            report.errors[0]
2768                .message
2769                .contains("Invalid JSON-RPC version")
2770        );
2771    }
2772
2773    #[test]
2774    fn test_validate_dangerous_method_name() {
2775        let validator = create_test_validator();
2776        let request = JsonRpcRequest {
2777            jsonrpc: Cow::Borrowed("2.0"),
2778            method: "../system/admin".to_string(),
2779            params: None,
2780            id: Some(RequestId::String("1".to_string())),
2781            meta: std::collections::HashMap::new(),
2782        };
2783        let message = JsonRpcMessage::Request(request);
2784
2785        let result = validator.validate_message(&message);
2786        assert!(result.is_ok());
2787        let report = result.unwrap();
2788        assert!(!report.is_valid());
2789        assert!(
2790            report
2791                .errors
2792                .iter()
2793                .any(|e| e.message.contains("unsafe patterns"))
2794        );
2795    }
2796
2797    #[test]
2798    fn test_validate_script_injection_in_params() {
2799        let validator = create_test_validator();
2800        let request = JsonRpcRequest {
2801            jsonrpc: Cow::Borrowed("2.0"),
2802            method: "tools/call".to_string(),
2803            params: Some(json!({
2804                "name": "test_tool",
2805                "arguments": {"input": "<script>alert('xss')</script>"}
2806            })),
2807            id: Some(RequestId::String("1".to_string())),
2808            meta: std::collections::HashMap::new(),
2809        };
2810        let message = JsonRpcMessage::Request(request);
2811
2812        let result = validator.validate_message(&message);
2813        assert!(result.is_ok());
2814        let report = result.unwrap();
2815        assert!(!report.is_valid());
2816        assert!(
2817            report
2818                .errors
2819                .iter()
2820                .any(|e| e.message.contains("blocked pattern"))
2821        );
2822    }
2823
2824    #[test]
2825    fn test_validate_tool_call_params() {
2826        let validator = create_test_validator();
2827        let request = JsonRpcRequest {
2828            jsonrpc: Cow::Borrowed("2.0"),
2829            method: "tools/call".to_string(),
2830            params: Some(json!({
2831                "name": "_private_tool",
2832                "arguments": {"input": "test"}
2833            })),
2834            id: Some(RequestId::String("1".to_string())),
2835            meta: std::collections::HashMap::new(),
2836        };
2837        let message = JsonRpcMessage::Request(request);
2838
2839        let result = validator.validate_message(&message);
2840        assert!(result.is_ok());
2841        let report = result.unwrap();
2842        assert!(!report.is_valid());
2843        assert!(
2844            report
2845                .errors
2846                .iter()
2847                .any(|e| e.message.contains("unsafe characters"))
2848        );
2849    }
2850
2851    #[test]
2852    fn test_validate_initialize_params() {
2853        let validator = create_test_validator();
2854        let request = JsonRpcRequest {
2855            jsonrpc: Cow::Borrowed("2.0"),
2856            method: "initialize".to_string(),
2857            params: Some(json!({
2858                "protocolVersion": "1.0.0",
2859                "clientInfo": {"name": "test", "version": "1.0"}
2860            })),
2861            id: Some(RequestId::String("1".to_string())),
2862            meta: std::collections::HashMap::new(),
2863        };
2864        let message = JsonRpcMessage::Request(request);
2865
2866        let result = validator.validate_message(&message);
2867        assert!(result.is_ok());
2868        let report = result.unwrap();
2869        assert!(!report.is_valid());
2870        assert!(
2871            report
2872                .errors
2873                .iter()
2874                .any(|e| e.message.contains("Unsupported protocol version"))
2875        );
2876    }
2877
2878    #[test]
2879    fn test_validate_response_with_both_result_and_error() {
2880        let validator = create_test_validator();
2881        let response = JsonRpcResponse {
2882            jsonrpc: Cow::Borrowed("2.0"),
2883            result: Some(json!({"status": "ok"})),
2884            error: Some(JsonRpcError::new(-32600, "Invalid request".to_string())),
2885            id: Some(RequestId::String("1".to_string())),
2886            meta: std::collections::HashMap::new(),
2887        };
2888        let message = JsonRpcMessage::Response(response);
2889
2890        let result = validator.validate_message(&message);
2891        assert!(result.is_ok());
2892        let report = result.unwrap();
2893        assert!(!report.is_valid());
2894        assert!(
2895            report
2896                .errors
2897                .iter()
2898                .any(|e| e.message.contains("cannot have both result and error"))
2899        );
2900    }
2901
2902    #[test]
2903    fn test_validate_large_array() {
2904        let validator = create_test_validator();
2905        let large_array: Vec<serde_json::Value> = (0..60000).map(|i| json!(i)).collect();
2906        let request = JsonRpcRequest {
2907            jsonrpc: Cow::Borrowed("2.0"),
2908            method: "test".to_string(),
2909            params: Some(json!({"items": large_array})),
2910            id: Some(RequestId::String("1".to_string())),
2911            meta: std::collections::HashMap::new(),
2912        };
2913        let message = JsonRpcMessage::Request(request);
2914
2915        let result = validator.validate_message(&message);
2916        assert!(result.is_ok());
2917        let report = result.unwrap();
2918        assert!(!report.is_valid());
2919        assert!(
2920            report
2921                .errors
2922                .iter()
2923                .any(|e| e.message.contains("Array too large"))
2924        );
2925    }
2926
2927    #[test]
2928    fn test_validate_deep_nested_object() {
2929        let validator = create_test_validator();
2930
2931        // Create deeply nested object
2932        let mut nested = json!("deep_value");
2933        for _ in 0..60 {
2934            nested = json!({"nested": nested});
2935        }
2936
2937        let request = JsonRpcRequest {
2938            jsonrpc: Cow::Borrowed("2.0"),
2939            method: "test".to_string(),
2940            params: Some(nested),
2941            id: Some(RequestId::String("1".to_string())),
2942            meta: std::collections::HashMap::new(),
2943        };
2944        let message = JsonRpcMessage::Request(request);
2945
2946        let result = validator.validate_message(&message);
2947        assert!(result.is_ok());
2948        let report = result.unwrap();
2949        assert!(!report.is_valid());
2950        assert!(
2951            report
2952                .errors
2953                .iter()
2954                .any(|e| e.message.contains("depth") && e.message.contains("exceeds maximum"))
2955        );
2956    }
2957
2958    #[test]
2959    fn test_validate_uri_security() {
2960        let validator = create_test_validator();
2961        let request = JsonRpcRequest {
2962            jsonrpc: Cow::Borrowed("2.0"),
2963            method: "resources/read".to_string(),
2964            params: Some(json!({
2965                "uri": "file:///../../etc/passwd"
2966            })),
2967            id: Some(RequestId::String("1".to_string())),
2968            meta: std::collections::HashMap::new(),
2969        };
2970        let message = JsonRpcMessage::Request(request);
2971
2972        let result = validator.validate_message(&message);
2973        assert!(result.is_ok());
2974        let report = result.unwrap();
2975        assert!(!report.is_valid());
2976        assert!(
2977            report
2978                .errors
2979                .iter()
2980                .any(|e| e.message.contains("path traversal"))
2981        );
2982    }
2983
2984    #[test]
2985    fn test_validate_long_strings() {
2986        let validator = create_test_validator();
2987        let long_string = "a".repeat(2_000_000); // 2MB string
2988        let request = JsonRpcRequest {
2989            jsonrpc: Cow::Borrowed("2.0"),
2990            method: "test".to_string(),
2991            params: Some(json!({"data": long_string})),
2992            id: Some(RequestId::String("1".to_string())),
2993            meta: std::collections::HashMap::new(),
2994        };
2995        let message = JsonRpcMessage::Request(request);
2996
2997        let result = validator.validate_message(&message);
2998        assert!(result.is_ok());
2999        let report = result.unwrap();
3000        assert!(!report.is_valid());
3001        assert!(
3002            report
3003                .errors
3004                .iter()
3005                .any(|e| e.message.contains("String too long"))
3006        );
3007    }
3008
3009    #[test]
3010    fn test_validate_custom_blocked_pattern() {
3011        let mut validator = create_test_validator();
3012        validator.add_blocked_pattern(r"SECRET_\w+").unwrap();
3013
3014        let request = JsonRpcRequest {
3015            jsonrpc: Cow::Borrowed("2.0"),
3016            method: "test".to_string(),
3017            params: Some(json!({"token": "SECRET_API_KEY_12345"})),
3018            id: Some(RequestId::String("1".to_string())),
3019            meta: std::collections::HashMap::new(),
3020        };
3021        let message = JsonRpcMessage::Request(request);
3022
3023        let result = validator.validate_message(&message);
3024        assert!(result.is_ok());
3025        let report = result.unwrap();
3026        assert!(!report.is_valid());
3027        assert!(
3028            report
3029                .errors
3030                .iter()
3031                .any(|e| e.message.contains("blocked pattern"))
3032        );
3033    }
3034
3035    #[test]
3036    fn test_validate_dangerous_content_allowed() {
3037        let validator = MCPMessageValidator::default().with_dangerous_content(true);
3038        let request = JsonRpcRequest {
3039            jsonrpc: Cow::Borrowed("2.0"),
3040            method: "test".to_string(),
3041            params: Some(json!({"script": "<script>alert('test')</script>"})),
3042            id: Some(RequestId::String("1".to_string())),
3043            meta: std::collections::HashMap::new(),
3044        };
3045        let message = JsonRpcMessage::Request(request);
3046
3047        let result = validator.validate_message(&message);
3048        assert!(result.is_ok());
3049        let report = result.unwrap();
3050        // Should have warnings but be valid
3051        assert!(report.is_valid());
3052        assert!(!report.warnings.is_empty());
3053    }
3054
3055    #[test]
3056    fn test_validate_notification() {
3057        let validator = create_test_validator();
3058        let notification = JsonRpcRequest {
3059            jsonrpc: Cow::Borrowed("2.0"),
3060            method: "logging/log".to_string(),
3061            params: Some(json!({
3062                "level": "info",
3063                "message": "Test message"
3064            })),
3065            id: None, // Notification has no ID
3066            meta: std::collections::HashMap::new(),
3067        };
3068        let message = JsonRpcMessage::Notification(notification);
3069
3070        let result = validator.validate_message(&message);
3071        assert!(result.is_ok());
3072        let report = result.unwrap();
3073        assert!(
3074            report.is_valid(),
3075            "Expected valid notification: {:?}",
3076            report.errors
3077        );
3078    }
3079}
3080
3081#[cfg(test)]
3082mod tests {
3083    use super::*;
3084
3085    #[test]
3086    fn test_validate_resource_unsubscribe_params() {
3087        let validator = MCPMessageValidator::new(1000, 100, 50, 10);
3088        let mut report = ValidationReport::new("test".to_string());
3089
3090        // Test valid unsubscribe request
3091        let valid_params = serde_json::json!({
3092            "uri": "test://static/resource/0"
3093        });
3094        let result = validator.validate_resource_unsubscribe_params(&valid_params, &mut report);
3095        assert!(result.is_ok());
3096        assert!(report.errors.is_empty());
3097
3098        // Test missing URI
3099        let mut report = ValidationReport::new("test".to_string());
3100        let invalid_params = serde_json::json!({});
3101        let result = validator.validate_resource_unsubscribe_params(&invalid_params, &mut report);
3102        assert!(result.is_ok());
3103        assert!(!report.errors.is_empty());
3104        assert!(
3105            report
3106                .errors
3107                .iter()
3108                .any(|e| e.path == "resources/unsubscribe.uri")
3109        );
3110
3111        // Test non-string URI
3112        let mut report = ValidationReport::new("test".to_string());
3113        let invalid_params = serde_json::json!({
3114            "uri": 123
3115        });
3116        let result = validator.validate_resource_unsubscribe_params(&invalid_params, &mut report);
3117        assert!(result.is_ok());
3118        assert!(!report.errors.is_empty());
3119        assert!(
3120            report
3121                .errors
3122                .iter()
3123                .any(|e| e.path == "resources/unsubscribe.uri")
3124        );
3125    }
3126}