securial 1.0.1 → 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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/README.md +19 -12
  4. data/app/controllers/concerns/securial/identity.rb +91 -2
  5. data/app/controllers/securial/accounts_controller.rb +68 -5
  6. data/app/controllers/securial/application_controller.rb +34 -2
  7. data/app/controllers/securial/passwords_controller.rb +44 -4
  8. data/app/controllers/securial/role_assignments_controller.rb +55 -4
  9. data/app/controllers/securial/roles_controller.rb +54 -0
  10. data/app/controllers/securial/sessions_controller.rb +77 -3
  11. data/app/controllers/securial/status_controller.rb +24 -0
  12. data/app/controllers/securial/users_controller.rb +54 -0
  13. data/app/jobs/securial/application_job.rb +9 -0
  14. data/app/mailers/securial/application_mailer.rb +12 -0
  15. data/app/mailers/securial/securial_mailer.rb +30 -0
  16. data/app/models/concerns/securial/password_resettable.rb +70 -0
  17. data/app/models/securial/application_record.rb +19 -0
  18. data/app/models/securial/current.rb +13 -0
  19. data/app/models/securial/role.rb +17 -0
  20. data/app/models/securial/role_assignment.rb +16 -0
  21. data/app/models/securial/session.rb +79 -1
  22. data/app/models/securial/user.rb +34 -0
  23. data/lib/generators/factory_bot/model/model_generator.rb +1 -0
  24. data/lib/securial/auth/auth_encoder.rb +78 -0
  25. data/lib/securial/auth/session_creator.rb +49 -0
  26. data/lib/securial/auth/token_generator.rb +74 -0
  27. data/lib/securial/auth.rb +44 -6
  28. data/lib/securial/cli.rb +124 -0
  29. data/lib/securial/config/signature.rb +116 -5
  30. data/lib/securial/config/validation.rb +91 -0
  31. data/lib/securial/config.rb +49 -2
  32. data/lib/securial/engine.rb +41 -0
  33. data/lib/securial/error/auth.rb +52 -0
  34. data/lib/securial/error/base_securial_error.rb +51 -0
  35. data/lib/securial/error/config.rb +33 -0
  36. data/lib/securial/error.rb +33 -3
  37. data/lib/securial/helpers/key_transformer.rb +106 -0
  38. data/lib/securial/helpers/normalizing_helper.rb +69 -0
  39. data/lib/securial/helpers/regex_helper.rb +122 -0
  40. data/lib/securial/helpers/roles_helper.rb +71 -2
  41. data/lib/securial/helpers.rb +48 -4
  42. data/lib/securial/logger/broadcaster.rb +89 -1
  43. data/lib/securial/logger/builder.rb +54 -1
  44. data/lib/securial/logger/formatter.rb +73 -0
  45. data/lib/securial/logger.rb +42 -1
  46. data/lib/securial/middleware/request_tag_logger.rb +80 -0
  47. data/lib/securial/middleware/response_headers.rb +51 -3
  48. data/lib/securial/middleware/transform_request_keys.rb +143 -20
  49. data/lib/securial/middleware/transform_response_keys.rb +84 -4
  50. data/lib/securial/middleware.rb +40 -9
  51. data/lib/securial/security/request_rate_limiter.rb +47 -1
  52. data/lib/securial/security.rb +37 -6
  53. data/lib/securial/version.rb +8 -1
  54. data/lib/securial.rb +36 -0
  55. metadata +21 -15
@@ -1,9 +1,39 @@
1
+ # Requires all error classes used in the Securial framework.
2
+ #
3
+ # This file serves as the central error management system for Securial,
4
+ # loading all specialized error types and establishing the Error namespace.
5
+ # Each error type extends from BaseSecurialError and provides specific
6
+ # error handling for different parts of the framework.
7
+ #
8
+ # @example Accessing a specific error type
9
+ # begin
10
+ # # Some operation that might fail
11
+ # rescue Securial::Error::Auth::TokenDecodeError => e
12
+ # # Handle token decoding errors
13
+ # rescue Securial::Error::Config::ValidationError => e
14
+ # # Handle configuration validation errors
15
+ # rescue Securial::Error::BaseError => e
16
+ # # Handle any other Securial errors
17
+ # end
18
+ #
1
19
  require "securial/error/base_securial_error"
