Skip to content

Commit

Permalink
#52 - Add support for translated route paths
Browse files Browse the repository at this point in the history
  • Loading branch information
ellmetha committed Nov 18, 2024
1 parent ab935d3 commit 8ffabc4
Show file tree
Hide file tree
Showing 24 changed files with 869 additions and 133 deletions.
5 changes: 5 additions & 0 deletions spec/ext/marten/routing/reverser.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Marten::Routing::Reverser
def exposed_path_for_interpolations
@path_for_interpolations
end
end
52 changes: 35 additions & 17 deletions spec/marten/routing/map_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ require "./spec_helper"

describe Marten::Routing::Match do
describe "#path" do
it "can be used with a translated path" do
map = Marten::Routing::Map.new
map.path(Marten::Routing::TranslatedPath.new("routes.foo_bar"), Marten::Handlers::Base, name: "foo_bar")

map.reverse("foo_bar").should eq "/foo/bar"
end

it "raises if the inserted rule is an empty string" do
map = Marten::Routing::Map.new
expect_raises(
Expand Down Expand Up @@ -29,23 +36,6 @@ describe Marten::Routing::Match do
map.path("/bis", Marten::Handlers::Base, name: "home")
end
end

it "raises if the inserted rule is a path that contains duplicated parameter names" do
map = Marten::Routing::Map.new
expect_raises(Marten::Routing::Errors::InvalidRulePath) do
map.path("/path/xyz/<id:int>/test<id:slug>/bad", Marten::Handlers::Base, name: "home")
end
end

it "raises if the inserted rule is a map that contains duplicated parameter names" do
map = Marten::Routing::Map.new
expect_raises(Marten::Routing::Errors::InvalidRulePath) do
sub_map = Marten::Routing::Map.draw do
path("/bad/<id:int>/foobar", Marten::Handlers::Base, name: "home")
end
map.path("/path/xyz/<id:int>", sub_map, name: "included")
end
end
end

describe "#resolve" do
Expand Down Expand Up @@ -149,6 +139,27 @@ describe Marten::Routing::Match do
map.reverse("home", sid: "hello-world")
end
end

it "raises if the inserted rule is a path that contains duplicated parameter names" do
map = Marten::Routing::Map.new
map.path("/path/xyz/<id:int>/test<id:slug>/bad", Marten::Handlers::Base, name: "home")

expect_raises(Marten::Routing::Errors::InvalidRulePath) do
map.reverse("home", id: 42)
end
end

it "raises if the inserted rule is a map that contains duplicated parameter names" do
map = Marten::Routing::Map.new
sub_map = Marten::Routing::Map.draw do
path("/bad/<id:int>/foobar", Marten::Handlers::Base, name: "home")
end
map.path("/path/xyz/<id:int>", sub_map, name: "included")

expect_raises(Marten::Routing::Errors::InvalidRulePath) do
map.reverse("included:home", id: 42)
end
end
end

describe "#reverse(name, params)" do
Expand Down Expand Up @@ -268,4 +279,11 @@ describe Marten::Routing::Match do
end
end
end

describe "#t" do
it "returns a translated path" do
map = Marten::Routing::Map.new
map.t("simple.translation").should eq Marten::Routing::TranslatedPath.new("simple.translation")
end
end
end
1 change: 1 addition & 0 deletions spec/marten/routing/path/spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require "../spec_helper"
156 changes: 156 additions & 0 deletions spec/marten/routing/path/spec/static_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
require "./spec_helper"

describe Marten::Routing::Path::Spec::Static do
describe "#parameters" do
it "returns the path specification parameters" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

spec.parameters.should eq(
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)
end
end

describe "#path_for_interpolation" do
it "returns the path for interpolation" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

spec.path_for_interpolation.should eq("/test/%{param1}/xyz/%{param2}")
end
end

describe "#regex" do
it "returns the path specification regex" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

spec.regex.should eq(/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/)
end
end

describe "#resolve" do
it "returns the expected match if the path matches a specification without parameters" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/foo\/bar$/,
"/foo/bar",
{} of String => Marten::Routing::Parameter::Base
)

match = spec.resolve("/foo/bar")

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(8)
match.try(&.parameters).should eq(Marten::Routing::MatchParameters.new)
end

it "returns the expected match if the path matches a specification with parameters" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

match = spec.resolve("/test/123/xyz/456")

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(17)
match.try(&.parameters).should eq(
{
"param1" => 123,
"param2" => 456,
}
)
end

it "returns nil if the path does not match a specification without parameters" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/foo\/bar$/,
"/foo/bar",
{} of String => Marten::Routing::Parameter::Base
)

spec.resolve("/bad").should be_nil
end

