Module: SolidLoop::Mcp::Toolset::SchemaValidator
- Defined in:
- lib/solid_loop/mcp/toolset.rb
Overview
A focused, dependency-free validator for the subset of JSON Schema the
DSL actually uses: object required and top-level properties types
(plus additionalProperties: false to reject unknown fields). It is not
a full JSON-Schema implementation — the aim is to keep obviously wrong
calls out of handlers, not to validate deep structures.
Constant Summary collapse
- TYPE_CHECKS =
JSON Schema type name => a predicate on the (string-keyed, JSON-parsed) argument value.
{ "string" => ->(v) { v.is_a?(String) }, "integer" => ->(v) { v.is_a?(Integer) }, "number" => ->(v) { v.is_a?(Numeric) && !v.is_a?(TrueClass) && !v.is_a?(FalseClass) }, "boolean" => ->(v) { v == true || v == false }, "array" => ->(v) { v.is_a?(Array) }, "object" => ->(v) { v.is_a?(Hash) }, "null" => ->(v) { v.nil? } }.freeze
Class Method Summary collapse
- .additional_violation(schema, arguments) ⇒ Object
-
.normalize(value) ⇒ Object
Schemas are declared with symbol keys in the DSL but arrive JSON-parsed (string keys) over the wire; normalize to string keys so both match.
- .property_type_violation(schema, arguments) ⇒ Object
- .required_violation(schema, arguments) ⇒ Object
-
.violation(input_schema, arguments) ⇒ Object
Returns a human-readable violation string, or nil when arguments satisfy the schema.
Class Method Details
.additional_violation(schema, arguments) ⇒ Object
72 73 74 75 76 77 78 |
# File 'lib/solid_loop/mcp/toolset.rb', line 72 def self.additional_violation(schema, arguments) return nil unless schema["additionalProperties"] == false allowed = (schema["properties"] || {}).keys extra = arguments.keys - allowed "unexpected argument(s): #{extra.join(', ')}" if extra.any? end |
.normalize(value) ⇒ Object
Schemas are declared with symbol keys in the DSL but arrive JSON-parsed (string keys) over the wire; normalize to string keys so both match.
103 104 105 106 107 |
# File 'lib/solid_loop/mcp/toolset.rb', line 103 def self.normalize(value) return value unless value.is_a?(Hash) || value.is_a?(Array) JSON.parse(JSON.generate(value)) end |
.property_type_violation(schema, arguments) ⇒ Object
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/solid_loop/mcp/toolset.rb', line 80 def self.property_type_violation(schema, arguments) properties = schema["properties"] return nil unless properties.is_a?(Hash) properties.each do |name, spec| next unless arguments.key?(name) expected = spec.is_a?(Hash) ? spec["type"] : nil next unless expected checks = Array(expected).map { |t| TYPE_CHECKS[t] } # If any declared type is unknown to us, don't enforce this property # (best-effort — we can't judge what we don't understand). next if checks.any?(&:nil?) ok = checks.any? { |check| check.call(arguments[name]) } return "argument '#{name}' must be #{Array(expected).join(' or ')}" unless ok end nil end |
.required_violation(schema, arguments) ⇒ Object
67 68 69 70 |
# File 'lib/solid_loop/mcp/toolset.rb', line 67 def self.required_violation(schema, arguments) missing = Array(schema["required"]).reject { |name| arguments.key?(name.to_s) } "missing required argument(s): #{missing.join(', ')}" if missing.any? end |
.violation(input_schema, arguments) ⇒ Object
Returns a human-readable violation string, or nil when arguments satisfy the schema. A non-object schema (or a schema whose type is not "object") is treated as "no constraints" — validation is best-effort.
54 55 56 57 58 59 60 61 62 63 64 65 |
# File 'lib/solid_loop/mcp/toolset.rb', line 54 def self.violation(input_schema, arguments) schema = normalize(input_schema) return nil unless schema.is_a?(Hash) type = schema["type"] return nil if type && type != "object" return "arguments must be an object" unless arguments.is_a?(Hash) required_violation(schema, arguments) || additional_violation(schema, arguments) || property_type_violation(schema, arguments) end |