ree_lib 1.0.35 → 1.0.37

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +11 -2
  3. data/lib/ree_lib/Packages.schema.json +8 -0
  4. data/lib/ree_lib/packages/ree_actions/.gitignore +0 -0
  5. data/lib/ree_lib/packages/ree_actions/.rspec +2 -0
  6. data/lib/ree_lib/packages/ree_actions/Package.schema.json +20 -0
  7. data/lib/ree_lib/packages/ree_actions/bin/console +5 -0
  8. data/lib/ree_lib/packages/ree_actions/package/ree_actions/action.rb +10 -0
  9. data/lib/ree_lib/packages/ree_actions/package/ree_actions/action_builder.rb +65 -0
  10. data/lib/ree_lib/packages/ree_actions/package/ree_actions/action_dsl.rb +60 -0
  11. data/lib/ree_lib/packages/ree_actions/package/ree_actions/dsl.rb +102 -0
  12. data/lib/ree_lib/packages/ree_actions/package/ree_actions.rb +12 -0
  13. data/lib/ree_lib/packages/ree_actions/spec/package_schema_spec.rb +14 -0
  14. data/lib/ree_lib/packages/ree_actions/spec/ree_actions/action_dsl_spec.rb +62 -0
  15. data/lib/ree_lib/packages/ree_actions/spec/ree_actions/dsl_spec.rb +93 -0
  16. data/lib/ree_lib/packages/ree_actions/spec/spec_helper.rb +3 -0
  17. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/functions/build_mapper_factory.rb +7 -7
  18. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper.rb +7 -2
  19. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory.rb +20 -13
  20. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/mapper_factory_proxy.rb +2 -2
  21. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/abstract_type.rb +0 -15
  22. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper/types/float.rb +2 -0
  23. data/lib/ree_lib/packages/ree_mapper/package/ree_mapper.rb +1 -1
  24. data/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_factory_spec.rb +77 -20
  25. data/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/mapper_spec.rb +25 -1
  26. data/lib/ree_lib/packages/ree_mapper/spec/ree_mapper/types/float_spec.rb +9 -0
  27. data/lib/ree_lib/packages/ree_object/package/ree_object/functions/to_hash.rb +5 -3
  28. data/lib/ree_lib/packages/ree_object/spec/ree_object/functions/to_hash_spec.rb +5 -0
  29. data/lib/ree_lib/packages/ree_roda/.gitignore +0 -0
  30. data/lib/ree_lib/packages/ree_roda/.rspec +2 -0
  31. data/lib/ree_lib/packages/ree_roda/Package.schema.json +58 -0
  32. data/lib/ree_lib/packages/ree_roda/bin/console +5 -0
  33. data/lib/ree_lib/packages/ree_roda/package/ree_roda/app.rb +46 -0
  34. data/lib/ree_lib/packages/ree_roda/package/ree_roda/plugins/ree_actions.rb +150 -0
  35. data/lib/ree_lib/packages/ree_roda/package/ree_roda/plugins/ree_logger.rb +66 -0
  36. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/build_action_errors.rb +76 -0
  37. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/build_swagger_from_actions.rb +55 -0
  38. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/status_from_error.rb +25 -0
  39. data/lib/ree_lib/packages/ree_roda/package/ree_roda.rb +19 -0
  40. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/build_action_errors.schema.json +28 -0
  41. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/build_swagger_from_actions.schema.json +63 -0
  42. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/status_from_error.schema.json +28 -0
  43. data/lib/ree_lib/packages/ree_roda/spec/package_schema_spec.rb +14 -0
  44. data/lib/ree_lib/packages/ree_roda/spec/ree_roda/app_spec.rb +84 -0
  45. data/lib/ree_lib/packages/ree_roda/spec/spec_helper.rb +3 -0
  46. data/lib/ree_lib/packages/ree_swagger/spec/functions/build_serializer_schema_spec.rb +8 -11
  47. data/lib/ree_lib/packages/ree_swagger/spec/functions/register_type_spec.rb +2 -7
  48. data/lib/ree_lib/version.rb +1 -1
  49. metadata +60 -2
