macaw_framework 1.3.0 → 1.3.3

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.
@@ -1,23 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../errors/endpoint_not_mapped_error"
3
+ require_relative '../errors/endpoint_not_mapped_error'
4
4
 
5
5
  ##
6
6
  # Module containing methods to filter Strings
7
7
  module RequestDataFiltering
8
- VARIABLE_PATTERN = %r{:[^/]+}.freeze
8
+ VARIABLE_PATTERN = %r{:[^/]+}
9
9
 
10
10
  ##
11
11
  # Method responsible for extracting information
12
12
  # provided by the client like Headers and Body
13
13
  def self.parse_request_data(client, routes)
14
- path, parameters = extract_url_parameters(client.gets.gsub("HTTP/1.1", ""))
14
+ path, parameters = extract_url_parameters(client.gets&.gsub('HTTP/1.1', ''))
15
15
  parameters = {} if parameters.nil?
16
16
 
17
17
  method_name = sanitize_method_name(path)
18
18
  method_name = select_path(method_name, routes, parameters)
19
19
  body_first_line, headers = extract_headers(client)
20
- body = extract_body(client, body_first_line, headers["Content-Length"].to_i)
20
+ body = extract_body(client, body_first_line, headers['Content-Length'].to_i)
21
21
  [path, method_name, headers, body, parameters]
22
22
  end
23
23
 
@@ -26,15 +26,15 @@ module RequestDataFiltering
26
26
 
27
27
  selected_route = nil
28
28
  routes.each do |route|
29
- split_route = route.split(".")
30
- split_name = method_name.split(".")
29
+ split_route = route&.split('.')
30
+ split_name = method_name&.split('.')
31
31
 
32
- next unless split_route.length == split_name.length
32
+ next unless split_route&.length == split_name&.length
33
33
  next unless match_path_with_route(split_name, split_route)
34
34
 
35
35
  selected_route = route
36
- split_route.each_with_index do |var, index|
37
- parameters[var[1..].to_sym] = split_name[index] if var =~ VARIABLE_PATTERN
36
+ split_route&.each_with_index do |var, index|
37
+ parameters[var[1..].to_sym] = split_name&.dig(index) if var =~ VARIABLE_PATTERN
38
38
  end
39
39
  break
40
40
  end
@@ -45,7 +45,7 @@ module RequestDataFiltering
45
45
  end
46
46
 
47
47
  def self.match_path_with_route(split_path, split_route)
48
- split_route.each_with_index do |var, index|
48
+ split_route&.each_with_index do |var, index|
49
49
  return false if var != split_path[index] && !var.match?(VARIABLE_PATTERN)
50
50
  end
51
51
 
@@ -56,26 +56,28 @@ module RequestDataFiltering
56
56
  # Method responsible for sanitizing the method name
57
57
  def self.sanitize_method_name(path)
58
58
  path = extract_path(path)
59
- method_name = path.gsub("/", ".").strip.downcase
60
- method_name.gsub!(" ", "")
59
+ method_name = path&.gsub('/', '.')&.strip&.downcase
60
+ method_name&.gsub!(' ', '')
61
61
  method_name
62
62
  end
63
63
 
64
64
  ##
65
65
  # Method responsible for extracting the path from URI
66
66
  def self.extract_path(path)
67
- path[0] == "/" ? path[1..].gsub("/", ".") : path.gsub("/", ".")
67
+ return path if path.nil?
68
+
69
+ path[0] == '/' ? path[1..].gsub('/', '.') : path.gsub('/', '.')
68
70
  end
69
71
 
70
72
  ##
71
73
  # Method responsible for extracting the headers from request
72
74
  def self.extract_headers(client)
73
- header = client.gets.delete("\n").delete("\r")
75
+ header = client.gets&.delete("\n")&.delete("\r")
74
76
  headers = {}
