Skip to content

feat(srv): Add HTTP 301 redirects for common URL mistakes#2528

Merged
CommanderStorm merged 39 commits intomainfrom
copilot/add-permanent-redirects
Feb 9, 2026
Merged

feat(srv): Add HTTP 301 redirects for common URL mistakes#2528
CommanderStorm merged 39 commits intomainfrom
copilot/add-permanent-redirects

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 27, 2026

Implementation Plan for URL Redirects

  • Explore existing route handlers and understand Actix-Web routing patterns
  • Create redirect handlers co-located with their targets
    • Add pluralization redirect handlers (styles→style, sprites→sprite, etc.)
    • Add tile format suffix redirect handlers (.pbf, .mvt, .mlt)
    • Preserve query strings in all redirects
    • Use HTTP 301 (Permanent Redirect)
    • Use proper visibility (pub(crate))
  • Update route registration in martin/src/srv/server.rs
    • Tile suffix redirects before main tile route (for correct precedence)
    • Pluralization redirects after main routes (as fallbacks)
  • Build and test the implementation
    • Run cargo build to verify compilation
    • Test redirect handlers manually with curl (completed earlier)
    • Verify query string preservation (completed earlier)
    • Fix clippy warnings
  • Manual testing completed successfully
    • Verified all pluralization redirects work
    • Verified all tile format suffix redirects work
    • Verified /tiles/ prefix redirect works
    • Verified query string preservation
  • Code refactoring per new requirement
    • Moved redirects to be co-located with their target handlers
    • Removed separate redirects.rs module
    • Simpler, more maintainable code organization
  • Address code review feedback
    • Changed redirect handler visibility to pub(crate)
    • Created DebouncedWarning helper to eliminate code duplication
    • Updated warning message format per feedback
    • Refactored DebouncedWarning to use closure API for proper tracing integration
  • Add integration tests
    • Created test_redirect function in test.sh
    • Added tests for all pluralization redirects
    • Added tests for tile format suffix redirects
    • Added tests for query string preservation (tiles only)
    • Fixed test function to not create litter files
  • Final validation and completion
Original prompt

Problem

Currently, when users make common pluralization mistakes (like /styles/ instead of /style/) or add explicit file extensions to tile endpoints (like .pbf, .mvt, .mlt), they receive a 404 error. These should instead return a permanent redirect (HTTP 301) to the correct canonical URL.

Examples of URLs that should redirect:

Pluralization redirects:

  • /styles/{style_id}/style/{style_id} (301)
  • /sprites/{source_ids}.json/sprite/{source_ids}.json (301)
  • /sprites/{source_ids}.png/sprite/{source_ids}.png (301)
  • /sdf_sprites/{source_ids}.json/sdf_sprite/{source_ids}.json (301)
  • /sdf_sprites/{source_ids}.png/sdf_sprite/{source_ids}.png (301)
  • /fonts/{fontstack}/{start}-{end}/font/{fontstack}/{start}-{end} (301)
  • /tiles/{source_ids}/{z}/{x}/{y}/{source_ids}/{z}/{x}/{y} (301)

Tile format suffix redirects:

For tile endpoints like /{source_ids}/{z}/{x}/{y}, also support:

  • /{source_ids}/{z}/{x}/{y}.pbf/{source_ids}/{z}/{x}/{y} (301)
  • /{source_ids}/{z}/{x}/{y}.mvt/{source_ids}/{z}/{x}/{y} (301)
  • /{source_ids}/{z}/{x}/{y}.mlt/{source_ids}/{z}/{x}/{y} (301)

