shogun 1.0.0

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 (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