@@ -20,6 +20,8 @@ class ReeMapper::Float < ReeMapper::AbstractType
20
20
  rescue ArgumentError => e
21
21
  raise ReeMapper::CoercionError, "`#{name}` is invalid float"
22
22
  end
23
+ elsif defined?(BigDecimal) && value.is_a?(BigDecimal)
24
+ value.to_f
23
25
  else
24
26
  raise ReeMapper::TypeError, "`#{name}` should be a float"
25
27
  end
@@ -160,7 +160,7 @@ Create `mapper_factory.rb` file to declare `MapperFactory` class.
160
160
  build_mapper_strategy(method: :db_load, dto: Object)
161
161
  ])
162
162
 
163
- mapper_factory.register(:cart_user, user_caster)
163
+ mapper_factory.register_mapper(:cart_user, user_caster)
164
164
 
165
165
  mapper_factory
166
166
  end
@@ -5,35 +5,76 @@ RSpec.describe ReeMapper::MapperFactory do
5
5
  link :build_mapper_factory, from: :ree_mapper
6
6
  link :build_mapper_strategy, from: :ree_mapper
7
7
 
8
- let(:mapper_factory) {
9
- build_mapper_factory(strategies: [
10
- build_mapper_strategy(method: :cast, dto: Hash)
11
- ])
12
- }
8
+ let(:cast_strategy) { build_mapper_strategy(method: :cast, dto: Hash) }
9
+ let(:serialize_strategy) { build_mapper_strategy(method: :serialize, dto: Hash) }
10
+ let(:mapper_factory) { build_mapper_factory(strategies: [cast_strategy, serialize_strategy]) }
11
+
12
+ describe '.register_type' do
13
+ let(:mapper_type) {
14
+ Class.new(ReeMapper::AbstractType) do
15
+ def cast(*); :value end
16
+ def serialize(*); end
17
+ end
18
+ }
13
19
 