Implementation Requirements

  1. Create a new module martin/src/srv/redirects.rs that handles:

    • Middleware or route handlers for common pluralization mistakes
    • Handlers for tile format suffix redirects
    • All redirects must use HTTP 301 (Permanent Redirect)
    • Preserve query strings when redirecting
  2. Register redirect routes in martin/src/srv/server.rs:

    • Add redirect handlers to the router
    • Ensure they are registered after the main routes so they act as fallbacks
    • Use Actix-Web's route macro system similar to existing handlers
  3. Pattern matching approach:

    • For pluralization: Use route patterns that match the plural form and redirect to singular
    • For tile suffixes: Use route patterns like /{source_ids}/{z}/{x}/{y}.{ext:pbf|mvt|mlt} that strip the extension
    • Preserve all path parameters and query strings in the redirect
  4. Code quality requirements:

    • Follow the existing code style in martin/src/srv/ modules
    • Use proper error types (ActixResult)
    • Add appropriate comments explaining the redirect logic
    • Ensure proper imports and module structure
  5. Testing considerations:

    • The implementation should work with existing integration tests
    • Redirects should preserve the full path including source_ids, coordinates, etc.
    • Query parameters should be preserved across redirects

Key Files to Modify

  1. Create: martin/src/srv/redirects.rs - New module for redirect handlers
  2. Modify: martin/src/srv/server.rs - Add redirect route registration
  3. Modify: martin/src/srv/mod.rs - Add module declaration if needed

Expected Behavior

Before:

$ curl -I http://localhost:3000/styles/my_style
HTTP/1.1 404 Not Found

After:

$ curl -I http://localhost:3000/styles/my_style
HTTP/1.1 301 Moved Permanently
Location: /style/my_style

Before:

$ curl -I http://localhost:3000/points/0/0/0.pbf
HTTP/1.1 404 Not Found

After:

$ curl -I http://localhost:3000/points/0/0/0.pbf
HTTP/1.1 301 Moved Permanently
Location: /points/0/0/0

Notes

  • This is a backend-only change (Rust scope)
  • Follows Martin's engineering principles of user experience and correctness
  • Improves developer experience by being forgiving of common mistakes
  • Uses HTTP standards properly (301 for permanent redirects)

This pull request was created from Copilot chat.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

- Created martin/src/srv/redirects.rs module with redirect handlers
- Added pluralization redirects (styles→style, sprites→sprite, etc.)
- Added tile format suffix redirects (.pbf, .mvt, .mlt)
- All redirects use HTTP 301 and preserve query strings
- Updated server.rs to register redirects with correct precedence
- Tile suffix redirects registered before main tile route
- Pluralization redirects registered after main routes

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
pre-commit-ci bot and others added 5 commits January 27, 2026 10:02
Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
- Moved redirect handlers from separate redirects.rs to their respective modules
- Styles redirects now in styles.rs
- Sprites redirects now in sprites.rs
- Fonts redirects now in fonts.rs
- Tile redirects now in tiles/content.rs
- Updated server.rs to register handlers from their new locations
- Removed redirects.rs module
- Removed test file
- Simpler code organization - redirects next to their targets

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Redirect handlers are only used internally within the server module
and should not be part of the public API.

Co-authored-by: CommanderStorm <26258709+CommanderStorm@users.noreply.github.com>
Copilot AI changed the title [WIP] Add permanent redirects for pluralization and file extension errors Add HTTP 301 redirects for common URL mistakes Jan 27, 2026
Copilot AI requested a review from CommanderStorm January 27, 2026 10:20
CommanderStorm

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Jan 29, 2026

