Class: McpToolkit::FieldSelection
- Inherits:
-
Object
- Object
- McpToolkit::FieldSelection
- Defined in:
- lib/mcp_toolkit/field_selection.rb
Overview
A parsed, validated sparse-fieldset request (JSON:API's fields[type]) for a
single resource. Requested names share ONE flat namespace covering both declared
ATTRIBUTES and relationship link keys — exactly as JSON:API conflates them.
Built by the List/Get executors from the tool's fields argument. A nil
selection (blank/absent fields) means "all fields": the executors skip it
entirely and serialize as before, so the default path is completely untouched.
A present selection is applied one of two ways, decided by McpToolkit::Serialization:
* NATIVELY — `names` is passed to a serializer whose `serialize_one` /
`serialize_collection` declares a `fields:` keyword (the gem's Base does),
so unselected attributes and relationships are never computed at all.
* By PRUNING the fully-serialized hash (`prune_record` / `prune_collection`)
for an injected serializer that predates the `fields:` kwarg — a pure
shape-level filter, so ANY contract-satisfying serializer stays sparse-able.
Constant Summary collapse
- LINKS_KEY =
"links"
Instance Attribute Summary collapse
-
#names ⇒ Object
readonly
The validated requested field names (symbols), passed to a fields-aware serializer as its
fields:argument.
Class Method Summary collapse
-
.build(resource:, raw:) ⇒ Object
Parse the raw tool argument (a comma-separated string OR an array of names) into a selection, or nil when nothing was requested.
-
.parse(raw) ⇒ Object
Normalizes a CSV string or an array into a de-duplicated list of symbols.
Instance Method Summary collapse
-
#initialize(resource:, names:) ⇒ FieldSelection
constructor
A new instance of FieldSelection.
-
#prune_collection(wrapper) ⇒ Object
Prune the collection wrapper: each array value holds the rows (prune each); the
metahash and any other non-array entry pass through untouched. -
#prune_record(hash) ⇒ Object
Prune a single serialized record hash down to the requested members.
-
#validate! ⇒ Object
Raises InvalidParams if any requested name is neither a declared attribute nor a relationship link key.
Constructor Details
#initialize(resource:, names:) ⇒ FieldSelection
Returns a new instance of FieldSelection.
43 44 45 46 |
# File 'lib/mcp_toolkit/field_selection.rb', line 43 def initialize(resource:, names:) @resource = resource @names = names end |
Instance Attribute Details
#names ⇒ Object (readonly)
The validated requested field names (symbols), passed to a fields-aware
serializer as its fields: argument.
41 42 43 |
# File 'lib/mcp_toolkit/field_selection.rb', line 41 def names @names end |
Class Method Details
.build(resource:, raw:) ⇒ Object
Parse the raw tool argument (a comma-separated string OR an array of names)
into a selection, or nil when nothing was requested. Validates the requested
names against the resource's known members when the resource can describe them
(the Base serializer exposes declared_attributes); an unknown name is a
clean InvalidParams so a typo is actionable rather than silently dropped.
26 27 28 29 30 31 |
# File 'lib/mcp_toolkit/field_selection.rb', line 26 def self.build(resource:, raw:) names = parse(raw) return nil if names.empty? new(resource:, names:).tap(&:validate!) end |
.parse(raw) ⇒ Object
Normalizes a CSV string or an array into a de-duplicated list of symbols.
34 35 36 37 |
# File 'lib/mcp_toolkit/field_selection.rb', line 34 def self.parse(raw) list = raw.is_a?(Array) ? raw : raw.to_s.split(",") list.map { |name| name.to_s.strip }.reject(&:empty?).map(&:to_sym).uniq end |
Instance Method Details
#prune_collection(wrapper) ⇒ Object
Prune the collection wrapper: each array value holds the rows (prune each);
the meta hash and any other non-array entry pass through untouched.
80 81 82 83 84 |
# File 'lib/mcp_toolkit/field_selection.rb', line 80 def prune_collection(wrapper) wrapper.transform_values do |value| value.is_a?(Array) ? value.map { |row| prune_record(row) } : value end end |
#prune_record(hash) ⇒ Object
Prune a single serialized record hash down to the requested members.
Shape-driven (needs no serializer metadata): every key is an attribute except
the string "links" block, which is itself narrowed to the requested link
keys and dropped entirely when nothing under it was requested.
66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/mcp_toolkit/field_selection.rb', line 66 def prune_record(hash) requested = @names.map(&:to_s) hash.each_with_object({}) do |(key, value), pruned| if key.to_s == LINKS_KEY links = (value || {}).select { |link_key, _| requested.include?(link_key.to_s) } pruned[LINKS_KEY] = links unless links.empty? elsif requested.include?(key.to_s) pruned[key] = value end end end |
#validate! ⇒ Object
Raises InvalidParams if any requested name is neither a declared attribute nor a relationship link key. Skipped for serializers that can't describe their members (resource_schema degrades the same way) — those are pruned leniently.
51 52 53 54 55 56 57 58 59 60 |
# File 'lib/mcp_toolkit/field_selection.rb', line 51 def validate! return unless @resource.serializer.respond_to?(:declared_attributes) unknown = @names - known_members return if unknown.empty? selectable = known_members.map(&:to_s).sort.join(", ").presence || "(none)" raise McpToolkit::Errors::InvalidParams, "unknown field(s): #{unknown.join(", ")}. Selectable fields for this resource: #{selectable}" end |