14
- describe '.register' do
15
20
  it {
16
- mapper_factory.register(:new_type, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
17
- expect(mapper_factory.instance_methods).to include(:new_type)
21
+ mapper_factory.register_type(:new_type, mapper_type.new)
22
+ expect(
23
+ mapper_factory.call.use(:cast) { new_type :val }.cast({ val: :any })
24
+ ).to eq({ val: :value })
18
25
  }
19
26
 
20
- it 'raise an error if the type is already registered' do
21
- mapper_factory.register(:new_type, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
27
+ it {
28
+ mapper_factory.register_type(:new_type, mapper_type.new, strategies: [cast_strategy])
29
+ expect(
30
+ mapper_factory.call.use(:cast) { new_type :val }.cast({ val: :any })
31
+ ).to eq({ val: :value })
32
+ }
33
+ end
34
+
35
+ describe '.register_mapper' do
36
+ let(:serializer) { mapper_factory.call.use(:serialize) { integer :id } }
37
+
38
+ it {
39
+ mapper_factory.register_mapper(:new_type, serializer)
40
+
41
+ expect(
42
+ mapper_factory.call.use(:serialize) { new_type :val }.serialize({ val: { id: 1 } })
43
+ ).to eq({ val: { id: 1 } })
44
+ }
45
+
46
+ it 'allow to register caster and serializer with same name' do
47
+ caster = mapper_factory.call.use(:cast) { string :name }
48
+
49
+ mapper_factory.register_mapper(:new_type, serializer)
50
+ mapper_factory.register_mapper(:new_type, caster)
51
+
52
+ expect(
53
+ mapper_factory.call.use(:serialize) { new_type :val }.serialize({ val: { id: 1 } })
54
+ ).to eq({ val: { id: 1 } })
55
+
56
+ expect(
57
+ mapper_factory.call.use(:cast) { new_type :val }.cast({ val: { name: '1' } })
58
+ ).to eq({ val: { name: '1' } })
59
+ end
60
+
61
+ it 'raise an error if the mapper is already registered' do
62
+ mapper_factory.register_mapper(:new_type, serializer)
22
63
 
23
64
  expect {
24
- mapper_factory.register(:new_type, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
25
- }.to raise_error(ArgumentError, 'type :new_type already registered')
65
+ mapper_factory.register_mapper(:new_type, serializer)
66
+ }.to raise_error(ArgumentError, 'type :new_type with `serialize` strategy already registered')
26
67
  end
27
68
 
28
- it 'raise an error if the type is ended by ?' do
69
+ it 'raise an error if the mapper name is ended by ?' do
29
70
  expect {
30
- mapper_factory.register(:new_type?, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
31
- }.to raise_error(ArgumentError)
71
+ mapper_factory.register_mapper(:new_type?, serializer)
72
+ }.to raise_error(ArgumentError, 'name of mapper type should not end with `?`')
32
73
  end
33
74
 
34
- it 'raise an error if the type method is already registered' do
75
+ it 'raise an error if the mapper name is reserved' do
35
76
  expect {
36
- mapper_factory.register(:array, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
77
+ mapper_factory.register_mapper(:array, serializer)
37
78
  }.to raise_error(ArgumentError, 'method :array already defined')
38
79
  end
39
80
  end
@@ -48,13 +89,17 @@ RSpec.describe ReeMapper::MapperFactory do
48
89
  }
49
90
 
50
91
  it {
51
- mapper_factory.register(:new_type, ReeMapper::Mapper.build([], ReeMapper::AbstractType.new))
92
+ serializer = mapper_factory.call.use(:serialize) do
93
+ integer :my_field
94
+ end
95
+
96
+ mapper_factory.register_mapper(:new_type, serializer)
52
97
 
53
98
  expect {
54
99
  mapper_factory.call.use(:cast) do
55
100
  new_type :settings
56
101
  end
57
- }.to raise_error(ReeMapper::UnsupportedTypeError)
102
+ }.to raise_error(ReeMapper::UnsupportedTypeError, 'type :new_type should implement `cast`')
58
103
  }
59
104
 
60
105
  it {
@@ -62,7 +107,9 @@ RSpec.describe ReeMapper::MapperFactory do
62
107
  integer :id
63
108
  end
64
109
 
65
- expect(mapper_factory.instance_methods).to include(:user)
110
+ expect(
111
+ mapper_factory.call.use(:cast) { user :user }.cast({ user: { id: 1 } })
112
+ ).to eq({ user: { id: 1 } })
66
113
  }
67
114
 
68
115
  it {
@@ -80,4 +127,14 @@ RSpec.describe ReeMapper::MapperFactory do
80
127
  }.to raise_error(ReeMapper::ArgumentError, "mapper should contain at least one field")
81
128
  }
82
129
  end
130
+
131
+ describe '.find_strategy' do
132
+ it {
133
+ expect(mapper_factory.find_strategy(:cast)).to eq(cast_strategy)
134
+ }
135
+
136
+ it {
137
+ expect(mapper_factory.find_strategy(:unknown)).to be_nil
138
+ }
139
+ end
83
140
  end
@@ -92,7 +92,7 @@ RSpec.describe ReeMapper::Mapper do
92
92
  )
93
93
  }
94
94
 
95
- it {
95
+ it {
96
96
  expect(mapper.cast({ my_field: 1, hsh: { nested_field: 1 } })).to be_a(Struct)
97
97
  }
98
98
  end
@@ -166,4 +166,28 @@ RSpec.describe ReeMapper::Mapper do
166
166
  expect { mapper.dto(:db_dump) }.to raise_error(ArgumentError, "there is no :db_dump strategy")
167
167
  }
168
168
  end
169
+
170
+ describe '#find_strategy' do
171
+ let(:mapper_factory) {
172
+ build_mapper_factory(
173
+ strategies: [
174
+ build_mapper_strategy(method: :cast),
175
+ build_mapper_strategy(method: :serialize),
176
+ ]
177
+ )
178
+ }
179
+ let(:mapper) {
180
+ mapper_factory.call.use(:cast) do
181
+ integer :my_field
182
+ end
183
+ }
184
+
185
+ it {
186
+ expect(mapper.find_strategy(:cast)).to be_a(ReeMapper::MapperStrategy)
187
+ }
188
+
189
+ it {
190
+ expect(mapper.find_strategy(:serialize)).to be_nil
191
+ }
192
+ end
169
193
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'bigdecimal'
2
3
 