2
20
  require "securial/error/config"
3
21
  require "securial/error/auth"
4
22
 
5
23
  module Securial
6
- module Error
7
- # This serves as a namespace for all Securial errors.
8
- end
24
+ # Namespace for all Securial-specific errors and exceptions.
25
+ #
26
+ # The Error module contains specialized error classes for different
27
+ # components of the Securial framework, organized into submodules
28
+ # for better categorization:
29
+ #
30
+ # - {BaseError} - Base class for all Securial errors
31
+ # - {Config} - Configuration-related errors
32
+ # - {Auth} - Authentication and authorization errors
33
+ #
34
+ # @see Securial::Error::BaseError
35
+ # @see Securial::Error::Config
36
+ # @see Securial::Error::Auth
37
+ #
38
+ module Error; end
9
39
  end
@@ -1,8 +1,56 @@
1
+ # @title Securial Key Transformer Helpers
2
+ #
3
+ # Key transformation utilities for API response formatting in the Securial framework.
4
+ #
5
+ # This module provides utilities for transforming hash keys between different
6
+ # naming conventions (snake_case, camelCase, PascalCase) to ensure API responses
7
+ # follow consistent formatting standards regardless of Ruby's internal conventions.
8
+ #
9
+ # @example Converting keys to camelCase
10
+ # data = { user_name: "john", email_address: "john@example.com" }
11
+ # transformed = KeyTransformer.deep_transform_keys(data) { |key| KeyTransformer.camelize(key, :lowerCamelCase) }
12
+ # # => { userName: "john", emailAddress: "john@example.com" }
13
+ #
14
+ # @example Converting nested structures
15
+ # nested = { user: { first_name: "John", last_name: "Doe" } }
16
+ # camelized = KeyTransformer.deep_transform_keys(nested) { |key| KeyTransformer.camelize(key, :lowerCamelCase) }
17
+ # # => { user: { firstName: "John", lastName: "Doe" } }
18
+ #
1
19
  module Securial
2
20
  module Helpers
21
+ # Transforms hash keys between different naming conventions.
22
+ #
23
+ # This module provides methods to convert between snake_case (Ruby convention),
24
+ # camelCase (JavaScript convention), and PascalCase (C# convention) for API
25
+ # response formatting. It supports deep transformation of nested structures.
26
+ #
3
27
  module KeyTransformer
4
28
  extend self
5
29
 
30
+ # Converts a string to camelCase or PascalCase format.
31
+ #
32
+ # Transforms snake_case strings into camelCase variants based on the
33
+ # specified format. Useful for converting Ruby hash keys to JavaScript
34
+ # or other language conventions.
35
+ #
36
+ # @param [String, Symbol] str The string to transform
37
+ # @param [Symbol] format The target camelCase format
38
+ # @option format [Symbol] :lowerCamelCase Converts to lowerCamelCase (firstName)
39
+ # @option format [Symbol] :UpperCamelCase Converts to PascalCase (FirstName)
40
+ # @return [String] The transformed string, or original if format not recognized
41
+ #
42
+ # @example Converting to lowerCamelCase
43
+ # KeyTransformer.camelize("user_name", :lowerCamelCase)
44
+ # # => "userName"
45
+ #
46
+ # @example Converting to PascalCase
47
+ # KeyTransformer.camelize("email_address", :UpperCamelCase)
48
+ # # => "EmailAddress"
49
+ #
50
+ # @example Unrecognized format returns original
51
+ # KeyTransformer.camelize("user_name", :snake_case)
52
+ # # => "user_name"
53
+ #
6
54
  def camelize(str, format)
7
55
  case format
8
56
  when :lowerCamelCase
