Skip to content

Commit

Permalink
Merge pull request #5141 from rmosolgo/fix-dynamic-enum-values-with-v…
Browse files Browse the repository at this point in the history
…isibility-profiles

Add support for dynamic enum_values in Visibility::Profile
  • Loading branch information
rmosolgo authored Nov 4, 2024
2 parents dcaaed1 + 56d58da commit 4ade767
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 24 deletions.
8 changes: 3 additions & 5 deletions lib/graphql/query/null_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@ class NullSchema < GraphQL::Schema
extend Forwardable

attr_reader :schema, :query, :warden, :dataloader
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?
def_delegators GraphQL::EmptyObjects::EMPTY_HASH, :[], :fetch, :dig, :key?, :to_h

def initialize
@query = NullQuery.new
@dataloader = GraphQL::Dataloader::NullDataloader.new
@schema = NullSchema
@warden = Schema::Warden::NullWarden.new(context: self, schema: @schema)
end

def types
@types ||= Schema::Warden::VisibilityProfile.new(@warden)
@types = @warden.visibility_profile
freeze
end
end
end
Expand Down
20 changes: 17 additions & 3 deletions lib/graphql/schema/enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,24 @@ def value(*args, **kwargs, &block)
def enum_values(context = GraphQL::Query::NullContext.instance)
inherited_values = superclass.respond_to?(:enum_values) ? superclass.enum_values(context) : nil
visible_values = []
warden = Warden.from_context(context)
types = Warden.types_from_context(context)
own_values.each do |key, values_entry|
if (v = Warden.visible_entry?(:visible_enum_value?, values_entry, context, warden))
visible_values << v
visible_value = nil
if values_entry.is_a?(Array)
values_entry.each do |v|
if types.visible_enum_value?(v, context)
if visible_value.nil?
visible_value = v
visible_values << v
else
raise DuplicateNamesError.new(
duplicated_name: v.path, duplicated_definition_1: visible_value.inspect, duplicated_definition_2: v.inspect
)
end
end
end
elsif types.visible_enum_value?(values_entry, context)
visible_values << values_entry
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/schema/enum_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def value(new_val = nil)
end

def inspect
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}>"
"#<#{self.class} #{path} @value=#{@value.inspect}#{description ? " @description=#{description.inspect}" : ""}#{deprecation_reason ? " @deprecation_reason=#{deprecation_reason.inspect}" : ""}>"
end

def visible?(_ctx); true; end
Expand Down
3 changes: 2 additions & 1 deletion lib/graphql/schema/visibility/migration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def loaded_types
:mutation_root,
:possible_types,
:subscription_root,
:reachable_type?
:reachable_type?,
:visible_enum_value?,
]

PUBLIC_PROFILE_METHODS.each do |profile_method|
Expand Down
12 changes: 8 additions & 4 deletions lib/graphql/schema/visibility/profile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ def self.from_context(ctx, schema)
end
end

def self.pass_thru(context:, schema:)
profile = self.new(context: context, schema: schema)
profile.instance_variable_set(:@cached_visible, Hash.new { |h,k| h[k] = true })
def self.null_profile(context:, schema:)
profile = self.new(name: "NullProfile", context: context, schema: schema)
profile.instance_variable_set(:@cached_visible, Hash.new { |k, v| k[v] = true }.compare_by_identity)
profile
end

Expand Down Expand Up @@ -123,7 +123,7 @@ def initialize(name: nil, context:, schema:)
end.compare_by_identity

@cached_enum_values = Hash.new do |h, enum_t|
values = non_duplicate_items(enum_t.all_enum_value_definitions, @cached_visible)
values = non_duplicate_items(enum_t.enum_values(@context), @cached_visible)
if values.size == 0
raise GraphQL::Schema::Enum::MissingValuesError.new(enum_t)
end
Expand Down Expand Up @@ -327,6 +327,10 @@ def reachable_type?(name)
!!@all_types[name]
end

def visible_enum_value?(enum_value, _ctx = nil)
@cached_visible[enum_value]
end

private

