Class: GraphWeaver::Codegen::ScalarType
- Inherits:
-
Object
- Object
- GraphWeaver::Codegen::ScalarType
- 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
-
#graphql_name ⇒ Object
readonly
Returns the value of attribute graphql_name.
-
#requires ⇒ Object
readonly
Returns the value of attribute requires.
-
#type ⇒ Object
readonly
Returns the value of attribute type.
Instance Method Summary collapse
- #cast(expr) ⇒ Object
- #cast? ⇒ Boolean
- #coerce? ⇒ Boolean
-
#coerce_input(expr) ⇒ Object
The code that normalizes a variable input before it's serialized.
-
#coerce_type ⇒ Object
the accepted Sorbet type for a coercible variable kwarg.
-
#effective_coerce ⇒ Object
Explicit coerce: always wins (false means never).
-
#initialize(graphql_name, type, cast: nil, serialize: nil, requires: nil, coerce: nil) ⇒ ScalarType
constructor
A new instance of ScalarType.
- #serialize(expr) ⇒ Object
- #serialize? ⇒ Boolean
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_name ⇒ Object (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 |
#requires ⇒ Object (readonly)
Returns the value of attribute requires.
59 60 61 |
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 59 def requires @requires end |
#type ⇒ Object (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
83 |
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 83 def cast? = !@cast.nil? |
#coerce? ⇒ 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_type ⇒ Object
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_coerce ⇒ Object
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
85 |
# File 'lib/graph_weaver/codegen/scalar_type.rb', line 85 def serialize? = !@serialize.nil? |