jpie 0.1.0 → 0.3.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/.aiconfig +65 -0
- data/.rubocop.yml +110 -35
- data/CHANGELOG.md +93 -0
- data/LICENSE.txt +21 -0
- data/README.md +776 -1903
- data/Rakefile +14 -3
- data/jpie.gemspec +35 -18
- data/lib/jpie/configuration.rb +12 -0
- data/lib/jpie/controller/crud_actions.rb +110 -0
- data/lib/jpie/controller/error_handling.rb +41 -0
- data/lib/jpie/controller/parameter_parsing.rb +35 -0
- data/lib/jpie/controller/rendering.rb +60 -0
- data/lib/jpie/controller.rb +18 -0
- data/lib/jpie/deserializer.rb +110 -0
- data/lib/jpie/errors.rb +70 -0
- data/lib/jpie/generators/resource_generator.rb +39 -0
- data/lib/jpie/generators/templates/resource.rb.erb +12 -0
- data/lib/jpie/railtie.rb +36 -0
- data/lib/jpie/resource/attributable.rb +98 -0
- data/lib/jpie/resource/inferrable.rb +43 -0
- data/lib/jpie/resource/sortable.rb +93 -0
- data/lib/jpie/resource.rb +107 -0
- data/lib/jpie/serializer.rb +205 -0
- data/lib/{json_api → jpie}/version.rb +2 -2
- data/lib/jpie.rb +23 -3
- metadata +145 -50
- data/.gitignore +0 -21
- data/.rspec +0 -3
- data/.travis.yml +0 -7
- data/Gemfile +0 -21
- data/Gemfile.lock +0 -312
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/kiln/app/resources/user_message_resource.rb +0 -2
- data/lib/json_api/active_storage/deserialization.rb +0 -106
- data/lib/json_api/active_storage/detection.rb +0 -74
- data/lib/json_api/active_storage/serialization.rb +0 -32
- data/lib/json_api/configuration.rb +0 -58
- data/lib/json_api/controllers/base_controller.rb +0 -26
- data/lib/json_api/controllers/concerns/controller_helpers.rb +0 -223
- data/lib/json_api/controllers/concerns/resource_actions.rb +0 -657
- data/lib/json_api/controllers/relationships_controller.rb +0 -504
- data/lib/json_api/controllers/resources_controller.rb +0 -6
- data/lib/json_api/errors/parameter_not_allowed.rb +0 -19
- data/lib/json_api/railtie.rb +0 -75
- data/lib/json_api/resources/active_storage_blob_resource.rb +0 -11
- data/lib/json_api/resources/resource.rb +0 -238
- data/lib/json_api/resources/resource_loader.rb +0 -35
- data/lib/json_api/routing.rb +0 -72
- data/lib/json_api/serialization/deserializer.rb +0 -362
- data/lib/json_api/serialization/serializer.rb +0 -320
- data/lib/json_api/support/active_storage_support.rb +0 -85
- data/lib/json_api/support/collection_query.rb +0 -406
- data/lib/json_api/support/instrumentation.rb +0 -42
- data/lib/json_api/support/param_helpers.rb +0 -51
- data/lib/json_api/support/relationship_guard.rb +0 -16
- data/lib/json_api/support/relationship_helpers.rb +0 -74
- data/lib/json_api/support/resource_identifier.rb +0 -87
- data/lib/json_api/support/responders.rb +0 -100
- data/lib/json_api/support/response_helpers.rb +0 -10
- data/lib/json_api/support/sort_parsing.rb +0 -21
- data/lib/json_api/support/type_conversion.rb +0 -21
- data/lib/json_api/testing/test_helper.rb +0 -76
- data/lib/json_api/testing.rb +0 -3
- data/lib/json_api.rb +0 -50
- data/lib/rubocop/cop/custom/hash_value_omission.rb +0 -53
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_support/concern"
|
|
4
|
-
require "rack/utils"
|
|
5
|
-
|
|
6
|
-
module JSONAPI
|
|
7
|
-
module Responders
|
|
8
|
-
extend ActiveSupport::Concern
|
|
9
|
-
|
|
10
|
-
included do
|
|
11
|
-
before_action :ensure_jsonapi_content_type, if: -> { request.post? || request.patch? || request.put? }
|
|
12
|
-
before_action :ensure_jsonapi_accept_header
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
def ensure_jsonapi_content_type
|
|
18
|
-
return if request.content_type&.include?("application/vnd.api+json")
|
|
19
|
-
|
|
20
|
-
render json: {
|
|
21
|
-
errors: [
|
|
22
|
-
{
|
|
23
|
-
status: "415",
|
|
24
|
-
title: "Unsupported Media Type",
|
|
25
|
-
detail: "Content-Type must be application/vnd.api+json"
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
}, status: :unsupported_media_type
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def ensure_jsonapi_accept_header
|
|
32
|
-
# Allow requests without Accept header or with */* (browser defaults)
|
|
33
|
-
# Only validate when Accept header is explicitly set to non-JSON:API media types
|
|
34
|
-
accept_header = request.headers["Accept"]
|
|
35
|
-
|
|
36
|
-
# Allow blank Accept header (browser default)
|
|
37
|
-
return if accept_header.blank?
|
|
38
|
-
|
|
39
|
-
# Allow */* Accept header (browser default)
|
|
40
|
-
return if accept_header == "*/*"
|
|
41
|
-
|
|
42
|
-
# Check if request accepts */* (wildcard)
|
|
43
|
-
return if request.accepts.any? { |mime| mime.to_s == "*/*" }
|
|
44
|
-
|
|
45
|
-
# Check if JSON:API media type is explicitly requested
|
|
46
|
-
return if jsonapi_requested?
|
|
47
|
-
|
|
48
|
-
# If Accept header is present and doesn't include JSON:API, return 406
|
|
49
|
-
# This ensures we honor explicit Accept preferences while allowing defaults
|
|
50
|
-
render_not_acceptable_error
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def jsonapi_requested?
|
|
54
|
-
request.accepts.any? { |mime| mime.to_s.include?("application/vnd.api+json") }
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def render_not_acceptable_error
|
|
58
|
-
render_parameter_errors(
|
|
59
|
-
[nil],
|
|
60
|
-
title: "Not Acceptable",
|
|
61
|
-
detail_proc: ->(_) { "Accept header must include application/vnd.api+json or be omitted" },
|
|
62
|
-
status: :not_acceptable
|
|
63
|
-
)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def render_jsonapi_error(status:, title:, detail: nil, source: nil)
|
|
67
|
-
render_jsonapi_errors([{ status:, title:, detail:, source: }], status:)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def render_jsonapi_errors(errors, status:)
|
|
71
|
-
normalized_errors = Array(errors).map do |error|
|
|
72
|
-
normalized = error.compact
|
|
73
|
-
normalized_status = normalized[:status] || status
|
|
74
|
-
normalized[:status] = status_code_for(normalized_status)
|
|
75
|
-
normalized
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
render json: { errors: normalized_errors }, status:
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def render_parameter_errors(values, title:, detail_proc:, source_proc: nil, status: :bad_request)
|
|
82
|
-
errors = Array(values).map do |value|
|
|
83
|
-
error = {
|
|
84
|
-
title:,
|
|
85
|
-
detail: detail_proc.call(value)
|
|
86
|
-
}
|
|
87
|
-
error[:source] = source_proc.call(value) if source_proc
|
|
88
|
-
error
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
render_jsonapi_errors(errors, status:)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def status_code_for(status)
|
|
95
|
-
return status if status.is_a?(String) && status.match?(/\A\d+\z/)
|
|
96
|
-
|
|
97
|
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(status, status).to_s
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module JSONAPI
|
|
4
|
-
module SortParsing
|
|
5
|
-
module_function
|
|
6
|
-
|
|
7
|
-
def parse_sort_field(sort_field)
|
|
8
|
-
descending = sort_field.start_with?("-")
|
|
9
|
-
field = descending ? sort_field[1..] : sort_field
|
|
10
|
-
{ field:, direction: descending ? :desc : :asc }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def extract_sort_field_name(sort_field)
|
|
14
|
-
parse_sort_field(sort_field)[:field]
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def extract_sort_direction(sort_field)
|
|
18
|
-
parse_sort_field(sort_field)[:direction]
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module JSONAPI
|
|
4
|
-
module TypeConversion
|
|
5
|
-
module_function
|
|
6
|
-
|
|
7
|
-
def type_to_class_name(type)
|
|
8
|
-
type.to_s.singularize.classify
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def model_type_name(model_class)
|
|
12
|
-
model_class.name.underscore.pluralize
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def resource_type_name(definition_class)
|
|
16
|
-
type_name = definition_class.name.sub(/Resource$/, "").underscore.pluralize
|
|
17
|
-
# Remove namespace prefix if present (e.g., "json_api/active_storage_blobs" -> "active_storage_blobs")
|
|
18
|
-
type_name.split("/").last
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module JSONAPI
|
|
4
|
-
module Testing
|
|
5
|
-
# Test helper for Rails integration/request tests that provides consistent
|
|
6
|
-
# `as: :jsonapi` behavior across all HTTP methods.
|
|
7
|
-
#
|
|
8
|
-
# Usage in RSpec:
|
|
9
|
-
# RSpec.configure do |config|
|
|
10
|
-
# config.include JSONAPI::Testing::TestHelper, type: :request
|
|
11
|
-
# end
|
|
12
|
-
#
|
|
13
|
-
# Then use `as: :jsonapi` in your tests:
|
|
14
|
-
# get users_path, params: { filter: { active: true } }, headers:, as: :jsonapi
|
|
15
|
-
# post users_path, params: payload, headers:, as: :jsonapi
|
|
16
|
-
#
|
|
17
|
-
# Behavior:
|
|
18
|
-
# - GET: Sets Accept header, params go to query string (no body encoding)
|
|
19
|
-
# - POST/PATCH/PUT/DELETE: Sets Content-Type and Accept headers, JSON-encodes body
|
|
20
|
-
#
|
|
21
|
-
module TestHelper
|
|
22
|
-
JSONAPI_MIME = "application/vnd.api+json"
|
|
23
|
-
|
|
24
|
-
def get(path, **options)
|
|
25
|
-
if options[:as] == :jsonapi
|
|
26
|
-
options = apply_jsonapi_headers(options, include_content_type: false)
|
|
27
|
-
options.delete(:as)
|
|
28
|
-
end
|
|
29
|
-
super
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def post(path, **options)
|
|
33
|
-
options = apply_jsonapi_options_for_body(options) if options[:as] == :jsonapi
|
|
34
|
-
super
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def patch(path, **options)
|
|
38
|
-
options = apply_jsonapi_options_for_body(options) if options[:as] == :jsonapi
|
|
39
|
-
super
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def put(path, **options)
|
|
43
|
-
options = apply_jsonapi_options_for_body(options) if options[:as] == :jsonapi
|
|
44
|
-
super
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def delete(path, **options)
|
|
48
|
-
options = apply_jsonapi_options_for_body(options) if options[:as] == :jsonapi
|
|
49
|
-
super
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def apply_jsonapi_headers(options, include_content_type: true)
|
|
55
|
-
options = options.dup
|
|
56
|
-
headers = (options[:headers] || {}).dup
|
|
57
|
-
|
|
58
|
-
headers["Accept"] = JSONAPI_MIME
|
|
59
|
-
headers["Content-Type"] = JSONAPI_MIME if include_content_type
|
|
60
|
-
|
|
61
|
-
options[:headers] = headers
|
|
62
|
-
options
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def apply_jsonapi_options_for_body(options)
|
|
66
|
-
options = apply_jsonapi_headers(options, include_content_type: true)
|
|
67
|
-
options.delete(:as)
|
|
68
|
-
|
|
69
|
-
# JSON-encode params for request body
|
|
70
|
-
options[:params] = options[:params].to_json if options[:params].is_a?(Hash)
|
|
71
|
-
|
|
72
|
-
options
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
data/lib/json_api/testing.rb
DELETED
data/lib/json_api.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json_api/version"
|
|
4
|
-
require "json_api/configuration"
|
|
5
|
-
|
|
6
|
-
module JSONAPI
|
|
7
|
-
class Error < StandardError; end
|
|
8
|
-
class AuthorizationError < Error; end
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
require "json_api/errors/parameter_not_allowed"
|
|
12
|
-
|
|
13
|
-
module JSONAPI
|
|
14
|
-
# Rebuild BaseController and RelationshipsController to reflect the current
|
|
15
|
-
# base_controller_class configuration. Safe to call repeatedly.
|
|
16
|
-
def self.rebuild_base_controllers!
|
|
17
|
-
remove_const(:BaseController) if const_defined?(:BaseController)
|
|
18
|
-
load "json_api/controllers/base_controller.rb"
|
|
19
|
-
|
|
20
|
-
remove_const(:RelationshipsController) if const_defined?(:RelationshipsController)
|
|
21
|
-
load "json_api/controllers/relationships_controller.rb"
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
require "json_api/resources/resource"
|
|
26
|
-
require "json_api/resources/resource_loader"
|
|
27
|
-
require "json_api/support/type_conversion"
|
|
28
|
-
require "json_api/support/sort_parsing"
|
|
29
|
-
require "json_api/support/resource_identifier"
|
|
30
|
-
require "json_api/support/relationship_helpers"
|
|
31
|
-
require "json_api/support/param_helpers"
|
|
32
|
-
require "json_api/active_storage/detection"
|
|
33
|
-
require "json_api/active_storage/serialization"
|
|
34
|
-
require "json_api/active_storage/deserialization"
|
|
35
|
-
require "json_api/support/active_storage_support"
|
|
36
|
-
require "json_api/support/collection_query"
|
|
37
|
-
require "json_api/routing"
|
|
38
|
-
require "json_api/support/responders"
|
|
39
|
-
require "json_api/support/instrumentation"
|
|
40
|
-
require "json_api/serialization/serializer"
|
|
41
|
-
require "json_api/serialization/deserializer"
|
|
42
|
-
require "json_api/support/response_helpers"
|
|
43
|
-
require "json_api/controllers/concerns/controller_helpers"
|
|
44
|
-
require "json_api/controllers/concerns/resource_actions"
|
|
45
|
-
require "json_api/controllers/base_controller"
|
|
46
|
-
require "json_api/controllers/resources_controller"
|
|
47
|
-
require "json_api/controllers/relationships_controller"
|
|
48
|
-
require "json_api/resources/active_storage_blob_resource" if defined?(ActiveStorage)
|
|
49
|
-
|
|
50
|
-
require "json_api/railtie" if defined?(Rails)
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RuboCop
|
|
4
|
-
module Cop
|
|
5
|
-
module Custom
|
|
6
|
-
# Enforces hash value omission syntax introduced in Ruby 3.1
|
|
7
|
-
# When a hash key and value have the same name, use the shorthand syntax.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# # bad
|
|
11
|
-
# { controller: controller }
|
|
12
|
-
# { foo: foo, bar: bar }
|
|
13
|
-
#
|
|
14
|
-
# # good
|
|
15
|
-
# { controller: }
|
|
16
|
-
# { foo:, bar: }
|
|
17
|
-
class HashValueOmission < Base
|
|
18
|
-
extend AutoCorrector
|
|
19
|
-
|
|
20
|
-
MSG = "Use hash value omission (`%<key>s:`) when the value is the same-named local variable."
|
|
21
|
-
|
|
22
|
-
def on_pair(node)
|
|
23
|
-
return unless valid_pair_for_omission?(node)
|
|
24
|
-
|
|
25
|
-
key_name = node.key.value.to_s
|
|
26
|
-
return if already_omitted?(node, key_name)
|
|
27
|
-
return unless same_name?(node, key_name)
|
|
28
|
-
|
|
29
|
-
add_offense(node, message: format(MSG, key: key_name)) do |corrector|
|
|
30
|
-
corrector.replace(node.source_range, "#{key_name}:")
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def valid_pair_for_omission?(node)
|
|
35
|
-
key = node.key
|
|
36
|
-
value = node.value
|
|
37
|
-
|
|
38
|
-
key.sym_type? && value&.lvar_type?
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def already_omitted?(node, key_name)
|
|
42
|
-
source = node.source.strip
|
|
43
|
-
source == "#{key_name}:"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def same_name?(node, key_name)
|
|
47
|
-
value_name = node.value.source
|
|
48
|
-
key_name == value_name
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|