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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.aiconfig +65 -0
  3. data/.rubocop.yml +110 -35
  4. data/CHANGELOG.md +93 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +776 -1903
  7. data/Rakefile +14 -3
  8. data/jpie.gemspec +35 -18
  9. data/lib/jpie/configuration.rb +12 -0
  10. data/lib/jpie/controller/crud_actions.rb +110 -0
  11. data/lib/jpie/controller/error_handling.rb +41 -0
  12. data/lib/jpie/controller/parameter_parsing.rb +35 -0
  13. data/lib/jpie/controller/rendering.rb +60 -0
  14. data/lib/jpie/controller.rb +18 -0
  15. data/lib/jpie/deserializer.rb +110 -0
  16. data/lib/jpie/errors.rb +70 -0
  17. data/lib/jpie/generators/resource_generator.rb +39 -0
  18. data/lib/jpie/generators/templates/resource.rb.erb +12 -0
  19. data/lib/jpie/railtie.rb +36 -0
  20. data/lib/jpie/resource/attributable.rb +98 -0
  21. data/lib/jpie/resource/inferrable.rb +43 -0
  22. data/lib/jpie/resource/sortable.rb +93 -0
  23. data/lib/jpie/resource.rb +107 -0
  24. data/lib/jpie/serializer.rb +205 -0
  25. data/lib/{json_api → jpie}/version.rb +2 -2
  26. data/lib/jpie.rb +23 -3
  27. metadata +145 -50
  28. data/.gitignore +0 -21
  29. data/.rspec +0 -3
  30. data/.travis.yml +0 -7
  31. data/Gemfile +0 -21
  32. data/Gemfile.lock +0 -312
  33. data/bin/console +0 -15
  34. data/bin/setup +0 -8
  35. data/kiln/app/resources/user_message_resource.rb +0 -2
  36. data/lib/json_api/active_storage/deserialization.rb +0 -106
  37. data/lib/json_api/active_storage/detection.rb +0 -74
  38. data/lib/json_api/active_storage/serialization.rb +0 -32
  39. data/lib/json_api/configuration.rb +0 -58
  40. data/lib/json_api/controllers/base_controller.rb +0 -26
  41. data/lib/json_api/controllers/concerns/controller_helpers.rb +0 -223
  42. data/lib/json_api/controllers/concerns/resource_actions.rb +0 -657
  43. data/lib/json_api/controllers/relationships_controller.rb +0 -504
  44. data/lib/json_api/controllers/resources_controller.rb +0 -6
  45. data/lib/json_api/errors/parameter_not_allowed.rb +0 -19
  46. data/lib/json_api/railtie.rb +0 -75
  47. data/lib/json_api/resources/active_storage_blob_resource.rb +0 -11
  48. data/lib/json_api/resources/resource.rb +0 -238
  49. data/lib/json_api/resources/resource_loader.rb +0 -35
  50. data/lib/json_api/routing.rb +0 -72
  51. data/lib/json_api/serialization/deserializer.rb +0 -362
  52. data/lib/json_api/serialization/serializer.rb +0 -320
  53. data/lib/json_api/support/active_storage_support.rb +0 -85
  54. data/lib/json_api/support/collection_query.rb +0 -406
  55. data/lib/json_api/support/instrumentation.rb +0 -42
  56. data/lib/json_api/support/param_helpers.rb +0 -51
  57. data/lib/json_api/support/relationship_guard.rb +0 -16
  58. data/lib/json_api/support/relationship_helpers.rb +0 -74
  59. data/lib/json_api/support/resource_identifier.rb +0 -87
  60. data/lib/json_api/support/responders.rb +0 -100
  61. data/lib/json_api/support/response_helpers.rb +0 -10
  62. data/lib/json_api/support/sort_parsing.rb +0 -21
  63. data/lib/json_api/support/type_conversion.rb +0 -21
  64. data/lib/json_api/testing/test_helper.rb +0 -76
  65. data/lib/json_api/testing.rb +0 -3
  66. data/lib/json_api.rb +0 -50
  67. 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,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module JSONAPI
4
- module ResponseHelpers
5
- # Delegates to Serializer.jsonapi_object for backward compatibility
6
- def self.jsonapi_object
7
- JSONAPI::Serializer.jsonapi_object
8
- end
9
- end
10
- 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
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "testing/test_helper"
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