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.
- checksums.yaml +5 -13
- data/CHANGELOG +2 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +65 -102
- data/Rakefile +12 -20
- data/fauna.gemspec +19 -44
- data/lib/fauna.rb +12 -14
- data/lib/fauna/client.rb +225 -27
- data/lib/fauna/client_logger.rb +52 -0
- data/lib/fauna/context.rb +135 -0
- data/lib/fauna/errors.rb +181 -0
- data/lib/fauna/json.rb +60 -0
- data/lib/fauna/objects.rb +96 -0
- data/lib/fauna/query.rb +601 -0
- data/lib/fauna/request_result.rb +58 -0
- data/lib/fauna/util.rb +41 -0
- data/lib/fauna/version.rb +4 -0
- data/spec/client_logger_spec.rb +73 -0
- data/spec/client_spec.rb +202 -0
- data/spec/context_spec.rb +121 -0
- data/spec/errors_spec.rb +144 -0
- data/spec/fauna_helper.rb +87 -0
- data/spec/json_spec.rb +123 -0
- data/spec/query_spec.rb +675 -0
- data/spec/ref_spec.rb +77 -0
- data/spec/setref_spec.rb +23 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/util_spec.rb +19 -0
- metadata +65 -83
- data/Manifest +0 -25
- data/lib/fauna/cache.rb +0 -64
- data/lib/fauna/connection.rb +0 -152
- data/lib/fauna/named_resource.rb +0 -17
- data/lib/fauna/rails.rb +0 -120
- data/lib/fauna/resource.rb +0 -175
- data/lib/fauna/set.rb +0 -240
- data/lib/tasks/fauna.rake +0 -71
- data/test/class_test.rb +0 -65
- data/test/client_test.rb +0 -63
- data/test/connection_test.rb +0 -66
- data/test/database_test.rb +0 -48
- data/test/query_test.rb +0 -48
- data/test/readme_test.rb +0 -30
- data/test/set_test.rb +0 -71
- data/test/test_helper.rb +0 -86
data/lib/fauna/connection.rb
DELETED
@@ -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
|
data/lib/fauna/named_resource.rb
DELETED
data/lib/fauna/rails.rb
DELETED
@@ -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
|
data/lib/fauna/resource.rb
DELETED
@@ -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
|