3
4
  RSpec.describe 'ReeMapper::Float' do
4
5
  link :build_mapper_factory, from: :ree_mapper
@@ -30,6 +31,10 @@ RSpec.describe 'ReeMapper::Float' do
30
31
  expect(mapper.cast({ float: '1.1' })).to eq({ float: 1.1 })
31
32
  }
32
33
 
34
+ it {
35
+ expect(mapper.db_load({ float: BigDecimal("1.1") })).to eq({ float: 1.1 })
36
+ }
37
+
33
38
  it {
34
39
  expect { mapper.cast({ float: 'a1.1' }) }.to raise_error(ReeMapper::CoercionError, '`float` is invalid float')
35
40
  }
@@ -88,6 +93,10 @@ RSpec.describe 'ReeMapper::Float' do
88
93
  expect(mapper.db_load({ float: '1.1' })).to eq({ float: 1.1 })
89
94
  }
90
95
 
96
+ it {
97
+ expect(mapper.db_load({ float: BigDecimal("1.1") })).to eq({ float: 1.1 })
98
+ }
99
+
91
100
  it {
92
101
  expect { mapper.db_load({ float: 'a1.1' }) }.to raise_error(ReeMapper::CoercionError, '`float` is invalid float')
93
102
  }
@@ -6,7 +6,7 @@ class ReeObject::ToHash
6
6
  fn :to_hash do
7
7
  def_error { RecursiveObjectErr }
8
8
  end
9
-
9
+
10
10
  BASIC_TYPES = [
11
11
  Date, Time, Numeric, String, FalseClass, TrueClass, NilClass, Symbol,
12
12
  Module, Class
@@ -57,8 +57,8 @@ class ReeObject::ToHash
57
57
  raise RecursiveObjectErr, "Recursive object found: #{obj}"
58
58
  end
59
59
 
60
- cache[obj.object_id] = acc
61
-
60
+ cache[obj.object_id] = true
61
+
62
62
  obj.instance_variables.each do |var|
63
63
  key_name = var.to_s.delete("@")
64
64
  key_sym = key_name.to_sym
@@ -69,6 +69,8 @@ class ReeObject::ToHash
69
69
  acc[key] = recursively_convert(value, {}, cache)
70
70
  end
71
71
 
72
+ cache.delete(obj.object_id)
73
+
72
74
  acc
73
75
  end
74
76
  end
@@ -167,6 +167,11 @@ RSpec.describe :to_hash do
167
167
  to_hash(obj)
168
168
  }.to raise_error(ReeObject::ToHash::RecursiveObjectErr, /Recursive object found: /)
169
169
  }
170
+
171
+ it {
172
+ obj = obj_klass.new
173
+ expect(to_hash([obj, obj])).to eq([{}, {}])
174
+ }
170
175
  end
171
176
  end
172
177
  end
