timi-wang/swifturlpattern
The pure swift version of url-pattern, easy way to match urls and other strings.
Simple usage
// create pattern with string
let pattern = try URLPattern("/api/users(/:id)")
// match pattern against string and extract values
pattern.match("/api/users/10") // ["id": "10"]
pattern.match("/api/users") // [:]
pattern.match("/api/products/5") // nil
// generate string from pattern and values
pattern.stringify() // "/api/users"
pattern.stringify(["id": 20]) // "/api/users/20"More complecate usages can check the UsageExample test file
Make pattern from string
let pattern = try URLPattern("/api/users/:id");a pattern is immutable after construction. none of its methods changes its state. that makes it easier to reason about. if the pattern cannot be parsed, an error will be thrown
Match pattern against string
match returns the extracted segments:
pattern.match("/api/users/10"); // ["id": "10"]or nil if there was no match:
pattern.match("/api/products/5"); // nilpatterns are compiled into regexes which makes .match() superfast.
Named segments
:id (in the example above) is a named segment:
a named segment starts with : followed by the name. the name must be at least one character in the regex character set a-zA-Z0-9.
when matching, a named segment consumes all characters in the regex character set a-zA-Z0-9-~ %. a named segment match stops at /, ., ... but not at , -, , %...
you can change these character sets. click here to see how.
if a named segment name occurs more than once in the pattern string, and you want to match them all, you can use the pattern.matchAll function, it will return an array with all matched results.
let pattern = try URLPattern("/api/users/:ids/posts/:ids")
pattern.matchAll("/api/users/10/posts/5") // ["ids": ["10", "5"]]Optional segments, wildcards and escaping
to make part of a pattern optional just wrap it in ( and ):
let pattern = try URLPattern(
"(http(s)\\://)(:subdomain.):domain.:tld(/*)"
)note that \\ escapes the : in http(s)\\://. you can use \\ to escape (, ), : and * which have special meaning within URLPattern.
optional named segments are stored in the corresponding property only if they are present in the source string:
pattern.match("google.de")
// ["domain": "google", "tld": "de"]pattern.match("https://www.google.com")
// ["subdomain": "www", "domain": "google", "tld": "com"]* in patterns are wildcards and match anything. wildcard matches are collected in the _ property:
pattern.match("http://mail.google.com/mail");
// ["subdomain": "mail", "domain": "google", "tld": "com", "_": "mail"]if there is more than one wildcard use matchAll to make _ contains an array of matching strings.
Make pattern from regex
let pattern = try URLPattern(regex: "^\/api\/(.*)$");if the pattern was created from a regex, a dict of the captured groups is returned with a key of the group index starts from "1"
pattern.match("/api/users") // ["1": "users"]
pattern.match("/apiii/test") // nilwhen making a pattern from a regex you can pass an array of keys as the second argument. returns dict on match with each key mapped to a captured value:
let pattern = try URLPattern(
regex: "^\/api\/([^\/]+)(?:\/(\d+))?$",
["resource", "id"]
)
pattern.match("/api/users") // ["resource": "users"]
pattern.match("/api/users/5") // ["resource": "users", "id": "5"]
pattern.match("/api/users/foo") // nilStringify patterns
let pattern = try URLPattern("/api/users/:id")
pattern.stringify(["id": 10])
// "/api/users/10"optional segments are only included in the output if they contain named segments and/or wildcards and values for those are provided:
let pattern = try URLPattern("/api/users(/:id)")
pattern.stringify() // "/api/users"
pattern.stringify(["id": 10]) // "/api/users/10"
wildcards (key = _), deeply nested optional groups and multiple value arrays should stringify as expected.
if a value that is not in an optional group is not provided stringify will return nil
if an optional segment contains multiple params and not all of them are provided stringify will return nil one provided value for an optional segment makes all values in that optional segment required.
Customize the pattern syntax
of cause you can completely change pattern-parsing and regex-compilation to suit your needs:
var options = ParserOptions();let's change the char used for escaping (default \\):
options.escapeChar = "!"let's change the char used to start a named segment (default :):
options.segmentNameStartChar = "$"let's change the set of chars allowed in named segment names (default a-zA-Z0-9) to also include _ and -:
options.segmentNameCharset = "a-zA-Z0-9_-"let's change the set of chars allowed in named segment values (default a-zA-Z0-9-_~ %) to not allow non-alphanumeric chars:
options.segmentValueCharset = "a-zA-Z0-9"let's change the chars used to surround an optional segment (default ( and )):
options.optionalSegmentStartChar = "["
options.optionalSegmentEndChar = "]"let's change the char used to denote a wildcard (default *):
options.wildcardChar = "?"pass options as the second argument to the constructor:
let pattern = try URLPattern(
"[http[s]!://][$sub_domain.]$domain.$toplevel-domain[/?]",
options
)then match:
pattern.match("http://mail.google.com/mail")
/*
[
"sub_domain": "mail",
"domain": "google",
"toplevel-domain": "com",
"_": "mail"
]
*/Package Metadata
Repository: timi-wang/swifturlpattern
Default branch: main
README: README.md