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 +7 -0
- data/lib/microcon.rb +18 -0
- data/lib/microcon/contextualizer.rb +35 -0
- data/lib/microcon/controller.rb +76 -0
- data/lib/microcon/functions.rb +8 -0
- data/lib/microcon/request.rb +45 -0
- data/lib/microcon/response.rb +19 -0
- data/lib/microcon/result_handlers.rb +56 -0
- data/lib/microcon/result_types.rb +10 -0
- data/lib/microcon/version.rb +3 -0
- metadata +179 -0
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,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
|
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: []
|