File without changes
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,58 @@
1
+ {
2
+ "schema_type": "package",
3
+ "schema_version": "1.1",
4
+ "name": "ree_roda",
5
+ "entry_path": "packages/ree_roda/package/ree_roda.rb",
6
+ "tags": [
7
+ "ree_roda"
8
+ ],
9
+ "depends_on": [
10
+ {
11
+ "name": "ree_actions"
12
+ },
13
+ {
14
+ "name": "ree_errors"
15
+ },
16
+ {
17
+ "name": "ree_hash"
18
+ },
19
+ {
20
+ "name": "ree_json"
21
+ },
22
+ {
23
+ "name": "ree_logger"
24
+ },
25
+ {
26
+ "name": "ree_object"
27
+ },
28
+ {
29
+ "name": "ree_swagger"
30
+ }
31
+ ],
32
+ "env_vars": [
33
+
34
+ ],
35
+ "objects": [
36
+ {
37
+ "name": "build_action_errors",
38
+ "schema": "packages/ree_roda/schemas/ree_roda/services/build_action_errors.schema.json",
39
+ "tags": [
40
+ "fn"
41
+ ]
42
+ },
43
+ {
44
+ "name": "build_swagger_from_actions",
45
+ "schema": "packages/ree_roda/schemas/ree_roda/services/build_swagger_from_actions.schema.json",
46
+ "tags": [
47
+ "fn"
48
+ ]
49
+ },
50
+ {
51
+ "name": "status_from_error",
52
+ "schema": "packages/ree_roda/schemas/ree_roda/services/status_from_error.schema.json",
53
+ "tags": [
54
+ "fn"
55
+ ]
56
+ }
57
+ ]
58
+ }
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ require "bundler/setup"
3
+ require "ree"
4
+
5
+ Ree.init(__dir__, irb: true)
@@ -0,0 +1,46 @@
1
+ package_require("ree_errors/error")
2
+ package_require("ree_mapper/errors/type_error")
3
+
4
+ class ReeRoda::App < Roda
5
+ include Ree::LinkDSL
6
+
7
+ link :logger, from: :ree_logger
8
+ link :status_from_error
9
+ link :to_json, from: :ree_json
10
+
11
+ plugin :error_handler
12
+ plugin :json_parser
13
+ plugin :type_routing, default_type: :json
14
+
15
+ error do |e|
16
+ response["Content-Type"] = "application/json"
17
+
18
+ if e.is_a?(ReeErrors::Error)
19
+ body = {
20
+ code: e.code,
21
+ message: e.message,
22
+ type: e.type,
23
+ }
24
+
25
+ response.status = status_from_error(e.type)
26
+ response.write(to_json(body))
27
+ response.finish
28
+ elsif e.is_a?(ReeMapper::TypeError) || e.is_a?(ReeMapper::CoercionError)
29
+ body = {
30
+ code: "param",
31
+ message: e.message,
32
+ type: :invalid_param,
33
+ }
34
+
35
+ response.status = 400
36
+ response.write(to_json(body))
37
+ response.finish
38
+ else
39
+ logger.error(e.message, {}, e)
40
+ response["Content-Type"] = "text/plain"
41
+ response.status = 500
42
+ response.write("unhandled server error")
43
+ response.finish
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,150 @@
1
+ class Roda
2
+ module RodaPlugins
3
+ module ReeActions
4
+ def self.load_dependencies(app, opts = {})
5
+ package_require("ree_roda/services/build_swagger_from_actions")
6
+ package_require("ree_json/functions/to_json")
7
+ package_require("ree_hash/functions/transform_values")
8
+ package_require("ree_object/functions/not_blank")
9
+
10
+ app.plugin :all_verbs
11
+ end
12
+
13
+ def self.configure(app, opts = {})
14
+ app.opts[:ree_actions_before] = opts[:before] if opts[:before]
15
+ end
16
+
17
+ module ClassMethods
18
+ def ree_actions(actions, swagger_title: "", swagger_description: "",
19
+ swagger_version: "", swagger_url: "", api_url: "")
20
+ @ree_actions ||= []
21
+ @ree_actions += actions
22
+
23
+ opts[:ree_actions_swagger_title] = swagger_title
24
+ opts[:ree_actions_swagger_description] = swagger_description
25
+ opts[:ree_actions_swagger_version] = swagger_version
26
+ opts[:ree_actions_swagger_url] = swagger_url
27
+ opts[:ree_actions_api_url] = api_url
28
+
29
+ opts[:ree_actions_swagger] = ReeRoda::BuildSwaggerFromActions.new.call(
30
+ @ree_actions,
31
+ opts[:ree_actions_swagger_title],
32
+ opts[:ree_actions_swagger_description],
33
+ opts[:ree_actions_swagger_version],
34
+ opts[:ree_actions_api_url]
35
+ )
36
+
37
+ build_actions_proc
38
+ nil
39
+ end
40
+
41
+ private
42
+
43
+ def build_actions_proc
44
+ list = []
45
+ context = self
46
+
47
+ return list if @ree_actions.nil? || @ree_actions.empty?
48
+
49
+ if context.opts[:ree_actions_swagger_url]
50
+ list << Proc.new do |r|
51
+ r.get context.opts[:ree_actions_swagger_url] do
52
+ r.json do
53
+ response.status = 200
54
+ ReeJson::ToJson.new.call(context.opts[:ree_actions_swagger])
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ @ree_actions.each do |action|
61
+ route = []
62
+ route_args = []
63
+
64
+ action.path.split("/").each do |part|
65
+ if part.start_with?(":")
66
+ route << String
67
+ route_args << part.gsub(":", "")
68
+ else
69
+ route << part
70
+ end
71
+ end
72
+
73
+ list << Proc.new do |r|
74
+ r.send(action.request_method, *route) do |*args|
75
+ r.send(action.respond_to) do
76
+ env["warden"].authenticate!(scope: action.warden_scope)
77
+
78
+ if context.opts[:ree_actions_before]
79
+ self.instance_exec(@_request, action.warden_scope, &scope.opts[:ree_actions_before])
80
+ end
81
+
82
+ # TODO: implement me when migration to roda DSL happens
83
+ # if action.before; end
84
+
85
+ route_args.each_with_index do |arg, index|
86
+ r.params["#{arg}"] = args[index]
87
+ end
88
+
89
+ params = r.params
90
+
91
+ if r.body
92
+ body = begin
93
+ JSON.parse(r.body.read)
94
+ rescue => e
95
+ {}
96
+ end
97
+
98
+ params = params.merge(body)
99
+ end
100
+
101
+ not_blank = ReeObject::NotBlank.new
102
+
103
+ filtered_params = ReeHash::TransformValues.new.call(params) do |k, v|
104
+ v.is_a?(Array) ? v.select { not_blank.call(_1) } : v
105
+ end
106
+
107
+ accessor = env["warden"].user(action.warden_scope)
108
+ action_result = action.action.klass.new.call(accessor, filtered_params)
109
+
110
+ if action.serializer
111
+ serialized_result = action.serializer.klass.new.serialize(action_result)
112
+ else
113
+ serialized_result = {}
114
+ end
115
+
116
+ case action.request_method
117
+ when :post
118
+ response.status = 201
119
+ ReeJson::ToJson.new.call(serialized_result)
120
+ when :put, :delete, :patch
121
+ response.status = 204
122
+ ""
123
+ else
124
+ response.status = 200
125
+ ReeJson::ToJson.new.call(serialized_result)
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ opts[:ree_actions_proc] = list
133
+ end
134
+ end
135
+
136
+ module RequestMethods
137
+ def ree_actions
138
+ if scope.opts[:ree_actions_proc]
139
+ scope.opts[:ree_actions_proc].each do |request_proc|
140
+ self.instance_exec(self, &request_proc)
141
+ end
142
+ end
143
+ nil
144
+ end
145
+ end
146
+ end
147
+
148
+ register_plugin(:ree_actions, Roda::RodaPlugins::ReeActions)
149
+ end
150
+ end
@@ -0,0 +1,66 @@
1
+ package_require "ree_logger/beans/logger"
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ # The ree_logger plugin adds ReeLogger support to Roda
6
+ #
7
+ # Example:
8
+ #
9
+ # plugin :ree_logger
10
+ # plugin :ree_logger, log_params: true, filter: -> { request.path.include?("health") }
11
+ module ReeLogger
12
+ REE_LOGGER_DEFAULTS = {
13
+ method: :info,
14
+ log_params: true
15
+ }
16
+
17
+ def self.configure(app, **opts)
18
+ opts = REE_LOGGER_DEFAULTS.merge(opts)
19
+ app.opts[:ree_logger] = ::ReeLogger::Logger.new
20
+ app.opts[:ree_logger_filter] = opts[:filter] if opts[:filter]
21
+ app.opts[:ree_logger_log_params] = !!opts[:log_params]
22
+ end
23
+
24
+ def self.start_timer
25
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
26
+ end
27
+
28
+ module InstanceMethods
29
+ private
30
+
31
+ # Log request/response information in common log format to logger.
32
+ def _roda_after_90__ree_logger(result)
33
+ return unless result && result[0] && result[1]
34
+
35
+ if opts[:ree_logger_filter]
36
+ return if self.instance_exec(&opts[:ree_logger_filter])
37
+ end
38
+
39
+ elapsed_time = if timer = @_request_timer
40
+ "%0.4f" % (ReeLogger.start_timer - timer)
41
+ else
42
+ "-"
43
+ end
44
+
45
+ env = @_request.env
46
+
47
+ message = <<~DOC
48
+ Request/Response details:
49
+ Request: #{env["REQUEST_METHOD"]} #{env["QUERY_STRING"] && !env["QUERY_STRING"].empty? ? env["SCRIPT_NAME"].to_s + env["PATH_INFO"] + "?#{env["QUERY_STRING"]}": env["SCRIPT_NAME"].to_s + env["PATH_INFO"]} #{opts[:ree_logger_log_params] ? "\n Params: " + request.params.inspect : ""}
50
+ Response status: #{response.status || "-"}#{(400..499).include?(response.status) ? "\n Response body: " + response.body[0] : ""}
51
+ Time Taken: #{elapsed_time}
52
+ DOC
53
+
54
+ opts[:ree_logger].info(message)
55
+ end
56
+
57
+ # Create timer instance used for timing
58
+ def _roda_before_05__ree_logger
59
+ @_request_timer = ReeLogger.start_timer
60
+ end
61
+ end
62
+ end
63
+
64
+ register_plugin(:ree_logger, ReeLogger)
65
+ end
66
+ end
@@ -0,0 +1,76 @@
1
+ class ReeRoda::BuildActionErrors
2
+ include Ree::FnDSL
3
+
4
+ fn :build_action_errors do
5
+ link "ree_swagger/dto/error_dto", -> { ErrorDto }
6
+ end
7
+
8
+ contract(ReeActions::Action => ArrayOf[ErrorDto])
9
+ def call(action)
10
+ ree_object = action.action
11
+ errors = recursively_extract_errors(ree_object)
12
+
13
+ errors
14
+ .select {
15
+ _1.ancestors.include?(ReeErrors::Error) && status_from_error(_1)
16
+ }
17
+ .map {
18
+ e = _1.new
19
+ description = "type: **#{e.type}**, code: **#{e.code}**, message: **#{e.message}**"
20
+
21
+ ErrorDto.new(
22
+ status: status_from_error(_1),
23
+ description: description
24
+ )
25
+ }
26
+ .uniq { [_1.status, _1.description] }
27
+ end
28
+
29
+ private
30
+
31
+ def recursively_extract_errors(ree_object)
32
+ errors = extract_errors(ree_object)
33
+
34
+ ree_object.links.each do |link|
35
+ obj = Ree.container.packages_facade.get_object(
36
+ link.package_name, link.object_name
37
+ )
38
+
39
+ if obj.fn?
40
+ errors += recursively_extract_errors(obj)
41
+ end
42
+ end
43
+
44
+ errors
45
+ end
46
+
47
+ def extract_errors(ree_object)
48
+ klass = ree_object.klass
49
+ return [] if ree_object.object?
50
+
51
+ method_decorator = Ree::Contracts.get_method_decorator(
52
+ klass, :call, scope: :instance
53
+ )
54
+
55
+ method_decorator&.errors || []
56
+ end
57
+
58
+ def status_from_error(error)
59
+ case error.instance_variable_get(:@type)
60
+ when :not_found
61
+ 404
62
+ when :invalid_param
63
+ 400
64
+ when :conflict
65
+ 405
66
+ when :auth
67
+ 401
68
+ when :permission
69
+ 403
70
+ when :payment
71
+ 402
72
+ when :validation
73
+ 422
74
+ end
75
+ end
76
+ end