@@ -14,10 +62,68 @@ module Securial
14
62
  end
15
63
  end
16
64
 
65
+ # Converts a camelCase or PascalCase string to snake_case.
66
+ #
67
+ # Transforms camelCase or PascalCase strings back to Ruby's snake_case
68
+ # convention. Useful for converting API input keys to Ruby conventions.
69
+ #
70
+ # @param [String, Symbol] str The string to transform
71
+ # @return [String] The snake_case version of the string
72
+ #
73
+ # @example Converting from camelCase
74
+ # KeyTransformer.underscore("userName")
75
+ # # => "user_name"
76
+ #
77
+ # @example Converting from PascalCase
78
+ # KeyTransformer.underscore("EmailAddress")
79
+ # # => "email_address"
80
+ #
17
81
  def self.underscore(str)
18
82
  str.to_s.underscore
19
83
  end
20
84
 
85
+ # Recursively transforms all keys in a nested data structure.
86
+ #
87
+ # Applies a key transformation block to all hash keys in a deeply nested
88
+ # structure containing hashes, arrays, and other objects. The transformation
89
+ # preserves the structure while only modifying the keys.
90
+ #
91
+ # @param [Object] obj The object to transform (Hash, Array, or other)
92
+ # @yieldparam [String, Symbol] key Each hash key to be transformed
93
+ # @yieldreturn [String, Symbol] The transformed key
94
+ # @return [Object] The transformed object with modified keys
95
+ #
96
+ # @example Transforming a simple hash
97
+ # data = { user_name: "john", user_email: "john@example.com" }
98
+ # KeyTransformer.deep_transform_keys(data) { |key| key.to_s.upcase }
99
+ # # => { "USER_NAME" => "john", "USER_EMAIL" => "john@example.com" }
100
+ #
101
+ # @example Transforming nested structures
102
+ # nested = {
103
+ # user_info: {
104
+ # first_name: "John",
105
+ # addresses: [
106
+ # { street_name: "Main St", zip_code: "12345" }
107
+ # ]
108
+ # }
109
+ # }
110
+ # result = KeyTransformer.deep_transform_keys(nested) { |key|
111
+ # KeyTransformer.camelize(key, :lowerCamelCase)
112
+ # }
113
+ # # => {
114
+ # # userInfo: {
115
+ # # firstName: "John",
116
+ # # addresses: [
117
+ # # { streetName: "Main St", zipCode: "12345" }
118
+ # # ]
119
+ # # }
120
+ # # }
121
+ #
122
+ # @example Non-hash objects are preserved
123
+ # mixed = ["string", 123, { user_name: "john" }]
124
+ # KeyTransformer.deep_transform_keys(mixed) { |key| key.to_s.upcase }
125
+ # # => ["string", 123, { "USER_NAME" => "john" }]
126
+ #
21
127
  def self.deep_transform_keys(obj, &block)
22
128
  case obj
23
129
  when Hash
@@ -1,14 +1,83 @@
1
+ # @title Securial Normalizing Helpers
2
+ #
3
+ # Data normalization utilities for the Securial framework.
4
+ #
5
+ # This module provides utilities for normalizing user input data to ensure
6
+ # consistency across the application. It handles common normalization tasks
7
+ # like email address formatting and role name standardization to prevent
8
+ # data inconsistencies and improve user experience.
9
+ #
10
+ # @example Normalizing email addresses
11
+ # email = NormalizingHelper.normalize_email_address(" USER@EXAMPLE.COM ")
12
+ # # => "user@example.com"
13
+ #
14
+ # @example Normalizing role names
15
+ # role = NormalizingHelper.normalize_role_name(" admin user ")
16
+ # # => "Admin User"
17
+ #
1
18
  module Securial
2
19
  module Helpers
20
+ # Provides data normalization methods for common input sanitization.
21
+ #
22
+ # This module contains methods to normalize various types of user input
23
+ # to ensure data consistency and prevent common formatting issues that
24
+ # could lead to duplicate records or authentication problems.
25
+ #
3
26
  module NormalizingHelper
