shogun 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/bin/shogun +18 -0
  3. data/lib/shogun.rb +29 -0
  4. data/lib/shogun/authorizer.rb +7 -0
  5. data/lib/shogun/control.rb +82 -0
  6. data/lib/shogun/control/null.rb +15 -0
  7. data/lib/shogun/database.rb +44 -0
  8. data/lib/shogun/denormalizer.rb +54 -0
  9. data/lib/shogun/dispatch.rb +67 -0
  10. data/lib/shogun/dispatch/lookup.rb +57 -0
  11. data/lib/shogun/dispatch/payload.rb +18 -0
  12. data/lib/shogun/dispatch/route.rb +34 -0
  13. data/lib/shogun/dispatch/route/null.rb +11 -0
  14. data/lib/shogun/endpoint.rb +11 -0
  15. data/lib/shogun/normalizer.rb +31 -0
  16. data/lib/shogun/presenter.rb +57 -0
  17. data/lib/shogun/presenters.rb +28 -0
  18. data/lib/shogun/server.rb +49 -0
  19. data/lib/shogun/validator.rb +15 -0
  20. data/lib/shogun/verifier.rb +9 -0
  21. data/lib/shogun/verifier/allowed.rb +29 -0
  22. data/lib/shogun/verifier/match.rb +31 -0
  23. data/lib/shogun/verifier/presence.rb +26 -0
  24. data/lib/shogun/verifier/range.rb +30 -0
  25. data/lib/shogun/verifier/uniqueness.rb +23 -0
  26. data/lib/shogun/version.rb +3 -0
  27. data/spec/lib/shogun/authorizer_spec.rb +5 -0
  28. data/spec/lib/shogun/control/null_spec.rb +5 -0
  29. data/spec/lib/shogun/control_spec.rb +5 -0
  30. data/spec/lib/shogun/database_spec.rb +5 -0
  31. data/spec/lib/shogun/denormalizer_spec.rb +5 -0
  32. data/spec/lib/shogun/dispatch/lookup_spec.rb +5 -0
  33. data/spec/lib/shogun/dispatch/payload_spec.rb +5 -0
  34. data/spec/lib/shogun/dispatch/route/null_spec.rb +5 -0
  35. data/spec/lib/shogun/dispatch/route_spec.rb +5 -0
  36. data/spec/lib/shogun/dispatch_spec.rb +5 -0
  37. data/spec/lib/shogun/endpoint_spec.rb +5 -0
  38. data/spec/lib/shogun/normalizer_spec.rb +5 -0
  39. data/spec/lib/shogun/presenter_spec.rb +5 -0
  40. data/spec/lib/shogun/presenters_spec.rb +5 -0
  41. data/spec/lib/shogun/server_spec.rb +5 -0
  42. data/spec/lib/shogun/validator_spec.rb +5 -0
  43. data/spec/lib/shogun/verifier/allowed_spec.rb +5 -0
  44. data/spec/lib/shogun/verifier/match_spec.rb +5 -0
  45. data/spec/lib/shogun/verifier/presence_spec.rb +5 -0
  46. data/spec/lib/shogun/verifier/range_spec.rb +5 -0
  47. data/spec/lib/shogun/verifier/uniqueness_spec.rb +5 -0
  48. data/spec/lib/shogun/verifier_spec.rb +5 -0
  49. data/spec/lib/shogun_spec.rb +5 -0
  50. data/spec/spec_helper.rb +6 -0
  51. metadata +385 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: caae485be3f9b7816128451965ff1acbe5d0ff37
