diff --git a/lib/graphiti.rb b/lib/graphiti.rb index 9e23e554..46c9158c 100644 --- a/lib/graphiti.rb +++ b/lib/graphiti.rb @@ -190,6 +190,7 @@ def self.cache require "graphiti/query" require "graphiti/debugger" require "graphiti/util/cache_debug" +require "graphiti/util/uri_decoder" if defined?(ActiveRecord) require "graphiti/adapters/active_record" diff --git a/lib/graphiti/adapters/null.rb b/lib/graphiti/adapters/null.rb index d66af89d..ff869f5d 100644 --- a/lib/graphiti/adapters/null.rb +++ b/lib/graphiti/adapters/null.rb @@ -168,6 +168,14 @@ def filter_boolean_eq(scope, attribute, value) scope end + def filter_uuid_eq(scope, attribute, value) + scope + end + + def filter_uuid_not_eq(scope, attribute, value) + scope + end + def base_scope(model) {} end diff --git a/lib/graphiti/configuration.rb b/lib/graphiti/configuration.rb index 121c4317..1465aec9 100644 --- a/lib/graphiti/configuration.rb +++ b/lib/graphiti/configuration.rb @@ -18,6 +18,7 @@ class Configuration attr_accessor :before_sideload attr_reader :debug, :debug_models + attr_reader :uri_decoder attr_writer :schema_path attr_writer :cache_rendering @@ -37,6 +38,8 @@ def initialize self.debug = ENV.fetch("GRAPHITI_DEBUG", true) self.debug_models = ENV.fetch("GRAPHITI_DEBUG_MODELS", false) + @uri_decoder = infer_uri_decoder + # FIXME: Don't duplicate graphiti-rails efforts if defined?(::Rails.root) && (root = ::Rails.root) config_file = root.join(".graphiticfg.yml") @@ -85,6 +88,33 @@ def with_option(key, value) ensure send(:"#{key}=", original) end + + def uri_decoder=(decoder) + unless decoder.respond_to?(:call) + raise "uri_decoder must respond to `call`." + end + + @uri_decoder = decoder + end + + private + + def infer_uri_decoder + if defined?(::ActionDispatch::Journey::Router::Utils) + # available in all supported versions of Rails. + # This method should be preferred for comparing URI path segments + # to params, as it is the exact decoder used in the Rails router. + @uri_decoder = ::ActionDispatch::Journey::Router::Utils.method(:unescape_uri) + elsif URI.respond_to?(:decode_uri_component) + # available in Ruby >= 3.2 + @uri_decoder = URI.method(:decode_uri_component) + end + rescue => e + Kernel.warn("Error inferring Graphiti uri_decoder: #{e}") + ensure + # fallback + @uri_decoder ||= :itself.to_proc + end end msg = "Use graphiti-rails's `config.graphiti.respond_to_formats`" diff --git a/lib/graphiti/resource/links.rb b/lib/graphiti/resource/links.rb index 82a91c77..16bcb109 100644 --- a/lib/graphiti/resource/links.rb +++ b/lib/graphiti/resource/links.rb @@ -74,12 +74,12 @@ def allow_request?(request_path, params, action) endpoints.any? do |e| has_id = params[:id] || params[:data].try(:[], :id) path = request_path - if [:update, :show, :destroy].include?(context_namespace) && has_id + if [:update, :show, :destroy].include?(action) && has_id path = request_path.split("/") - path.pop if path.last == has_id.to_s + path.pop if Graphiti::Util::UriDecoder.decode_uri(path.last) == has_id.to_s path = path.join("/") end - e[:full_path].to_s == path && e[:actions].include?(context_namespace) + e[:full_path].to_s == path && e[:actions].include?(action) end end diff --git a/lib/graphiti/util/uri_decoder.rb b/lib/graphiti/util/uri_decoder.rb new file mode 100644 index 00000000..af181a2b --- /dev/null +++ b/lib/graphiti/util/uri_decoder.rb @@ -0,0 +1,9 @@ +module Graphiti + module Util + module UriDecoder + def self.decode_uri(uri) + Graphiti.config.uri_decoder.call(uri) + end + end + end +end diff --git a/spec/resource_spec.rb b/spec/resource_spec.rb index fae3bc21..3bf99565 100644 --- a/spec/resource_spec.rb +++ b/spec/resource_spec.rb @@ -1238,6 +1238,23 @@ def self.name end end end + + context "with a url-encoded path param" do + before do + request.env["PATH_INFO"] += "/123%3B456" + + klass.instance_exec do + attribute :id, :uuid + end + end + + it "works" do + klass + Graphiti.with_context ctx, :show do + expect { klass.find(id: "123;456") }.to_not raise_error + end + end + end end context "and the request matches a secondary endpoint" do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 26485819..585bb212 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -64,3 +64,13 @@ def check! database: ":memory:" Dir[File.dirname(__FILE__) + "/fixtures/**/*.rb"].sort.each { |f| require f } end + +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2.0") + unless defined?(::ActionDispatch::Journey) + require 'uri' + # NOTE: `decode_www_form_component` isn't an ideal default for production, + # because it varies slightly compared to typical uri parameterization, + # but it will allow tests to pass in non-rails contexts. + Graphiti.config.uri_decoder = URI.method(:decode_www_form_component) + end +end