| // Copyright 2020 The Chromium Authors | |
| // Use of this source code is governed by an MIT-style license that can be | |
| // found in the LICENSE file or at https://opensource.org/licenses/MIT. | |
| #include "third_party/liburlpattern/parse.h" | |
| #include <string_view> | |
| #include "base/types/expected.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| #include "third_party/abseil-cpp/absl/status/status.h" | |
| #include "third_party/abseil-cpp/absl/status/statusor.h" | |
| #include "third_party/abseil-cpp/absl/strings/str_format.h" | |
| #include "third_party/liburlpattern/pattern.h" | |
| namespace { | |
| base::expected<std::string, absl::Status> PassThrough(std::string_view input) { | |
| return std::string(input); | |
| } | |
| } // namespace | |
| namespace liburlpattern { | |
| base::expected<std::string, absl::Status> ToUpper(std::string_view input) { | |
| std::string output; | |
| std::transform(input.begin(), input.end(), std::back_inserter(output), | |
| [](unsigned char c) { return std::toupper(c); }); | |
| return output; | |
| } | |
| void RunParseTest(std::string_view pattern, | |
| absl::StatusOr<std::vector<Part>> expected, | |
| EncodeCallback callback = PassThrough) { | |
| auto result = Parse(pattern, std::move(callback)); | |
| ASSERT_EQ(result.has_value(), expected.ok()) | |
| << "parse status '" << result.error() << "' for: " << pattern; | |
| if (!expected.ok()) { | |
| ASSERT_EQ(result.error().code(), expected.status().code()) | |
| << "parse status code for: " << pattern; | |
| EXPECT_NE(result.error().message().find(expected.status().message()), | |
| std::string::npos) | |
| << "parse message '" << result.error().message() | |
| << "' does not contain '" << expected.status().message() | |
| << "' for: " << pattern; | |
| return; | |
| } | |
| const auto& expected_part_list = expected.value(); | |
| const auto& part_list = result.value().PartList(); | |
| EXPECT_EQ(part_list.size(), expected_part_list.size()) | |
| << "parser should produce expected number of parts for: " << pattern; | |
| for (size_t i = 0; i < part_list.size() && i < expected_part_list.size(); | |
| ++i) { | |
| EXPECT_EQ(part_list[i], expected_part_list[i]) | |
| << "token at index " << i << " wrong for: " << pattern; | |
| } | |
| } | |
| TEST(ParseTest, EmptyPattern) { | |
| RunParseTest("", std::vector<Part>()); | |
| } | |
| TEST(ParseTest, EncoderCallback) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/FOO/BAR", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo/bar", expected_parts, ToUpper); | |
| } | |
| TEST(ParseTest, Fixed) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("{/foo}", expected_parts); | |
| } | |
| TEST(ParseTest, FixedAndFixedInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("/{foo}", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroupAndFixed) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("{/}foo", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroupAndFixedInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("{/}{foo}", expected_parts); | |
| } | |
| TEST(ParseTest, FixedAndEmptyGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| }; | |
| RunParseTest("/f{}oo", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroupWithOptionalModifier) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kOptional), | |
| }; | |
| RunParseTest("{/foo}?", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroupWithZeroOrMoreModifier) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kZeroOrMore), | |
| }; | |
| RunParseTest("{/foo}*", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInGroupWithOneOrMoreModifier) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kOneOrMore), | |
| }; | |
| RunParseTest("{/foo}+", expected_parts); | |
| } | |
| TEST(ParseTest, FixedInEarlyTerminatedGroup) { | |
| RunParseTest("{/foo", absl::InvalidArgumentError("expected '}'")); | |
| } | |
| TEST(ParseTest, FixedInUnbalancedGroup) { | |
| RunParseTest("{/foo?", absl::InvalidArgumentError("expected '}'")); | |
| } | |
| TEST(ParseTest, FixedWithModifier) { | |
| RunParseTest("/foo?", absl::InvalidArgumentError("Unexpected modifier")); | |
| } | |
| TEST(ParseTest, Regex) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/f", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"0", /*prefix=*/"", "oo", /*suffix=*/"", | |
| Modifier::kNone), | |
| }; | |
| RunParseTest("/f(oo)", expected_parts); | |
| } | |
| TEST(ParseTest, RegexInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/f", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"0", /*prefix=*/"", "oo", /*suffix=*/"", | |
| Modifier::kNone), | |
| }; | |
| RunParseTest("/f{(oo)}", expected_parts); | |
| } | |
| TEST(ParseTest, RegexWithPrefixAndSuffixInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"0", /*prefix=*/"f", "o", /*suffix=*/"o", | |
| Modifier::kNone), | |
| }; | |
| RunParseTest("/{f(o)o}", expected_parts); | |
| } | |
| TEST(ParseTest, RegexAndRegexInGroup) { | |
| RunParseTest("/f{(o)(o)}", absl::InvalidArgumentError("expected '}'")); | |
| } | |
| TEST(ParseTest, RegexWithPrefix) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kRegex, /*name=*/"0", /*prefix=*/"/", "foo", /*suffix=*/"", | |
| Modifier::kNone), | |
| }; | |
| RunParseTest("/(foo)", expected_parts); | |
| } | |
| TEST(ParseTest, RegexWithNameAndPrefix) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"bar", /*prefix=*/"/", "[^/]+?", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo/:bar([^/]+?)", expected_parts); | |
| } | |
| TEST(ParseTest, RegexWithNameAndPrefixInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo/", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"bar", /*prefix=*/"", "[^/]+?", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo/{:bar([^/]+?)}", expected_parts); | |
| } | |
| TEST(ParseTest, RegexWithModifier) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kRegex, /*name=*/"0", /*prefix=*/"/", "foo", | |
| /*suffix=*/"", Modifier::kOptional), | |
| }; | |
| RunParseTest("/(foo)?", expected_parts); | |
| } | |
| TEST(ParseTest, RegexLikeFullWildcard) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/(.*)", expected_parts); | |
| } | |
| TEST(ParseTest, Wildcard) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/*", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardWithModifierStar) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kZeroOrMore), | |
| }; | |
| RunParseTest("/**", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardWithModifierPlus) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kOneOrMore), | |
| }; | |
| RunParseTest("/*+", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardWithModifierQuestion) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kOptional), | |
| }; | |
| RunParseTest("/*?", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardFollowingWildcardWithModifierStart) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"/", /*value=*/"", | |
| /*suffix=*/"", Modifier::kZeroOrMore), | |
| Part(PartType::kFullWildcard, /*name=*/"1", /*prefix=*/"", /*value=*/"", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/***", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardWithMultipleModifiersPlus) { | |
| RunParseTest("/**+", absl::InvalidArgumentError("expected end of pattern")); | |
| } | |
| TEST(ParseTest, WildcardWithMultipleModifiersQuestion) { | |
| RunParseTest("/**?", absl::InvalidArgumentError("expected end of pattern")); | |
| } | |
| TEST(ParseTest, WildcardInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/f", Modifier::kNone), | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"", /*value=*/"", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/f{*}", expected_parts); | |
| } | |
| TEST(ParseTest, WildcardWithPrefixAndSuffixInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/", Modifier::kNone), | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"f", /*value=*/"", | |
| /*suffix=*/"o", Modifier::kNone), | |
| }; | |
| RunParseTest("/{f*o}", expected_parts); | |
| } | |
| TEST(ParseTest, Name) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| Part(PartType::kSegmentWildcard, /*name=*/"bar", /*prefix=*/"", | |
| /*value=*/"", /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo:bar", expected_parts); | |
| } | |
| TEST(ParseTest, NameStartsWithNumber) { | |
| RunParseTest("/foo/:0", absl::InvalidArgumentError("Missing parameter name")); | |
| } | |
| TEST(ParseTest, NameInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| Part(PartType::kSegmentWildcard, /*name=*/"bar", /*prefix=*/"", | |
| /*value=*/"", /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo{:bar}", expected_parts); | |
| } | |
| TEST(ParseTest, NameAndNameInGroup) { | |
| RunParseTest("/foo{:bar:baz}", absl::InvalidArgumentError("expected '}'")); | |
| } | |
| TEST(ParseTest, NameWithPrefixAndSuffixInGroup) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo/", Modifier::kNone), | |
| Part(PartType::kSegmentWildcard, /*name=*/"bar", /*prefix=*/"data_", | |
| /*value=*/"", /*suffix=*/".jpg", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo/{data_:bar.jpg}", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithPrefix) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| Part(PartType::kSegmentWildcard, /*name=*/"bar", /*prefix=*/"/", | |
| /*value=*/"", /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo/:bar", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithEscapedPrefix) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo/", Modifier::kNone), | |
| Part(PartType::kSegmentWildcard, /*name=*/"bar", /*prefix=*/"", | |
| /*value=*/"", /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo\\/:bar", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithCustomRegex) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kFixed, "/foo", Modifier::kNone), | |
| Part(PartType::kRegex, /*name=*/"bar", /*prefix=*/"", "[^/]+?", | |
| /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/foo:bar([^/]+?)", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithModifier) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kSegmentWildcard, /*name=*/"foo", /*prefix=*/"/", | |
| /*value=*/"", /*suffix=*/"", Modifier::kOptional), | |
| }; | |
| RunParseTest("/:foo?", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithModifierStarAndWildcard) { | |
| std::vector<Part> expected_parts = { | |
| Part(PartType::kSegmentWildcard, /*name=*/"foo", /*prefix=*/"/", | |
| /*value=*/"", /*suffix=*/"", Modifier::kZeroOrMore), | |
| Part(PartType::kFullWildcard, /*name=*/"0", /*prefix=*/"", | |
| /*value=*/"", /*suffix=*/"", Modifier::kNone), | |
| }; | |
| RunParseTest("/:foo**", expected_parts); | |
| } | |
| TEST(ParseTest, NameWithModifierStarAndModifierQuestion) { | |
| RunParseTest("/:foo*?", | |
| absl::InvalidArgumentError("expected end of pattern")); | |
| } | |
| TEST(ParseTest, NameWithModifierStarAndModifierPlus) { | |
| RunParseTest("/:foo*+", | |
| absl::InvalidArgumentError("expected end of pattern")); | |
| } | |
| TEST(ParseTest, DuplicateName) { | |
| RunParseTest("/:foo/:foo", absl::InvalidArgumentError("Duplicate")); | |
| } | |
| } // namespace liburlpattern |