it "returns nil if the path does not match a specification with parameters because the path does not match" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

spec.resolve("/bad/123/xyz/456").should be_nil
end

it "returns nil if the path does not match a specification with parameters because the parameters do not match" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

spec.resolve("/test/notanumber/xyz/4567").should be_nil
end
end

describe "#reverser" do
it "returns the expected reverser" do
spec = Marten::Routing::Path::Spec::Static.new(
/^\/test\/(?<param1>\d+)\/xyz\/(?<param2>\d+)$/,
"/test/%{param1}/xyz/%{param2}",
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)

reverser = spec.reverser("route:name")
reverser.should be_a(Marten::Routing::Reverser)
reverser.name.should eq("route:name")
reverser.path_for_interpolation.should eq("/test/%{param1}/xyz/%{param2}")
reverser.exposed_path_for_interpolations.should eq(
{
nil => "/test/%{param1}/xyz/%{param2}",
} of String? => String
)
reverser.parameters.should eq(
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["int"],
}
)
end
end
end
151 changes: 151 additions & 0 deletions spec/marten/routing/path/spec/translated_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
require "./spec_helper"

describe Marten::Routing::Path::Spec::Translated do
describe "#resolve" do
it "returns the expected match if the path matches the spec for the current locale" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_with_args"),
nil,
)

match = spec.resolve("/foo/123/bar/abc")

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(16)
match.try(&.parameters).should eq(
{
"param1" => 123,
"param2" => "abc",
}
)
end

it "returns the expected match if the translated path matches the spec for a non-default locale" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar"),
nil,
)

match = I18n.with_locale("fr") { spec.resolve("/foo-french/bar-french") }

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(22)
match.try(&.parameters).should eq(Marten::Routing::MatchParameters.new)
end

it "returns the expected match if the path matches the default locale path when no translation exists" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_with_args"),
nil,
)

match = I18n.with_locale("fr") { spec.resolve("/foo/123/bar/abc") }

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(16)
match.try(&.parameters).should eq(
{
"param1" => 123,
"param2" => "abc",
}
)
end

it "returns the expected match if the path matches the spec for the current locale when a regex suffix is used" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_with_args"),
"/suffix/test",
)

match = spec.resolve("/foo/123/bar/abc/suffix/test")

match.should be_a(Marten::Routing::Path::Match)
match.try(&.end_index).should eq(28)
match.try(&.parameters).should eq(
{
"param1" => 123,
"param2" => "abc",
}
)
end

it "returns nil if the the path does not match the spec for the current locale" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_with_args"),
nil,
)

spec.resolve("/unknown/foo/123/bar/xyz").should be_nil
end

it "returns nil if the the path does not match the spec for a non-default locale" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar"),
nil,
)

I18n.with_locale("fr") { spec.resolve("/unknown/foo-french/bar-french") }.should be_nil
end
end

describe "#reverser" do
it "returns the expected reverser for a path without parameters" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar"),
nil,
)

reverser = spec.reverser("route:name")
reverser.should be_a(Marten::Routing::Reverser)
reverser.name.should eq("route:name")
reverser.exposed_path_for_interpolations.should eq(
{
nil => "/foo/bar",
"en" => "/foo/bar",
"fr" => "/foo-french/bar-french",
"es" => "/foo/bar",
} of String? => String
)
reverser.parameters.should eq(Marten::Routing::MatchParameters.new)
end

it "returns the expected reverser for a path with parameters" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_with_args"),
nil,
)

reverser = spec.reverser("route:name")
reverser.should be_a(Marten::Routing::Reverser)
reverser.name.should eq("route:name")
reverser.exposed_path_for_interpolations.should eq(
{
nil => "/foo/%{param1}/bar/%{param2}",
"en" => "/foo/%{param1}/bar/%{param2}",
"fr" => "/foo/%{param1}/bar/%{param2}",
"es" => "/foo/%{param1}/bar/%{param2}",
} of String? => String
)
reverser.parameters.should eq(
{
"param1" => Marten::Routing::Parameter.registry["int"],
"param2" => Marten::Routing::Parameter.registry["string"],
}
)
end

it "raises if no translation can be found for the default locale" do
spec = Marten::Routing::Path::Spec::Translated.new(
Marten::Routing::TranslatedPath.new("routes.foo_bar_unknown"),
nil,
)

expect_raises(
Marten::Routing::Errors::InvalidRulePath,
"No default locale translation found for route associated with 'routes.foo_bar_unknown' translation key"
) do
spec.reverser("route:name")
end
end
end
end
1 change: 1 addition & 0 deletions spec/marten/routing/path/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
require "../spec_helper"
Loading

0 comments on commit 8ffabc4

Please sign in to comment.