securial 1.0.2 → 1.0.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,35 +1,158 @@
1
+ # @title Securial Transform Request Keys Middleware
2
+ #
3
+ # Rack middleware for transforming JSON request body keys to Ruby conventions.
4
+ #
5
+ # This middleware automatically converts incoming JSON request body keys from
6
+ # camelCase or PascalCase to snake_case, allowing Rails applications to receive
7
+ # JavaScript-style APIs while maintaining Ruby naming conventions internally.
8
+ # It only processes JSON requests and gracefully handles malformed JSON.
9
+ #
10
+ # @example Adding middleware to Rails application
11
+ # # config/application.rb
12
+ # config.middleware.use Securial::Middleware::TransformRequestKeys
13
+ #
14
+ # @example Request transformation
15
+ # # Incoming JSON request body:
16
+ # { "userName": "john", "emailAddress": "john@example.com" }
17
+ #
18
+ # # Transformed for Rails application:
19
+ # { "user_name": "john", "email_address": "john@example.com" }
20
+ #
21
+ require "json"
22
+ require "stringio"
23
+
1
24
  module Securial
2
25
  module Middleware
3
- # This middleware transforms request keys to a specified format.
4
- # It uses the KeyTransformer helper to apply the transformation.
26
+ # Rack middleware that transforms JSON request body keys to snake_case.
27
+ #
28
+ # This middleware enables Rails applications to accept camelCase JSON
29
+ # from frontend applications while automatically converting keys to
30
+ # Ruby's snake_case convention before they reach the application.
5
31
  #
6
- # It reads the request body if the content type is JSON and transforms
7
- # the keys to underscore format. If the body is not valid JSON, it does nothing.
8
32
  class TransformRequestKeys
33
+ # Initializes the middleware with the Rack application.
34
+ #
35
+ # @param [#call] app The Rack application to wrap
36
+ #
37
+ # @example
38
+ # middleware = TransformRequestKeys.new(app)
39
+ #
9
40
  def initialize(app)
10
41
  @app = app
11
42
  end
12
43
 
44
+ # Processes the request and transforms JSON body keys if applicable.
45
+ #
46
+ # Intercepts JSON requests, parses the body, transforms all keys to
47
+ # snake_case using the KeyTransformer helper, and replaces the request
48
+ # body with the transformed JSON. Non-JSON requests pass through unchanged.
49
+ #
50
+ # @param [Hash] env The Rack environment hash
51
+ # @return [Array] The Rack response array [status, headers, body]
52
+ #
53
+ # @example JSON transformation
54
+ # # Original request body: { "firstName": "John", "lastName": "Doe" }
55
+ # # Transformed body: { "first_name": "John", "last_name": "Doe" }
56
+ #
57
+ # @example Non-JSON requests
58
+ # # Form data, XML, and other content types pass through unchanged
59
+ #
60
+ # @example Malformed JSON handling
61
+ # # Invalid JSON is left unchanged and passed to the application
62
+ # # The application can handle the JSON parsing error appropriately
63
+ #
13
64
  def call(env)
14
- if env["CONTENT_TYPE"]&.include?("application/json")
15
- req = Rack::Request.new(env)
16
- if (req.body&.size || 0) > 0
17
- raw = req.body.read
18
- req.body.rewind
19
- begin
20
- parsed = JSON.parse(raw)
21
- transformed = Securial::Helpers::KeyTransformer.deep_transform_keys(parsed) do |key|
22
- Securial::Helpers::KeyTransformer.underscore(key)
23
- end
24
- env["rack.input"] = StringIO.new(JSON.dump(transformed))
25
- env["rack.input"].rewind
26
- rescue JSON::ParserError
27
- # no-op
28
- end
29
- end
65
+ if json_request?(env)
66
+ transform_json_body(env)
30
67
  end
68
+
31
69
  @app.call(env)
32
70
  end
