jpie 3.0.3 → 3.1.0
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/.github/workflows/ci.yml +27 -0
- data/Gemfile.lock +1 -1
- data/lib/json_api/controllers/base_controller.rb +1 -0
- data/lib/json_api/controllers/concerns/controller_helpers/parsing.rb +28 -14
- data/lib/json_api/serialization/concerns/deserialization_helpers.rb +19 -0
- data/lib/json_api/serialization/concerns/relationship_processing.rb +1 -0
- data/lib/json_api/support/concerns/regular_filters.rb +1 -1
- data/lib/json_api/version.rb +1 -1
- metadata +2 -2
- data/.travis.yml +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67ba9f4553fe73ffa121c436107bb030327211038e4e96130fabb48d585419b4
|
|
4
|
+
data.tar.gz: 41cd0fa175eb9ddaca6287945aa81b7e2be49349e2db05d1fbfa4bfcf4fc78f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9ad6c4938d4132a0730ac1f2872f66658ffbf23ae3814f923ede8cd63d57b87dfffaab8843c40c3aee1a38b3d524ab4fca1ce15a805e9d4fddc5db7861b26b73
|
|
7
|
+
data.tar.gz: ec1fc23309a91b8e38f9720cedf255f908585d01b952e62ae2b78f87398e13119b50cd0b14507e401f2141e2c28c70beae6b060d7734db7d5621365cf6745e9a
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
rubocop:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v5
|
|
13
|
+
- uses: ruby/setup-ruby@v1
|
|
14
|
+
with:
|
|
15
|
+
ruby-version: "3.4"
|
|
16
|
+
bundler-cache: true
|
|
17
|
+
- run: bundle exec rubocop
|
|
18
|
+
|
|
19
|
+
rspec:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v5
|
|
23
|
+
- uses: ruby/setup-ruby@v1
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: "3.4"
|
|
26
|
+
bundler-cache: true
|
|
27
|
+
- run: bundle exec rspec
|
data/Gemfile.lock
CHANGED
|
@@ -8,6 +8,7 @@ module JSONAPI
|
|
|
8
8
|
rescue_from JSONAPI::AuthorizationError, with: :render_jsonapi_authorization_error
|
|
9
9
|
rescue_from Pundit::NotAuthorizedError, with: :render_jsonapi_authorization_error if defined?(Pundit)
|
|
10
10
|
rescue_from JSONAPI::Support::Sort::InvalidFieldError, with: :render_invalid_sort_field_error
|
|
11
|
+
rescue_from JSONAPI::Errors::ParameterNotAllowed, with: :render_parameter_not_allowed_error
|
|
11
12
|
|
|
12
13
|
private
|
|
13
14
|
|
|
@@ -39,9 +39,7 @@ module JSONAPI
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def jsonapi_params
|
|
42
|
-
data = params.require(:data)
|
|
43
|
-
return data if data.is_a?(Array)
|
|
44
|
-
|
|
42
|
+
data = require_query_object(params.require(:data), "data")
|
|
45
43
|
permitted = data.permit(:type, :id, attributes: {})
|
|
46
44
|
permitted[:relationships] = permit_relationships(data) if data[:relationships].present?
|
|
47
45
|
permitted
|
|
@@ -64,17 +62,11 @@ module JSONAPI
|
|
|
64
62
|
end
|
|
65
63
|
|
|
66
64
|
def jsonapi_type
|
|
67
|
-
|
|
68
|
-
return data.first[:type] if data.is_a?(Array)
|
|
69
|
-
|
|
70
|
-
data[:type]
|
|
65
|
+
jsonapi_params[:type]
|
|
71
66
|
end
|
|
72
67
|
|
|
73
68
|
def jsonapi_id
|
|
74
|
-
|
|
75
|
-
return data.first[:id].to_s.presence if data.is_a?(Array)
|
|
76
|
-
|
|
77
|
-
data[:id].to_s.presence
|
|
69
|
+
jsonapi_params[:id].to_s.presence
|
|
78
70
|
end
|
|
79
71
|
|
|
80
72
|
def parse_include_param
|
|
@@ -86,7 +78,7 @@ module JSONAPI
|
|
|
86
78
|
def parse_fields_param
|
|
87
79
|
return {} unless params[:fields]
|
|
88
80
|
|
|
89
|
-
params[:fields].permit!.to_h.each_with_object({}) do |(type, fields), hash|
|
|
81
|
+
require_query_object(params[:fields], "fields").permit!.to_h.each_with_object({}) do |(type, fields), hash|
|
|
90
82
|
hash[type.to_sym] = fields.to_s.split(",").map(&:strip)
|
|
91
83
|
end
|
|
92
84
|
end
|
|
@@ -94,7 +86,7 @@ module JSONAPI
|
|
|
94
86
|
def parse_filter_param
|
|
95
87
|
return {} unless params[:filter]
|
|
96
88
|
|
|
97
|
-
raw_filters = params[:filter].permit!.to_h
|
|
89
|
+
raw_filters = require_query_object(params[:filter], "filter").permit!.to_h
|
|
98
90
|
JSONAPI::ParamHelpers.flatten_filter_hash(raw_filters)
|
|
99
91
|
end
|
|
100
92
|
|
|
@@ -107,7 +99,29 @@ module JSONAPI
|
|
|
107
99
|
def parse_page_param
|
|
108
100
|
return {} unless params[:page]
|
|
109
101
|
|
|
110
|
-
params[:page].permit(:number, :size).to_h
|
|
102
|
+
page = require_query_object(params[:page], "page").permit(:number, :size).to_h
|
|
103
|
+
page.each { |key, value| require_positive_integer(value, "page[#{key}]") }
|
|
104
|
+
page
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# JSON:API query family params (filter/page/fields) are objects. A client can
|
|
108
|
+
# instead send a scalar (?filter=x) or array (?filter[]=x), which Rails parses
|
|
109
|
+
# as a String/Array that does not respond to permit!/permit. Reject those as a
|
|
110
|
+
# bad request rather than letting the NoMethodError escape as a 500.
|
|
111
|
+
def require_query_object(value, name)
|
|
112
|
+
return value if value.is_a?(ActionController::Parameters)
|
|
113
|
+
|
|
114
|
+
raise JSONAPI::Errors::ParameterNotAllowed, ["#{name} must be a JSON object"]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# page[number]/page[size] are coerced with to_i and used as a divisor and as
|
|
118
|
+
# LIMIT/OFFSET. A non-positive or non-numeric value (0, -5, "abc") produces a
|
|
119
|
+
# divide-by-zero (FloatDomainError) or a negative LIMIT/OFFSET. Reject it as a
|
|
120
|
+
# bad request instead of letting the raise escape as a 500.
|
|
121
|
+
def require_positive_integer(value, name)
|
|
122
|
+
return if value.to_s.match?(/\A\d+\z/) && value.to_i.positive?
|
|
123
|
+
|
|
124
|
+
raise JSONAPI::Errors::ParameterNotAllowed, ["#{name} must be a positive integer"]
|
|
111
125
|
end
|
|
112
126
|
|
|
113
127
|
def render_sort_errors(invalid)
|
|
@@ -69,6 +69,25 @@ module JSONAPI
|
|
|
69
69
|
type
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
+
# jpie routes a relationship to the to-one or to-many path based on the shape of
|
|
73
|
+
# the incoming data, not the association definition. A to-one association given an
|
|
74
|
+
# array (or a to-many given a single identifier) lands on the wrong setter
|
|
75
|
+
# (e.g. user_ids= for a belongs_to) and raises UnknownAttributeError. Reject the
|
|
76
|
+
# mismatch as a client error instead of letting it escape as a 500. AS attachments
|
|
77
|
+
# have no plain reflection and are validated separately, so they are skipped here.
|
|
78
|
+
def validate_relationship_arity!(association_name, data)
|
|
79
|
+
association = @model_class&.reflect_on_association(association_name.to_sym)
|
|
80
|
+
return unless association
|
|
81
|
+
|
|
82
|
+
if association.collection? && !data.is_a?(Array)
|
|
83
|
+
raise ArgumentError,
|
|
84
|
+
"Relationship #{association_name} is to-many and expects an array of resource identifiers"
|
|
85
|
+
elsif !association.collection? && data.is_a?(Array)
|
|
86
|
+
raise ArgumentError,
|
|
87
|
+
"Relationship #{association_name} is to-one and expects a single resource identifier, not an array"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
72
91
|
def valid_relationship_data?(data)
|
|
73
92
|
if data.is_a?(Array)
|
|
74
93
|
data.all? { |r| valid_resource_identifier?(r) }
|
|
@@ -14,6 +14,7 @@ module JSONAPI
|
|
|
14
14
|
return handle_empty_array_relationship(attrs, param_name, association_name) if ParamHelpers.empty_array?(data)
|
|
15
15
|
|
|
16
16
|
validate_relationship_data_format!(data, association_name)
|
|
17
|
+
validate_relationship_arity!(association_name, data)
|
|
17
18
|
process_relationship_data(attrs, association_name, param_name, data)
|
|
18
19
|
end
|
|
19
20
|
|
data/lib/json_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jpie
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -93,10 +93,10 @@ files:
|
|
|
93
93
|
- ".cursor/agents/nasa-power-of-ten-reviewer.md"
|
|
94
94
|
- ".cursor/agents/systematic-debugging.md"
|
|
95
95
|
- ".cursor/rules/release.mdc"
|
|
96
|
+
- ".github/workflows/ci.yml"
|
|
96
97
|
- ".gitignore"
|
|
97
98
|
- ".rspec"
|
|
98
99
|
- ".rubocop.yml"
|
|
99
|
-
- ".travis.yml"
|
|
100
100
|
- Gemfile
|
|
101
101
|
- Gemfile.lock
|
|
102
102
|
- PERFORMANCE_BASELINE.md
|