ree_lib 1.0.36 → 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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +9 -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_roda/.gitignore +0 -0
  18. data/lib/ree_lib/packages/ree_roda/.rspec +2 -0
  19. data/lib/ree_lib/packages/ree_roda/Package.schema.json +58 -0
  20. data/lib/ree_lib/packages/ree_roda/bin/console +5 -0
  21. data/lib/ree_lib/packages/ree_roda/package/ree_roda/app.rb +46 -0
  22. data/lib/ree_lib/packages/ree_roda/package/ree_roda/plugins/ree_actions.rb +150 -0
  23. data/lib/ree_lib/packages/ree_roda/package/ree_roda/plugins/ree_logger.rb +66 -0
  24. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/build_action_errors.rb +76 -0
  25. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/build_swagger_from_actions.rb +55 -0
  26. data/lib/ree_lib/packages/ree_roda/package/ree_roda/services/status_from_error.rb +25 -0
  27. data/lib/ree_lib/packages/ree_roda/package/ree_roda.rb +19 -0
  28. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/build_action_errors.schema.json +28 -0
  29. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/build_swagger_from_actions.schema.json +63 -0
  30. data/lib/ree_lib/packages/ree_roda/schemas/ree_roda/services/status_from_error.schema.json +28 -0
  31. data/lib/ree_lib/packages/ree_roda/spec/package_schema_spec.rb +14 -0
  32. data/lib/ree_lib/packages/ree_roda/spec/ree_roda/app_spec.rb +84 -0
  33. data/lib/ree_lib/packages/ree_roda/spec/spec_helper.rb +3 -0
  34. data/lib/ree_lib/version.rb +1 -1
  35. metadata +60 -2