71
+
72
+ private
73
+
74
+ # Checks if the request contains JSON content.
75
+ #
76
+ # @param [Hash] env The Rack environment hash
77
+ # @return [Boolean] true if the request has JSON content type
78
+ # @api private
79
+ #
80
+ def json_request?(env)
81
+ env["CONTENT_TYPE"]&.include?("application/json")
82
+ end
83
+
84
+ # Transforms JSON request body keys to snake_case.
85
+ #
86
+ # Reads the request body, parses it as JSON, transforms all keys
87
+ # to snake_case, and replaces the original body with the transformed
88
+ # JSON. Gracefully handles empty bodies and malformed JSON.
89
+ #
90
+ # @param [Hash] env The Rack environment hash
91
+ # @return [void]
92
+ # @api private
93
+ #
94
+ def transform_json_body(env)
95
+ req = Rack::Request.new(env)
96
+
97
+ return unless request_has_body?(req)
98
+
99
+ raw_body = read_request_body(req)
100
+
101
+ begin
102
+ parsed_json = JSON.parse(raw_body)
103
+ transformed_json = transform_keys_to_snake_case(parsed_json)
104
+ replace_request_body(env, transformed_json)
105
+ rescue JSON::ParserError
106
+ # Malformed JSON - leave unchanged for application to handle
107
+ end
108
+ end
109
+
110
+ # Checks if the request has a body to process.
111
+ #
112
+ # @param [Rack::Request] req The Rack request object
113
+ # @return [Boolean] true if the request has a non-empty body
114
+ # @api private
115
+ #
116
+ def request_has_body?(req)
117
+ (req.body&.size || 0) > 0
118
+ end
119
+
120
+ # Reads and rewinds the request body.
121
+ #
122
+ # @param [Rack::Request] req The Rack request object
123
+ # @return [String] The raw request body content
124
+ # @api private
125
+ #
126
+ def read_request_body(req)
127
+ raw = req.body.read
128
+ req.body.rewind
129
+ raw
130
+ end
131
+
132
+ # Transforms all keys in the JSON structure to snake_case.
133
+ #
134
+ # @param [Object] json_data The parsed JSON data structure
135
+ # @return [Object] The data structure with transformed keys
136
+ # @api private
137
+ #
138
+ def transform_keys_to_snake_case(json_data)
139
+ Securial::Helpers::KeyTransformer.deep_transform_keys(json_data) do |key|
140
+ Securial::Helpers::KeyTransformer.underscore(key)
141
+ end
142
+ end
143
+
144
+ # Replaces the request body with transformed JSON.
145
+ #
146
+ # @param [Hash] env The Rack environment hash
147
+ # @param [Object] transformed_data The transformed JSON data
148
+ # @return [void]
149
+ # @api private
150
+ #
151
+ def replace_request_body(env, transformed_data)
152
+ new_body = JSON.dump(transformed_data)
153
+ env["rack.input"] = StringIO.new(new_body)
154
+ env["rack.input"].rewind
155
+ end
33
156
  end
34
157
  end
35
158
  end
@@ -1,15 +1,76 @@
1
+ # @title Securial Transform Response Keys Middleware
2
+ #
3
+ # Rack middleware for transforming JSON response keys to client-preferred formats.
4
+ #
5
+ # This middleware automatically converts outgoing JSON response keys from Ruby's
6
+ # snake_case convention to the configured format (camelCase, PascalCase, etc.) to
7
+ # match client application expectations. It only processes JSON responses and
8
+ # preserves the original response structure and content.
9
+ #
10
+ # @example Adding middleware to Rails application
11
+ # # config/application.rb
12
+ # config.middleware.use Securial::Middleware::TransformResponseKeys
13
+ #
14
+ # @example Response transformation with lowerCamelCase
15
+ # # Original Rails response:
16
+ # { "user_name": "john", "email_address": "john@example.com" }
17
+ #
18
+ # # Transformed for client:
19
+ # { "userName": "john", "emailAddress": "john@example.com" }
20
+ #
21
+ # @example Response transformation with UpperCamelCase
22
+ # # Configuration: config.response_keys_format = :UpperCamelCase
23
+ # # Original: { "user_profile": { "first_name": "John" } }
24
+ # # Transformed: { "UserProfile": { "FirstName": "John" } }
25
+ #
26
+ require "json"
27
+
1
28
  module Securial