def add_if_visible(t)
Expand Down
22 changes: 13 additions & 9 deletions lib/graphql/schema/warden.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ def self.from_context(context)
PassThruWarden
end

def self.types_from_context(context)
context.types || PassThruWarden
rescue NoMethodError
# this might be a hash which won't respond to #warden
PassThruWarden
end

def self.use(schema)
# no-op
end
Expand Down Expand Up @@ -80,24 +87,17 @@ def initialize(_filter = nil, context:, schema:)
# No-op, but for compatibility:
attr_writer :skip_warning

# @api private
module NullVisibilityProfile
def self.new(context:, schema:)
NullWarden.new(context: context, schema: schema).visibility_profile
end
end

attr_reader :visibility_profile

def visible_field?(field_defn, _ctx = nil, owner = nil); true; end
def visible_argument?(arg_defn, _ctx = nil); true; end
def visible_type?(type_defn, _ctx = nil); true; end
def visible_enum_value?(enum_value, _ctx = nil); true; end
def visible_enum_value?(enum_value, _ctx = nil); enum_value.visible?(Query::NullContext.instance); end
def visible_type_membership?(type_membership, _ctx = nil); true; end
def interface_type_memberships(obj_type, _ctx = nil); obj_type.interface_type_memberships; end
def get_type(type_name); @schema.get_type(type_name, Query::NullContext.instance, false); end # rubocop:disable Development/ContextIsPassedCop
def arguments(argument_owner, ctx = nil); argument_owner.all_argument_definitions; end
def enum_values(enum_defn); enum_defn.enum_values; end # rubocop:disable Development/ContextIsPassedCop
def enum_values(enum_defn); enum_defn.enum_values(Query::NullContext.instance); end # rubocop:disable Development/ContextIsPassedCop
def get_argument(parent_type, argument_name); parent_type.get_argument(argument_name); end # rubocop:disable Development/ContextIsPassedCop
def types; @schema.types; end # rubocop:disable Development/ContextIsPassedCop
def root_type_for_operation(op_name); @schema.root_type_for_operation(op_name); end
Expand Down Expand Up @@ -183,6 +183,10 @@ def loadable?(t, ctx) # TODO remove ctx here?
def reachable_type?(type_name)
!!@warden.reachable_type?(type_name)
end

def visible_enum_value?(enum_value, ctx = nil)
@warden.visible_enum_value?(enum_value, ctx)
end
end

# @param context [GraphQL::Query::Context]
Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/testing/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def run_graphql_field(schema, field_path, object, arguments: {}, context: {}, as
end
graphql_result
else
unfiltered_type = Schema::Visibility::Profile.pass_thru(schema: schema, context: context).type(type_name)
unfiltered_type = Schema::Visibility::Profile.null_profile(schema: schema, context: context).type(type_name)
if unfiltered_type
raise TypeNotVisibleError.new(type_name: type_name)
else
Expand Down
36 changes: 36 additions & 0 deletions spec/graphql/schema/enum_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,40 @@ def names(things:)
end
end
end

describe "when values are defined on-the-fly inside #enum_values" do
class DynamicEnumValuesSchema < GraphQL::Schema
class TransportationMode < GraphQL::Schema::Enum
def self.enum_values(context = {})
[
GraphQL::Schema::EnumValue.new("BICYCLE", owner: self),
GraphQL::Schema::EnumValue.new("CAR", owner: self),
GraphQL::Schema::EnumValue.new("BUS", owner: self),
GraphQL::Schema::EnumValue.new("SCOOTER", owner: self),
]
end
end
class Query < GraphQL::Schema::Object
field :mode, TransportationMode, fallback_value: "SCOOTER"
end
query(Query)
end

it "uses them" do
expected_sdl = <<~GRAPHQL
type Query {
mode: TransportationMode
}
enum TransportationMode {
BICYCLE
BUS
CAR
SCOOTER
}
GRAPHQL

assert_equal expected_sdl, DynamicEnumValuesSchema.to_definition
end
end
end

0 comments on commit 4ade767

Please sign in to comment.