4
27
  extend self
5
28
 
29
+ # Normalizes an email address for consistent storage and comparison.
30
+ #
31
+ # Strips whitespace and converts to lowercase to ensure email addresses
32
+ # are stored consistently regardless of how users input them. This prevents
33
+ # duplicate accounts and authentication issues caused by case sensitivity.
34
+ #
35
+ # @param [String] email The email address to normalize
36
+ # @return [String] The normalized email address, or empty string if input is empty
37
+ #
38
+ # @example Standard normalization
39
+ # normalize_email_address(" USER@EXAMPLE.COM ")
40
+ # # => "user@example.com"
41
+ #
42
+ # @example Handling empty input
43
+ # normalize_email_address("")
44
+ # # => ""
45
+ #
46
+ # @example Preserving valid format
47
+ # normalize_email_address("user@example.com")
48
+ # # => "user@example.com"
49
+ #
6
50
  def normalize_email_address(email)
7
51
  return "" if email.empty?
8
52
 
9
53
  email.strip.downcase
10
54
  end
11
55
 
56
+ # Normalizes a role name for consistent display and storage.
57
+ #
58
+ # Strips whitespace, converts to lowercase, then applies title case formatting
59
+ # to ensure role names are displayed consistently across the application.
60
+ # This helps maintain a professional appearance and prevents formatting issues.
61
+ #
62
+ # @param [String] role_name The role name to normalize
63
+ # @return [String] The normalized role name in title case, or empty string if input is empty
64
+ #
65
+ # @example Standard normalization
66
+ # normalize_role_name(" admin user ")
67
+ # # => "Admin User"
68
+ #
69
+ # @example Handling mixed case input
70
+ # normalize_role_name("SUPER_ADMIN")
71
+ # # => "Super Admin"
72
+ #
73
+ # @example Handling empty input
74
+ # normalize_role_name("")
75
+ # # => ""
76
+ #
77
+ # @example Single word roles
78
+ # normalize_role_name("admin")
79
+ # # => "Admin"
80
+ #
12
81
  def normalize_role_name(role_name)
13
82
  return "" if role_name.empty?
14
83
 
@@ -1,18 +1,140 @@
1
+ # @title Securial Regex Helpers
2
+ #
3
+ # Regular expression utilities for data validation in the Securial framework.
4
+ #
5
+ # This module provides commonly used regular expressions and validation methods
6
+ # for user input such as email addresses, usernames, and passwords. It ensures
7
+ # consistent validation rules across the application and helps maintain data
8
+ # integrity and security standards.
9
+ #
10
+ # @example Validating an email address
11
+ # RegexHelper.valid_email?("user@example.com")
12
+ # # => true
13
+ #
14
+ # @example Validating a username
15
+ # RegexHelper.valid_username?("john_doe123")
16
+ # # => true
17
+ #
18
+ # @example Checking password complexity
19
+ # password = "MyPass123!"
20
+ # password.match?(RegexHelper::PASSWORD_REGEX)
21
+ # # => true
22
+ #
23
+ require "uri"
24
+
1
25
  module Securial
2
26
  module Helpers
27
+ # Regular expression patterns and validation methods for common data types.
28
+ #
29
+ # This module centralizes regex patterns used throughout Securial for
30
+ # validating user input. It provides both the raw regex patterns as
31
+ # constants and convenience methods for validation.
32
+ #
3
33
  module RegexHelper
34
+ # RFC-compliant email address validation pattern.
35
+ #
36
+ # Uses Ruby's built-in URI::MailTo::EMAIL_REGEXP which follows RFC 3696
37
+ # specifications for email address validation.
38
+ #
39
+ # @return [Regexp] Email validation regular expression
40
+ #
4
41
  EMAIL_REGEX = URI::MailTo::EMAIL_REGEXP
