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