instructure/canvas-lms · error · GraphQL::CoercionError

# is not a valid StringMap

Error message

#{ruby_value.inspect} is not a valid StringMap

What it means

StringMapType is a custom GraphQL scalar mapping String keys to String values. coerce_result runs on every value the server returns through this scalar; if the value is not a Hash whose keys are all Symbols/Strings and values are all Strings, a GraphQL::CoercionError is raised so the client receives a well-formed GraphQL error instead of silently serializing bad data. Valid values are normalized with transform_keys(&:to_s).

Solutions

  1. Inspect the value in the message — it prints the offending ruby_value.inspect — and make the resolver return a Hash of String/Symbol keys to String values
  2. Coerce before returning: value.transform_values(&:to_s) when values are non-string primitives
  3. Filter out nil or non-hash lookups: return {} instead of nil
  4. Add a spec covering the resolver's return shape through GraphQL execution

Example fix

// before
{ counts: { apples: 3, bananas: 1 } }
// after
{ counts: { apples: '3', bananas: '1' } }
Defensive patterns

Strategy: type-guard

Validate before calling

def valid_string_map?(v)
  v.is_a?(Hash) && v.all? { |k, val| (k.is_a?(Symbol) || k.is_a?(String)) && val.is_a?(String) }
end

Type guard

def valid_string_map?(v)
  v.is_a?(Hash) && v.all? { |k, val| (k.is_a?(Symbol) || k.is_a?(String)) && val.is_a?(String) }
end

Try / catch

begin
  result = resolver_value
rescue GraphQL::CoercionError => e
  Rails.logger.error("StringMap coercion failed: #{e.message}")
  {}
end

Prevention

When it happens

Trigger: A resolver/field returning a StringMap scalar yields a non-Hash (nil, array, model object), a Hash with non-string/symbol keys (e.g. integer keys), or a Hash whose values are not Strings (integers, booleans, nested hashes, nil).

Common situations: Returning parsed JSON where some values are numbers; returning ActiveRecord attributes with integer values; forgetting to to_s values before returning; passing nil when a lookup misses.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/ce11bce5cc49ba40. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/types/string_map_type.rb:41

    description "A hash with string keys and string values"

    def self.coerce_input(input_value, _context)
      return nil if input_value.nil?

      if input_value.is_a?(ActionController::Parameters)
        input_value = input_value.to_unsafe_h
      end

      unless input_value.is_a?(Hash) && input_value.all? { |k, v| k.is_a?(String) && v.is_a?(String) }
        raise GraphQL::CoercionError, "#{input_value.inspect} is not a valid StringMap"
      end

      input_value
    end

    def self.coerce_result(ruby_value, _context)
      unless ruby_value.is_a?(Hash) && ruby_value.all? { |k, v| (k.is_a?(Symbol) || k.is_a?(String)) && v.is_a?(String) }
        raise GraphQL::CoercionError, "#{ruby_value.inspect} is not a valid StringMap"
      end

      ruby_value.transform_keys(&:to_s)
    end
  end
end

View on GitHub (pinned to 1c9f0bb801)