Class: GraphWeaver::Codegen::ScalarType

Inherits:
Object
  • Object
show all
Defined in:
lib/graph_weaver/codegen/scalar_type.rb

Overview

How one GraphQL scalar maps to Ruby: the Sorbet prop type, the (optional) code emitted to deserialize a wire value into a rich Ruby object and serialize it back, and any requires the generated file needs. A single registry (below) holds one of these per scalar name; the built-in scalars are just pre-registered entries, so custom scalars and overrides go through the same path.

cast/serialize normalize to procs that, given a Ruby expression string, return the code to inline. Left nil (the default) they are inferred from the Ruby type when it is a real class, by probing for a known deserializer and pairing its serializer (see CODECS) — so the common case needs no more than a class: type: Money (defines .parse) => Money.parse(expr) / expr.to_s type: Blob (defines .load) => Blob.load(expr) / Blob.dump(expr) Probing the deserialize side is deliberate: every object has #to_s, so inferring a serializer off it would wrongly wrap plain types (String, Integer) — pairing off a deserializer the type actually defines avoids that. Override with an explicit value:

- a Symbol names a method, so there is no string to misspell:
      cast: :load        => "Blob.load(expr)"    (class method on type)
      serialize: :to_json => "expr.to_json"      (instance method)
- a Proc handles anything a Symbol can't express:
      cast: ->(e) { "Money.new(#{e})" }
- :itself opts out — force identity pass-through even when a codec
would otherwise match (rare)

requires: a String or Array of paths emitted as requires atop the generated file (e.g. "bigdecimal") so the cast/type resolve.

Defined Under Namespace

Classes: Codec

Constant Summary collapse

CODECS =
[
  Codec.new(:parse, # Type.parse(wire) <-> value.to_s
    ->(type, expr) { "#{type}.parse(#{expr})" },
    ->(_type, expr) { "#{expr}.to_s" }),
  Codec.new(:load, # Type.load(wire) <-> Type.dump(value)
    ->(type, expr) { "#{type}.load(#{expr})" },
    ->(type, expr) { "#{type}.dump(#{expr})" }),
].freeze
CONVERT_INPUTS =

Accepted kwarg types for Symbol (instance-method) coercion — the looser inputs the conversion sensibly handles. #to_s is defined on every object, so it accepts anything; #to_f/#to_i only make sense for numerics and strings.

{
  to_f: "T.any(Float, Integer, String)",
  to_i: "T.any(Integer, Float, String)",
  to_s: "T.anything",
}.freeze
AUTO_CONVERSIONS =

conversions applied to the four convertible built-ins when the global GraphWeaver.auto_coerce is on and no explicit coerce: given

{
  "ID" => :to_s, "String" => :to_s, "Int" => :to_i, "Float" => :to_f,
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) ⇒ ScalarType

Returns a new instance of ScalarType.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 61

def initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil)
  @graphql_name = graphql_name.to_s
  @klass = type.is_a?(Module) ? type : nil
  @type = type_name(type)
  # requires: load BEFORE codec probing — the probe method may come
  # from the required file (core Time has no .parse until the "time"
  # stdlib loads)
  @requires = normalize_requires(requires)
  codec = @klass && CODECS.find { |c| @klass.respond_to?(c.probe) }
  @cast = normalize_cast(cast, codec&.cast)
  @serialize = normalize_serialize(serialize, codec&.serialize)
  @coerce = coerce
  validate_coerce!
end

Instance Attribute Details

#graphql_nameObject (readonly)

Returns the value of attribute graphql_name.



59
60
61
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 59

def graphql_name
  @graphql_name
end

#requiresObject (readonly)

Returns the value of attribute requires.



59
60
61
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 59

def requires
  @requires
end

#typeObject (readonly)

Returns the value of attribute type.



59
60
61
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 59

def type
  @type
end

Instance Method Details

#cast(expr) ⇒ Object



82
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 82

def cast(expr) = @cast&.call(expr)

#cast?Boolean

Returns:

  • (Boolean)


83
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 83

def cast? = !@cast.nil?

#coerce?Boolean

Returns:

  • (Boolean)


86
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 86

def coerce? = !!effective_coerce

#coerce_input(expr) ⇒ Object

The code that normalizes a variable input before it's serialized. Two shapes: coerce: true parses a raw value into the rich type via the cast (guarded so an already-typed value passes through); coerce: :to_f (a Symbol) calls that instance method, for built-ins where a plain conversion is the whole story (5, "5" -> 5.0). serialize still runs afterward, but is identity for the conversion built-ins, so the converted value goes on the wire natively (a Float, not "5.0").



107
108
109
110
111
112
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 107

def coerce_input(expr)
  case effective_coerce
  when true then "(#{expr}.is_a?(#{@type}) ? #{expr} : #{cast(expr)})"
  when Symbol then "#{expr}.#{effective_coerce}"
  end
end

#coerce_typeObject

the accepted Sorbet type for a coercible variable kwarg



115
116
117
118
119
120
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 115

def coerce_type
  case effective_coerce
  when true then "T.any(#{@type}, String)"
  when Symbol then CONVERT_INPUTS.fetch(effective_coerce, "T.untyped")
  end
end

#effective_coerceObject

Explicit coerce: always wins (false means never). Left unset, the global GraphWeaver.auto_coerce decides — resolved HERE, at generation time, so registration order doesn't matter: convertible built-ins get their conversion, anything with a full cast/serialize pair gets parse-style coercion.



93
94
95
96
97
98
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 93

def effective_coerce
  return @coerce unless @coerce.nil?
  return false unless GraphWeaver.auto_coerce

  AUTO_CONVERSIONS.fetch(@graphql_name) { (cast? && serialize?) || nil }
end

#serialize(expr) ⇒ Object



84
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 84

def serialize(expr) = @serialize&.call(expr)

#serialize?Boolean

Returns:

  • (Boolean)


85
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 85

def serialize? = !@serialize.nil?