hal_api-rails 0.5.0 → 0.6.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 +5 -5
- data/lib/hal_api/controller.rb +4 -0
- data/lib/hal_api/controller/filtering.rb +107 -0
- data/lib/hal_api/controller/sorting.rb +61 -0
- data/lib/hal_api/errors.rb +28 -2
- data/lib/hal_api/rails/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4f8ad6ad0044a8438881c35775259d46cce7448d
|
4
|
+
data.tar.gz: c4dd5e6a42a4cca9d744e7050a91dc6d06e335f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae0cf7fc44f171df5972c660d732cbd837f89216e001ef76f8b9aa97a0cc25b8581e975a90efdfdee7401cc7e9ae3ad947442239119828a374806ac023b3ce3a
|
7
|
+
data.tar.gz: 9d8eb8563e870a584b7eb69696a8d205cbb03b5b6ea68b33c64913ced078a158cfc21a37fb62a0c349b2a66c4cb026a8e3448080139f27ec88bc939ebd6e0d12
|
data/lib/hal_api/controller.rb
CHANGED
@@ -9,12 +9,16 @@ module HalApi::Controller
|
|
9
9
|
require 'hal_api/controller/cache'
|
10
10
|
require 'hal_api/controller/resources'
|
11
11
|
require 'hal_api/controller/exceptions'
|
12
|
+
require 'hal_api/controller/sorting'
|
13
|
+
require 'hal_api/controller/filtering'
|
12
14
|
require 'hal_api/responders/api_responder'
|
13
15
|
|
14
16
|
include HalApi::Controller::Actions
|
15
17
|
include HalApi::Controller::Cache
|
16
18
|
include HalApi::Controller::Resources
|
17
19
|
include HalApi::Controller::Exceptions
|
20
|
+
include HalApi::Controller::Sorting
|
21
|
+
include HalApi::Controller::Filtering
|
18
22
|
|
19
23
|
included do
|
20
24
|
include Roar::Rails::ControllerAdditions
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module HalApi::Controller::Filtering
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_eval do
|
6
|
+
class_attribute :allowed_filter_names
|
7
|
+
class_attribute :allowed_filter_types
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FilterParams < OpenStruct
|
12
|
+
def initialize(filters = {})
|
13
|
+
@filters = filters.with_indifferent_access
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(m, *args, &_block)
|
17
|
+
if @filters.key?(m) && args.empty?
|
18
|
+
@filters[m]
|
19
|
+
elsif m.to_s[-1] == '?' && args.empty? && @filters.key?(m.to_s.chop)
|
20
|
+
!!@filters[m.to_s.chop]
|
21
|
+
else
|
22
|
+
msg = "Unknown filter param '#{m}'"
|
23
|
+
hint = "Valid filters are: #{@filters.keys.join(' ')}"
|
24
|
+
raise HalApi::Errors::UnknownFilterError.new(msg, hint)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module ClassMethods
|
30
|
+
def filter_params(*args)
|
31
|
+
self.allowed_filter_names = []
|
32
|
+
self.allowed_filter_types = {}
|
33
|
+
(args || []).map do |arg|
|
34
|
+
if arg.is_a? Hash
|
35
|
+
arg.to_a.each { |key, val| add_filter_param(key.to_s, val.to_s) }
|
36
|
+
else
|
37
|
+
add_filter_param(arg.to_s)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def add_filter_param(name, type = nil)
|
45
|
+
unless allowed_filter_names.include? name
|
46
|
+
allowed_filter_names << name
|
47
|
+
allowed_filter_types[name] = type unless type.nil?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def filters
|
53
|
+
@filters ||= parse_filters_param
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_filters_param
|
59
|
+
filters_map = {}
|
60
|
+
filters = self.class.allowed_filter_names
|
61
|
+
force_types = self.class.allowed_filter_types
|
62
|
+
|
63
|
+
# set nils
|
64
|
+
filters.each do |name|
|
65
|
+
filters_map[name] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# parse query param
|
69
|
+
(params[:filters] || '').split(',').each do |str|
|
70
|
+
name, value = str.split('=', 2)
|
71
|
+
next unless filters_map.key?(name)
|
72
|
+
|
73
|
+
# convert/guess type of known params
|
74
|
+
filters_map[name] =
|
75
|
+
if force_types[name] == 'date'
|
76
|
+
parse_date(value)
|
77
|
+
elsif force_types[name] == 'time'
|
78
|
+
parse_time(value)
|
79
|
+
elsif value.nil?
|
80
|
+
true
|
81
|
+
elsif value.blank?
|
82
|
+
''
|
83
|
+
elsif [false, 'false'].include? value
|
84
|
+
false
|
85
|
+
elsif [true, 'true'].include? value
|
86
|
+
true
|
87
|
+
elsif value =~ /\A[-+]?\d+\z/
|
88
|
+
value.to_i
|
89
|
+
else
|
90
|
+
value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
FilterParams.new(filters_map)
|
94
|
+
end
|
95
|
+
|
96
|
+
def parse_date(str)
|
97
|
+
Date.parse(str)
|
98
|
+
rescue ArgumentError
|
99
|
+
raise HalApi::Errors::BadFilterValueError.new "Invalid filter date: '#{str}'"
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_time(str)
|
103
|
+
Time.find_zone('UTC').parse(str) || (raise ArgumentError.new 'Nil result!')
|
104
|
+
rescue ArgumentError
|
105
|
+
raise HalApi::Errors::BadFilterValueError.new "Invalid filter time: '#{str}'"
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module HalApi::Controller::Sorting
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
class_eval do
|
6
|
+
class_attribute :allowed_sort_names
|
7
|
+
class_attribute :default_sort
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def sort_params(args)
|
13
|
+
self.allowed_sort_names = args[:allowed].map(&:to_s).uniq
|
14
|
+
self.default_sort = args[:default]
|
15
|
+
if default_sort && !default_sort.is_a?(Array)
|
16
|
+
self.default_sort = Array[default_sort]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def sorts
|
22
|
+
@sorts ||= parse_sorts_param
|
23
|
+
end
|
24
|
+
|
25
|
+
def sorted(arel)
|
26
|
+
apply_sorts = !sorts.blank? ? sorts : default_sort
|
27
|
+
if apply_sorts.blank?
|
28
|
+
super
|
29
|
+
else
|
30
|
+
arel.order(*apply_sorts)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# support ?sorts=attribute,attribute:direction params
|
37
|
+
# e.g. ?sorts=published_at,updated_at:desc
|
38
|
+
# desc is default if a direction is not specified
|
39
|
+
def parse_sorts_param
|
40
|
+
sorts_array = []
|
41
|
+
allowed_sorts = self.class.allowed_sort_names
|
42
|
+
|
43
|
+
# parse sort param for name of the column and direction
|
44
|
+
# default is descending, because I say so, and we have a bias towards the new
|
45
|
+
(params[:sorts] || '').split(',').each do |str|
|
46
|
+
name, direction = str.split(':', 2).map { |s| s.to_s.strip }
|
47
|
+
name = name.underscore
|
48
|
+
direction = direction.blank? ? 'desc' : direction.downcase
|
49
|
+
unless allowed_sorts.include?(name)
|
50
|
+
hint = "Valid sorts are: #{allowed_sorts.join(' ')}"
|
51
|
+
raise HalApi::Errors::BadSortError.new("Invalid sort: #{name}", hint)
|
52
|
+
end
|
53
|
+
unless ['asc', 'desc'].include?(direction)
|
54
|
+
hint = "Valid directions are: asc desc"
|
55
|
+
raise HalApi::Errors::BadSortError.new("Invalid sort direction: #{direction}", hint)
|
56
|
+
end
|
57
|
+
sorts_array << { name => direction }
|
58
|
+
end
|
59
|
+
sorts_array
|
60
|
+
end
|
61
|
+
end
|
data/lib/hal_api/errors.rb
CHANGED
@@ -4,10 +4,18 @@ module HalApi::Errors
|
|
4
4
|
|
5
5
|
class ApiError < StandardError
|
6
6
|
attr_accessor :status
|
7
|
+
attr_accessor :hint
|
7
8
|
|
8
|
-
def initialize(message = nil, status =
|
9
|
+
def initialize(message = nil, status = nil, hint = nil)
|
9
10
|
super(message || "API Error")
|
10
|
-
self.status = status
|
11
|
+
self.status = status || 500
|
12
|
+
self.hint = hint
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Forbidden < ApiError
|
17
|
+
def initialize(message = nil, hint = nil)
|
18
|
+
super(message || 'Forbidden', 403, hint)
|
11
19
|
end
|
12
20
|
end
|
13
21
|
|
@@ -23,6 +31,24 @@ module HalApi::Errors
|
|
23
31
|
end
|
24
32
|
end
|
25
33
|
|
34
|
+
class BadSortError < ApiError
|
35
|
+
def initialize(msg, hint = nil)
|
36
|
+
super(msg, 400, hint)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class UnknownFilterError < ApiError
|
41
|
+
def initialize(msg, hint = nil)
|
42
|
+
super(msg, 400, hint)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class BadFilterValueError < ApiError
|
47
|
+
def initialize(msg, hint = nil)
|
48
|
+
super(msg, 400, hint)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
26
52
|
module Representer
|
27
53
|
include Roar::JSON::HAL
|
28
54
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hal_api-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Rhoden
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-08-
|
12
|
+
date: 2019-08-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activemodel
|
@@ -216,7 +216,9 @@ files:
|
|
216
216
|
- lib/hal_api/controller/actions.rb
|
217
217
|
- lib/hal_api/controller/cache.rb
|
218
218
|
- lib/hal_api/controller/exceptions.rb
|
219
|
+
- lib/hal_api/controller/filtering.rb
|
219
220
|
- lib/hal_api/controller/resources.rb
|
221
|
+
- lib/hal_api/controller/sorting.rb
|
220
222
|
- lib/hal_api/errors.rb
|
221
223
|
- lib/hal_api/paged_collection.rb
|
222
224
|
- lib/hal_api/paged_collection_representer.rb
|
@@ -253,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
253
255
|
version: '0'
|
254
256
|
requirements: []
|
255
257
|
rubyforge_project:
|
256
|
-
rubygems_version: 2.
|
258
|
+
rubygems_version: 2.5.2.3
|
257
259
|
signing_key:
|
258
260
|
specification_version: 4
|
259
261
|
summary: JSON HAL APIs on Rails in the style of PRX
|