Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions lib/global_id/uri/gid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,23 @@ class InvalidModelIdError < URI::InvalidComponentError; end
COMPOSITE_MODEL_ID_MAX_SIZE = 20
COMPOSITE_MODEL_ID_DELIMITER = "/"

URI_PARSER = URI::RFC2396_Parser.new # :nodoc:
URI_PARSER = URI::RFC3986_Parser.new # :nodoc:

class << self
# Validates +app+'s as URI hostnames containing only alphanumeric characters
# and hyphens. An ArgumentError is raised if +app+ is invalid.
# Validates +app+'s as URI hostnames containing only alphanumeric characters,
# hyphens and dashes. An ArgumentError is raised if +app+ is invalid.
#
# URI::GID.validate_app('bcx') # => 'bcx'
# URI::GID.validate_app('foo-bar') # => 'foo-bar'
# URI::GID.validate_app('foo_bar') # => 'foo_bar'
#
# URI::GID.validate_app(nil) # => ArgumentError
# URI::GID.validate_app('foo/bar') # => ArgumentError
def validate_app(app)
parse("gid://#{app}/Model/1").app
rescue URI::Error
raise ArgumentError, 'Invalid app name. ' \
'App names must be valid URI hostnames: alphanumeric and hyphen characters only.'
'App names must be valid URI hostnames: alphanumeric, hyphen and underscore characters only.'
end

# Create a new URI::GID by parsing a gid string with argument check.
Expand All @@ -64,7 +65,7 @@ def validate_app(app)
# URI.parse('gid://bcx') # => URI::GID instance
# URI::GID.parse('gid://bcx/') # => raises URI::InvalidComponentError
def parse(uri)
generic_components = URI.split(uri) << URI_PARSER << true # RFC2396 parser, true arg_check
generic_components = URI.split(uri) << URI_PARSER << true # RFC3986 parser, true arg_check
new(*generic_components)
end

Expand Down
2 changes: 1 addition & 1 deletion test/cases/global_id_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class GlobalIDTest < ActiveSupport::TestCase
end

assert_raises ArgumentError do
GlobalID.app = 'blog_app'
GlobalID.app = 'blog:app'
end

assert_raises ArgumentError do
Expand Down
6 changes: 0 additions & 6 deletions test/cases/global_locator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,6 @@ def locate_many(gids, options = {}); gids.map(&:model_id); end
end
end

test 'locator name cannot have underscore' do
assert_raises ArgumentError do
GlobalID::Locator.use('under_score') { |gid| 'will never be found' }
end
end

test "by valid purpose returns right model" do
instance = Person.new
login_sgid = instance.to_signed_global_id(for: 'login')
Expand Down
26 changes: 15 additions & 11 deletions test/cases/uri_gid_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ class URI::GIDTest < ActiveSupport::TestCase
setup do
@gid_string = 'gid://bcx/Person/5'
@gid = URI::GID.parse(@gid_string)
@cpk_gid_string = 'gid://bcx/CompositePrimaryKeyModel/tenant-key-value/id-value'
@cpk_gid_string = 'gid://bcx/CompositePrimaryKeyModel/tenant_key-value/id-value'
@cpk_gid = URI::GID.parse(@cpk_gid_string)
end

test 'parsed' do
assert_equal @gid.app, 'bcx'
assert_equal @gid.model_name, 'Person'
assert_equal @gid.model_id, '5'
assert_equal ["tenant-key-value", "id-value"], @cpk_gid.model_id
assert_equal ["tenant_key-value", "id-value"], @cpk_gid.model_id
end

test 'parsed for non existing model class' do
flat_id_gid = URI::GID.parse("gid://bcx/NonExistingModel/1")
assert_equal("1", flat_id_gid.model_id)
assert_equal("NonExistingModel", flat_id_gid.model_name)

composite_id_gid = URI::GID.parse("gid://bcx/NonExistingModel/tenant-key-value/id-value")
assert_equal(["tenant-key-value", "id-value"], composite_id_gid.model_id)
composite_id_gid = URI::GID.parse("gid://bcx/NonExistingModel/tenant_key-value/id-value")
assert_equal(["tenant_key-value", "id-value"], composite_id_gid.model_id)
assert_equal("NonExistingModel", composite_id_gid.model_name)
end

Expand All @@ -35,7 +35,7 @@ class URI::GIDTest < ActiveSupport::TestCase
end

test 'create from a composite primary key model' do
model = CompositePrimaryKeyModel.new(id: ["tenant-key-value", "id-value"])
model = CompositePrimaryKeyModel.new(id: ["tenant_key-value", "id-value"])
assert_equal @cpk_gid_string, URI::GID.create('bcx', model).to_s
end

Expand All @@ -50,19 +50,19 @@ class URI::GIDTest < ActiveSupport::TestCase
end

test 'build with a composite primary key' do
array = URI::GID.build(['bcx', 'CompositePrimaryKeyModel', ["tenant-key-value", "id-value"], nil])
array = URI::GID.build(['bcx', 'CompositePrimaryKeyModel', ["tenant_key-value", "id-value"], nil])
assert array

hash = URI::GID.build(
app: 'bcx',
model_name: 'CompositePrimaryKeyModel',
model_id: ["tenant-key-value", "id-value"],
model_id: ["tenant_key-value", "id-value"],
params: nil
)
assert hash

assert_equal array, hash
assert_equal("gid://bcx/CompositePrimaryKeyModel/tenant-key-value/id-value", array.to_s)
assert_equal("gid://bcx/CompositePrimaryKeyModel/tenant_key-value/id-value", array.to_s)
end

test 'build with wrong ordered array creates a wrong ordered gid' do
Expand Down Expand Up @@ -157,13 +157,17 @@ class URI::GIDAppValidationTest < ActiveSupport::TestCase
end

test 'apps containing non alphanumeric characters are invalid' do
assert_invalid_app 'foo&bar'
assert_invalid_app 'foo:bar'
assert_invalid_app 'foo_bar'
end

test 'app with hyphen is allowed' do
test 'app with hyphen and/or underscore is allowed' do
assert_equal 'foo-bar', URI::GID.validate_app('foo-bar')
assert_equal 'foo_bar', URI::GID.validate_app('foo_bar')
assert_equal 'foo-bar_baz', URI::GID.validate_app('foo-bar_baz')

# RFC3986 allows ampersands in hostnames
# see: https://github.com/ruby/uri/issues/141#issuecomment-2541219003
assert_equal 'foo&bar', URI::GID.validate_app('foo&bar')
Comment on lines +168 to +170
Copy link
Author

@Drowze Drowze Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is one thing that got confused me on RFC3986 parser... Apparently it does allow hostnames with ampersands. That doesn't look right, but I added a test documenting the behaviour regardless.

# ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin23]

require "uri"
URI::VERSION
# => "1.0.4"

URI::RFC2396_Parser.new.parse("gid://foo&bar/bar/1")
#=> raises URI::InvalidURIError

URI::RFC3986_Parser.new.parse("gid://foo&bar/bar/1")
# => #<URI::Generic gid://foo&bar/bar/1>

end

private
Expand Down
Loading