hal_api-rails 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|