42
+
43
+ # Username validation pattern with specific formatting rules.
44
+ #
45
+ # Enforces the following username requirements:
46
+ # - Must start with a letter (not a number)
47
+ # - Can contain letters, numbers, periods, and underscores
48
+ # - Cannot have consecutive periods or underscores
49
+ # - Must end with a letter or number
50
+ # - Minimum length of 2 characters
51
+ #
52
+ # @return [Regexp] Username validation regular expression
53
+ #
5
54
  USERNAME_REGEX = /\A(?![0-9])[a-zA-Z](?:[a-zA-Z0-9]|[._](?![._]))*[a-zA-Z0-9]\z/
55
+
56
+ # Password complexity validation pattern.
57
+ #
58
+ # Enforces strong password requirements including:
59
+ # - At least one digit (0-9)
60
+ # - At least one lowercase letter (a-z)
61
+ # - At least one uppercase letter (A-Z)
62
+ # - At least one special character (non-alphanumeric)
63
+ # - Must start with a letter
64
+ #
65
+ # @return [Regexp] Password complexity validation regular expression
66
+ #
6
67
  PASSWORD_REGEX = %r{\A(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9])[a-zA-Z].*\z}
7
68
 
8
69
  extend self
70
+
71
+ # Validates if an email address matches the RFC-compliant pattern.
72
+ #
73
+ # Uses the EMAIL_REGEX pattern to check if the provided email address
74
+ # follows proper email formatting standards.
75
+ #
76
+ # @param [String] email The email address to validate
77
+ # @return [Boolean] true if the email is valid, false otherwise
78
+ #
79
+ # @example Valid email addresses
80
+ # valid_email?("user@example.com") # => true
81
+ # valid_email?("test.email@domain.co.uk") # => true
82
+ # valid_email?("user+tag@example.org") # => true
83
+ #
84
+ # @example Invalid email addresses
85
+ # valid_email?("invalid.email") # => false
86
+ # valid_email?("@example.com") # => false
87
+ # valid_email?("user@") # => false
88
+ #
9
89
  def valid_email?(email)
10
90
  email.match?(EMAIL_REGEX)
11
91
  end
12
92
 
93
+ # Validates if a username follows the defined formatting rules.
94
+ #
95
+ # Uses the USERNAME_REGEX pattern to check if the provided username
96
+ # meets the application's username requirements.
97
+ #
98
+ # @param [String] username The username to validate
99
+ # @return [Boolean] true if the username is valid, false otherwise
100
+ #
101
+ # @example Valid usernames
102
+ # valid_username?("john_doe") # => true
103
+ # valid_username?("user123") # => true
104
+ # valid_username?("test.user") # => true
105
+ # valid_username?("a1") # => true
106
+ #
107
+ # @example Invalid usernames
108
+ # valid_username?("123user") # => false (starts with number)
109
+ # valid_username?("user__name") # => false (consecutive underscores)
110
+ # valid_username?("user.") # => false (ends with period)
111
+ # valid_username?("a") # => false (too short)
112
+ #
13
113
  def valid_username?(username)
14
114
  username.match?(USERNAME_REGEX)
15
115
  end
116
+
117
+ # Validates if a password meets complexity requirements.
118
+ #
119
+ # Uses the PASSWORD_REGEX pattern to check if the provided password
120
+ # meets the application's security standards.
121
+ #
122
+ # @param [String] password The password to validate
123
+ # @return [Boolean] true if the password meets complexity requirements, false otherwise
124
+ #
125
+ # @example Valid passwords
126
+ # valid_password?("MyPass123!") # => true
127
+ # valid_password?("Secure@2024") # => true
128
+ # valid_password?("aB3#defgh") # => true
129
+ #
130
+ # @example Invalid passwords
131
+ # valid_password?("password") # => false (no uppercase, number, or special char)
132
+ # valid_password?("PASSWORD123") # => false (no lowercase or special char)
133
+ # valid_password?("123Password!") # => false (starts with number)
134
+ #
135
+ def valid_password?(password)
136
+ password.match?(PASSWORD_REGEX)
137
+ end
16
138
  end
17
139
  end
18
140
  end
@@ -1,13 +1,82 @@
1
+ # @title Securial Roles Helper
2
+ #
3
+ # Role and permission utilities for the Securial framework.
4
+ #
5
+ # This module provides helper methods for working with user roles and permissions
6
+ # throughout the application. It handles role name formatting, namespace generation,
7
+ # and other role-related operations to ensure consistency across the system.
8
+ #
9
+ # @example Getting the admin namespace for routing
10
+ # RolesHelper.protected_namespace
11
+ # # => "admins" (if admin_role is :admin)
12
+ #
13
+ # @example Getting a formatted admin role name for display
14
+ # RolesHelper.titleized_admin_role
15
+ # # => "Super Admin" (if admin_role is :super_admin)
16
+ #
1
17
  module Securial
2
18
  module Helpers
19
+ # Helper methods for role management and formatting.
20
+ #
21
+ # This module provides utility methods for working with user roles,
22
+ # including namespace generation for protected routes and role name
23
+ # formatting for user interfaces. It centralizes role-related logic
24
+ # to ensure consistent behavior across the application.
25
+ #
3
26
  module RolesHelper
4
- # This module provides helper methods related to roles.
5
- # It can be extended with additional role-related functionality as needed.
6
27
  extend self
28
+
29
+ # Generates a namespace string for protected admin routes.
30
+ #
31
+ # Converts the configured admin role into a pluralized, underscored
32
+ # namespace suitable for use in Rails routing and controller organization.
33
+ # This ensures admin routes are consistently organized under the
34
+ # appropriate namespace.
35
+ #
36
+ # @return [String] The pluralized, underscored admin role namespace
37
+ #
38
+ # @example With default admin role
39
+ # # When Securial.configuration.admin_role = :admin
40
+ # protected_namespace
41
+ # # => "admins"
42
+ #
43
+ # @example With custom admin role
44
+ # # When Securial.configuration.admin_role = :super_admin
45
+ # protected_namespace
46
+ # # => "super_admins"
47
+ #
48
+ # @example With spaced role name
49
+ # # When Securial.configuration.admin_role = "Site Administrator"
50
+ # protected_namespace
51
+ # # => "site_administrators"
52
+ #
7
53
  def protected_namespace
8
54
  Securial.configuration.admin_role.to_s.strip.underscore.pluralize
9
55
  end
10
56
 
57
+ # Formats the admin role name for user interface display.
58
+ #
59
+ # Converts the configured admin role into a human-readable, title-cased
60
+ # string suitable for display in user interfaces, form labels, and
61
+ # user-facing messages.
62
+ #
63
+ # @return [String] The title-cased admin role name
64
+ #
65
+ # @example With default admin role
66
+ # # When Securial.configuration.admin_role = :admin
67
+ # titleized_admin_role
68
+ # # => "Admin"
69
+ #
70
+ # @example With underscored role name
71
+ # # When Securial.configuration.admin_role = :super_admin
72
+ # titleized_admin_role
73
+ # # => "Super Admin"
74
+ #
75
+ # @example With already formatted role
76
+ # # When Securial.configuration.admin_role = "Site Administrator"
77
+ # titleized_admin_role
78
+ # # => "Site Administrator"
79
+ #
11
80
  def titleized_admin_role
12
81
  Securial.configuration.admin_role.to_s.strip.titleize
13
82
  end
@@ -1,11 +1,55 @@
1
+ # @title Securial Helper Utilities
2
+ #
3
+ # Centralized collection of helper modules for the Securial framework.
4
+ #
5
+ # This file serves as the entry point for all helper functionality in Securial,
6
+ # loading specialized utility modules that provide common functions used
7
+ # throughout the framework. These helpers handle tasks such as normalization,
8
+ # pattern matching, role management, and data transformation, making common
9
+ # operations consistent across the codebase.
10
+ #
11
+ # @example Normalizing user input
12
+ # # For normalizing email addresses (converts to lowercase)
13
+ # email = Securial::Helpers::NormalizingHelper.normalize_email("USER@EXAMPLE.COM")
14
+ # # => "user@example.com"
15
+ #
16
+ # @example Validating input format
17
+ # # For validating email format
18
+ # valid = Securial::Helpers::RegexHelper.valid_email?("user@example.com")
19
+ # # => true
20
+ #
21
+ # invalid = Securial::Helpers::RegexHelper.valid_email?("not-an-email")
22
+ # # => false
23
+ #
24
+ # @example Transforming data structure keys
25
+ # # For transforming keys in response data (snake_case to camelCase)
26
+ # data = { user_id: 1, first_name: "John", last_name: "Doe" }
27
+ # json = Securial::Helpers::KeyTransformer.transform(data, :lower_camel_case)
28
+ # # => { userId: 1, firstName: "John", lastName: "Doe" }
29
+ #
1
30
  require "securial/helpers/normalizing_helper"
2
31
  require "securial/helpers/regex_helper"
3
32
  require "securial/helpers/roles_helper"
4
33
  require "securial/helpers/key_transformer"
5
34
 
6
35
  module Securial
7
- module Helpers
8
- # This module acts as a namespace for helper modules.
9
- # It requires all helper modules to make them available for consumers.
10
- end
36
+ # Namespace containing utility modules for common operations.
37
+ #
38
+ # The Helpers module serves as a container for specialized utility modules
39
+ # that provide reusable functionality across the Securial framework:
40
+ #
41
+ # - {NormalizingHelper} - Methods for normalizing user input (emails, usernames, etc.)
42
+ # - {RegexHelper} - Regular expressions and validation methods for input validation
43
+ # - {RolesHelper} - Functions for working with user roles and permissions management
44
+ # - {KeyTransformer} - Tools for transforming data structure keys between formats (snake_case, camelCase)
45
+ #
46
+ # These helpers ensure consistent behavior across the application and reduce
47
+ # code duplication by centralizing common operations.
48
+ #
49
+ # @see Securial::Helpers::NormalizingHelper
50
+ # @see Securial::Helpers::RegexHelper
51
+ # @see Securial::Helpers::RolesHelper
52
+ # @see Securial::Helpers::KeyTransformer
53
+ #
54
+ module Helpers; end
11
55
  end
@@ -1,36 +1,109 @@
1
+ # @title Securial Logger Broadcaster
2
+ #
3
+ # Logger broadcasting utility for the Securial framework.
4
+ #
5
+ # This file defines a broadcaster class that sends logging messages to multiple
6
+ # destination loggers simultaneously. This enables unified logging across different
7
+ # outputs (like console and file) while maintaining appropriate formatting for each.
8
+ #
9
+ # @example Creating a broadcaster with multiple loggers
10
+ # # Create individual loggers
11
+ # file_logger = Logger.new('application.log')
12
+ # stdout_logger = Logger.new(STDOUT)
13
+ #
14
+ # # Combine them into a broadcaster
15
+ # broadcaster = Securial::Logger::Broadcaster.new([file_logger, stdout_logger])
16
+ #
17
+ # # Log messages go to both destinations
18
+ # broadcaster.info("Processing authentication request")
19
+ #
1
20
  module Securial
2
21
  module Logger
22
+ # Broadcasts log messages to multiple logger instances simultaneously.
23
+ #
24
+ # This class implements the Logger interface and forwards all logging calls
25
+ # to a collection of underlying loggers. This allows unified logging to
26
+ # multiple destinations (e.g., file and stdout) while maintaining specific
27
+ # formatting for each destination.
28
+ #
29
+ # @example Using a broadcaster with two loggers
30
+ # file_logger = Logger.new('app.log')
31
+ # console_logger = Logger.new(STDOUT)
32
+ # broadcaster = Broadcaster.new([file_logger, console_logger])
33
+ #
34
+ # # This logs to both destinations
35
+ # broadcaster.info("User logged in")
36
+ #
3
37
  class Broadcaster
38
+ # Initializes a new broadcaster with the provided loggers.
39
+ #
40
+ # @param loggers [Array<Logger>] Collection of logger instances
41
+ # @return [Broadcaster] New broadcaster instance
4
42
  def initialize(loggers)
5
43
  @loggers = loggers
6
44
  end
7
45
 
46
+ # Dynamically define logging methods for each severity level
47
+ # (debug, info, warn, error, fatal, unknown)
8
48
  ::Logger::Severity.constants.each do |severity|
9
49
  define_method(severity.downcase) do |*args, &block|
10
50
  @loggers.each { |logger| logger.public_send(severity.downcase, *args, &block) }
11
51
  end
12
52
  end
13
53
 
54
+ # Writes a message to each underlying logger.
55
+ #
56
+ # @param msg [String] Message to be written
57
+ # @return [Array] Results from each logger's << method
14
58
  def <<(msg)
15
59
  @loggers.each { |logger| logger << msg }
16
60
  end
17
61
 
62
+ # Closes all underlying loggers.
63
+ #
64
+ # @return [void]
18
65
  def close
19
66
  @loggers.each(&:close)
20
67
  end
21
68
 
69
+ # No-op method to satisfy the Logger interface.
70
+ #
71
+ # Since each underlying logger has its own formatter, setting a
72
+ # formatter on the broadcaster is not supported.
73
+ #
74
+ # @param _formatter [Object] Ignored formatter object
75
+ # @return [void]
22
76
  def formatter=(_formatter)
23
- # Do nothing
77
+ # Do nothing - each logger maintains its own formatter
24
78
  end
25
79
 
80
+ # Returns nil to satisfy the Logger interface.
81
+ #
82
+ # Since each underlying logger has its own formatter, there is
83
+ # no single formatter for the broadcaster.
84
+ #
85
+ # @return [nil] Always returns nil
26
86
  def formatter
27
87
  nil
28
88
  end
29
89
 
90
+ # Returns the collection of managed loggers.
91
+ #
92
+ # @return [Array<Logger>] All loggers managed by this broadcaster
93
+ #
30
94
  def loggers
31
95
  @loggers
32
96
  end
33
97
 
98
+ # Executes a block with the specified tags added to the log.
99
+ #
100
+ # Supports ActiveSupport::TaggedLogging by forwarding tagged blocks
101
+ # to all underlying loggers that support tagging.
102
+ #
103
+ # @param tags [Array<String>] Tags to apply to log messages
104
+ # @yield Block to be executed with the tags applied
105
+ # @return [Object] Result of the block
106
+ #
34
107
  def tagged(*tags, &block)
35
108
  # If all loggers support tagged, nest the calls, otherwise just yield.
36
109
  taggable_loggers = @loggers.select { |logger| logger.respond_to?(:tagged) }
@@ -44,10 +117,25 @@ module Securial
44
117
  end
45
118
  end
46
119
 
120
+ # Checks if the broadcaster responds to the given method.
121
+ #
122
+ # @param method [Symbol] Method name to check
123
+ # @param include_private [Boolean] Whether to include private methods
124
+ # @return [Boolean] True if any of the underlying loggers respond to the method
47
125
  def respond_to_missing?(method, include_private = false)
48
126
  @loggers.any? { |logger| logger.respond_to?(method, include_private) } || super
49
127
  end
50
128
 
129
+ # Delegates missing methods to all underlying loggers.
130
+ #
131
+ # If all loggers respond to the method, it will be called on each logger.
132
+ # Otherwise, raises a NoMethodError.
133
+ #
134
+ # @param method [Symbol] Method name to call
135
+ # @param args [Array] Arguments to pass to the method
136
+ # @param block [Proc] Block to pass to the method
137
+ # @return [Array] Results from each logger's method call
138
+ # @raise [NoMethodError] If any logger doesn't respond to the method
51
139
  def method_missing(method, *args, &block)
52
140
  if @loggers.all? { |logger| logger.respond_to?(method) }
53
141
  @loggers.map { |logger| logger.send(method, *args, &block) }