Skip to content

Commit 1477dc5

Browse files
ericproulxclaude
andauthored
Inline mustermann-grape into Grape::Router::MustermannPattern (#2755)
mustermann-grape is a ~40-line Mustermann::AST::Pattern subclass that exists only for Grape's path syntax, and it had already grown Grape-specific: the Integer constraint reads Grape's own `params` option. Maintaining it as a separate gem meant a two-step release for every syntax tweak (cut a mustermann-grape release, then bump Grape's pin) plus its own CI/versioning. Move the grammar into Grape as Grape::Router::MustermannPattern (attribution to the original authors preserved), depend on `mustermann` directly, and instantiate it directly from Grape::Router::Pattern as Grape always did. The `mustermann` core dependency is unchanged; the graph collapses from grape -> mustermann-grape -> mustermann to grape -> mustermann. The class is no longer registered as a Mustermann `type: :grape` (Grape never used the registry). Apps that called `Mustermann.new(_, type: :grape)` relying on Grape pulling in mustermann-grape transitively should depend on it directly; see UPGRADING. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 18e69cc commit 1477dc5

6 files changed

Lines changed: 59 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* [#2754](https://github.com/ruby-grape/grape/pull/2754): Merge routing args in place in `Router#process_route` instead of allocating a new Hash via `merge` - [@ericproulx](https://github.com/ericproulx).
6060
* [#2753](https://github.com/ruby-grape/grape/pull/2753): Lazy-allocate `Grape::Validations::ParamScopeTracker`'s identity-keyed hashes so validating requests that never use the index / qualifying-params trackers allocate no hash - [@ericproulx](https://github.com/ericproulx).
6161
* [#2752](https://github.com/ruby-grape/grape/pull/2752): Skip per-request `ActiveSupport::Notifications` payload and dispatch when no subscriber is listening, via private `instrument_<event>` guards on `Endpoint`/`Middleware::Formatter` - [@ericproulx](https://github.com/ericproulx).
62+
* [#2755](https://github.com/ruby-grape/grape/pull/2755): Inline `mustermann-grape` into `Grape::Router::MustermannPattern` and depend on `mustermann` directly - [@ericproulx](https://github.com/ericproulx).
6263
* [#2757](https://github.com/ruby-grape/grape/pull/2757): Build the `Grape::Cookies` jar only when a cookie is read or written (via a new `Grape::Request#cookies?` predicate gating response-cookie flushing), and drop the jar's now-redundant lazy-parse `Proc` - [@ericproulx](https://github.com/ericproulx).
6364
* [#2756](https://github.com/ruby-grape/grape/pull/2756): Tighten dependency lower bounds to their compatibility floors (`rack >= 2.2.4`, `zeitwerk >= 2.6`, `dry-configurable >= 1.0`) - [@ericproulx](https://github.com/ericproulx).
6465
* [#2762](https://github.com/ruby-grape/grape/pull/2762): Parse request bodies with `JSON.parse` in the stdlib JSON fallback, dropping the `create_additions: false` wrapper from #2759 (`JSON.parse` never honours `json_class`) - [@ericproulx](https://github.com/ericproulx).

UPGRADING.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ Upgrading Grape
77

88
Grape no longer supports Ruby 3.2; 3.3 is now the minimum (`required_ruby_version = '>= 3.3'`). Upgrade your runtime to Ruby 3.3 or newer before bumping Grape.
99

10+
#### `mustermann-grape` is no longer a dependency
11+
12+
Grape's path-pattern grammar (previously the `mustermann-grape` gem) now lives in Grape itself as `Grape::Router::MustermannPattern`, and Grape depends on `mustermann` directly. This is transparent for normal Grape usage.
13+
14+
The inlined class is no longer registered as a Mustermann type, so if your app called `Mustermann.new(pattern, type: :grape)` and relied on Grape loading `mustermann-grape` for you, add it to your Gemfile explicitly:
15+
16+
```ruby
17+
gem 'mustermann-grape'
18+
```
19+
1020
#### `Grape::Exceptions::ValidationErrors.new` keyword renamed `errors:``exceptions:`
1121

1222
`Grape::Exceptions::ValidationErrors#initialize` now takes its input array under the `exceptions:` keyword instead of `errors:`. The kwarg accepts a mix of `Grape::Exceptions::Validation` and `Grape::Exceptions::ValidationArrayErrors` instances; `ValidationArrayErrors` wrappers are flattened internally via `flat_map(&:errors)`. The `errors` reader on the constructed instance (the grouped `{params => [Validation, ...]}` Hash) is unchanged.

grape.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
2323
s.add_dependency 'activesupport', '>= 7.2'
2424
s.add_dependency 'dry-configurable', '>= 1.0'
2525
s.add_dependency 'dry-types', '>= 1.1'
26-
s.add_dependency 'mustermann-grape', '~> 1.1.0'
26+
s.add_dependency 'mustermann', '>= 4.0'
2727
s.add_dependency 'rack', '>= 2.2.4'
2828
s.add_dependency 'zeitwerk', '>= 2.6'
2929

lib/grape.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
require 'dry-configurable'
2727
require 'forwardable'
2828
require 'json'
29-
require 'mustermann/grape'
29+
require 'mustermann'
30+
require 'mustermann/ast/pattern'
3031
require 'rack'
3132
require 'rack/auth/basic'
3233
require 'rack/builder'
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
class Router
5+
# Grape-style path patterns for Mustermann: `:param`, `*splat`, `{name}` /
6+
# `{+splat}`, `( )` optionals, `|`, and an Integer digit-only constraint
7+
# (driven by Grape's `params` option).
8+
#
9+
# Inlined from the mustermann-grape gem (MIT) by namusyaka, Konstantin Haase
10+
# and Daniel Doubrovkine. Grape instantiates this class directly (see
11+
# {Grape::Router::Pattern}), so unlike the gem it is not registered as a
12+
# Mustermann `type: :grape`.
13+
class MustermannPattern < ::Mustermann::AST::Pattern
14+
supported_options :params
15+
16+
on(nil, '?', ')') { |c| unexpected(c) }
17+
18+
on('*') { |_c| scan(/\w+/) ? node(:named_splat, buffer.matched) : node(:splat) }
19+
on(':') do |_c|
20+
param_name = scan(/\w+/)
21+
# Integer params (declared via Grape's `params` option) match digits only;
22+
# any other capture matches a single path segment (anything but / ? # .).
23+
param_type = pattern&.options&.dig(:params, param_name, :type)
24+
constraint = param_type == 'Integer' ? /\d/ : '[^/?#.]'
25+
node(:capture, param_name, constraint:) { scan(/\w+/) }
26+
end
27+
on('\\') { |_c| node(:char, expect(/./)) }
28+
on('(') { |_c| node(:optional, node(:group) { read unless scan(')') }) }
29+
on('|') { |_c| node(:or) }
30+
31+
on('{') do |_c|
32+
type = scan('+') ? :named_splat : :capture
33+
name = expect(/[\w.]+/)
34+
type = :splat if (type == :named_splat) && (name == 'splat')
35+
expect('}')
36+
node(type, name)
37+
end
38+
39+
suffix('?') do |_c, element|
40+
node(:optional, element)
41+
end
42+
end
43+
end
44+
end

lib/grape/router/pattern.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Pattern
1616
def initialize(origin:, suffix:, anchor:, params:, format:, version:, requirements:)
1717
@origin = origin
1818
@path = PatternCache[[build_path_from_pattern(@origin, anchor), suffix]]
19-
@pattern = Mustermann::Grape.new(@path, uri_decode: true, params:, capture: extract_capture(format, version, requirements))
19+
@pattern = MustermannPattern.new(@path, uri_decode: true, params:, capture: extract_capture(format, version, requirements))
2020
@to_regexp = @pattern.to_regexp
2121
end
2222

0 commit comments

Comments
 (0)