@copilot the API should be DEBOUNCED_FN.once_per_hour(|| warn!(...
The reason is that this way tracings log filtering works as expected.

Fixed in commit 58513e8 - refactored DebouncedWarning API to accept a closure. The new API is WARNING.once_per_hour(|| warn!(...)) which keeps the warn! call site in the caller's context, ensuring tracing's log filtering works correctly.

@CommanderStorm CommanderStorm merged commit e67c4f8 into main Feb 9, 2026
40 checks passed
@CommanderStorm CommanderStorm deleted the copilot/add-permanent-redirects branch February 9, 2026 19:08
@CommanderStorm CommanderStorm mentioned this pull request Feb 6, 2026
CommanderStorm added a commit that referenced this pull request Feb 11, 2026
## 🤖 New release

* `martin-tile-utils`: 0.6.9 -> 0.6.10 (✓ API compatible changes)
* `mbtiles`: 0.15.1 -> 0.15.2 (✓ API compatible changes)
* `martin-core`: 0.2.6 -> 0.3.0 (⚠ API breaking changes)
* `martin`: 1.3.0 -> 2.0.0

### ⚠ `martin-core` breaking changes

```text
--- failure enum_missing: pub enum removed or renamed ---

Description:
A publicly-visible enum cannot be imported by its prior path. A `pub use` may have been removed, or the enum itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/enum_missing.ron

Failed in:
  enum martin_core::config::OptOneMany, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/cfg_containers.rs:29
  enum martin_core::config::OptBoolObj, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/cfg_containers.rs:8

--- failure module_missing: pub module removed or renamed ---

Description:
A publicly-visible module cannot be imported by its prior path. A `pub use` may have been removed, or the module may have been renamed, removed, or made non-public.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/module_missing.ron

Failed in:
  mod martin_core::config, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/mod.rs:1
  mod martin_core::config::env, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/env.rs:1

--- failure struct_missing: pub struct removed or renamed ---

Description:
A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/struct_missing.ron

Failed in:
  struct martin_core::config::env::OsEnv, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/env.rs:53
  struct martin_core::config::IdResolver, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/id_resolver.rs:10
  struct martin_core::config::env::FauxEnv, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/env.rs:76

--- failure trait_missing: pub trait removed or renamed ---

Description:
A publicly-visible trait cannot be imported by its prior path. A `pub use` may have been removed, or the trait itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/trait_missing.ron

Failed in:
  trait martin_core::config::env::Env, previously in file /tmp/.tmpiF3o7Z/martin-core/src/config/env.rs:20
```

<details><summary><i><b>Changelog</b></i></summary><p>


## `mbtiles`

<blockquote>

##
[0.15.2](mbtiles-v0.15.1...mbtiles-v0.15.2)
- 2026-02-11

### Other

- restrict `unused_trait_names` for trait imports
([#2542](#2542))
</blockquote>

## `martin-core`

<blockquote>

##
[0.3.0](martin-core-v0.2.6...martin-core-v0.3.0)
- 2026-02-11

### Added

- *(unstable-cog)* Change tile path semantics for COG sources to match
other sources, expose COG bounds, center and tileSize in TileJSON
([#2510](#2510))

### Other

- *(martin-core)* [**breaking**] remove the configration from the
martin-core crate
([#2521](#2521))
- restrict `unused_trait_names` for trait imports
([#2542](#2542))
</blockquote>

## `martin`

<blockquote>

##
[2.0.0](martin-v1.3.0...martin-v2.0.0)
- 2026-02-11

### Added

- *(srv)* Add HTTP 301 redirects for common URL mistakes
([#2528](#2528))
- *(unstable-cog)* Change tile path semantics for COG sources to match
other sources, expose COG bounds, center and tileSize in TileJSON
([#2510](#2510))

### Fixed

- logs not being integrated with the `path-prefix` correctly
([#2549](#2549))
- Make sure that `route-prefix` does not break the UI when using
trailing slash urls
([#2541](#2541))

### Other

- *(deps)* Bump the all-npm-version-updates group across 2 directories
with 7 updates ([#2553](#2553))
- Add test coverage for header handling in tilejson requests
([#2529](#2529))
- *(martin-core)* [**breaking**] remove the configration from the
martin-core crate
([#2521](#2521))
- *(deps)* autoupdate pre-commit
([#2545](#2545))
- restrict `unused_trait_names` for trait imports
([#2542](#2542))
- *(deps)* Bump the all-npm-version-updates group across 2 directories
with 12 updates ([#2533](#2533))
</blockquote>


</p></details>

---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants