jpie 0.4.1 → 0.4.2
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 +4 -4
- data/.cursorrules +7 -3
- data/.overcommit.yml +35 -0
- data/CHANGELOG.md +14 -0
- data/README.md +58 -198
- data/examples/pagination.md +303 -0
- data/lib/jpie/controller/crud_actions.rb +23 -2
- data/lib/jpie/controller/error_handling/handler_setup.rb +124 -0
- data/lib/jpie/controller/error_handling/handlers.rb +109 -0
- data/lib/jpie/controller/error_handling.rb +6 -175
- data/lib/jpie/controller/json_api_validation.rb +55 -33
- data/lib/jpie/controller/parameter_parsing.rb +43 -0
- data/lib/jpie/controller/rendering.rb +95 -1
- data/lib/jpie/resource/attributable.rb +2 -7
- data/lib/jpie/resource.rb +22 -8
- data/lib/jpie/version.rb +1 -1
- metadata +6 -2
@@ -39,7 +39,12 @@ module JPie
|
|
39
39
|
resources = resource_class.scope(context)
|
40
40
|
sort_fields = parse_sort_params
|
41
41
|
resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
|
42
|
-
|
42
|
+
|
43
|
+
pagination_params = parse_pagination_params
|
44
|
+
original_resources = resources
|
45
|
+
resources = apply_pagination(resources, pagination_params)
|
46
|
+
|
47
|
+
render_jsonapi(resources, pagination: pagination_params, original_scope: original_resources)
|
43
48
|
end
|
44
49
|
end
|
45
50
|
|
@@ -86,7 +91,12 @@ module JPie
|
|
86
91
|
resources = resource_class.scope(context)
|
87
92
|
sort_fields = parse_sort_params
|
88
93
|
resources = resource_class.sort(resources, sort_fields) if sort_fields.any?
|
89
|
-
|
94
|
+
|
95
|
+
pagination_params = parse_pagination_params
|
96
|
+
original_resources = resources
|
97
|
+
resources = apply_pagination(resources, pagination_params)
|
98
|
+
|
99
|
+
render_jsonapi(resources, pagination: pagination_params, original_scope: original_resources)
|
90
100
|
end
|
91
101
|
|
92
102
|
def show
|
@@ -115,6 +125,17 @@ module JPie
|
|
115
125
|
resource.destroy!
|
116
126
|
head :no_content
|
117
127
|
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
def apply_pagination(resources, pagination_params)
|
132
|
+
return resources unless pagination_params[:per_page]
|
133
|
+
|
134
|
+
page = pagination_params[:page] || 1
|
135
|
+
per_page = pagination_params[:per_page]
|
136
|
+
|
137
|
+
resources.limit(per_page).offset((page - 1) * per_page)
|
138
|
+
end
|
118
139
|
end
|
119
140
|
end
|
120
141
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JPie
|
4
|
+
module Controller
|
5
|
+
module ErrorHandling
|
6
|
+
module HandlerSetup
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
# Allow applications to easily disable all JPie error handlers
|
11
|
+
def disable_jpie_error_handlers
|
12
|
+
self.jpie_error_handlers_enabled = false
|
13
|
+
# Remove any already-added handlers
|
14
|
+
remove_jpie_handlers
|
15
|
+
end
|
16
|
+
|
17
|
+
# Allow applications to enable specific handlers
|
18
|
+
def enable_jpie_error_handler(error_class, method_name = nil)
|
19
|
+
method_name ||= :"handle_#{error_class.name.demodulize.underscore}"
|
20
|
+
rescue_from error_class, with: method_name
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check for application-defined error handlers
|
24
|
+
def rescue_handler?(exception_class)
|
25
|
+
# Use Rails' rescue_handlers method to check for existing handlers
|
26
|
+
return false unless respond_to?(:rescue_handlers, true)
|
27
|
+
|
28
|
+
begin
|
29
|
+
rescue_handlers.any? { |handler| handler.first == exception_class.name }
|
30
|
+
rescue NoMethodError
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def setup_jpie_error_handlers
|
38
|
+
setup_jpie_specific_handlers
|
39
|
+
end
|
40
|
+
|
41
|
+
def setup_jpie_specific_handlers
|
42
|
+
setup_core_error_handlers
|
43
|
+
setup_activerecord_handlers
|
44
|
+
setup_json_api_compliance_handlers
|
45
|
+
end
|
46
|
+
|
47
|
+
def setup_core_error_handlers
|
48
|
+
return if rescue_handler?(JPie::Errors::Error)
|
49
|
+
|
50
|
+
rescue_from JPie::Errors::Error, with: :handle_jpie_error
|
51
|
+
end
|
52
|
+
|
53
|
+
def setup_activerecord_handlers
|
54
|
+
setup_not_found_handler
|
55
|
+
setup_invalid_record_handler
|
56
|
+
end
|
57
|
+
|
58
|
+
def setup_not_found_handler
|
59
|
+
return if rescue_handler?(ActiveRecord::RecordNotFound)
|
60
|
+
|
61
|
+
rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup_invalid_record_handler
|
65
|
+
return if rescue_handler?(ActiveRecord::RecordInvalid)
|
66
|
+
|
67
|
+
rescue_from ActiveRecord::RecordInvalid, with: :handle_record_invalid
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup_json_api_compliance_handlers
|
71
|
+
setup_json_api_request_handler
|
72
|
+
setup_include_handlers
|
73
|
+
setup_sort_handlers
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_json_api_request_handler
|
77
|
+
return if rescue_handler?(JPie::Errors::InvalidJsonApiRequestError)
|
78
|
+
|
79
|
+
rescue_from JPie::Errors::InvalidJsonApiRequestError, with: :handle_invalid_json_api_request
|
80
|
+
end
|
81
|
+
|
82
|
+
def setup_include_handlers
|
83
|
+
setup_unsupported_include_handler
|
84
|
+
setup_invalid_include_handler
|
85
|
+
end
|
86
|
+
|
87
|
+
def setup_unsupported_include_handler
|
88
|
+
return if rescue_handler?(JPie::Errors::UnsupportedIncludeError)
|
89
|
+
|
90
|
+
rescue_from JPie::Errors::UnsupportedIncludeError, with: :handle_unsupported_include
|
91
|
+
end
|
92
|
+
|
93
|
+
def setup_invalid_include_handler
|
94
|
+
return if rescue_handler?(JPie::Errors::InvalidIncludeParameterError)
|
95
|
+
|
96
|
+
rescue_from JPie::Errors::InvalidIncludeParameterError, with: :handle_invalid_include_parameter
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_sort_handlers
|
100
|
+
setup_unsupported_sort_handler
|
101
|
+
setup_invalid_sort_handler
|
102
|
+
end
|
103
|
+
|
104
|
+
def setup_unsupported_sort_handler
|
105
|
+
return if rescue_handler?(JPie::Errors::UnsupportedSortFieldError)
|
106
|
+
|
107
|
+
rescue_from JPie::Errors::UnsupportedSortFieldError, with: :handle_unsupported_sort_field
|
108
|
+
end
|
109
|
+
|
110
|
+
def setup_invalid_sort_handler
|
111
|
+
return if rescue_handler?(JPie::Errors::InvalidSortParameterError)
|
112
|
+
|
113
|
+
rescue_from JPie::Errors::InvalidSortParameterError, with: :handle_invalid_sort_parameter
|
114
|
+
end
|
115
|
+
|
116
|
+
def remove_jpie_handlers
|
117
|
+
# This is a placeholder - Rails doesn't provide an easy way to remove specific handlers
|
118
|
+
# In practice, applications should use the disable_jpie_error_handlers before including
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JPie
|
4
|
+
module Controller
|
5
|
+
module ErrorHandling
|
6
|
+
module Handlers
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Handle JPie-specific errors
|
12
|
+
def handle_jpie_error(error)
|
13
|
+
render_json_api_error(
|
14
|
+
status: error.status,
|
15
|
+
title: error.title,
|
16
|
+
detail: error.detail
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Handle ActiveRecord::RecordNotFound
|
21
|
+
def handle_record_not_found(error)
|
22
|
+
render_json_api_error(
|
23
|
+
status: 404,
|
24
|
+
title: 'Not Found',
|
25
|
+
detail: error.message
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Handle ActiveRecord::RecordInvalid
|
30
|
+
def handle_record_invalid(error)
|
31
|
+
errors = error.record.errors.full_messages.map do |message|
|
32
|
+
{
|
33
|
+
status: '422',
|
34
|
+
title: 'Validation Error',
|
35
|
+
detail: message
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
render json: { errors: errors }, status: :unprocessable_content
|
40
|
+
end
|
41
|
+
|
42
|
+
# Render a single JSON:API error
|
43
|
+
def render_json_api_error(status:, title:, detail:)
|
44
|
+
render json: {
|
45
|
+
errors: [{
|
46
|
+
status: status.to_s,
|
47
|
+
title: title,
|
48
|
+
detail: detail
|
49
|
+
}]
|
50
|
+
}, status: status
|
51
|
+
end
|
52
|
+
|
53
|
+
# Handle JSON:API compliance errors
|
54
|
+
def handle_invalid_json_api_request(error)
|
55
|
+
render_json_api_error(
|
56
|
+
status: error.status,
|
57
|
+
title: error.title || 'Invalid JSON:API Request',
|
58
|
+
detail: error.detail
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_unsupported_include(error)
|
63
|
+
render_json_api_error(
|
64
|
+
status: error.status,
|
65
|
+
title: error.title || 'Unsupported Include',
|
66
|
+
detail: error.detail
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def handle_unsupported_sort_field(error)
|
71
|
+
render_json_api_error(
|
72
|
+
status: error.status,
|
73
|
+
title: error.title || 'Unsupported Sort Field',
|
74
|
+
detail: error.detail
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
def handle_invalid_sort_parameter(error)
|
79
|
+
render_json_api_error(
|
80
|
+
status: error.status,
|
81
|
+
title: error.title || 'Invalid Sort Parameter',
|
82
|
+
detail: error.detail
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_invalid_include_parameter(error)
|
87
|
+
render_json_api_error(
|
88
|
+
status: error.status,
|
89
|
+
title: error.title || 'Invalid Include Parameter',
|
90
|
+
detail: error.detail
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Backward compatibility aliases
|
95
|
+
alias jpie_handle_error handle_jpie_error
|
96
|
+
alias jpie_handle_not_found handle_record_not_found
|
97
|
+
alias jpie_handle_invalid handle_record_invalid
|
98
|
+
|
99
|
+
# Legacy method name aliases
|
100
|
+
alias render_jpie_error handle_jpie_error
|
101
|
+
alias render_jpie_not_found_error handle_record_not_found
|
102
|
+
alias render_jpie_validation_error handle_record_invalid
|
103
|
+
alias render_jsonapi_error handle_jpie_error
|
104
|
+
alias render_not_found_error handle_record_not_found
|
105
|
+
alias render_validation_error handle_record_invalid
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'error_handling/handler_setup'
|
4
|
+
require_relative 'error_handling/handlers'
|
5
|
+
|
3
6
|
module JPie
|
4
7
|
module Controller
|
5
8
|
module ErrorHandling
|
6
9
|
extend ActiveSupport::Concern
|
7
10
|
|
11
|
+
include HandlerSetup
|
12
|
+
include Handlers
|
13
|
+
|
8
14
|
included do
|
9
15
|
# Use class_attribute to allow easy overriding
|
10
16
|
class_attribute :jpie_error_handlers_enabled, default: true
|
@@ -12,181 +18,6 @@ module JPie
|
|
12
18
|
# Set up default handlers unless explicitly disabled
|
13
19
|
setup_jpie_error_handlers if jpie_error_handlers_enabled
|
14
20
|
end
|
15
|
-
|
16
|
-
class_methods do
|
17
|
-
# Allow applications to easily disable all JPie error handlers
|
18
|
-
def disable_jpie_error_handlers
|
19
|
-
self.jpie_error_handlers_enabled = false
|
20
|
-
# Remove any already-added handlers
|
21
|
-
remove_jpie_handlers
|
22
|
-
end
|
23
|
-
|
24
|
-
# Allow applications to enable specific handlers
|
25
|
-
def enable_jpie_error_handler(error_class, method_name = nil)
|
26
|
-
method_name ||= :"handle_#{error_class.name.demodulize.underscore}"
|
27
|
-
rescue_from error_class, with: method_name
|
28
|
-
end
|
29
|
-
|
30
|
-
# Check for application-defined error handlers
|
31
|
-
def rescue_handler?(exception_class)
|
32
|
-
# Use Rails' rescue_handlers method to check for existing handlers
|
33
|
-
return false unless respond_to?(:rescue_handlers, true)
|
34
|
-
|
35
|
-
begin
|
36
|
-
rescue_handlers.any? { |handler| handler.first == exception_class.name }
|
37
|
-
rescue NoMethodError
|
38
|
-
false
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
def setup_jpie_error_handlers
|
45
|
-
setup_jpie_specific_handlers
|
46
|
-
end
|
47
|
-
|
48
|
-
def setup_jpie_specific_handlers
|
49
|
-
# Only add handlers if they don't already exist
|
50
|
-
rescue_from JPie::Errors::Error, with: :handle_jpie_error unless rescue_handler?(JPie::Errors::Error)
|
51
|
-
unless rescue_handler?(ActiveRecord::RecordNotFound)
|
52
|
-
rescue_from ActiveRecord::RecordNotFound,
|
53
|
-
with: :handle_record_not_found
|
54
|
-
end
|
55
|
-
return if rescue_handler?(ActiveRecord::RecordInvalid)
|
56
|
-
|
57
|
-
rescue_from ActiveRecord::RecordInvalid,
|
58
|
-
with: :handle_record_invalid
|
59
|
-
|
60
|
-
# JSON:API compliance error handlers
|
61
|
-
unless rescue_handler?(JPie::Errors::InvalidJsonApiRequestError)
|
62
|
-
rescue_from JPie::Errors::InvalidJsonApiRequestError,
|
63
|
-
with: :handle_invalid_json_api_request
|
64
|
-
end
|
65
|
-
|
66
|
-
unless rescue_handler?(JPie::Errors::UnsupportedIncludeError)
|
67
|
-
rescue_from JPie::Errors::UnsupportedIncludeError,
|
68
|
-
with: :handle_unsupported_include
|
69
|
-
end
|
70
|
-
|
71
|
-
unless rescue_handler?(JPie::Errors::UnsupportedSortFieldError)
|
72
|
-
rescue_from JPie::Errors::UnsupportedSortFieldError,
|
73
|
-
with: :handle_unsupported_sort_field
|
74
|
-
end
|
75
|
-
|
76
|
-
unless rescue_handler?(JPie::Errors::InvalidSortParameterError)
|
77
|
-
rescue_from JPie::Errors::InvalidSortParameterError,
|
78
|
-
with: :handle_invalid_sort_parameter
|
79
|
-
end
|
80
|
-
|
81
|
-
return if rescue_handler?(JPie::Errors::InvalidIncludeParameterError)
|
82
|
-
|
83
|
-
rescue_from JPie::Errors::InvalidIncludeParameterError,
|
84
|
-
with: :handle_invalid_include_parameter
|
85
|
-
end
|
86
|
-
|
87
|
-
def remove_jpie_handlers
|
88
|
-
# This is a placeholder - Rails doesn't provide an easy way to remove specific handlers
|
89
|
-
# In practice, applications should use the disable_jpie_error_handlers before including
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
private
|
94
|
-
|
95
|
-
# Handle JPie-specific errors
|
96
|
-
def handle_jpie_error(error)
|
97
|
-
render_json_api_error(
|
98
|
-
status: error.status,
|
99
|
-
title: error.title,
|
100
|
-
detail: error.detail
|
101
|
-
)
|
102
|
-
end
|
103
|
-
|
104
|
-
# Handle ActiveRecord::RecordNotFound
|
105
|
-
def handle_record_not_found(error)
|
106
|
-
render_json_api_error(
|
107
|
-
status: 404,
|
108
|
-
title: 'Not Found',
|
109
|
-
detail: error.message
|
110
|
-
)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Handle ActiveRecord::RecordInvalid
|
114
|
-
def handle_record_invalid(error)
|
115
|
-
errors = error.record.errors.full_messages.map do |message|
|
116
|
-
{
|
117
|
-
status: '422',
|
118
|
-
title: 'Validation Error',
|
119
|
-
detail: message
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
|
-
render json: { errors: errors }, status: :unprocessable_content
|
124
|
-
end
|
125
|
-
|
126
|
-
# Render a single JSON:API error
|
127
|
-
def render_json_api_error(status:, title:, detail:)
|
128
|
-
render json: {
|
129
|
-
errors: [{
|
130
|
-
status: status.to_s,
|
131
|
-
title: title,
|
132
|
-
detail: detail
|
133
|
-
}]
|
134
|
-
}, status: status
|
135
|
-
end
|
136
|
-
|
137
|
-
# Handle JSON:API compliance errors
|
138
|
-
def handle_invalid_json_api_request(error)
|
139
|
-
render_json_api_error(
|
140
|
-
status: error.status,
|
141
|
-
title: error.title || 'Invalid JSON:API Request',
|
142
|
-
detail: error.detail
|
143
|
-
)
|
144
|
-
end
|
145
|
-
|
146
|
-
def handle_unsupported_include(error)
|
147
|
-
render_json_api_error(
|
148
|
-
status: error.status,
|
149
|
-
title: error.title || 'Unsupported Include',
|
150
|
-
detail: error.detail
|
151
|
-
)
|
152
|
-
end
|
153
|
-
|
154
|
-
def handle_unsupported_sort_field(error)
|
155
|
-
render_json_api_error(
|
156
|
-
status: error.status,
|
157
|
-
title: error.title || 'Unsupported Sort Field',
|
158
|
-
detail: error.detail
|
159
|
-
)
|
160
|
-
end
|
161
|
-
|
162
|
-
def handle_invalid_sort_parameter(error)
|
163
|
-
render_json_api_error(
|
164
|
-
status: error.status,
|
165
|
-
title: error.title || 'Invalid Sort Parameter',
|
166
|
-
detail: error.detail
|
167
|
-
)
|
168
|
-
end
|
169
|
-
|
170
|
-
def handle_invalid_include_parameter(error)
|
171
|
-
render_json_api_error(
|
172
|
-
status: error.status,
|
173
|
-
title: error.title || 'Invalid Include Parameter',
|
174
|
-
detail: error.detail
|
175
|
-
)
|
176
|
-
end
|
177
|
-
|
178
|
-
# Backward compatibility aliases
|
179
|
-
alias jpie_handle_error handle_jpie_error
|
180
|
-
alias jpie_handle_not_found handle_record_not_found
|
181
|
-
alias jpie_handle_invalid handle_record_invalid
|
182
|
-
|
183
|
-
# Legacy method name aliases
|
184
|
-
alias render_jpie_error handle_jpie_error
|
185
|
-
alias render_jpie_not_found_error handle_record_not_found
|
186
|
-
alias render_jpie_validation_error handle_record_invalid
|
187
|
-
alias render_jsonapi_error handle_jpie_error
|
188
|
-
alias render_not_found_error handle_record_not_found
|
189
|
-
alias render_validation_error handle_record_invalid
|
190
21
|
end
|
191
22
|
end
|
192
23
|
end
|
@@ -28,26 +28,34 @@ module JPie
|
|
28
28
|
|
29
29
|
# Validate basic JSON:API request structure
|
30
30
|
def validate_json_api_structure
|
31
|
+
request_body = read_request_body
|
32
|
+
return if request_body.blank?
|
33
|
+
|
34
|
+
parsed_body = parse_json_body(request_body)
|
35
|
+
validate_top_level_structure(parsed_body)
|
36
|
+
validate_data_structure(parsed_body['data'])
|
37
|
+
end
|
38
|
+
|
39
|
+
def read_request_body
|
31
40
|
request_body = request.body.read
|
32
41
|
request.body.rewind # Reset for later reading
|
42
|
+
request_body
|
43
|
+
end
|
33
44
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
42
|
-
end
|
45
|
+
def parse_json_body(request_body)
|
46
|
+
JSON.parse(request_body)
|
47
|
+
rescue JSON::ParserError => e
|
48
|
+
raise JPie::Errors::InvalidJsonApiRequestError.new(
|
49
|
+
detail: "Invalid JSON: #{e.message}"
|
50
|
+
)
|
51
|
+
end
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
detail: 'JSON:API request must have a top-level "data" member'
|
47
|
-
)
|
48
|
-
end
|
53
|
+
def validate_top_level_structure(parsed_body)
|
54
|
+
return if parsed_body.is_a?(Hash) && parsed_body.key?('data')
|
49
55
|
|
50
|
-
|
56
|
+
raise JPie::Errors::InvalidJsonApiRequestError.new(
|
57
|
+
detail: 'JSON:API request must have a top-level "data" member'
|
58
|
+
)
|
51
59
|
end
|
52
60
|
|
53
61
|
# Validate the structure of the data member
|
@@ -102,30 +110,43 @@ module JPie
|
|
102
110
|
|
103
111
|
# Validate a single include path
|
104
112
|
def validate_include_path(include_path, supported_includes)
|
105
|
-
# Handle nested includes (e.g., "posts.comments")
|
106
113
|
path_parts = include_path.split('.')
|
107
114
|
current_level = supported_includes
|
108
115
|
|
109
116
|
path_parts.each_with_index do |part, index|
|
110
|
-
|
111
|
-
|
112
|
-
available_at_level = current_level.is_a?(Hash) ? current_level.keys : current_level
|
113
|
-
|
114
|
-
raise JPie::Errors::UnsupportedIncludeError.new(
|
115
|
-
include_path: current_path,
|
116
|
-
supported_includes: available_at_level.map(&:to_s)
|
117
|
-
)
|
118
|
-
end
|
119
|
-
|
120
|
-
# Move to next level for nested validation
|
121
|
-
if current_level.is_a?(Hash) && current_level[part.to_sym].is_a?(Hash)
|
122
|
-
current_level = current_level[part.to_sym]
|
123
|
-
elsif current_level.is_a?(Hash) && current_level[part].is_a?(Hash)
|
124
|
-
current_level = current_level[part]
|
125
|
-
end
|
117
|
+
validate_include_part(part, current_level, path_parts, index)
|
118
|
+
current_level = move_to_next_include_level(part, current_level)
|
126
119
|
end
|
127
120
|
end
|
128
121
|
|
122
|
+
def validate_include_part(part, current_level, path_parts, index)
|
123
|
+
return if include_part_supported?(part, current_level)
|
124
|
+
|
125
|
+
current_path = path_parts[0..index].join('.')
|
126
|
+
available_at_level = extract_available_includes(current_level)
|
127
|
+
|
128
|
+
raise JPie::Errors::UnsupportedIncludeError.new(
|
129
|
+
include_path: current_path,
|
130
|
+
supported_includes: available_at_level.map(&:to_s)
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def include_part_supported?(part, current_level)
|
135
|
+
current_level.include?(part.to_sym) || current_level.include?(part)
|
136
|
+
end
|
137
|
+
|
138
|
+
def extract_available_includes(current_level)
|
139
|
+
current_level.is_a?(Hash) ? current_level.keys : current_level
|
140
|
+
end
|
141
|
+
|
142
|
+
def move_to_next_include_level(part, current_level)
|
143
|
+
return current_level unless current_level.is_a?(Hash)
|
144
|
+
|
145
|
+
current_level[part.to_sym] if current_level[part.to_sym].is_a?(Hash)
|
146
|
+
current_level[part] if current_level[part].is_a?(Hash)
|
147
|
+
current_level
|
148
|
+
end
|
149
|
+
|
129
150
|
# Validate sort parameters against supported fields
|
130
151
|
def validate_sort_params
|
131
152
|
return if params[:sort].blank?
|
@@ -148,7 +169,8 @@ module JPie
|
|
148
169
|
# Validate field name format
|
149
170
|
unless field_name.match?(/\A[a-zA-Z][a-zA-Z0-9_]*\z/)
|
150
171
|
raise JPie::Errors::InvalidSortParameterError.new(
|
151
|
-
detail: "Invalid sort field format: '#{sort_field}'.
|
172
|
+
detail: "Invalid sort field format: '#{sort_field}'. " \
|
173
|
+
'Field names must start with a letter and contain only letters, numbers, and underscores'
|
152
174
|
)
|
153
175
|
end
|
154
176
|
|
@@ -11,6 +11,16 @@ module JPie
|
|
11
11
|
params[:sort]&.split(',')&.map(&:strip) || []
|
12
12
|
end
|
13
13
|
|
14
|
+
def parse_pagination_params
|
15
|
+
page_params = params[:page] || {}
|
16
|
+
per_page_param = params[:per_page]
|
17
|
+
|
18
|
+
{
|
19
|
+
page: extract_page_number(page_params, per_page_param),
|
20
|
+
per_page: extract_per_page_size(page_params, per_page_param)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
14
24
|
def deserialize_params
|
15
25
|
deserializer.deserialize(request.body.read, context)
|
16
26
|
rescue JSON::ParserError => e
|
@@ -30,6 +40,39 @@ module JPie
|
|
30
40
|
action: action_name
|
31
41
|
}
|
32
42
|
end
|
43
|
+
|
44
|
+
def extract_page_number(page_params, per_page_param)
|
45
|
+
page_number = determine_page_number(page_params)
|
46
|
+
page_number = '1' if page_number.nil? && per_page_param.present?
|
47
|
+
return 1 if page_number.blank?
|
48
|
+
|
49
|
+
parsed_page = page_number.to_i
|
50
|
+
parsed_page.positive? ? parsed_page : 1
|
51
|
+
end
|
52
|
+
|
53
|
+
def extract_per_page_size(page_params, per_page_param)
|
54
|
+
per_page_size = determine_per_page_size(page_params, per_page_param)
|
55
|
+
return nil if per_page_size.blank?
|
56
|
+
|
57
|
+
parsed_size = per_page_size.to_i
|
58
|
+
parsed_size.positive? ? parsed_size : nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def determine_page_number(page_params)
|
62
|
+
if page_params.is_a?(String) || page_params.is_a?(Integer)
|
63
|
+
page_params
|
64
|
+
else
|
65
|
+
page_params[:number] || page_params['number']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def determine_per_page_size(page_params, per_page_param)
|
70
|
+
if page_params.is_a?(String) || page_params.is_a?(Integer)
|
71
|
+
per_page_param
|
72
|
+
else
|
73
|
+
page_params[:size] || page_params['size'] || per_page_param
|
74
|
+
end
|
75
|
+
end
|
33
76
|
end
|
34
77
|
end
|
35
78
|
end
|