@@ -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
@@ -0,0 +1,55 @@
1
+ class ReeRoda::BuildSwaggerFromActions
2
+ include Ree::FnDSL
3
+
4
+ fn :build_swagger_from_actions do
5
+ link :build_action_errors
6
+ link :build_schema, from: :ree_swagger
7
+ link "ree_swagger/dto/endpoint_dto", -> { EndpointDto }
8
+ end
9
+
10
+ contract(ArrayOf[ReeActions::Action], String, String, String, String => Hash)
11
+ def call(actions, title, description, version, api_url)
12
+ endpoints = actions.map do |action|
13
+ method_decorator = Ree::Contracts.get_method_decorator(
14
+ action.action.klass, :call, scope: :instance
15
+ )
16
+
17
+ response_status = case action.request_method
18
+ when :post
19
+ 201
20
+ when :put, :delete, :patch
21
+ 204
22
+ else
23
+ 200
24
+ end
25
+
26
+ caster = if action.action.klass.const_defined?(:ActionCaster)
27
+ action.action.klass.const_get(:ActionCaster)
28
+ end
29
+
30
+ EndpointDto.new(
31
+ method: action.request_method,
32
+ sections: action.sections,
33
+ respond_to: action.respond_to,
34
+ path: action.path.start_with?("/") ? action.path : "/#{action.path}",
35
+ caster: caster,
36
+ serializer: action.serializer&.klass&.new,
37
+ summary: action.summary,
38
+ authenticate: action.warden_scope != :visitor,
39
+ description: method_decorator&.doc || "",
40
+ response_status: response_status,
41
+ response_description: nil,
42
+ errors: build_action_errors(action)
43
+ )
44
+ end
45
+
46
+ build_schema(
47
+ title: title,
48
+ description: description,
49
+ version: version,
50
+ api_url: api_url,
51
+ endpoints: endpoints
52
+ )
53
+ end
54
+ end
55
+
@@ -0,0 +1,25 @@
1
+ class ReeRoda::StatusFromError
2
+ include Ree::FnDSL
3
+
4
+ fn :status_from_error
5
+
6
+ contract(Symbol => Nilor[Integer])
7
+ def call(error_type)
8
+ case error_type
9
+ when :not_found
10
+ 404
11
+ when :invalid_param
12
+ 400
13
+ when :conflict
14
+ 405
15
+ when :auth
16
+ 401
17
+ when :permission
18
+ 403
19
+ when :payment
20
+ 402
21
+ when :validation
22
+ 422
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ require "roda"
2
+
3
+ module ReeRoda
4
+ include Ree::PackageDSL
5
+
6
+ package do
7
+ depends_on :ree_actions
8
+ depends_on :ree_logger
9
+ depends_on :ree_json
10
+ depends_on :ree_hash
11
+ depends_on :ree_object
12
+ depends_on :ree_swagger
13
+ depends_on :ree_errors
14
+ end
15
+ end
16
+
17
+ package_require "ree_roda/plugins/ree_logger"
18
+ package_require "ree_roda/plugins/ree_actions"
19
+ package_require "ree_roda/app"
@@ -0,0 +1,28 @@
1
+ {
2
+ "schema_type": "object",
3
+ "schema_version": "1.1",
4
+ "name": "build_action_errors",
5
+ "path": "packages/ree_roda/package/ree_roda/services/build_action_errors.rb",
6
+ "mount_as": "fn",
7
+ "class": "ReeRoda::BuildActionErrors",
8
+ "factory": null,
9
+ "methods": [
10
+ {
11
+ "doc": "",
12
+ "throws": [
13
+
14
+ ],
15
+ "return": "ArrayOf[ReeSwagger::ErrorDto]",
16
+ "args": [
17
+ {
18
+ "arg": "action",
19
+ "arg_type": "req",
20
+ "type": "ReeActions::Action"
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "links": [
26
+
27
+ ]
28
+ }
@@ -0,0 +1,63 @@
1
+ {
2
+ "schema_type": "object",
3
+ "schema_version": "1.1",
4
+ "name": "build_swagger_from_actions",
5
+ "path": "packages/ree_roda/package/ree_roda/services/build_swagger_from_actions.rb",
6
+ "mount_as": "fn",
7
+ "class": "ReeRoda::BuildSwaggerFromActions",
8
+ "factory": null,
9
+ "methods": [
10
+ {
11
+ "doc": "",
12
+ "throws": [
13
+
14
+ ],
15
+ "return": "Hash",
16
+ "args": [
17
+ {
18
+ "arg": "actions",
19
+ "arg_type": "req",
20
+ "type": "ArrayOf[ReeActions::Action]"
21
+ },
22
+ {
23
+ "arg": "title",
24
+ "arg_type": "req",
25
+ "type": "String"
26
+ },
27
+ {
28
+ "arg": "description",
29
+ "arg_type": "req",
30
+ "type": "String"
31
+ },
32
+ {
33
+ "arg": "version",
34
+ "arg_type": "req",
35
+ "type": "String"
36
+ },
37
+ {
38
+ "arg": "api_url",
39
+ "arg_type": "req",
40
+ "type": "String"
41
+ }
42
+ ]
43
+ }
44
+ ],
45
+ "links": [
46
+ {
47
+ "target": "build_action_errors",
48
+ "package_name": "ree_roda",
49
+ "as": "build_action_errors",
50
+ "imports": [
51
+
52
+ ]
53
+ },
54
+ {
55
+ "target": "build_schema",
56
+ "package_name": "ree_swagger",
57
+ "as": "build_schema",
58
+ "imports": [
59
+
60
+ ]
61
+ }
62
+ ]
63
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "schema_type": "object",
3
+ "schema_version": "1.1",
4
+ "name": "status_from_error",
5
+ "path": "packages/ree_roda/package/ree_roda/services/status_from_error.rb",
6
+ "mount_as": "fn",
7
+ "class": "ReeRoda::StatusFromError",
8
+ "factory": null,
9
+ "methods": [
10
+ {
11
+ "doc": "",
12
+ "throws": [
13
+
14
+ ],
15
+ "return": "Nilor[Integer]",
16
+ "args": [
17
+ {
18
+ "arg": "error_type",
19
+ "arg_type": "req",
20
+ "type": "Symbol"
21
+ }
22
+ ]
23
+ }
24
+ ],
25
+ "links": [
26
+
27
+ ]
28
+ }
@@ -0,0 +1,14 @@
1
+ RSpec.describe "ReeRoda" do
2
+ it "generates package schema" do
3
+ require "fileutils"
4
+
5
+ packages_schema_path = Ree.locate_packages_schema(__dir__)
6
+ packages_schema_dir = Pathname.new(packages_schema_path).dirname.to_s
7
+
8
+ FileUtils.cd packages_schema_dir do
9
+ expect(
10
+ system("REE_SKIP_ENV_VARS_CHECK=true bundle exec ree gen.package_json ree_roda --silence")
11
+ ).to eq(true)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,84 @@
1
+ require "rack/test"
2
+
3
+ package_require("ree_roda/app")
4
+ package_require("ree_actions/dsl")
5
+ package_require("ree_roda/plugins/ree_actions")
6
+
7
+ RSpec.describe ReeRoda::App do
8
+ include Rack::Test::Methods
9
+
10
+ before :all do
11
+ Ree.enable_irb_mode
12
+
13
+ module ReeRodaTest
14
+ include Ree::PackageDSL
15
+
16
+ package
17
+ end
18
+
19
+ class ReeRodaTest::Cmd
20
+ include ReeActions::ActionDSL
21
+
22
+ action :cmd
23
+
24
+ ActionCaster = build_mapper.use(:cast) do
25
+ integer :id
26
+ end
27
+
28
+ def call(access, attrs)
29
+ end
30
+ end
31
+
32
+ class ReeRodaTest::TestActions
33
+ include ReeActions::DSL
34
+
35
+ actions :test_actions do
36
+ default_warden_scope :identity
37
+ opts = {from: :ree_roda_test}
38
+
39
+ get "api/action/:id" do
40
+ summary "Some action"
41
+ warden_scope :visitor
42
+ sections "some_action"
43
+ action :cmd, **opts
44
+ end
45
+ end
46
+ end
47
+
48
+ class TestApp < ReeRoda::App
49
+ plugin :ree_actions
50
+
51
+ ree_actions ReeRodaTest::TestActions.new,
52
+ api_url: "http://some.api.url:1337",
53
+ swagger_url: "swagger"
54
+
55
+ route do |r|
56
+ r.get "health" do
57
+ "success"
58
+ end
59
+
60
+ r.ree_actions
61
+ end
62
+ end
63
+ end
64
+
65
+ after :all do
66
+ Ree.disable_irb_mode
67
+ end
68
+
69
+ let(:app) { TestApp.app }
70
+
71
+ it {
72
+ get "health"
73
+ expect(last_response.body).to eq("success")
74
+ expect(last_response.status).to eq(200)
75
+ }
76
+
77
+ it {
78
+ get "swagger"
79
+ expect(last_response.status).to eq(200)
80
+
81
+ get "api/v1/swagger"
82
+ expect(last_response.status).to eq(404)
83
+ }
84
+ end
@@ -0,0 +1,3 @@
1
+ RSpec.configure do |config|
2
+ config.extend Ree::RSpecLinkDSL
3
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReeLib
4
- VERSION = "1.0.36"
4
+ VERSION = "1.0.37"
5
5
  end