2
29
  module Middleware
3
- # This middleware transforms response keys to a specified format.
4
- # It uses the KeyTransformer helper to apply the transformation.
30
+ # Rack middleware that transforms JSON response keys to configured format.
31
+ #
32
+ # This middleware enables Rails applications to output responses in
33
+ # the naming convention expected by client applications (JavaScript,
34
+ # mobile apps, etc.) while maintaining Ruby's snake_case internally.
5
35
  #
6
- # It reads the response body if the content type is JSON and transforms
7
- # the keys to the specified format (default is lowerCamelCase).
8
36
  class TransformResponseKeys
37
+ # Initializes the middleware with the Rack application and optional format.
38
+ #
39
+ # The format parameter is deprecated in favor of the global configuration
40
+ # setting `Securial.configuration.response_keys_format`.
41
+ #
42
+ # @param [#call] app The Rack application to wrap
43
+ # @param [Symbol] format Deprecated: The key format to use (defaults to :lowerCamelCase)
44
+ #
45
+ # @example
46
+ # middleware = TransformResponseKeys.new(app)
47
+ #
48
+ # @deprecated Use `Securial.configuration.response_keys_format` instead of format parameter
49
+ #
9
50
  def initialize(app, format: :lowerCamelCase)
10
51
  @app = app
11
52
  end
12
53
 
54
+ # Processes the response and transforms JSON keys if applicable.
55
+ #
56
+ # Intercepts JSON responses, parses the body, transforms all keys to
57
+ # the configured format using the KeyTransformer helper, and replaces
58
+ # the response body with the transformed JSON. Non-JSON responses
59
+ # pass through unchanged.
60
+ #
61
+ # @param [Hash] env The Rack environment hash
62
+ # @return [Array] The Rack response array [status, headers, body] with transformed keys
63
+ #
64
+ # @example JSON transformation
65
+ # # Original response: { "first_name": "John", "last_name": "Doe" }
66
+ # # Transformed response: { "firstName": "John", "lastName": "Doe" }
67
+ #
68
+ # @example Non-JSON responses
69
+ # # HTML, XML, and other content types pass through unchanged
70
+ #
71
+ # @example Empty or malformed JSON handling
72
+ # # Empty responses and invalid JSON are left unchanged
73
+ #
13
74
  def call(env)
14
75
  status, headers, response = @app.call(env)
15
76
 
@@ -33,10 +94,29 @@ module Securial
33
94
 
34
95
  private
35
96
 
97
+ # Checks if the response contains JSON content.
98
+ #
99
+ # @param [Hash] headers The response headers hash
100
+ # @return [Boolean] true if the response has JSON content type
101
+ # @api private
102
+ #
36
103
  def json_response?(headers)
37
104
  headers["Content-Type"]&.include?("application/json")
38
105
  end
39
106
 
107
+ # Extracts the complete response body from the response object.
108
+ #
109
+ # Iterates through the response body parts and concatenates them
110
+ # into a single string for JSON parsing and transformation.
111
+ #
112
+ # @param [#each] response The response body object (responds to #each)
113
+ # @return [String] The complete response body as a string
114
+ # @api private
115
+ #
116
+ # @example
117
+ # body = extract_body(response)
118
+ # # => '{"user_name":"john","email":"john@example.com"}'
119
+ #
40
120
  def extract_body(response)
41
121
  response_body = ""
42
122
  response.each { |part| response_body << part }
@@ -6,5 +6,5 @@ module Securial
6
6
  #
7
7
  # @see https://semver.org/ Semantic Versioning 2.0.0
8
8
  # @return [String] the current version in the format "major.minor.patch"
