acfs 1.3.3 → 1.3.4

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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +339 -0
  3. data/LICENSE +22 -0
  4. data/README.md +335 -0
  5. data/acfs.gemspec +46 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +24 -0
  8. data/lib/acfs/adapter/typhoeus.rb +69 -0
  9. data/lib/acfs/collection.rb +28 -0
  10. data/lib/acfs/collections/paginatable.rb +76 -0
  11. data/lib/acfs/configuration.rb +120 -0
  12. data/lib/acfs/errors.rb +127 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +82 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +29 -0
  17. data/lib/acfs/middleware/logger.rb +25 -0
  18. data/lib/acfs/middleware/msgpack.rb +32 -0
  19. data/lib/acfs/middleware/print.rb +23 -0
  20. data/lib/acfs/middleware/serializer.rb +41 -0
  21. data/lib/acfs/operation.rb +83 -0
  22. data/lib/acfs/request.rb +39 -0
  23. data/lib/acfs/request/callbacks.rb +54 -0
  24. data/lib/acfs/resource.rb +39 -0
  25. data/lib/acfs/resource/attributes.rb +269 -0
  26. data/lib/acfs/resource/attributes/base.rb +29 -0
  27. data/lib/acfs/resource/attributes/boolean.rb +39 -0
  28. data/lib/acfs/resource/attributes/date_time.rb +32 -0
  29. data/lib/acfs/resource/attributes/dict.rb +39 -0
  30. data/lib/acfs/resource/attributes/float.rb +33 -0
  31. data/lib/acfs/resource/attributes/integer.rb +29 -0
  32. data/lib/acfs/resource/attributes/list.rb +36 -0
  33. data/lib/acfs/resource/attributes/string.rb +26 -0
  34. data/lib/acfs/resource/attributes/uuid.rb +48 -0
  35. data/lib/acfs/resource/dirty.rb +37 -0
  36. data/lib/acfs/resource/initialization.rb +31 -0
  37. data/lib/acfs/resource/loadable.rb +35 -0
  38. data/lib/acfs/resource/locatable.rb +132 -0
  39. data/lib/acfs/resource/operational.rb +23 -0
  40. data/lib/acfs/resource/persistence.rb +260 -0
  41. data/lib/acfs/resource/query_methods.rb +266 -0
  42. data/lib/acfs/resource/service.rb +44 -0
  43. data/lib/acfs/resource/validation.rb +39 -0
  44. data/lib/acfs/response.rb +30 -0
  45. data/lib/acfs/response/formats.rb +27 -0
  46. data/lib/acfs/response/status.rb +33 -0
  47. data/lib/acfs/rspec.rb +13 -0
  48. data/lib/acfs/runner.rb +102 -0
  49. data/lib/acfs/service.rb +97 -0
  50. data/lib/acfs/service/middleware.rb +58 -0
  51. data/lib/acfs/service/middleware/stack.rb +65 -0
  52. data/lib/acfs/singleton_resource.rb +85 -0
  53. data/lib/acfs/stub.rb +194 -0
  54. data/lib/acfs/util.rb +22 -0
  55. data/lib/acfs/version.rb +16 -0
  56. data/lib/acfs/yard.rb +6 -0
  57. data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
  58. data/spec/acfs/collection_spec.rb +157 -0
  59. data/spec/acfs/configuration_spec.rb +53 -0
  60. data/spec/acfs/global_spec.rb +140 -0
  61. data/spec/acfs/location_spec.rb +25 -0
  62. data/spec/acfs/middleware/json_spec.rb +65 -0
  63. data/spec/acfs/middleware/msgpack_spec.rb +62 -0
  64. data/spec/acfs/operation_spec.rb +12 -0
  65. data/spec/acfs/request/callbacks_spec.rb +48 -0
  66. data/spec/acfs/request_spec.rb +79 -0
  67. data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
  68. data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
  69. data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
  70. data/spec/acfs/resource/attributes/float_spec.rb +61 -0
  71. data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
  72. data/spec/acfs/resource/attributes/list_spec.rb +60 -0
  73. data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
  74. data/spec/acfs/resource/attributes_spec.rb +181 -0
  75. data/spec/acfs/resource/dirty_spec.rb +49 -0
  76. data/spec/acfs/resource/initialization_spec.rb +36 -0
  77. data/spec/acfs/resource/loadable_spec.rb +22 -0
  78. data/spec/acfs/resource/locatable_spec.rb +118 -0
  79. data/spec/acfs/resource/persistance_spec.rb +322 -0
  80. data/spec/acfs/resource/query_methods_spec.rb +548 -0
  81. data/spec/acfs/resource/validation_spec.rb +129 -0
  82. data/spec/acfs/response/formats_spec.rb +52 -0
  83. data/spec/acfs/response/status_spec.rb +71 -0
  84. data/spec/acfs/runner_spec.rb +95 -0
  85. data/spec/acfs/service/middleware_spec.rb +35 -0
  86. data/spec/acfs/service_spec.rb +48 -0
  87. data/spec/acfs/singleton_resource_spec.rb +17 -0
  88. data/spec/acfs/stub_spec.rb +345 -0
  89. data/spec/acfs_spec.rb +205 -0
  90. data/spec/fixtures/config.yml +14 -0
  91. data/spec/spec_helper.rb +43 -0
  92. data/spec/support/hash.rb +11 -0
  93. data/spec/support/response.rb +12 -0
  94. data/spec/support/service.rb +92 -0
  95. data/spec/support/shared/find_callbacks.rb +50 -0
  96. metadata +136 -3
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ # Included by Acfs::Model. Allows to configure the service
5
+ # a resource belongs to.
6
+ #
7
+ module Service
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ # @api public
12
+ #
13
+ # @overload service()
14
+ # Return service instance.
15
+ #
16
+ # @return [Service] Service class instance.
17
+ #
18
+ # @overload service(klass, options = {})
19
+ # Link to service this model belongs to. Connection
20
+ # settings like base URL are fetched from service.
21
+ # Return assigned service if no arguments are given.
22
+ #
23
+ # @example
24
+ # class AccountService < Acfs::Client
25
+ # self.base_url = 'http://acc.serv.org'
26
+ # end
27
+ #
28
+ # class MyUser < Acfs::Resource
29
+ # service AccountService
30
+ # end
31
+ # MyUser.find 5 # Will fetch `http://acc.serv.org/users/5`
32
+ #
33
+ # @param klass [Class] Service class derived from {Acfs::Service}.
34
+ # @param options [Object] Option delegated to
35
+ # service class initializer.
36
+ #
37
+ def service(klass = nil, options = {})
38
+ return (@service = klass.new options) if klass
39
+
40
+ @service || superclass.service
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Acfs::Resource
4
+ module Validation
5
+ def valid?(*args)
6
+ super
7
+ remote_errors.each {|f, e| errors.add f, e }
8
+ errors.empty?
9
+ end
10
+
11
+ def remote_errors
12
+ @remote_errors ||= ActiveModel::Errors.new self
13
+ end
14
+
15
+ def remote_errors=(errors)
16
+ if errors.respond_to?(:each_pair)
17
+ errors.each_pair do |field, errs|
18
+ Array(errs).each do |err|
19
+ self.errors.add field.to_sym, err
20
+ remote_errors.add field.to_sym, err
21
+ end
22
+ end
23
+ else
24
+ Array(errors).each do |err|
25
+ self.errors.add :base, err
26
+ remote_errors.add :base, err
27
+ end
28
+ end
29
+ end
30
+
31
+ def save!(*_)
32
+ unless valid?(new? ? :create : :save)
33
+ raise ::Acfs::InvalidResource.new resource: self, errors: errors.to_a
34
+ end
35
+
36
+ super
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acfs/response/formats'
4
+ require 'acfs/response/status'
5
+ require 'active_support/core_ext/module/delegation'
6
+
7
+ module Acfs
8
+ # This represents a response. In addition to an standard HTTP
9
+ # it has a field `data` for storing the encoded body.
10
+ #
11
+ class Response
12
+ attr_accessor :data
13
+ attr_reader :headers, :body, :request, :status
14
+
15
+ include Response::Formats
16
+ include Response::Status
17
+
18
+ # delegate :status, :status_message, :success?, :modified?, :timed_out?,
19
+ # :response_body, :response_headers, :response_code, :headers,
20
+ # to: :response
21
+
22
+ def initialize(request, data = {})
23
+ @request = request
24
+ @status = data[:status] || 0
25
+ @headers = data[:headers] || {}
26
+ @body = data[:body] || ''
27
+ @data = data[:data] || nil
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch'
4
+
5
+ module Acfs
6
+ class Response
7
+ # Quick accessors for format handling.
8
+ module Formats
9
+ def content_type
10
+ @content_type ||= read_content_type
11
+ end
12
+
13
+ def json?
14
+ content_type == Mime[:json]
15
+ end
16
+
17
+ private
18
+
19
+ def read_content_type
20
+ return 'text/plain' unless headers && headers['Content-Type']
21
+
22
+ content_type = headers['Content-Type'].split(/;\s*\w+="?\w+"?/).first
23
+ Mime::Type.parse(content_type).first
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Acfs
4
+ class Response
5
+ # Method to fetch information about response status.
6
+ #
7
+ module Status
8
+ # Return response status code. Will return zero if
9
+ # request was not executed or failed on client side.
10
+ #
11
+ def status_code
12
+ return @status.to_i if defined? :@status
13
+ # return response.response_code unless response.nil?
14
+ # 0
15
+ end
16
+ alias code status_code
17
+
18
+ # Return true if response was successful indicated by
19
+ # response status code.
20
+ #
21
+ def success?
22
+ code >= 200 && code < 300
23
+ end
24
+
25
+ # Return true unless response status code indicates that
26
+ # resource was not modified according to send precondition headers.
27
+ #
28
+ def modified?
29
+ code != 304
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acfs'
4
+
5
+ RSpec.configure do |config|
6
+ config.before(:each) do
7
+ Acfs::Stub.enable
8
+ end
9
+
10
+ config.after(:each) do
11
+ Acfs.reset
12
+ end
13
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acfs/service/middleware'
4
+
5
+ module Acfs
6
+ # @api private
7
+ #
8
+ class Runner
9
+ include Service::Middleware
10
+ attr_reader :adapter
11
+
12
+ def initialize(adapter)
13
+ @adapter = adapter
14
+ @running = false
15
+ end
16
+
17
+ # Process an operation. Synchronous operations will be run
18
+ # and parallel operations will be queued.
19
+ #
20
+ def process(op)
21
+ ::ActiveSupport::Notifications.instrument 'acfs.operation.before_process', operation: op
22
+ op.synchronous? ? run(op) : enqueue(op)
23
+ end
24
+
25
+ # Run operation right now skipping queue.
26
+ #
27
+ def run(op)
28
+ ::ActiveSupport::Notifications.instrument 'acfs.runner.sync_run', operation: op do
29
+ op_request(op) {|req| adapter.run req }
30
+ end
31
+ end
32
+
33
+ # List of current queued operations.
34
+ #
35
+ def queue
36
+ @queue ||= []
37
+ end
38
+
39
+ # Enqueue operation to be run later.
40
+ #
41
+ def enqueue(op)
42
+ ::ActiveSupport::Notifications.instrument 'acfs.runner.enqueue', operation: op do
43
+ if running?
44
+ op_request(op) {|req| adapter.queue req }
45
+ else
46
+ queue << op
47
+ end
48
+ end
49
+ end
50
+
51
+ # Return true if queued operations are currently processed.
52
+ #
53
+ def running?
54
+ @running
55
+ end
56
+
57
+ # Start processing queued operations.
58
+ #
59
+ def start
60
+ return if running?
61
+
62
+ enqueue_operations
63
+ start_all
64
+ rescue StandardError
65
+ queue.clear
66
+ raise
67
+ end
68
+
69
+ def clear
70
+ queue.clear
71
+ adapter.abort
72
+ @running = false
73
+ end
74
+
75
+ private
76
+
77
+ def start_all
78
+ @running = true
79
+ adapter.start
80
+ ensure
81
+ @running = false
82
+ end
83
+
84
+ def enqueue_operations
85
+ while (op = queue.shift)
86
+ op_request(op) {|req| adapter.queue req }
87
+ end
88
+ end
89
+
90
+ def op_request(op)
91
+ return if Acfs::Stub.enabled? && Acfs::Stub.stubbed(op)
92
+
93
+ req = op.service.prepare op.request
94
+ return unless req.is_a? Acfs::Request
95
+
96
+ req = prepare req
97
+ return unless req.is_a? Acfs::Request
98
+
99
+ yield req
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acfs/service/middleware'
4
+
5
+ module Acfs
6
+ # User {Acfs::Service} to define your services. That includes
7
+ # an identity used to identify the service in configuration files
8
+ # and middlewares the service uses.
9
+ #
10
+ # Configure your service URLs in a YAML file loaded in an
11
+ # initializer using the identity as a key:
12
+ #
13
+ # production:
14
+ # services:
15
+ # user_service_key: "http://users.service.org/base/path"
16
+ #
17
+ # @example
18
+ # class UserService < Acfs::Service
19
+ # identity :user_service_key
20
+ #
21
+ # use Acfs::Middleware::MessagePackDecoder
22
+ # end
23
+ #
24
+ class Service
25
+ attr_accessor :options
26
+
27
+ include Service::Middleware
28
+
29
+ # @api private
30
+ #
31
+ def initialize(options = {})
32
+ @options = options
33
+ end
34
+
35
+ # @api private
36
+ # @return [Location]
37
+ #
38
+ def location(resource_class, opts = {})
39
+ opts.reverse_merge! options
40
+
41
+ action = opts[:action] || :list
42
+
43
+ path = if opts[:path].is_a?(Hash) && opts[:path].key?(action)
44
+ opts[:path].fetch(action)
45
+ else
46
+ path = if opts[:path].is_a?(Hash)
47
+ opts[:path][:all].to_s
48
+ else
49
+ opts[:path].to_s
50
+ end
51
+
52
+ if path.blank?
53
+ path = (resource_class.name || 'class').pluralize.underscore
54
+ end
55
+
56
+ resource_class.location_default_path(action, path.strip)
57
+ end
58
+
59
+ if path.nil?
60
+ raise ArgumentError.new "Location for `#{action}' explicit disabled by set to nil."
61
+ end
62
+
63
+ Location.new [self.class.base_url.to_s, path.to_s].join('/')
64
+ end
65
+
66
+ class << self
67
+ # @api public
68
+ #
69
+ # @overload identity()
70
+ # Return configured identity key or derive key from class name.
71
+ #
72
+ # @return [Symbol] Service identity key.
73
+ #
74
+ # @overload identity(identity)
75
+ # Set identity key.
76
+ #
77
+ # @param [#to_s] identity New identity key.
78
+ # @return [Symbol] New set identity key.
79
+ #
80
+ def identity(identity = nil)
81
+ @identity = identity.to_s.to_sym unless identity.nil?
82
+ @identity ||= name.to_sym
83
+ end
84
+
85
+ # @api private
86
+ # @return [String]
87
+ #
88
+ def base_url
89
+ unless (base = Acfs::Configuration.current.locate identity)
90
+ raise ArgumentError.new "#{identity} not configured. Add `locate '#{identity.to_s.underscore}', 'http://service.url/'` to your configuration."
91
+ end
92
+
93
+ base.to_s
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'acfs/service/middleware/stack'
4
+
5
+ module Acfs
6
+ class Service
7
+ # Module providing all function to register middlewares
8
+ # on services and process queued request through the
9
+ # middleware stack.
10
+ #
11
+ module Middleware
12
+ extend ActiveSupport::Concern
13
+
14
+ # @api private
15
+ # @return [Request]
16
+ #
17
+ def prepare(request)
18
+ self.class.middleware.call request
19
+ end
20
+
21
+ module ClassMethods
22
+ # @!method use(klass, *args, &block)
23
+ # @api public
24
+ #
25
+ # Register a new middleware to be used for this service.
26
+ #
27
+ # @example
28
+ # class MyService < Acfs::Service
29
+ # self.base_url = 'http://my.srv'
30
+ # use Acfs::Middleware::JSON
31
+ # end
32
+ #
33
+ # @param [Class] klass Middleware class to append
34
+ # @param [Array<Object>] args Arguments passed to klass initialize
35
+ # @param [Proc] block Block passed to klass initialize
36
+ # @return [undefined]
37
+ #
38
+ def use(klass, *args, &block)
39
+ # Backward compatible behavior
40
+ middleware.insert(0, klass, *args, &block)
41
+ end
42
+
43
+ # @api private
44
+ #
45
+ # Return top most middleware.
46
+ #
47
+ # @return [#call]
48
+ #
49
+ def middleware
50
+ @middleware ||= Stack.new
51
+ end
52
+
53
+ # @deprecated
54
+ delegate :clear, to: :middleware
55
+ end
56
+ end
57
+ end
58
+ end