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 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: []