9
- VERSION = "1.0.2".freeze
9
+ VERSION = "1.0.3".freeze
10
10
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: securial
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aly Badawy
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-25 00:00:00.000000000 Z
10
+ date: 2025-06-27 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails
@@ -206,22 +206,27 @@ homepage: https://github.com/AlyBadawy/Securial/wiki
206
206
  licenses:
207
207
  - MIT
208
208
  metadata:
209
- release_date: '2025-06-25'
210
- allowed_push_host: https://rubygems.org
211
209
  homepage_uri: https://github.com/AlyBadawy/Securial/wiki
210
+ release_date: '2025-06-27'
211
+ allowed_push_host: https://rubygems.org
212
212
  source_code_uri: https://github.com/AlyBadawy/Securial
213
+ documentation_uri: https://alybadawy.github.io/Securial/_index.html
213
214
  changelog_uri: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md
214
- post_install_message: "\n ---\n [SECURIAL] Thank you for installing Securial!\n\n
215
- \ Securial is a mountable Rails engine that provides robust, extensible\n authentication
216
- and access control for Rails applications. It supports JWT,\n API tokens, session-based
217
- auth, and is designed for easy integration with\n modern web and mobile apps.\n\n
218
- \ Usage:\n securial new APP_NAME [rails_options...] # Create a new Rails
219
- app with Securial pre-installed\n securial -v, --version #
220
- Show the Securial gem version\n securial -h, --help #
221
- Show this help message\n\n Example:\n securial new myapp --api --database=postgresql
222
- -T\n\n More Info:\n review the [changelog] and [WIKI] for more info on the
223
- latest\n changes and how to use this gem/engine:\n [Changelog]: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md\n
224
- \ [WIKI]: https://github.com/AlyBadawy/Securial/wiki\n ---\n "
215
+ wiki_uri: https://github.com/AlyBadawy/Securial/wiki
216
+ post_install_message: "\e[36m\n ▗▄▄▖▗▄▄▄▖ ▗▄▄▖▗▖ ▗▖▗▄▄▖ ▗▄▄▄▖ ▗▄▖ ▗▖\n▐▌ ▐▌ ▐▌
217
+ \ ▐▌ ▐▌▐▌ ▐▌ █ ▐▌ ▐▌▐▌\n ▝▀▚▖▐▛▀▀▘▐▌ ▐▌ ▐▌▐▛▀▚▖ █ ▐▛▀▜▌▐▌\n▗▄▄▞▘▐▙▄▄▖▝▚▄▄▖▝▚▄▞▘▐▌
218
+ ▐▌▗▄█▄▖▐▌ ▐▌▐▙▄▄▖\n\e[0m\n\n\e[32mThank you for installing Securial!\e[0m\n\n\e[37mSecurial
219
+ is a mountable Rails engine that provides robust, extensible\nauthentication and
220
+ access control for Rails applications. It supports JWT,\nAPI tokens, session-based
221
+ auth, and is designed for easy integration with\nmodern web and mobile apps.\e[0m\n\n\e[33mUsage:\e[0m\n
222
+ \ \e[36msecurial new APP_NAME [rails_options...]\e[0m # Create a new Rails app
223
+ with Securial pre-installed\n \e[36msecurial -v, --version\e[0m #
224
+ Show the Securial gem version\n \e[36msecurial -h, --help\e[0m #
225
+ Show this help message\n\n\e[33mExample:\e[0m\n \e[32msecurial new myapp --api
226
+ --database=postgresql -T\e[0m\n\n\e[33mMore Info:\e[0m\n \e[37mreview the [changelog]
227
+ and [WIKI] for more info on the latest\n changes and how to use this gem/engine:\e[0m\n
228
+ \ \e[34m[Changelog]: https://github.com/AlyBadawy/Securial/blob/main/CHANGELOG.md\e[0m\n
229
+ \ \e[34m[WIKI]: https://github.com/AlyBadawy/Securial/wiki\e[0m\n\e[90m---\e[0m\n"
225
230
  rdoc_options: []
226
231
  require_paths:
227
232
  - lib