4
+ data.tar.gz: ef5108915c2044a505a1f57f1bb3d8f965d22d28
5
+ SHA512:
6
+ metadata.gz: 42da9e33491a23787326a5c8ec8ce174b4430a7a7c9ebfa60d2b0d610d0cd4b2338e4f6b44416cf5053a3f35b266e990a8f3fd1a5ece4fda0a06058b526b0c73
7
+ data.tar.gz: 0a55db9e31daef5f4056f522f6ead270ce1b854fa83d76f7bbc487032603ddcaee5f99527d6237a86308cd714e6c1bb9887447bbddc8d0e6f66e7aa2b23a3cfd
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "shogun"
4
+ require "pry"
5
+ require Dir["./lib/*.rb"].first
6
+
7
+ case ARGV.shift
8
+ when "database" then
9
+ case ARGV.shift
10
+ when "reset" then
11
+ Shogun::Database.setup!(logger: Logger.new(STDOUT))
12
+ when "console" then
13
+ Shogun::Database.new(logger: Logger.new(STDOUT))
14
+ binding.pry
15
+ end
16
+ when "console" then
17
+ binding.pry
18
+ end
@@ -0,0 +1,29 @@
1
+ require "adamantium"
2
+ require "rack-protection"
3
+ require "rack-commonlogger"
4
+ require "rack-body_serializer"
5
+ require "rack-authentication_bearer"
6
+ require "rack-body_deserializer"
7
+ require "rack-accept_setter"
8
+ require "rack-chunker"
9
+ require "rack-content_length_setter"
10
+ require "rack-runtime"
11
+ require "rack-log"
12
+ require "scrawl"
13
+ require "active_record"
14
+
15
+ module Shogun
16
+ require_relative "shogun/authorizer"
17
+ require_relative "shogun/control"
18
+ require_relative "shogun/database"
19
+ require_relative "shogun/denormalizer"
20
+ require_relative "shogun/dispatch"
21
+ require_relative "shogun/endpoint"
22
+ require_relative "shogun/normalizer"
23
+ require_relative "shogun/presenter"
24
+ require_relative "shogun/presenters"
25
+ require_relative "shogun/server"
26
+ require_relative "shogun/validator"
27
+ require_relative "shogun/verifier"
28
+ require_relative "shogun/version"
29
+ end
@@ -0,0 +1,7 @@
1
+ module Shogun
2
+ module Authorizer
3
+ def denied?
4
+ !allowed?
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,82 @@
1
+ module Shogun
2
+ module Control
3
+ CONTINUE = 100
4
+ SWITCHING_PROTOCOLS = 101
5
+ PROCESSING = 102
6
+ OK = 200
7
+ CREATED = 201
8
+ ACCEPTED = 202
9
+ NONAUTHORITATIVE_INFORMATION = 203
10
+ NO_CONTENT = 204
11
+ RESET_CONTENT = 205
12
+ PARTIAL_CONTENT = 206
13
+ MULTISTATUS = 207
14
+ ALREADY_REPORTED = 208
15
+ IM_USED = 226
16
+ MULTIPLE_CHOICES = 300
17
+ MOVED_PERMANENTLY = 301
18
+ FOUND = 302
19
+ SEE_OTHER = 303
20
+ NOT_MODIFIED = 304
21
+ USE_PROXY = 305
22
+ RESERVED = 306
23
+ TEMPORARY_REDIRECT = 307
24
+ PERMANENT_REDIRECT = 308
25
+ BAD_REQUEST = 400
26
+ UNAUTHORIZED = 401
27
+ PAYMENT_REQUIRED = 402
28
+ FORBIDDEN = 403
29
+ NOT_FOUND = 404
30
+ METHOD_NOT_ALLOWED = 405
31
+ NOT_ACCEPTABLE = 406
32
+ PROXY_AUTHENTICATION_REQUIRED = 407
33
+ REQUEST_TIMEOUT = 408
34
+ CONFLICT = 409
35
+ GONE = 410
36
+ LENGTH_REQUIRED = 411
37
+ PRECONDITION_FAILED = 412
38
+ REQUEST_ENTITY_TOO_LARGE = 413
39
+ REQUEST_URI_TOO_LONG = 414
40
+ UNSUPPORTED_MEDIA_TYPE = 415
41
+ REQUESTED_RANGE_NOT_SATISFIABLE = 416
42
+ EXPECTATION_FAILED = 417
43
+ IM_A_TEAPOT = 418
44
+ UNPROCESSABLE_ENTITY = 422
45
+ LOCKED = 423
46
+ FAILED_DEPENDENCY = 424
47
+ UPGRADE_REQUIRED = 426
48
+ PRECONDITION_REQUIRED = 428
49
+ TOO_MANY_REQUESTS = 429
50
+ REQUEST_HEADER_FIELDS_TOO_LARGE = 431
51
+ INTERNAL_SERVER_ERROR = 500
52
+ NOT_IMPLEMENTED = 501
53
+ BAD_GATEWAY = 502
54
+ SERVICE_UNAVAILABLE = 503
55
+ GATEWAY_TIMEOUT = 504
56
+ HTTP_VERSION_NOT_SUPPORTED = 505
57
+ VARIANT_ALSO_NEGOTIATES = 506
58
+ INSUFFICIENT_STORAGE = 507
59
+ LOOP_DETECTED = 508
60
+ NOT_EXTENDED = 510
61
+ NETWORK_AUTHENTICATION_REQUIRED = 511
62
+
63
+ require_relative "control/null"
64
+
65
+
66
+ def to_a
67
+ [status, headers, body]
68
+ end
69
+
70
+ private def status
71
+ @status || INTERNAL_SERVER_ERROR
72
+ end
73
+
74
+ private def headers
75
+ @headers || {}
76
+ end
77
+
78
+ private def body
79
+ if @denormalizer then @denormalizer.as_document else "" end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,15 @@
1
+ module Shogun
2
+ module Control
3
+ class Null
4
+ include Shogun::Control
5
+
6
+ def initialize(payload:)
7
+ # Don't bother with anything here
8
+ end
9
+
10
+ def to_a
11
+ [NOT_FOUND, {}, ""]
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ module Shogun
2
+ class Database
3
+ URL_NAME = "DATABASE_URL"
4
+ TIMEZONE_NAME = "DATABASE_TIMEZONE"
5
+ REAP_FREQUENCY_NAME = "DATABASE_REAP_FREQUENCY"
6
+ POOL_NAME = "DATABASE_POOL"
7
+ TIMEOUT_NAME = "DATABASE_TIMEOUT"
8
+ URL = ENV[URL_NAME]
9
+ TIMEZONE = ENV[TIMEZONE_NAME]
10
+ REAP_FREQUENCY = ENV[REAP_FREQUENCY_NAME]
11
+ POOL = ENV[POOL_NAME]
12
+ TIMEOUT = ENV[TIMEOUT_NAME]
13
+ DEFAULT_TIMEOUT = 20
14
+ DEFAULT_REAP_FREQUENCY = 15
15
+ DEFAULT_POOL = 10
16
+ DEFAULT_TIMEZONE = "utc"
17
+ CONFIGURATION = {
18
+ "reaping_frequency" => Integer(REAP_FREQUENCY || DEFAULT_REAP_FREQUENCY),
19
+ "pool" => Integer(POOL || DEFAULT_POOL),
20
+ "connect_timeout" => Integer(TIMEOUT || DEFAULT_TIMEOUT)
21
+ }
22
+
23
+ def self.connection
24
+ ActiveRecord::Base.connection
25
+ end
26
+
27
+ def self.setup!(logger:)
28
+ new(logger: logger).tap do
29
+ connection.enable_extension("uuid-ossp")
30
+ ActiveRecord::Base.descendants.each(&:setup!)
31
+ end
32
+ end
33
+
34
+ def initialize(logger:)
35
+ @logger = logger
36
+ ActiveRecord::LogSubscriber.logger = @logger
37
+ ActiveRecord::LogSubscriber.colorize_logging = false
38
+ ActiveRecord::Base.time_zone_aware_attributes = true
39
+ ActiveRecord::Base.default_timezone = (self.class.const_get("TIMEZONE") || self.class.const_get("DEFAULT_TIMEZONE")).to_sym
40
+ ActiveRecord::Base.logger = @logger
41
+ ActiveRecord::Base.establish_connection("#{self.class.const_get("URL")}?#{self.class.const_get("CONFIGURATION").to_query}")
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ module Shogun
2
+ module Denormalizer
3
+ META_KEY = "meta"
4
+ LINKS_KEY = "links"
5
+ LINKED_KEY = "linked"
6
+
7
+ def initialize(object:, fields:)
8
+ @object = object
9
+ @fields = fields
10
+ @meta = object.meta
11
+ @links = object.links
12
+ @linked = object.linked
13
+ end
14
+
15
+ def as_document
16
+ {
17
+ document_key => document,
18
+ meta_key => @meta,
19
+ links_key => @links,
20
+ linked_key => @linked
21
+ }
22
+ end
23
+
24
+ private def document
25
+ if @fields.one?
26
+ @object.public_send(@fields.first)
27
+ else
28
+ @fields.inject({}) do |hash, field|
29
+ hash.merge(field => @object.public_send(mapping[field]))
30
+ end
31
+ end
32
+ end
33
+
34
+ private def document_key
35
+ self.class.const_get("DOCUMENT_KEY")
36
+ end
37
+
38
+ private def meta_key
39
+ self.class.const_get("META_KEY")
40
+ end
41
+
42
+ private def links_key
43
+ self.class.const_get("LINKS_KEY")
44
+ end
45
+
46
+ private def linked_key
47
+ self.class.const_get("LINKED_KEY")
48
+ end
49
+
50
+ private def mapping
51
+ self.class.const_get("MAPPING")
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,67 @@
1
+ module Shogun
2
+ class Dispatch
3
+ PATH_KEY = "REQUEST_PATH"
4
+ METHOD_KEY = "REQUEST_METHOD"
5
+ NO_MATCH = -> { // }
6
+
7
+ require_relative "dispatch/route"
8
+ require_relative "dispatch/payload"
9
+ require_relative "dispatch/lookup"
10
+
11
+ attr_reader :routes
12
+ private :routes
13
+
14
+ attr_reader :payload
15
+ private :payload
16
+
17
+ attr_reader :lookup
18
+ private :lookup
19
+
20
+ attr_reader :route
21
+ private :route
22
+
23
+ def initialize(logger:)
24
+ @routes = {}
25
+ @logger = logger
26
+ yield(self)
27
+ end
28
+
29
+ def call(state)
30
+ @state = state
31
+
32
+ lookup = Lookup.new(routes: routes, verb: state[METHOD_KEY], path: state[PATH_KEY])
33
+
34
+ payload = Payload.new(state: state, lookup: lookup)
35
+
36
+ route = lookup.to_route
37
+
38
+ control = route.control.new(payload: payload)
39
+
40
+ control.to_a
41
+ end
42
+
43
+ def push(route:)
44
+ routes[route.as_key] = route
45
+ end
46
+
47
+ private def stack
48
+ @stack
49
+ end
50
+
51
+ private def state
52
+ @state
53
+ end
54
+
55
+ private def headers
56
+ @headers
57
+ end
58
+
59
+ private def status
60
+ @status
61
+ end
62
+
63
+ private def body
64
+ @body
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,57 @@
1
+ module Shogun
2
+ class Dispatch
3
+ class Lookup
4
+ include Adamantium
5
+
6
+ attr_reader :routes
7
+ private :routes
8
+
9
+ def initialize(routes:, verb:, path:)
10
+ @routes = routes
11
+ @verb = verb
12
+ @path = path
13
+ end
14
+
15
+ def request
16
+ "#{@verb} #{@path}"
17
+ end
18
+ memoize :request
19
+
20
+ def payload
21
+ if match.captures.any?
22
+ match.names.zip(match.captures).to_h
23
+ else
24
+ {}
25
+ end
26
+ end
27
+ memoize :payload
28
+
29
+ def to_route
30
+ regular || regexp || Route::Null.new
31
+ end
32
+ memoize :to_route
33
+
34
+ private def regexp
35
+ routes[pattern]
36
+ end
37
+ memoize :regexp
38
+
39
+ private def regular
40
+ routes[request]
41
+ end
42
+ memoize :regular
43
+
44
+ private def match
45
+ request.match(pattern)
46
+ end
47
+ memoize :match
48
+
49
+ private def pattern
50
+ routes.keys.detect(NO_MATCH) do |key|
51
+ if key.is_a?(Regexp) then request =~ key else next end
52
+ end
53
+ end
54
+ memoize :pattern
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,18 @@
1
+ module Shogun
2
+ class Dispatch
3
+ class Payload
4
+ QUERY_KEY = "rack.request.query_hash"
5
+
6
+ def initialize(state:, lookup:)
7
+ @body = state[Rack::BodyDeserializer::RACK_KEY] || {}
8
+ @query = state[QUERY_KEY] || {}
9
+ @uri = lookup.payload
10
+ @sections = [state, @uri, @query, @body]
11
+ end
12
+
13
+ def to_hash
14
+ @sections.inject(:merge)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ module Shogun
2
+ class Dispatch
3
+ class Route
4
+ require_relative "route/null"
5
+
6
+ attr_reader :control
7
+
8
+ def initialize(namespace:, verb:, matcher:, control:)
9
+ @namespace = namespace
10
+ @verb = verb
11
+ @matcher = matcher
12
+ @control = control
13
+ end
14
+
15
+ def as_key
16
+ if expression? then Regexp.new("^#{verb} /#{path}$") else "#{verb} /#{path}" end
17
+ end
18
+
19
+ private def path
20
+ @path ||= [@namespace, @matcher].compact.join("/")
21
+ end
22
+
23
+ private def verb
24
+ @verb.to_s.upcase
25
+ end
26
+
27
+ private def expression?
28
+ [@namespace, @matcher].any? do |item|
29
+ item.is_a?(Regexp)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end