rack-http_router 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/rack-http_router/action.rb +156 -0
- data/lib/rack-http_router/router/build_request.rb +50 -0
- data/lib/rack-http_router/router/route.rb +50 -0
- data/lib/rack-http_router/router.rb +137 -0
- data/lib/rack-http_router.rb +57 -0
- metadata +82 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5575554ab4629e83f2524f4fe47ff58963909b658452a3646457da97c378d744
|
4
|
+
data.tar.gz: a409c6bdd4df80abba1206e79a4adefd05d75142cbdd775e1418de33bee23ea7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 13eac202ecb0c2c1bfc1287298cfa45ae202949e6738c8dec1689e8a674a0d8b5b60438a8d990e1dd77159eb75cfea3a90ddd60ab30376aa6f35e21bbc4f1c51
|
7
|
+
data.tar.gz: 8a43962f554a10aef1696ff990cb5096b1a211f3e8f28199b52b7ad36a6c079ad3bf070f51ad0710aec284fdb0156ff50ae7dfcfb0b6e2951cc7f0c17046de17
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'erubi'
|
4
|
+
require 'json'
|
5
|
+
require 'rack'
|
6
|
+
|
7
|
+
module Rack
|
8
|
+
class HttpRouter
|
9
|
+
module Action
|
10
|
+
def self.included(base)
|
11
|
+
base.class_eval do
|
12
|
+
attr_reader :route if self != Rack::HttpRouter
|
13
|
+
|
14
|
+
def initialize(route)
|
15
|
+
@route = route
|
16
|
+
end
|
17
|
+
|
18
|
+
def view_response(a_path, a_view_params = {}, status: 200)
|
19
|
+
Rack::HttpRouter::Action.view_response(
|
20
|
+
a_path,
|
21
|
+
a_view_params,
|
22
|
+
status: status, route: route
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def view(
|
27
|
+
a_path, a_view_params = {}, status: 200, response_instance: false
|
28
|
+
)
|
29
|
+
Rack::HttpRouter::Action.view(
|
30
|
+
a_path,
|
31
|
+
a_view_params,
|
32
|
+
status: status, response_instance: response_instance, route: route
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def html(content, status: 200)
|
39
|
+
Rack::HttpRouter::Action.html(content, status: status)
|
40
|
+
end
|
41
|
+
|
42
|
+
def html_response(content, status: 200)
|
43
|
+
Rack::HttpRouter::Action.html_response(content, status: status)
|
44
|
+
end
|
45
|
+
|
46
|
+
def json(content = {}, status: 200)
|
47
|
+
Rack::HttpRouter::Action.json(content, status: status)
|
48
|
+
end
|
49
|
+
|
50
|
+
def json_response(content = {}, status: 200)
|
51
|
+
Rack::HttpRouter::Action.json_response(content, status: status)
|
52
|
+
end
|
53
|
+
|
54
|
+
def text(content, status: 200)
|
55
|
+
Rack::HttpRouter::Action.text(content, status: status)
|
56
|
+
end
|
57
|
+
|
58
|
+
def text_response(content, status: 200)
|
59
|
+
Rack::HttpRouter::Action.text_response(content, status: status)
|
60
|
+
end
|
61
|
+
|
62
|
+
def erb(path, view_params = {})
|
63
|
+
Rack::HttpRouter::Action.erb(path, view_params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def redirect_to(url)
|
67
|
+
Rack::HttpRouter::Action.redirect_to(url)
|
68
|
+
end
|
69
|
+
|
70
|
+
def response(body = nil, status = 200, headers = {})
|
71
|
+
Rack::Response.new(body, status, headers)
|
72
|
+
end
|
73
|
+
|
74
|
+
class << self
|
75
|
+
def html(content, status: 200)
|
76
|
+
[status, { 'Content-Type' => 'text/html' }, [content]]
|
77
|
+
end
|
78
|
+
|
79
|
+
def html_response(content, status: 200)
|
80
|
+
Rack::Response.new(content, status, { 'Content-Type' => 'text/html' })
|
81
|
+
end
|
82
|
+
|
83
|
+
def view_response(paths, view_params = {}, status: 200, route: nil)
|
84
|
+
view(
|
85
|
+
paths,
|
86
|
+
view_params,
|
87
|
+
status: status, response_instance: true,
|
88
|
+
route: route
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def view(
|
93
|
+
paths,
|
94
|
+
view_params = {},
|
95
|
+
status: 200,
|
96
|
+
response_instance: false,
|
97
|
+
route: nil
|
98
|
+
)
|
99
|
+
erb = if paths.is_a?(Array)
|
100
|
+
paths.map { |path| erb("views/#{path}", route, view_params) }.join
|
101
|
+
else
|
102
|
+
erb("views/#{paths}", route, view_params)
|
103
|
+
end
|
104
|
+
|
105
|
+
if response_instance
|
106
|
+
return Rack::Response.new(
|
107
|
+
erb,
|
108
|
+
status,
|
109
|
+
{ 'Content-Type' => 'text/html' }
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
[status, { 'Content-Type' => 'text/html' }, [erb]]
|
114
|
+
end
|
115
|
+
|
116
|
+
def json(content = {}, status: 200)
|
117
|
+
[status, { 'Content-Type' => 'application/json' }, [content.to_json]]
|
118
|
+
end
|
119
|
+
|
120
|
+
def json_response(content = {}, status: 200)
|
121
|
+
Rack::Response.new(
|
122
|
+
content.to_json,
|
123
|
+
status,
|
124
|
+
{ 'Content-Type' => 'application/json' }
|
125
|
+
)
|
126
|
+
end
|
127
|
+
|
128
|
+
def text(content, status: 200)
|
129
|
+
[status, { 'Content-Type' => 'text/plain' }, [content]]
|
130
|
+
end
|
131
|
+
|
132
|
+
def text_response(content, status: 200)
|
133
|
+
Rack::Response.new(
|
134
|
+
content,
|
135
|
+
status,
|
136
|
+
{ 'Content-Type' => 'text/plain' }
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
def erb(path, _route, view_params = {})
|
141
|
+
@view = OpenStruct.new(view_params)
|
142
|
+
|
143
|
+
eval(Erubi::Engine.new(::File.read("#{path}.html.erb")).src)
|
144
|
+
end
|
145
|
+
|
146
|
+
def redirect_to(url)
|
147
|
+
[302, { 'Location' => url }, []]
|
148
|
+
end
|
149
|
+
|
150
|
+
def response(body = nil, status = 200, headers = {})
|
151
|
+
Rack::Response.new(body, status, headers)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class HttpRouter
|
5
|
+
class Router
|
6
|
+
class BuildRequest
|
7
|
+
def initialize(env)
|
8
|
+
@env = env
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(route = nil)
|
12
|
+
request = Rack::Request.new(@env)
|
13
|
+
|
14
|
+
return request if route.nil?
|
15
|
+
return request unless route.has_params
|
16
|
+
|
17
|
+
update_request_params(request, route)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def update_request_params(request, route)
|
23
|
+
splitted_request_path = request.path.split('/')
|
24
|
+
|
25
|
+
route
|
26
|
+
.splitted_path
|
27
|
+
.each
|
28
|
+
.with_index do |route_word, route_word_position|
|
29
|
+
if route_word.start_with?(':')
|
30
|
+
param = splitted_request_path[route_word_position]
|
31
|
+
param = param.to_i if is_a_integer_string?(param)
|
32
|
+
|
33
|
+
update_request_param(request, route_word, param)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
request
|
38
|
+
end
|
39
|
+
|
40
|
+
def is_a_integer_string?(string)
|
41
|
+
string =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/
|
42
|
+
end
|
43
|
+
|
44
|
+
def update_request_param(request, word, param)
|
45
|
+
request.update_param(word.sub(':', '').to_sym, param)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
class HttpRouter
|
5
|
+
class Router
|
6
|
+
class Route
|
7
|
+
attr_reader :endpoint, :splitted_path, :has_params
|
8
|
+
|
9
|
+
def initialize(path, endpoint)
|
10
|
+
@path = path
|
11
|
+
@splitted_path = @path.split('/')
|
12
|
+
@endpoint = endpoint
|
13
|
+
@params = fetch_params
|
14
|
+
@has_params = @params != []
|
15
|
+
end
|
16
|
+
|
17
|
+
def match?(env)
|
18
|
+
return match_with_params?(env) if @has_params
|
19
|
+
|
20
|
+
env['REQUEST_PATH'] == @path
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def fetch_params
|
26
|
+
@splitted_path.select { |value| value.start_with? ':' }
|
27
|
+
end
|
28
|
+
|
29
|
+
def match_with_params?(env)
|
30
|
+
splitted_request_path = env['REQUEST_PATH'].split('/')
|
31
|
+
|
32
|
+
return false if @splitted_path.size != splitted_request_path.size
|
33
|
+
|
34
|
+
matched_path_pieces =
|
35
|
+
@splitted_path
|
36
|
+
.map
|
37
|
+
.with_index do |segment, i|
|
38
|
+
if segment.start_with?(':')
|
39
|
+
true
|
40
|
+
else
|
41
|
+
splitted_request_path[i] == segment
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
!matched_path_pieces.include?(false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'router/route'
|
4
|
+
require_relative 'router/build_request'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
class HttpRouter
|
8
|
+
class Router
|
9
|
+
class UndefinedNamedRoute < StandardError; end
|
10
|
+
|
11
|
+
attr_writer :not_found
|
12
|
+
attr_reader :route
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@routes = {}
|
16
|
+
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |method|
|
17
|
+
@routes[method] = { __instances: [] }
|
18
|
+
end
|
19
|
+
@route = Hash.new do |_hash, key|
|
20
|
+
raise(UndefinedNamedRoute, "Undefined named route: '#{key}'")
|
21
|
+
end
|
22
|
+
@scopes = []
|
23
|
+
@error = proc { |_req, e| raise e }
|
24
|
+
@not_found = proc { [404, {}, ['Not found']] }
|
25
|
+
end
|
26
|
+
|
27
|
+
def call(env)
|
28
|
+
request_builder = BuildRequest.new(env)
|
29
|
+
env['REQUEST_METHOD'] = 'GET' if env['REQUEST_METHOD'] == 'HEAD'
|
30
|
+
|
31
|
+
route_instance = match_route(env)
|
32
|
+
|
33
|
+
return render_not_found(request_builder.call) if route_instance.nil?
|
34
|
+
|
35
|
+
if route_instance.endpoint.respond_to?(:call)
|
36
|
+
return route_instance.endpoint.call(request_builder.call(route_instance))
|
37
|
+
end
|
38
|
+
|
39
|
+
if route_instance.endpoint.include?(Rack::HttpRouter::Action)
|
40
|
+
return route_instance.endpoint.new(@route).call(request_builder.call(route_instance))
|
41
|
+
end
|
42
|
+
|
43
|
+
route_instance.endpoint.new.call(request_builder.call(route_instance))
|
44
|
+
rescue Exception => e
|
45
|
+
@error.call(request_builder.call, e)
|
46
|
+
end
|
47
|
+
|
48
|
+
def add(method, path, endpoint, as = nil)
|
49
|
+
method = :get if method == :head
|
50
|
+
|
51
|
+
path_with_scopes = "/#{@scopes.join('/')}#{put_path_slash(path)}"
|
52
|
+
@route[as] = path_with_scopes if as
|
53
|
+
|
54
|
+
route_instance = Route.new(path_with_scopes, endpoint)
|
55
|
+
|
56
|
+
return push_to_scope(method.to_s.upcase, route_instance) if @scopes.size >= 1
|
57
|
+
|
58
|
+
@routes[method.to_s.upcase][:__instances].push(route_instance)
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_not_found(endpoint)
|
62
|
+
@not_found = endpoint
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_error(endpoint)
|
66
|
+
@error = endpoint
|
67
|
+
end
|
68
|
+
|
69
|
+
def append_scope(name)
|
70
|
+
@scopes.push(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def clear_last_scope
|
74
|
+
@scopes = @scopes.first(@scopes.size - 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def push_to_scope(method, route_instance)
|
80
|
+
scopes_with_slash = @scopes + [:__instances]
|
81
|
+
push_it(@routes[method], *scopes_with_slash, route_instance)
|
82
|
+
end
|
83
|
+
|
84
|
+
def push_it(h, first_key, *rest_keys, val)
|
85
|
+
if rest_keys.empty?
|
86
|
+
(h[first_key] ||= []) << val
|
87
|
+
else
|
88
|
+
h[first_key] = push_it(h[first_key] ||= {}, *rest_keys, val)
|
89
|
+
end
|
90
|
+
h
|
91
|
+
end
|
92
|
+
|
93
|
+
def put_path_slash(path)
|
94
|
+
return '' if ['/', ''].include?(path) && @scopes != []
|
95
|
+
return "/#{path}" if @scopes != []
|
96
|
+
|
97
|
+
path
|
98
|
+
end
|
99
|
+
|
100
|
+
def render_not_found(env)
|
101
|
+
return @not_found.call(env) if @not_found.respond_to?(:call)
|
102
|
+
|
103
|
+
@not_found.new.call(env)
|
104
|
+
end
|
105
|
+
|
106
|
+
def match_route(env, last_tail = nil, found_scopes = [])
|
107
|
+
routes =
|
108
|
+
if last_tail.nil?
|
109
|
+
last_tail = env['REQUEST_PATH'].split('/').drop(1)
|
110
|
+
|
111
|
+
@routes[env['REQUEST_METHOD']]
|
112
|
+
else
|
113
|
+
@routes[env['REQUEST_METHOD']].dig(*found_scopes)
|
114
|
+
end
|
115
|
+
|
116
|
+
segment, *tail = last_tail
|
117
|
+
|
118
|
+
routes.each do |scope, _v|
|
119
|
+
next if scope == :__instances
|
120
|
+
|
121
|
+
if segment == scope || scope.start_with?(':')
|
122
|
+
found_scopes.push(scope)
|
123
|
+
break
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if tail.empty? || found_scopes == []
|
128
|
+
return @routes[env['REQUEST_METHOD']].dig(*(found_scopes << :__instances)).detect do |route_instance|
|
129
|
+
route_instance.match?(env)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
match_route(env, tail, found_scopes)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rack-http_router/router'
|
4
|
+
require_relative 'rack-http_router/action'
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
class HttpRouter
|
8
|
+
include Action
|
9
|
+
|
10
|
+
def initialize(router: Router.new)
|
11
|
+
@router = router
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(&block)
|
15
|
+
instance_eval(&block)
|
16
|
+
|
17
|
+
@router
|
18
|
+
end
|
19
|
+
|
20
|
+
def route
|
21
|
+
@router.route
|
22
|
+
end
|
23
|
+
|
24
|
+
def scope(name, &block)
|
25
|
+
@router.append_scope(name)
|
26
|
+
instance_eval(&block)
|
27
|
+
|
28
|
+
@router.clear_last_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def not_found(endpoint = -> {}, &block)
|
32
|
+
if block_given?
|
33
|
+
@router.add_not_found(block)
|
34
|
+
else
|
35
|
+
@router.add_not_found(endpoint)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def error(endpoint = -> {}, &block)
|
40
|
+
if block_given?
|
41
|
+
@router.add_error(block)
|
42
|
+
else
|
43
|
+
@router.add_error(endpoint)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
%w[GET POST DELETE PUT TRACE OPTIONS PATCH].each do |http_method|
|
48
|
+
define_method(http_method.downcase.to_sym) do |path = '', endpoint = -> {}, as: nil, &block|
|
49
|
+
if block.respond_to?(:call)
|
50
|
+
@router.add(http_method, path, block, as)
|
51
|
+
else
|
52
|
+
@router.add(http_method, path, endpoint, as)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-http_router
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Henrique F. Teixeira
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: erubi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '4.0'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '4.0'
|
47
|
+
description: A complete http router solution that fit well with pure rack apps
|
48
|
+
email: hriqueft@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- lib/rack-http_router.rb
|
54
|
+
- lib/rack-http_router/action.rb
|
55
|
+
- lib/rack-http_router/router.rb
|
56
|
+
- lib/rack-http_router/router/build_request.rb
|
57
|
+
- lib/rack-http_router/router/route.rb
|
58
|
+
homepage: https://github.com/henriquefernandez/rack-http_router
|
59
|
+
licenses:
|
60
|
+
- MIT
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubygems_version: 3.4.3
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: '"rack-http_router" come with a router and helper functions to build pure
|
81
|
+
Rack projects.'
|
82
|
+
test_files: []
|