ree_lib 1.0.35 → 1.0.37

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