From 702aea489eadd4c871d1d1d793bdb07e37bb7883 Mon Sep 17 00:00:00 2001 From: Michael Oberegger Date: Tue, 31 Mar 2026 20:53:15 -0400 Subject: [PATCH 1/5] Optimize Router#find_path_item --- lib/openapi_first/router.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/openapi_first/router.rb b/lib/openapi_first/router.rb index 5f7189fc..d31de3e1 100644 --- a/lib/openapi_first/router.rb +++ b/lib/openapi_first/router.rb @@ -97,15 +97,15 @@ def find_path_item(request_path) found = @static[request_path] return [found, {}] if found - matches = @dynamic.filter_map do |_path, path_item| + @dynamic.each_value.reduce(nil) do |best, path_item| params = path_item[:template].match(request_path) - next unless params + next best unless params - [path_item, params] - end - return matches.first if matches.length == 1 + candidate = [path_item, params] + next candidate unless best - matches&.min_by { |match| match[1].values.sum(&:length) } + params.values.sum(&:length) < best[1].values.sum(&:length) ? candidate : best + end end end end From ae90f2981ce93fadc43d1557138fdf45f709b349 Mon Sep 17 00:00:00 2001 From: Michael Oberegger Date: Tue, 31 Mar 2026 21:21:53 -0400 Subject: [PATCH 2/5] Optimize PathTemplate#match --- lib/openapi_first/router/path_template.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/openapi_first/router/path_template.rb b/lib/openapi_first/router/path_template.rb index 2a730853..41a99037 100644 --- a/lib/openapi_first/router/path_template.rb +++ b/lib/openapi_first/router/path_template.rb @@ -30,15 +30,18 @@ def match(path) matches = path.match(@pattern) return unless matches - values = matches.captures - @names.zip(values).to_h + matches.named_captures end private def build_pattern(template) parts = template.split(TEMPLATE_EXPRESSION).map! do |part| - part.start_with?('{') ? ALLOWED_PARAMETER_CHARACTERS : Regexp.escape(part) + if part.start_with?('{') + "(?<#{part[1..-2]}>[^/?#]+)" + else + Regexp.escape(part) + end end /^#{parts.join}$/ From 1a86f55666bbcfe23d0e5c2a67830e824c69d518 Mon Sep 17 00:00:00 2001 From: Michael Oberegger Date: Tue, 31 Mar 2026 21:23:18 -0400 Subject: [PATCH 3/5] Cleanup PathTemplate --- lib/openapi_first/router/path_template.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/openapi_first/router/path_template.rb b/lib/openapi_first/router/path_template.rb index 41a99037..783b3057 100644 --- a/lib/openapi_first/router/path_template.rb +++ b/lib/openapi_first/router/path_template.rb @@ -6,8 +6,6 @@ class Router class PathTemplate # See also https://spec.openapis.org/oas/v3.1.0#path-templating TEMPLATE_EXPRESSION = /(\{[^{}]+\})/ - TEMPLATE_EXPRESSION_NAME = /\{([^{}]+)\}/ - ALLOWED_PARAMETER_CHARACTERS = %r{([^/?#]+)} def self.template?(string) string.include?('{') @@ -15,7 +13,6 @@ def self.template?(string) def initialize(template) @template = template - @names = template.scan(TEMPLATE_EXPRESSION_NAME).flatten @pattern = build_pattern(template) end @@ -25,7 +22,6 @@ def to_s def match(path) return {} if path == @template - return if @names.empty? matches = path.match(@pattern) return unless matches From c468e6f357c7c36aba5d06cd0fb5494ba6ebfd7c Mon Sep 17 00:00:00 2001 From: Michael Oberegger Date: Tue, 31 Mar 2026 22:19:46 -0400 Subject: [PATCH 4/5] Optimize FindContent#call --- lib/openapi_first/router/find_content.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/openapi_first/router/find_content.rb b/lib/openapi_first/router/find_content.rb index 04ab42fb..0349dfa9 100644 --- a/lib/openapi_first/router/find_content.rb +++ b/lib/openapi_first/router/find_content.rb @@ -8,8 +8,10 @@ def self.call(contents, content_type) return contents[nil] if content_type.nil? || content_type.empty? contents.fetch(content_type) do - type = content_type.split(';')[0] - contents[type] || contents["#{type.split('/')[0]}/*"] || contents['*/*'] || contents[nil] + semi = content_type.index(';') + type = semi ? content_type[0, semi] : content_type + slash = type.index('/') || type.length + contents[type] || contents["#{type[0, slash]}/*"] || contents['*/*'] || contents[nil] end end end From c1ba08bfc6411b9f564ade6908a4038bf332fb23 Mon Sep 17 00:00:00 2001 From: Michael Oberegger Date: Wed, 1 Apr 2026 16:01:40 -0400 Subject: [PATCH 5/5] Add changlog for faster routing --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 822629a4..2f47754d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ ## Unreleased - OpenapiFirst will now cache the contents of files that have been loaded. If you need to reload your OpenAPI definition for tests or server hot reloading, you can call `OpenapiFirst.clear_cache!`. - +- Optimized `OpenapiFirst::Router#match` for faster path matching and reduced memory allocation. +- ## 3.2.1 - Don't raise `UnknownQueryParameterError` if request is ignored in tests. Fixes [#441](https://github.com/ahx/openapi_first/issues/441).