fauna 1.3.4 → 2.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.
@@ -1,152 +0,0 @@
1
- module Fauna
2
- class Connection # rubocop:disable Metrics/ClassLength
3
- class Error < RuntimeError
4
- attr_reader :error, :reason, :parameters
5
-
6
- def initialize(error, reason = nil, parameters = {})
7
- if error.is_a?(Hash)
8
- json = error
9
- @error = json['error']
10
- @reason = json['reason']
11
- @parameters = json['parameters'] || {}
12
- else
13
- @error = error
14
- @reason = reason
15
- @parameters = parameters
16
- end
17
-
18
- super(@reason || @error)
19
- end
20
- end
21
-
22
- class NotFound < Error; end
23
- class BadRequest < Error; end
24
- class Unauthorized < Error; end
25
- class PermissionDenied < Error; end
26
- class MethodNotAllowed < Error; end
27
- class NetworkError < Error; end
28
-
29
- attr_reader :domain, :scheme, :port, :credentials, :timeout, :connection_timeout, :adapter, :logger
30
-
31
- def initialize(params = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
32
- @logger = params[:logger] || nil
33
- @domain = params[:domain] || 'rest1.fauna.org'
34
- @scheme = params[:scheme] || 'https'
35
- @port = params[:port] || (@scheme == 'https' ? 443 : 80)
36
- @timeout = params[:timeout] || 60
37
- @connection_timeout = params[:connection_timeout] || 60
38
- @adapter = params[:adapter] || Faraday.default_adapter
39
- @credentials = params[:secret].to_s.split(':')
40
-
41
- if ENV['FAUNA_DEBUG']
42
- @logger = Logger.new(STDERR)
43
- @logger.formatter = proc { |_, _, _, msg| "#{msg}\n" }
44
- end
45
-
46
- @conn = Faraday.new(
47
- :url => "#{@scheme}://#{@domain}:#{@port}/",
48
- :headers => { 'Accept-Encoding' => 'gzip', 'Content-Type' => 'application/json;charset=utf-8' },
49
- :request => { :timeout => @timeout, :open_timeout => @connection_timeout },
50
- ) do |conn|
51
- conn.adapter(@adapter)
52
- conn.basic_auth(@credentials[0].to_s, @credentials[1].to_s)
53
- end
54
- end
55
-
56
- def get(ref, query = {})
57
- parse(*execute(:get, ref, nil, query))
58
- end
59
-
60
- def post(ref, data = {})
61
- parse(*execute(:post, ref, data))
62
- end
63
-
64
- def put(ref, data = {})
65
- parse(*execute(:put, ref, data))
66
- end
67
-
68
- def patch(ref, data = {})
69
- parse(*execute(:patch, ref, data))
70
- end
71
-
72
- def delete(ref, data = {})
73
- execute(:delete, ref, data)
74
- nil
75
- end
76
-
77
- private
78
-
79
- def parse(headers, body)
80
- obj = body.empty? ? {} : JSON.parse(body)
81
- obj.merge!('headers' => headers)
82
- obj
83
- end
84
-
85
- def log(indent)
86
- lines = Array(yield).collect { |string| string.split("\n") }
87
- lines.flatten.each { |line| @logger.debug(' ' * indent + line) }
88
- end
89
-
90
- def query_string_for_logging(query)
91
- return unless query && !query.empty?
92
-
93
- '?' + query.collect do |k, v|
94
- "#{k}=#{v}"
95
- end.join('&')
96
- end
97
-
98
- def inflate(response)
99
- if %w(gzip deflate).include?(response.headers['Content-Encoding'])
100
- Zlib::GzipReader.new(StringIO.new(response.body.to_s), :external_encoding => Encoding::UTF_8).read
101
- else
102
- response.body.to_s
103
- end
104
- end
105
-
106
- def execute(action, ref, data = nil, query = nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
107
- if @logger
108
- log(0) { "Fauna #{action.to_s.upcase} /#{ref}#{query_string_for_logging(query)}" }
109
- log(2) { "Credentials: #{@credentials}" }
110
- log(2) { "Request JSON: #{JSON.pretty_generate(data)}" } if data
111
-
112
- t0, r0 = Process.times, Time.now
113
- response = execute_without_logging(action, ref, data, query)
114
- t1, r1 = Process.times, Time.now
115
- body = inflate(response)
116
-
117
- real = r1.to_f - r0.to_f
118
- cpu = (t1.utime - t0.utime) + (t1.stime - t0.stime) + (t1.cutime - t0.cutime) + (t1.cstime - t0.cstime)
119
- log(2) { ["Response headers: #{JSON.pretty_generate(response.headers)}", "Response JSON: #{body}"] }
120
- log(2) { "Response (#{response.status}): API processing #{response.headers['X-HTTP-Request-Processing-Time']}ms, network latency #{((real - cpu) * 1000).to_i}ms, local processing #{(cpu * 1000).to_i}ms" }
121
- else
122
- response = execute_without_logging(action, ref, data, query)
123
- body = inflate(response)
124
- end
125
-
126
- case response.status
127
- when 200..299
128
- [response.headers, body]
129
- when 400
130
- fail BadRequest.new(JSON.parse(body))
131
- when 401
132
- fail Unauthorized.new(JSON.parse(body))
133
- when 403
134
- fail PermissionDenied.new(JSON.parse(body))
135
- when 404
136
- fail NotFound.new(JSON.parse(body))
137
- when 405
138
- fail MethodNotAllowed.new(JSON.parse(body))
139
- else
140
- fail NetworkError, body
141
- end
142
- end
143
-
144
- def execute_without_logging(action, ref, data, query)
145
- @conn.send(action) do |req|
146
- req.params = query if query.is_a?(Hash)
147
- req.body = data.to_json if data.is_a?(Hash)
148
- req.url(ref || '')
149
- end
150
- end
151
- end
152
- end
@@ -1,17 +0,0 @@
1
- module Fauna
2
- class NamedResource < Fauna::Resource
3
- def name
4
- struct['name']
5
- end
6
-
7
- def ref
8
- super || "#{fauna_class}/#{name}"
9
- end
10
-
11
- private
12
-
13
- def post
14
- fail Invalid, 'Cannot POST to named resource.'
15
- end
16
- end
17
- end
@@ -1,120 +0,0 @@
1
- require 'fauna'
2
-
3
- # Various and sundry Rails integration points
4
-
5
- if defined?(Rails)
6
- module Fauna
7
- mattr_accessor :root_connection
8
- mattr_accessor :connection
9
-
10
- @silent = false
11
-
12
- CONFIG_FILE = "#{Rails.root}/config/fauna.yml"
13
- LOCAL_CONFIG_FILE = "#{ENV['HOME']}/.fauna.yml"
14
- APP_NAME = Rails.application.class.name.split('::').first.underscore
15
- FIXTURES_DIR = "#{Rails.root}/test/fixtures/fauna"
16
-
17
- # rubocop:disable Metrics/BlockNesting
18
- def self.auth! # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
19
- if File.exist? CONFIG_FILE
20
- credentials = YAML.load_file(CONFIG_FILE)[Rails.env] || {}
21
-
22
- if File.exist? LOCAL_CONFIG_FILE
23
- credentials.merge!((YAML.load_file(LOCAL_CONFIG_FILE)[APP_NAME] || {})[Rails.env] || {})
24
- end
25
-
26
- unless @silent
27
- if credentials['secret']
28
- STDERR.puts ">> Using Fauna server key #{credentials['secret'].inspect} for #{APP_NAME.inspect}."
29
- else
30
- STDERR.puts ">> Using Fauna account #{credentials['email'].inspect} for #{APP_NAME.inspect}."
31
- end
32
-
33
- STDERR.puts '>> You can change this in config/fauna.yml or ~/.fauna.yml.'
34
- end
35
-
36
- if credentials['secret']
37
- secret = credentials['secret']
38
- else
39
- self.root_connection = Connection.new(
40
- :email => credentials['email'],
41
- :password => credentials['password'],
42
- :logger => Rails.logger)
43
-
44
- secret = root_connection.post('keys', 'role' => 'server')['resource']['key']
45
- end
46
-
47
- self.connection = Connection.new(:secret => secret, :logger => Rails.logger)
48
- else
49
- unless @silent
50
- STDERR.puts '>> Fauna account not configured. You can add one in config/fauna.yml.'
51
- end
52
- end
53
-
54
- @silent = true
55
- nil
56
- end
57
- # rubocop:enable Metrics/BlockNesting
58
-
59
- # Around filter to set up a default context
60
-
61
- # ActionDispatch's Auto reloader blows away some of Fauna's schema
62
- # configuration that does not live within the Model classes
63
- # themselves. Add a callback to Reloader to reload the schema config
64
- # before each request.
65
- def self.install_around_filter!
66
- if Fauna.connection && defined?(ActionController::Base)
67
- ApplicationController.class_eval do
68
- around_filter :default_fauna_context
69
-
70
- def default_fauna_context
71
- Fauna::Client.context(Fauna.connection) { yield }
72
- end
73
- end
74
- end
75
- end
76
-
77
- def self.install_reload_callback!
78
- if defined? ActionDispatch::Reloader
79
- ActionDispatch::Reloader.to_prepare do
80
- Fauna.install_around_filter!
81
- end
82
- end
83
- end
84
-
85
- def self.install_inflections!
86
- # ActiveSupport::Inflector's 'humanize' method handles the _id
87
- # suffix for association fields, but not _ref.
88
- if defined? ActiveSupport::Inflector
89
- ActiveSupport::Inflector.inflections do |inflect|
90
- inflect.human(/_ref$/i, '')
91
- end
92
- end
93
- end
94
-
95
- def self.install_test_helper!
96
- if defined? ActiveSupport::TestCase
97
- ActiveSupport::TestCase.setup do
98
- Fauna::Client.push_context(Fauna.connection)
99
- end
100
-
101
- ActiveSupport::TestCase.teardown do
102
- Fauna::Client.pop_context
103
- end
104
- end
105
- end
106
-
107
- def self.install_console_helper!
108
- Rails.application.class.console do
109
- Fauna::Client.push_context(Fauna.connection)
110
- end
111
- end
112
- end
113
-
114
- Fauna.auth!
115
- Fauna.install_around_filter!
116
- Fauna.install_reload_callback!
117
- Fauna.install_inflections!
118
- Fauna.install_test_helper!
119
- Fauna.install_console_helper!
120
- end
@@ -1,175 +0,0 @@
1
- module Fauna
2
- class Resource # rubocop:disable Metrics/ClassLength
3
- def self.resource_subclass(fauna_class)
4
- case fauna_class
5
- when 'databases' then Fauna::NamedResource
6
- when 'classes' then Fauna::NamedResource
7
- when 'indexes' then Fauna::NamedResource
8
- when 'resources' then Fauna::SetPage
9
- when 'events' then Fauna::EventsPage
10
- else Fauna::Resource
11
- end
12
- end
13
-
14
- def self.hydrate(struct)
15
- obj = resource_subclass(struct['class']).allocate
16
- obj.instance_variable_set('@struct', struct)
17
- obj
18
- end
19
-
20
- def self.new(fauna_class, attrs = {})
21
- obj = resource_subclass(fauna_class).allocate
22
- obj.instance_variable_set('@struct', 'ref' => nil, 'ts' => nil, 'deleted' => false, 'class' => fauna_class)
23
- obj.struct = attrs
24
- obj
25
- end
26
-
27
- def self.create(*args)
28
- new(*args).tap(&:save)
29
- end
30
-
31
- def self.find(ref, query = {}, pagination = {})
32
- hydrate(Fauna::Client.get(ref, query, pagination))
33
- end
34
-
35
- attr_reader :struct
36
- alias_method :to_hash, :struct
37
-
38
- def ts
39
- struct['ts'] ? Fauna.time_from_usecs(struct['ts']) : nil
40
- end
41
-
42
- def ts=(time)
43
- struct['ts'] = Fauna.usecs_from_time(time)
44
- end
45
-
46
- def ref
47
- struct['ref']
48
- end
49
-
50
- def fauna_class
51
- struct['class']
52
- end
53
-
54
- def deleted
55
- struct['deleted']
56
- end
57
-
58
- def constraints
59
- struct['constraints'] ||= {}
60
- end
61
-
62
- def data
63
- struct['data'] ||= {}
64
- end
65
-
66
- def references
67
- struct['references'] ||= {}
68
- end
69
-
70
- def permissions
71
- struct['permissions'] ||= {}
72
- end
73
-
74
- def events(pagination = {})
75
- EventsPage.find("#{ref}/events", {}, pagination)
76
- end
77
-
78
- def new_event(action, time)
79
- return unless persisted?
80
-
81
- Fauna::Event.new(
82
- 'resource' => ref,
83
- 'set' => ref,
84
- 'action' => action,
85
- 'ts' => Fauna.usecs_from_time(time),
86
- )
87
- end
88
-
89
- def set(name)
90
- CustomSet.new("#{ref}/sets/#{CGI.escape(name)}")
91
- end
92
-
93
- def eql?(other)
94
- fauna_class == other.fauna_class && ref == other.ref && !ref.nil?
95
- end
96
- alias_method :==, :eql?
97
-
98
- # dynamic field access
99
-
100
- def respond_to?(method, *args)
101
- !!getter_method(method) || !!setter_method(method) || super
102
- end
103
-
104
- def method_missing(method, *args)
105
- if (field = getter_method(method))
106
- struct[field]
107
- elsif (field = setter_method(method))
108
- struct[field] = args.first
109
- else
110
- super
111
- end
112
- end
113
-
114
- # object lifecycle
115
-
116
- def new_record?
117
- ref.nil?
118
- end
119
-
120
- def deleted?
121
- !!deleted
122
- end
123
-
124
- def persisted?
125
- !(new_record? || deleted?)
126
- end
127
-
128
- def save
129
- new_record? ? post : put
130
- end
131
-
132
- def delete
133
- Fauna::Client.delete(ref) if persisted?
134
- struct['deleted'] = true
135
- struct.freeze
136
- nil
137
- end
138
-
139
- def put
140
- @struct = Fauna::Client.put(ref, struct).to_hash
141
- end
142
-
143
- def patch
144
- @struct = Fauna::Client.patch(ref, struct).to_hash
145
- end
146
-
147
- def post
148
- @struct = Fauna::Client.post(fauna_class, struct).to_hash
149
- end
150
-
151
- # TODO: make this configurable, and possible to invert to a white list
152
- UNASSIGNABLE_ATTRIBUTES = %w(ts deleted fauna_class).inject({}) { |h, attr| h.update attr => true }
153
-
154
- def struct=(attributes)
155
- default_attributes.merge(attributes).each do |name, val|
156
- send "#{name}=", val unless UNASSIGNABLE_ATTRIBUTES[name.to_s]
157
- end
158
- end
159
-
160
- private
161
-
162
- def default_attributes
163
- { 'data' => {}, 'references' => {}, 'constraints' => {} }
164
- end
165
-
166
- def getter_method(method)
167
- field = method.to_s
168
- struct.include?(field) ? field : nil
169
- end
170
-
171
- def setter_method(method)
172
- (/(.*)=$/ =~ method.to_s) ? Regexp.last_match[1] : nil
173
- end
174
- end
175
- end