75
- while header.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
76
- split_header = header.split(":")
77
+ while header&.match(%r{[a-zA-Z0-9\-/*]*: [a-zA-Z0-9\-/*]})
78
+ split_header = header.split(':')
77
79
  headers[split_header[0].strip] = split_header[1].strip
78
- header = client.gets.delete("\n").delete("\r")
80
+ header = client.gets&.delete("\n")&.delete("\r")
79
81
  end
80
82
  [header, headers]
81
83
  end
@@ -83,7 +85,7 @@ module RequestDataFiltering
83
85
  ##
84
86
  # Method responsible for extracting the body from request
85
87
  def self.extract_body(client, body_first_line, content_length)
86
- body = client.read(content_length)
88
+ body = client&.read(content_length)
87
89
  body_first_line << body.to_s
88
90
  end
89
91
 
@@ -92,11 +94,11 @@ module RequestDataFiltering
92
94
  def self.extract_url_parameters(http_first_line)
93
95
  return http_first_line, nil unless http_first_line =~ /\?/
94
96
 
95
- path_and_parameters = http_first_line.split("?", 2)
97
+ path_and_parameters = http_first_line.split('?', 2)
96
98
  path = "#{path_and_parameters[0]} "
97
- parameters_array = path_and_parameters[1].split("&")
99
+ parameters_array = path_and_parameters[1].split('&')
98
100
  parameters_array.map! do |item|
99
- split_item = item.split("=")
101
+ split_item = item.split('=')
100
102
  { sanitize_parameter_name(split_item[0]) => sanitize_parameter_value(split_item[1]) }
101
103
  end
102
104
  parameters = {}
@@ -107,13 +109,13 @@ module RequestDataFiltering
107
109
  ##
108
110
  # Method responsible for sanitizing the parameter name
109
111
  def self.sanitize_parameter_name(name)
110
- name.gsub(/[^\w\s]/, "")
112
+ name&.gsub(/[^\w\s]/, '')
111
113
  end
112
114
 
113
115
  ##
114
116
  # Method responsible for sanitizing the parameter value
115
117
  def self.sanitize_parameter_value(value)
116
- value.gsub(/[^\w\s]/, "")
117
- value.gsub(/\s/, "")
118
+ value&.gsub(/[^\w\s]/, '')
119
+ value&.gsub(/\s/, '')
118
120
  end
119
121
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../utils/http_status_code"
3
+ require_relative '../utils/http_status_code'
4
4
 
5
5
  ##
6
6
  # Module responsible to filter and mount HTTP responses
@@ -19,9 +19,9 @@ module ResponseDataFilter
19
19
  end
20
20
 
21
21
  def self.mount_response_headers(headers)
22
- return "" if headers.nil?
22
+ return '' if headers.nil?
23
23
 
24
- response = ""
24
+ response = ''
25
25
  headers.each do |key, value|
26
26
  response += "#{key}: #{value}\r\n"
27
27
  end
@@ -4,7 +4,7 @@
4
4
  # Error raised when the client calls
5
5
  # for a path that doesn't exist.
6
6
  class EndpointNotMappedError < StandardError
7
- def initialize(msg = "Undefined endpoint")
7
+ def initialize(msg = 'Undefined endpoint')
8
8
  super
9
9
  end
10
10
  end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/endpoint_not_mapped_error'
4
+ require_relative 'middlewares/prometheus_middleware'
5
+ require_relative 'data_filters/request_data_filtering'
6
+ require_relative 'middlewares/memory_invalidation_middleware'
7
+ require_relative 'core/cron_runner'
8
+ require_relative 'core/thread_server'
9
+ require_relative 'version'
10
+ require 'prometheus/client'
11
+ require 'securerandom'
12
+ require 'singleton'
13
+ require 'pathname'
14
+ require 'logger'
15
+ require 'socket'
16
+ require 'json'
17
+
18
+ ##
19
+ # Main module for all Macaw classes
20
+ module MacawFramework; end
21
+
22
+ ##
23
+ # Class responsible for creating endpoints and
24
+ # starting the web server.
25
+ class MacawFramework::Macaw
26
+ attr_reader :routes, :macaw_log, :config, :jobs, :cached_methods, :secure_header, :session
27
+ attr_accessor :port, :bind, :threads
28
+
29
+ ##
30
+ # Initialize Macaw Class
31
+ # @param {Logger} custom_log
32
+ # @param {ThreadServer} server
33
+ # @param {String?} dir
34
+ def initialize(custom_log: Logger.new($stdout), server: ThreadServer, dir: nil)
35
+ apply_options(custom_log)
36
+ create_endpoint_public_files(dir)
37
+ setup_default_configs
38
+ @server_class = server
39
+ end
40
+
41
+ ##
42
+ # Creates a GET endpoint associated
43
+ # with the respective path.
44
+ # @param {String} path
45
+ # @param {Proc} block
46
+ #
47
+ # @example
48
+ # macaw = MacawFramework::Macaw.new
49
+ # macaw.get("/hello") do |context|
50
+ # return "Hello World!", 200, { "Content-Type" => "text/plain" }
51
+ # end
52
+ ##
53
+ def get(path, cache: [], &block)
54
+ map_new_endpoint('get', cache, path, &block)
55
+ end
56
+
57
+ ##
58
+ # Creates a POST endpoint associated
59
+ # with the respective path.
60
+ # @param {String} path
61
+ # @param {Boolean} cache
62
+ # @param {Proc} block
63
+ # @example
64
+ #
65
+ # macaw = MacawFramework::Macaw.new
66
+ # macaw.post("/hello") do |context|
67
+ # return "Hello World!", 200, { "Content-Type" => "text/plain" }
68
+ # end
69
+ ##
70
+ def post(path, cache: [], &block)
71
+ map_new_endpoint('post', cache, path, &block)
72
+ end
73
+
74
+ ##
75
+ # Creates a PUT endpoint associated
76
+ # with the respective path.
77
+ # @param {String} path
78
+ # @param {Proc} block
79
+ # @example
80
+ #
81
+ # macaw = MacawFramework::Macaw.new
82
+ # macaw.put("/hello") do |context|
83
+ # return "Hello World!", 200, { "Content-Type" => "text/plain" }
84
+ # end
85
+ ##
86
+ def put(path, cache: [], &block)
87
+ map_new_endpoint('put', cache, path, &block)
88
+ end
89
+
90
+ ##
91
+ # Creates a PATCH endpoint associated
92
+ # with the respective path.
93
+ # @param {String} path
94
+ # @param {Proc} block
95
+ # @example
96
+ #
97
+ # macaw = MacawFramework::Macaw.new
98
+ # macaw.patch("/hello") do |context|
99
+ # return "Hello World!", 200, { "Content-Type" => "text/plain" }
100
+ # end
101
+ ##
102
+ def patch(path, cache: [], &block)
103
+ map_new_endpoint('patch', cache, path, &block)
104
+ end
105
+
106
+ ##
107
+ # Creates a DELETE endpoint associated
108
+ # with the respective path.
109
+ # @param {String} path
110
+ # @param {Proc} block
111
+ # @example
112
+ #
113
+ # macaw = MacawFramework::Macaw.new
114
+ # macaw.delete("/hello") do |context|
115
+ # return "Hello World!", 200, { "Content-Type" => "text/plain" }
116
+ # end
117
+ ##
118
+ def delete(path, cache: [], &block)
119
+ map_new_endpoint('delete', cache, path, &block)
120
+ end
121
+
122
+ ##
123
+ # Spawn and start a thread running the defined periodic job.
124
+ # @param {Integer} interval
125
+ # @param {Integer?} start_delay
126
+ # @param {String} job_name
127
+ # @param {Proc} block
128
+ # @example
129
+ #
130
+ # macaw = MacawFramework::Macaw.new
131
+ # macaw.setup_job(interval: 60, start_delay: 60, job_name: "job 1") do
132
+ # puts "I'm a periodic job that runs every minute"
133
+ # end
134
+ ##
135
+ def setup_job(interval: 60, start_delay: 0, job_name: "job_#{SecureRandom.uuid}", &block)
136
+ @cron_runner ||= CronRunner.new(self)
137
+ @jobs ||= []
138
+ @cron_runner.start_cron_job_thread(interval, start_delay, job_name, &block)
139
+ @jobs << job_name
140
+ end
141
+
142
+ ##
143
+ # Starts the web server
144
+ def start!
145
+ if @macaw_log.nil?
146
+ puts('---------------------------------')
147
+ puts("Starting server at port #{@port}")
148
+ puts("Number of threads: #{@threads}")
149
+ puts('---------------------------------')
150
+ else
151
+ @macaw_log.info('---------------------------------')
152
+ @macaw_log.info("Starting server at port #{@port}")
153
+ @macaw_log.info("Number of threads: #{@threads}")
154
+ @macaw_log.info('---------------------------------')
155
+ end
156
+ @server = @server_class.new(self, @endpoints_to_cache, @cache, @prometheus, @prometheus_middleware)
157
+ server_loop(@server)
158
+ rescue Interrupt
159
+ if @macaw_log.nil?
160
+ puts('Stopping server')
161
+ @server.shutdown
162
+ puts('Macaw stop flying for some seeds...')
163
+ else
164
+ @macaw_log.info('Stopping server')
165
+ @server.shutdown
166
+ @macaw_log.info('Macaw stop flying for some seeds...')
167
+ end
168
+ end
169
+
170
+ ##
171
+ # This method is intended to start the framework
172
+ # without an web server. This can be useful when
173
+ # you just want to keep cron jobs running, without
174
+ # mapping any HTTP endpoints.
175
+ def start_without_server!
176
+ @macaw_log.nil? ? puts('Application starting') : @macaw_log.info('Application starting')
177
+ loop { sleep(3600) }
178
+ rescue Interrupt
179
+ @macaw_log.nil? ? puts('Macaw stop flying for some seeds.') : @macaw_log.info('Macaw stop flying for some seeds.')
180
+ end
181
+
182
+ private
183
+
184
+ def setup_default_configs
185
+ @port ||= 8080
186
+ @bind ||= 'localhost'
187
+ @config ||= nil
188
+ @threads ||= 200
189
+ @endpoints_to_cache = []
190
+ @prometheus ||= nil
191
+ @prometheus_middleware ||= nil
192
+ end
193
+
194
+ def apply_options(custom_log)
195
+ setup_basic_config(custom_log)
196
+ setup_session
197
+ setup_cache
198
+ setup_prometheus
199
+ rescue StandardError => e
200
+ @macaw_log&.warn(e.message)
201
+ end
202
+
203
+ def setup_cache
204
+ return if @config['macaw']['cache'].nil?
205
+
206
+ @cache = MemoryInvalidationMiddleware.new(@config['macaw']['cache']['cache_invalidation'].to_i || 3_600)
207
+ end
208
+
209
+ def setup_session
210
+ @session = false
211
+ return if @config['macaw']['session'].nil?
212
+
213
+ @session = true
214
+ @secure_header = @config['macaw']['session']['secure_header'] || 'X-Session-ID'
215
+ end
216
+
217
+ def setup_basic_config(custom_log)
218
+ @routes = []
219
+ @cached_methods = {}
220
+ @macaw_log ||= custom_log
221
+ @config = JSON.parse(File.read('application.json'))
222
+ @port = @config['macaw']['port'] || 8080
223
+ @bind = @config['macaw']['bind'] || 'localhost'
224
+ @threads = @config['macaw']['threads'] || 200
225
+ end
226
+
227
+ def setup_prometheus
228
+ return unless @config['macaw']['prometheus']
229
+
230
+ @prometheus = Prometheus::Client::Registry.new
231
+ @prometheus_middleware = PrometheusMiddleware.new
232
+ @prometheus_middleware&.configure_prometheus(@prometheus, @config, self)
233
+ end
234
+
235
+ def server_loop(server)
236
+ server.run
237
+ end
238
+
239
+ def map_new_endpoint(prefix, cache, path, &block)
240
+ @endpoints_to_cache << "#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}" unless cache.empty?
241
+ @cached_methods["#{prefix}.#{RequestDataFiltering.sanitize_method_name(path)}"] = cache unless cache.empty?
242
+ path_clean = RequestDataFiltering.extract_path(path)
243
+ slash = path[0] == '/' ? '' : '/'
244
+ @macaw_log&.info("Defining #{prefix.upcase} endpoint at #{slash}#{path}")
245
+ define_singleton_method("#{prefix}.#{path_clean}", block || lambda {
246
+ |context = { headers: {}, body: '', params: {} }|
247
+ })
248
+ @routes << "#{prefix}.#{path_clean}"
249
+ end
250
+
251
+ def get_files_public_folder(dir)
252
+ return [] if dir.nil?
253
+
254
+ folder_path = Pathname.new(File.expand_path('public', dir))
255
+ file_paths = folder_path.glob('**/*').select(&:file?)
256
+ file_paths.map { |path| "public/#{path.relative_path_from(folder_path)}" }
257
+ end
258
+
259
+ def create_endpoint_public_files(dir)
260
+ get_files_public_folder(dir).each do |file|
261
+ get(file) { |_context| return File.read(file).to_s, 200, {} }
262
+ end
263
+ end
264
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "prometheus/client"
4
- require "prometheus/client/formats/text"
3
+ require 'prometheus/client'
4
+ require 'prometheus/client/formats/text'
5
5
 
6
6
  ##
7
7
  # Middleware responsible to configure prometheus
@@ -14,20 +14,20 @@ class PrometheusMiddleware
14
14
 
15
15
  @request_duration_milliseconds = Prometheus::Client::Histogram.new(
16
16
  :request_duration_milliseconds,
17
- docstring: "The duration of each request in milliseconds",
17
+ docstring: 'The duration of each request in milliseconds',
18
18
  labels: [:endpoint],
19
19
  buckets: (100..1000).step(100).to_a + (2000..10_000).step(1000).to_a
20
20
  )
21
21
 
22
22
  @request_count = Prometheus::Client::Counter.new(
23
23
  :request_count,
24
- docstring: "The total number of requests received",
24
+ docstring: 'The total number of requests received',
25
25
  labels: [:endpoint]
26
26
  )
27
27
 
28
28
  @response_count = Prometheus::Client::Counter.new(
29
29
  :response_count,
30
- docstring: "The total number of responses sent",
30
+ docstring: 'The total number of responses sent',
31
31
  labels: %i[endpoint status]
32
32
  )
33
33
 
@@ -40,9 +40,9 @@ class PrometheusMiddleware
40
40
  private
41
41
 
42
42
  def prometheus_endpoint(prometheus_registry, configurations, macaw)
43
- endpoint = configurations["macaw"]["prometheus"]["endpoint"] || "/metrics"
43
+ endpoint = configurations['macaw']['prometheus']['endpoint'] || '/metrics'
44
44
  macaw.get(endpoint) do |_context|
45
- [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200, { "Content-Type" => "plaintext" }]
45
+ [Prometheus::Client::Formats::Text.marshal(prometheus_registry), 200, { 'Content-Type' => 'plaintext' }]
46
46
  end
47
47
  end
48
48
  end
@@ -7,66 +7,66 @@ module HttpStatusCode
7
7
  ##
8
8
  # Http Status Code Map
9
9
  HTTP_STATUS_CODE_MAP = {
10
- 100 => "Continue",
11
- 101 => "Switching Protocols",
12
- 102 => "Processing",
13
- 103 => "Early Hints",
14
- 200 => "OK",
15
- 201 => "Created",
16
- 202 => "Accepted",
17
- 203 => "Non-Authoritative Information",
18
- 204 => "No Content",
19
- 205 => "Reset Content",
20
- 206 => "Partial Content",
21
- 207 => "Multi-Status",
22
- 208 => "Already Reported",
23
- 226 => "IM Used",
24
- 300 => "Multiple Choices",
25
- 301 => "Moved Permanently",
26
- 302 => "Found",
27
- 303 => "See Other",
28
- 304 => "Not Modified",
29
- 305 => "Use Proxy",
30
- 307 => "Temporary Redirect",
31
- 308 => "Permanent Redirect",
32
- 400 => "Bad Request",
33
- 401 => "Unauthorized",
34
- 402 => "Payment Required",
35
- 403 => "Forbidden",
36
- 404 => "Not Found",
37
- 405 => "Method Not Allowed",
38
- 406 => "Not Acceptable",
39
- 407 => "Proxy Authentication Required",
40
- 408 => "Request Timeout",
41
- 409 => "Conflict",
42
- 410 => "Gone",
43
- 411 => "Length Required",
44
- 412 => "Precondition Failed",
45
- 413 => "Content Too Large",
46
- 414 => "URI Too Long",
47
- 415 => "Unsupported Media Type",
48
- 416 => "Range Not Satisfiable",
49
- 417 => "Expectation Failed",
50
- 421 => "Misdirected Request",
51
- 422 => "Unprocessable Content",
52
- 423 => "Locked",
53
- 424 => "Failed Dependency",
54
- 425 => "Too Early",
55
- 426 => "Upgrade Required",
56
- 428 => "Precondition Required",
57
- 429 => "Too Many Requests",
58
- 431 => "Request Header Fields Too Large",
59
- 451 => "Unavailable For Legal Reasons",
60
- 500 => "Internal Server Error",
61
- 501 => "Not Implemented",
62
- 502 => "Bad Gateway",
63
- 503 => "Service Unavailable",
64
- 504 => "Gateway Timeout",
65
- 505 => "HTTP Version Not Supported",
66
- 506 => "Variant Also Negotiates",
67
- 507 => "Insufficient Storage",
68
- 508 => "Loop Detected",
69
- 510 => "Not Extended (OBSOLETED)",
70
- 511 => "Network Authentication Required"
10
+ 100 => 'Continue',
11
+ 101 => 'Switching Protocols',
12
+ 102 => 'Processing',
13
+ 103 => 'Early Hints',
14
+ 200 => 'OK',
15
+ 201 => 'Created',
16
+ 202 => 'Accepted',
17
+ 203 => 'Non-Authoritative Information',
18
+ 204 => 'No Content',
19
+ 205 => 'Reset Content',
20
+ 206 => 'Partial Content',
21
+ 207 => 'Multi-Status',
22
+ 208 => 'Already Reported',
23
+ 226 => 'IM Used',
24
+ 300 => 'Multiple Choices',
25
+ 301 => 'Moved Permanently',
26
+ 302 => 'Found',
27
+ 303 => 'See Other',
28
+ 304 => 'Not Modified',
29
+ 305 => 'Use Proxy',
30
+ 307 => 'Temporary Redirect',
31
+ 308 => 'Permanent Redirect',
32
+ 400 => 'Bad Request',
33
+ 401 => 'Unauthorized',
34
+ 402 => 'Payment Required',
35
+ 403 => 'Forbidden',
36
+ 404 => 'Not Found',
37
+ 405 => 'Method Not Allowed',
38
+ 406 => 'Not Acceptable',
39
+ 407 => 'Proxy Authentication Required',
40
+ 408 => 'Request Timeout',
41
+ 409 => 'Conflict',
42
+ 410 => 'Gone',
43
+ 411 => 'Length Required',
44
+ 412 => 'Precondition Failed',
45
+ 413 => 'Content Too Large',
46
+ 414 => 'URI Too Long',
47
+ 415 => 'Unsupported Media Type',
48
+ 416 => 'Range Not Satisfiable',
49
+ 417 => 'Expectation Failed',
50
+ 421 => 'Misdirected Request',
51
+ 422 => 'Unprocessable Content',
52
+ 423 => 'Locked',
53
+ 424 => 'Failed Dependency',
54
+ 425 => 'Too Early',
55
+ 426 => 'Upgrade Required',
56
+ 428 => 'Precondition Required',
57
+ 429 => 'Too Many Requests',
58
+ 431 => 'Request Header Fields Too Large',
59
+ 451 => 'Unavailable For Legal Reasons',
60
+ 500 => 'Internal Server Error',
61
+ 501 => 'Not Implemented',
62
+ 502 => 'Bad Gateway',
63
+ 503 => 'Service Unavailable',
64
+ 504 => 'Gateway Timeout',
65
+ 505 => 'HTTP Version Not Supported',
66
+ 506 => 'Variant Also Negotiates',
67
+ 507 => 'Insufficient Storage',
68
+ 508 => 'Loop Detected',
69
+ 510 => 'Not Extended (OBSOLETED)',
70
+ 511 => 'Network Authentication Required'
71
71
  }.freeze
72
72
  end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "openssl"
3
+ require 'openssl'
4
4
 
5
5
  module SupportedSSLVersions
6
6
  VERSIONS = {
7
- "SSL2" => OpenSSL::SSL::SSL2_VERSION,
8
- "SSL3" => OpenSSL::SSL::SSL3_VERSION,
9
- "TLS1.1" => OpenSSL::SSL::TLS1_1_VERSION,
10
- "TLS1.2" => OpenSSL::SSL::TLS1_2_VERSION,
11
- "TLS1.3" => OpenSSL::SSL::TLS1_3_VERSION
7
+ 'SSL2' => OpenSSL::SSL::SSL2_VERSION,
8
+ 'SSL3' => OpenSSL::SSL::SSL3_VERSION,
9
+ 'TLS1.1' => OpenSSL::SSL::TLS1_1_VERSION,
10
+ 'TLS1.2' => OpenSSL::SSL::TLS1_2_VERSION,
11
+ 'TLS1.3' => OpenSSL::SSL::TLS1_3_VERSION
12
12
  }.freeze
13
13
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MacawFramework
4
- VERSION = "1.3.0"
4
+ VERSION = '1.3.3'
5
5
  end