microcon 0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5490ef93d83e8c638a5500d1304543f44252bdb7
4
+ data.tar.gz: 87adab9a51380b2c7ccdd17d52f807bcb2b6c6b1
5
+ SHA512:
6
+ metadata.gz: 45ed93df32e9168ac2b72bdb45477d254ff7b3065659b8e80a96aa499c2504fe6ebfc14003cf55a7c89145098e0fd997c25d203d77b8a10509625b031d36fb99
7
+ data.tar.gz: 4c2c37b2ebc80139f7da84b2b26104fa9555f8e2d325e0a8fe77b6bb4279ce65ea3fd6fb2feb925fd03482a3674b3913ef79cc0d4a7d2dabb21afd448eebbee1
data/lib/microcon.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'dry-monads'
2
+ require 'rack/request'
3
+ require 'oj'
4
+ require 'transproc'
5
+ require 'dry-container'
6
+ require 'adts'
7
+
8
+ require 'microcon/functions'
9
+ require 'microcon/request'
10
+ require 'microcon/response'
11
+ require 'microcon/contextualizer'
12
+ require 'microcon/result_types'
13
+ require 'microcon/result_handlers'
14
+ require 'microcon/controller'
15
+
16
+ Oj.default_options = { symbol_keys: true, indent: 2,
17
+ mode: :compat, bigdecimal_as_decimal: true,
18
+ quirks_mode: false }
@@ -0,0 +1,35 @@
1
+ module Microcon
2
+ module Contextualizer
3
+
4
+ def self.included(klass)
5
+ unless klass.instance_variable_defined? :@contextualizers
6
+ klass.instance_variable_set :@contextualizers, []
7
+ end
8
+ klass.extend Dry::Monads::Either::Mixin
9
+ klass.include Dry::Monads::Either::Mixin
10
+ klass.extend ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ attr_accessor :contextualizers
16
+
17
+ def included(klass)
18
+ if klass.instance_variable_defined? :@contextualizers
19
+ current = klass.instance_variable_get :@contextualizers
20
+ updated = current + @contextualizers
21
+ klass.instance_variable_set :@contextualizers, updated
22
+ end
23
+ end
24
+
25
+ def contextualize_with(&blk)
26
+ @contextualizers << blk
27
+ end
28
+
29
+ def inherited(klass)
30
+ klass.instance_variable_set :@contextualizers, @contextualizers
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,76 @@
1
+ module Microcon
2
+
3
+ class Controller
4
+
5
+ #@@contextualizers = []
6
+
7
+ include Microcon::Contextualizer
8
+
9
+ def call(env)
10
+ #binding.pry
11
+ #F . parse . contextualize . process . call(env)
12
+ result = parse(env)
13
+ .bind {|req| self.class.contextualize(req) }
14
+ .fmap {|context| self.class.process(context) }
15
+ .value
16
+ unless result.is_a?(Result)
17
+ raise "#{result.class} is not a valid result type. Should be a Result object"
18
+ end
19
+ render(result)
20
+ end
21
+
22
+ def self.process_with(handler = nil, &blk)
23
+ @processor = handler.nil? ? blk : handler
24
+ end
25
+
26
+ def self.call(env)
27
+ self.new.call(env)
28
+ end
29
+
30
+ def self.process(context)
31
+ @processor.call(context)
32
+ end
33
+
34
+ private
35
+
36
+ def parse(env)
37
+ Dry::Monads::Right Request.new(env)
38
+ rescue Oj::ParseError
39
+ Dry::Monads::Left Result::Error::BadRequest()
40
+ end
41
+
42
+ def self.contextualize(req)
43
+ # TODO this can be a single loop
44
+ context_parts = self.contextualizers.map do |contextualizer|
45
+ ctr = contextualizer.call **req.to_h
46
+ if ctr.is_a?(Result)
47
+ return Dry::Monads::Left(ctr)
48
+ elsif ctr.is_a?(Hash)
49
+ next Dry::Monads::Right(ctr)
50
+ else
51
+ raise "#{ctr.class} is not a valid return type for a contextualizer. Must be a Result or a Hash"
52
+ end
53
+ end
54
+ context_parts.reduce(Dry::Monads::Maybe({})) do |memo, obj|
55
+ # merge_fn = Dry::Monads::Maybe(-> x,y {x.merge(y)})
56
+ # merge_fn * memo * obj
57
+ #binding.pry
58
+ memo.bind {|m| obj.fmap {|o| m.merge(o)} }
59
+ end
60
+ end
61
+
62
+ def render(result)
63
+ # TODO check it's not nil
64
+ interpreted_result = ResultHandlers[result.class].call(result)
65
+ Response.new(**interpreted_result).to_rack
66
+ rescue Dry::Container::Error => e
67
+ raise "ResultHandlers cannot convert a #{result.class} to an HTTP response"
68
+ end
69
+
70
+ def process(context)
71
+ raise "You must implement the context method for the controller"
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,8 @@
1
+ module Microcon
2
+ module Functions
3
+ extend Transproc::Registry
4
+ # import all singleton methods from a module/class
5
+ import Transproc::HashTransformations
6
+ import Transproc::ArrayTransformations
7
+ end
8
+ end
@@ -0,0 +1,45 @@
1
+ module Microcon
2
+
3
+ class Request
4
+
5
+ attr_reader :headers, :body, :params
6
+
7
+ def initialize(env)
8
+ @env = env
9
+ @req = Rack::Request.new env
10
+ parse_body
11
+ parse_params
12
+ parse_headers
13
+ end
14
+
15
+ def to_h
16
+ {headers: headers, params: params, body: body}
17
+ end
18
+
19
+ private
20
+
21
+ def t(*args)
22
+ Functions[*args]
23
+ end
24
+
25
+ def parse_body
26
+ body = @req.body.read
27
+ parsed_body = body.empty? ? {} : Oj.load(body)
28
+ @body = t(:symbolize_keys)[parsed_body]
29
+ end
30
+
31
+ def parse_headers
32
+ # TODO snake case headers?
33
+ @headers = t(:symbolize_keys)[ @env.select {|k,v| k.start_with? 'HTTP_'}
34
+ .collect {|key, val| [key.sub(/^HTTP_/, '').downcase, val]}
35
+ .instance_eval {|hpairs| Hash[hpairs]} ]
36
+ end
37
+
38
+ def parse_params
39
+ router_params = @env['router.params'] || {} # From Hanami Router
40
+ @params = t(:symbolize_keys)[@req.params.merge(router_params)]
41
+ end
42
+
43
+
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ module Microcon
2
+ class Response #< BasicRequest
3
+
4
+ def initialize(status:, body: {}, headers: {})
5
+ @status = status
6
+ @body = encode(body)
7
+ @headers = headers.merge Rack::CONTENT_TYPE => "application/json; charset=utf-8"
8
+ end
9
+
10
+ def encode(body)
11
+ # TODO transformation
12
+ Oj.dump(body)
13
+ end
14
+
15
+ def to_rack
16
+ [@status, @headers, [@body]]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,56 @@
1
+ require 'dry-container'
2
+ #Core.finalize :base_controller do
3
+ module Microcon
4
+ class ResultHandlers
5
+
6
+ extend Dry::Container::Mixin
7
+
8
+ # configure do |config|
9
+ # #config.registry = ->(container, key, item, options) { container[key] = item }
10
+ # config.resolver = ->(container, key) {
11
+ # # TODO how to use this to compose?
12
+ # Some(container[key])
13
+ # }
14
+ # end
15
+
16
+ register Result::Unauthorized, -> r {
17
+ { status: 401, body: {error: :unauthorized } }
18
+ }
19
+
20
+ register Result::Void, -> r {
21
+ { status: 204, body: { } }
22
+ }
23
+
24
+ register Result::Unprocessable, -> r {
25
+ {
26
+ status: 422,
27
+ body: {
28
+ error: :unprocessable_entity, reason: error.reason
29
+ }
30
+ }
31
+ }
32
+
33
+ register Result::UnprocessableWithErrors, -> r {
34
+ {
35
+ status: 422,
36
+ body: {
37
+ error: :unprocessable_entity, messages: r.errors
38
+ }
39
+ }
40
+ }
41
+
42
+ register Result::NotFound, -> r {
43
+ {status: 404, body: {error: :not_found}}
44
+ }
45
+
46
+
47
+ register Result::RecordCreated, -> r {
48
+ {status: 201, body: r.data}
49
+ }
50
+
51
+ register Result::OK, -> r {
52
+ {status: 200, body: r.data}
53
+ }
54
+
55
+ end
56
+ end
@@ -0,0 +1,10 @@
1
+ Result = ADT do
2
+ BadRequest() | # 400
3
+ Unauthorized() | # 401
4
+ Unprocessable(reason: String) | # 422
5
+ UnprocessableWithErrors(errors: Hash) | # 422
6
+ NotFound() | # 404
7
+ Void() | # 204
8
+ RecordCreated(data: Hash) | # 201
9
+ OK(data: Hash) # 200
10
+ end
@@ -0,0 +1,3 @@
1
+ module Microcon
2
+ VERSION = '0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: microcon
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - David Peláez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-monads
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-container
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: adts
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.1.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.0'
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.0.1
65
+ type: :runtime
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - "~>"
70
+ - !ruby/object:Gem::Version
71
+ version: '2.0'
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 2.0.1
75
+ - !ruby/object:Gem::Dependency
76
+ name: oj
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '2.17'
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: 2.17.1
85
+ type: :runtime
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '2.17'
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 2.17.1
95
+ - !ruby/object:Gem::Dependency
96
+ name: transproc
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: 0.4.0
102
+ type: :runtime
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: 0.4.0
109
+ - !ruby/object:Gem::Dependency
110
+ name: bundler
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.7'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.7'
123
+ - !ruby/object:Gem::Dependency
124
+ name: rake
125
+ requirement: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '11.0'
130
+ type: :development
131
+ prerelease: false
132
+ version_requirements: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - "~>"
135
+ - !ruby/object:Gem::Version
136
+ version: '11.0'
137
+ description: Most JSON APIs have a limited number of respondes or outcomes. Microcon
138
+ tries to separate HTTP related concerns from business logic and removes most of
139
+ the mental clutter regarding JSON parsing, rendering and consistent API responses.
140
+ email:
141
+ - pelaez89@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - lib/microcon.rb
147
+ - lib/microcon/contextualizer.rb
148
+ - lib/microcon/controller.rb
149
+ - lib/microcon/functions.rb
150
+ - lib/microcon/request.rb
151
+ - lib/microcon/response.rb
152
+ - lib/microcon/result_handlers.rb
153
+ - lib/microcon/result_types.rb
154
+ - lib/microcon/version.rb
155
+ homepage: https://github.com/davidpelaez/microcon
156
+ licenses:
157
+ - MIT
158
+ metadata: {}
159
+ post_install_message:
160
+ rdoc_options: []
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ required_rubygems_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ requirements: []
174
+ rubyforge_project:
175
+ rubygems_version: 2.5.1
176
+ signing_key:
177
+ specification_version: 4
178
+ summary: Small controller for JSON APIs
179
+ test_files: []