Skip to content

Commit

Permalink
Merge pull request #199 from hotwired/add-url-params-check
Browse files Browse the repository at this point in the history
Add url params check
  • Loading branch information
jayohms authored Mar 22, 2024
2 parents dabd33d + c961266 commit 90227d6
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 12 deletions.
21 changes: 20 additions & 1 deletion Docs/PathConfiguration.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ let pathConfiguration = PathConfiguration(sources: [

Path properties are the core of the path configuration. The `rules` key of the JSON is an array of dictionaries. Each dictionary has a `patterns` array which is an array of regular expressions for matching on the URL, and a dictionary of `properties` that will get returned when a pattern matches.

You can lookup the properties for a URL by using the URL itself or the `url.path` value. Currently, the path configuration only looks at the path component of the URL, but likely we'll add support for other components in the future. The path configuration finds all matching rules in order, and then merges them into one dictionary, with later rules overriding earlier ones. This way you can group similar properties together.
You can lookup the properties for a URL by using the URL itself or the `url.path` value. The path configuration finds all matching rules in order, and then merges them into one dictionary, with later rules overriding earlier ones. This way you can group similar properties together.

Given the following rules:

Expand Down Expand Up @@ -106,6 +106,25 @@ The url `example.com/messages/new` however would match both the first and second

When the `Session` proposes a visit, it looks up the path properties for the proposed visit url if it has a `pathConfiguration` and it passes those path properties to your app in the `VisitProposal` via `proposal.properties`. This is for convenience, but you can also use the path configuration directly and do the same lookup in your application code.

### Query String Matching

By default, path patterns only match against the path component of the URL. Enable query string matching via:

```swift
Turbo.config.pathConfiguration.matchQueryStrings = true
```

To ensure the order of query string parameters don't affect matching, a wildcard `.*` before and after the match is recommended, like so:

```
{
"patterns": [".*\\?.*foo=bar.*"],
"properties": {
"foo": "bar"
}
}
```

## Settings

The path configuration optionally can have a top-level `settings` dictionary. This can be whatever data you want. We use it for controlling anything that we want the flexibility to change from the server without releasing an update. This might be different urls, configurations, feature flags, etc. If you don't want to use that, you can omit it entirely from the JSON.
Expand Down
13 changes: 6 additions & 7 deletions Source/Path Configuration/PathConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,13 @@ public final class PathConfiguration {
public subscript(url: URL) -> PathProperties {
properties(for: url)
}

/// Returns a merged dictionary containing all the properties
/// that match this url
/// Note: currently only looks at path, not query, but most likely will
/// add query support in the future, so it's best to always use this over the path variant
/// unless you're sure you'll never need to reference other parts of the URL in the future

/// Returns a merged dictionary containing all the properties that match this URL.
public func properties(for url: URL) -> PathProperties {
properties(for: url.path)
if Turbo.config.pathConfiguration.matchQueryStrings, let query = url.query {
return properties(for: "\(url.path)?\(query)")
}
return properties(for: url.path)
}

/// Returns a merged dictionary containing all the properties
Expand Down
19 changes: 19 additions & 0 deletions Source/Turbo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public enum Turbo {
public static var config = TurboConfig()
}

public class TurboConfig {
public var debugLoggingEnabled = false {
didSet {
TurboLog.debugLoggingEnabled = debugLoggingEnabled
}
}

public var pathConfiguration = PathConfiguration()
}

public extension TurboConfig {
class PathConfiguration {
public var matchQueryStrings = false
}
}
4 changes: 4 additions & 0 deletions Tests/Fixtures/test-configuration.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
{
"patterns": ["/edit$"],
"properties": {"background_color": "white"}
},
{
"patterns": [".*\\?.*open_in_external_browser=true.*"],
"properties": {"open_in_external_browser": true}
}
]
}
4 changes: 2 additions & 2 deletions Tests/PathConfigurationLoaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class PathConfigurationLoaderTests: XCTestCase {
loader.load { loadedConfig = $0 }

let config = try XCTUnwrap(loadedConfig)
XCTAssertEqual(config.rules.count, 4)
XCTAssertEqual(config.rules.count, 5)
}

func test_file_automaticallyLoadsFromTheLocalFileAndCallsTheHandler() throws {
Expand All @@ -25,7 +25,7 @@ class PathConfigurationLoaderTests: XCTestCase {
loader.load { loadedConfig = $0 }

let config = try XCTUnwrap(loadedConfig)
XCTAssertEqual(config.rules.count, 4)
XCTAssertEqual(config.rules.count, 5)
}

func test_server_automaticallyDownloadsTheFileAndCallsTheHandler() throws {
Expand Down
17 changes: 15 additions & 2 deletions Tests/PathConfigurationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class PathConfigurationTests: XCTestCase {

func test_init_automaticallyLoadsTheConfigurationFromTheSpecifiedLocation() {
XCTAssertEqual(configuration.settings.count, 2)
XCTAssertEqual(configuration.rules.count, 4)
XCTAssertEqual(configuration.rules.count, 5)
}

func test_settings_returnsCurrentSettings() {
Expand All @@ -39,6 +39,18 @@ class PathConfigurationTests: XCTestCase {
"background_color": "white"
])
}

func test_propertiesForURL_withParams() {
let url = URL(string: "http://turbo.test/sample.pdf?open_in_external_browser=true")!

Turbo.config.pathConfiguration.matchQueryStrings = false
XCTAssertEqual(configuration.properties(for: url), [:])

Turbo.config.pathConfiguration.matchQueryStrings = true
XCTAssertEqual(configuration.properties(for: url), [
"open_in_external_browser": true
])
}

func test_propertiesForPath_whenNoMatch_returnsEmptyProperties() {
XCTAssertEqual(configuration.properties(for: "/missing"), [:])
Expand All @@ -49,6 +61,7 @@ class PathConfigurationTests: XCTestCase {
XCTAssertEqual(configuration.properties(for: "/edit"), configuration["/edit"])
XCTAssertEqual(configuration.properties(for: "/"), configuration["/"])
XCTAssertEqual(configuration.properties(for: "/missing"), configuration["/missing"])
XCTAssertEqual(configuration.properties(for: "/sample.pdf?open_in_external_browser=true"), configuration["/sample.pdf?open_in_external_browser=true"])
}
}

Expand All @@ -61,7 +74,7 @@ class PathConfigTests: XCTestCase {
let config = try PathConfigurationDecoder(json: json)

XCTAssertEqual(config.settings.count, 2)
XCTAssertEqual(config.rules.count, 4)
XCTAssertEqual(config.rules.count, 5)
}

func test_json_withMissingRulesKey_failsToDecode() throws {
Expand Down

0 comments on commit 90227d6

Please sign in to comment.