fauna 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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