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
@@ -0,0 +1,58 @@
|
|
1
|
+
module Fauna
|
2
|
+
# The result of a request. Provided to observers and included within errors.
|
3
|
+
class RequestResult
|
4
|
+
# The Client.
|
5
|
+
attr_reader :client
|
6
|
+
# HTTP method. Either +:get+, +:post+, +:put+, +:patch+, or +:delete+
|
7
|
+
attr_reader :method
|
8
|
+
# Path that was queried. Relative to client's domain.
|
9
|
+
attr_reader :path
|
10
|
+
# URL query. +nil+ except for +GET+ requests.
|
11
|
+
attr_reader :query
|
12
|
+
# Request data.
|
13
|
+
attr_reader :request_content
|
14
|
+
# String value returned by the server.
|
15
|
+
attr_reader :response_raw
|
16
|
+
##
|
17
|
+
# Parsed value returned by the server.
|
18
|
+
# Includes "resource" wrapper hash, or may be an "errors" hash instead.
|
19
|
+
# In the case of a JSON parse error, this will be nil.
|
20
|
+
attr_reader :response_content
|
21
|
+
# HTTP status code.
|
22
|
+
attr_reader :status_code
|
23
|
+
# A hash of headers.
|
24
|
+
attr_reader :response_headers
|
25
|
+
# Time the request started.
|
26
|
+
attr_reader :start_time
|
27
|
+
# Time the response was received.
|
28
|
+
attr_reader :end_time
|
29
|
+
|
30
|
+
def initialize(
|
31
|
+
client,
|
32
|
+
method, path, query, request_content,
|
33
|
+
response_raw, response_content, status_code, response_headers,
|
34
|
+
start_time, end_time) # :nodoc:
|
35
|
+
@client = client
|
36
|
+
@method = method
|
37
|
+
@path = path
|
38
|
+
@query = query
|
39
|
+
@request_content = request_content
|
40
|
+
@response_raw = response_raw
|
41
|
+
@response_content = response_content
|
42
|
+
@status_code = status_code
|
43
|
+
@response_headers = response_headers
|
44
|
+
@start_time = start_time
|
45
|
+
@end_time = end_time
|
46
|
+
end
|
47
|
+
|
48
|
+
# Real time spent performing the request.
|
49
|
+
def time_taken
|
50
|
+
end_time - start_time
|
51
|
+
end
|
52
|
+
|
53
|
+
# Credentials used by the client.
|
54
|
+
def auth
|
55
|
+
client.credentials
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/fauna/util.rb
CHANGED
@@ -1,9 +1,50 @@
|
|
1
1
|
module Fauna
|
2
|
+
##
|
3
|
+
# Converts microseconds to a Time object.
|
4
|
+
#
|
5
|
+
# +microseconds+:: Time in microseconds.
|
2
6
|
def self.time_from_usecs(microseconds)
|
3
7
|
Time.at(microseconds / 1_000_000, microseconds % 1_000_000)
|
4
8
|
end
|
5
9
|
|
10
|
+
##
|
11
|
+
# Converts a Time object to microseconds.
|
12
|
+
#
|
13
|
+
# +time+:: A Time object.
|
6
14
|
def self.usecs_from_time(time)
|
7
15
|
time.to_i * 1_000_000 + time.usec
|
8
16
|
end
|
17
|
+
|
18
|
+
class DSLContext # :nodoc:
|
19
|
+
def self.eval_dsl(dsl, &blk)
|
20
|
+
ctx = eval('self', blk.binding)
|
21
|
+
dsl.instance_variable_set(:@__ctx__, ctx)
|
22
|
+
|
23
|
+
ctx.instance_variables.each do |iv|
|
24
|
+
dsl.instance_variable_set(iv, ctx.instance_variable_get(iv))
|
25
|
+
end
|
26
|
+
|
27
|
+
dsl.instance_exec(&blk)
|
28
|
+
|
29
|
+
ensure
|
30
|
+
dsl.instance_variables.each do |iv|
|
31
|
+
if iv.to_sym != :@__ctx__
|
32
|
+
ctx.instance_variable_set(iv, dsl.instance_variable_get(iv))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
NON_PROXIED_METHODS = Set.new %w(__send__ object_id __id__ == equal?
|
38
|
+
! != instance_exec instance_variables
|
39
|
+
instance_variable_get instance_variable_set
|
40
|
+
).map(&:to_sym)
|
41
|
+
|
42
|
+
instance_methods.each do |method|
|
43
|
+
undef_method(method) unless NON_PROXIED_METHODS.include?(method.to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
def method_missing(method, *args, &block)
|
47
|
+
@__ctx__.send(method, *args, &block)
|
48
|
+
end
|
49
|
+
end
|
9
50
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
RSpec.describe Fauna::ClientLogger do
|
2
|
+
before(:all) do
|
3
|
+
create_test_db
|
4
|
+
@test_class = client.post('classes', name: 'logger_test')[:ref]
|
5
|
+
end
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
destroy_test_db
|
9
|
+
end
|
10
|
+
|
11
|
+
# Captures logger output from wrapped client and splits it into lines
|
12
|
+
def capture_log
|
13
|
+
lines = nil
|
14
|
+
observer = Fauna::ClientLogger.logger { |log| lines = log.split("\n") }
|
15
|
+
|
16
|
+
yield get_client(secret: @server_secret, observer: observer)
|
17
|
+
|
18
|
+
lambda { lines.shift }
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'logs response' do
|
22
|
+
reader = capture_log do |client|
|
23
|
+
expect(client.ping).to eq('Scope global is OK')
|
24
|
+
end
|
25
|
+
|
26
|
+
expect(reader.call).to eq('Fauna GET /ping')
|
27
|
+
expect(reader.call).to match(/^ Credentials:/)
|
28
|
+
expect(reader.call).to eq(' Response headers: {')
|
29
|
+
|
30
|
+
# Skip through headers
|
31
|
+
loop do
|
32
|
+
line = reader.call
|
33
|
+
unless line.start_with? ' '
|
34
|
+
expect(line).to eq(' }')
|
35
|
+
break
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
expect(reader.call).to eq(' Response JSON: {')
|
40
|
+
expect(reader.call).to eq(' "resource": "Scope global is OK"')
|
41
|
+
expect(reader.call).to eq(' }')
|
42
|
+
expect(reader.call).to match(/^ Response \(200\): Network latency \d+ms$/)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'logs request content' do
|
46
|
+
value = random_number
|
47
|
+
reader = capture_log do |client|
|
48
|
+
client.post @test_class, data: { a: value }
|
49
|
+
end
|
50
|
+
|
51
|
+
expect(reader.call).to eq("Fauna POST /#{@test_class}")
|
52
|
+
expect(reader.call).to match(/^ Credentials:/)
|
53
|
+
expect(reader.call).to eq(' Request JSON: {')
|
54
|
+
expect(reader.call).to eq(' "data": {')
|
55
|
+
expect(reader.call).to eq(" \"a\": #{value}")
|
56
|
+
expect(reader.call).to eq(' }')
|
57
|
+
expect(reader.call).to eq(' }')
|
58
|
+
# Ignore the rest
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'logs request query' do
|
62
|
+
instance = client.post(@test_class)
|
63
|
+
ref = instance[:ref]
|
64
|
+
ts = instance[:ts]
|
65
|
+
|
66
|
+
reader = capture_log do |client|
|
67
|
+
client.get ref, ts: ts
|
68
|
+
end
|
69
|
+
|
70
|
+
expect(reader.call).to eq("Fauna GET /#{ref}?ts=#{ts}")
|
71
|
+
# Ignore the rest
|
72
|
+
end
|
73
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
RSpec.describe Fauna::Client do
|
2
|
+
before(:all) do
|
3
|
+
create_test_db
|
4
|
+
@test_class = client.post('classes', name: 'client_test')[:ref]
|
5
|
+
end
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
destroy_test_db
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'connection' do
|
12
|
+
# Compress string with gzip
|
13
|
+
def gzipped(str)
|
14
|
+
out = ''
|
15
|
+
StringIO.open out do |io|
|
16
|
+
writer = Zlib::GzipWriter.new io
|
17
|
+
writer.write str
|
18
|
+
writer.flush
|
19
|
+
writer.close
|
20
|
+
end
|
21
|
+
out
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'decodes gzip response' do
|
25
|
+
response = gzipped '{"resource": 1}'
|
26
|
+
test_client = stub_client(:get, 'tests/decode', response, 'Content-Encoding' => 'gzip')
|
27
|
+
expect(test_client.get('tests/decode')).to be(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'decodes deflate response' do
|
31
|
+
response = Zlib::Deflate.deflate '{"resource": 1}'
|
32
|
+
test_client = stub_client(:get, 'tests/decode', response, 'Content-Encoding' => 'deflate')
|
33
|
+
expect(test_client.get('tests/decode')).to be(1)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'serialization' do
|
38
|
+
it 'decodes ref' do
|
39
|
+
ref = Fauna::Ref.new('classes', random_string, random_number)
|
40
|
+
test_client = stub_client(:get, 'tests/ref', to_json(resource: ref))
|
41
|
+
|
42
|
+
response = test_client.get('tests/ref')
|
43
|
+
expect(response).to be_a(Fauna::Ref)
|
44
|
+
expect(response).to eq(ref)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'decodes set' do
|
48
|
+
set = Fauna::SetRef.new(match: random_string, index: Fauna::Ref.new('indexes', random_string))
|
49
|
+
test_client = stub_client(:get, 'tests/set', to_json(resource: set))
|
50
|
+
|
51
|
+
response = test_client.get('tests/set')
|
52
|
+
expect(response).to be_a(Fauna::SetRef)
|
53
|
+
expect(response).to eq(set)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'decodes obj' do
|
57
|
+
data = { random_string.to_sym => random_string }
|
58
|
+
obj = { :@obj => data }
|
59
|
+
test_client = stub_client(:get, 'tests/obj', to_json(resource: obj))
|
60
|
+
|
61
|
+
response = test_client.get('tests/obj')
|
62
|
+
expect(response).to be_a(Hash)
|
63
|
+
expect(response).to eq(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'decodes ts' do
|
67
|
+
ts = Time.at(0).utc
|
68
|
+
test_client = stub_client(:get, 'tests/ts', to_json(resource: ts))
|
69
|
+
|
70
|
+
response = test_client.get('tests/ts')
|
71
|
+
expect(response).to be_a(Time)
|
72
|
+
expect(response).to eq(ts)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'decodes date' do
|
76
|
+
date = Date.new(1970, 1, 1)
|
77
|
+
test_client = stub_client(:get, 'tests/date', to_json(resource: date))
|
78
|
+
|
79
|
+
response = test_client.get('tests/date')
|
80
|
+
expect(response).to be_a(Date)
|
81
|
+
expect(response).to eq(date)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#with_secret' do
|
86
|
+
it 'creates client with secret' do
|
87
|
+
old_secret = random_string
|
88
|
+
new_secret = random_string
|
89
|
+
|
90
|
+
old_client = get_client(secret: old_secret)
|
91
|
+
new_client = old_client.with_secret(new_secret)
|
92
|
+
|
93
|
+
expect(old_client.credentials).to eq([old_secret])
|
94
|
+
expect(new_client.credentials).to eq([new_secret])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#query' do
|
99
|
+
it 'performs query from expression' do
|
100
|
+
value = random_number
|
101
|
+
|
102
|
+
instance = client.query(Fauna::Query.create(@test_class, data: { a: value }))
|
103
|
+
|
104
|
+
expect(instance[:class]).to eq(@test_class)
|
105
|
+
expect(instance[:data][:a]).to eq(value)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'performs query from block' do
|
109
|
+
value = random_number
|
110
|
+
|
111
|
+
instance = client.query { create @test_class, data: { a: value } }
|
112
|
+
|
113
|
+
expect(instance[:class]).to eq(@test_class)
|
114
|
+
expect(instance[:data][:a]).to eq(value)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '#get' do
|
119
|
+
it 'performs GET' do
|
120
|
+
value = random_number
|
121
|
+
ref = client.post(@test_class, data: { a: value })[:ref]
|
122
|
+
|
123
|
+
instance = client.get(ref)
|
124
|
+
|
125
|
+
expect(instance[:ref]).to eq(ref)
|
126
|
+
expect(instance[:data][:a]).to eq(value)
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'performs GET with query' do
|
130
|
+
value = random_number
|
131
|
+
created = client.post(@test_class, data: { a: value })
|
132
|
+
ref = created[:ref]
|
133
|
+
ts = created[:ts]
|
134
|
+
|
135
|
+
instance = client.get(ref, ts: ts)
|
136
|
+
|
137
|
+
expect(instance[:ref]).to eq(ref)
|
138
|
+
expect(instance[:data][:a]).to eq(value)
|
139
|
+
|
140
|
+
expect { client.get(ref, ts: ts - 1) }.to raise_error(Fauna::NotFound)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe '#post' do
|
145
|
+
it 'performs POST' do
|
146
|
+
value = random_number
|
147
|
+
|
148
|
+
instance = client.post(@test_class, data: { a: value })
|
149
|
+
|
150
|
+
expect(instance[:class]).to eq(@test_class)
|
151
|
+
expect(instance[:data][:a]).to eq(value)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe '#put' do
|
156
|
+
it 'performs PUT' do
|
157
|
+
value = random_number
|
158
|
+
ref = client.post(@test_class, data: { a: random_number })[:ref]
|
159
|
+
|
160
|
+
instance = client.put(ref, data: { b: value })
|
161
|
+
|
162
|
+
expect(instance[:ref]).to eq(ref)
|
163
|
+
expect(instance[:data][:a]).to be_nil
|
164
|
+
expect(instance[:data][:b]).to eq(value)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#patch' do
|
169
|
+
it 'performs PATCH' do
|
170
|
+
value1 = random_number
|
171
|
+
value2 = random_number
|
172
|
+
ref = client.post(@test_class, data: { a: value1 })[:ref]
|
173
|
+
|
174
|
+
instance = client.patch(ref, data: { b: value2 })
|
175
|
+
|
176
|
+
expect(instance[:ref]).to eq(ref)
|
177
|
+
expect(instance[:data][:a]).to eq(value1)
|
178
|
+
expect(instance[:data][:b]).to eq(value2)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
describe '#delete' do
|
183
|
+
it 'performs DELETE' do
|
184
|
+
ref = client.post(@test_class, data: { a: random_number })[:ref]
|
185
|
+
|
186
|
+
instance = client.delete(ref)
|
187
|
+
|
188
|
+
expect(instance[:ref]).to eq(ref)
|
189
|
+
expect { client.get(ref) }.to raise_error(Fauna::NotFound)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#ping' do
|
194
|
+
it 'performs ping' do
|
195
|
+
expect(client.ping).to eq('Scope global is OK')
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'performs ping with scope' do
|
199
|
+
expect(client.ping(scope: 'node')).to eq('Scope node is OK')
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
RSpec.describe Fauna::Context do
|
2
|
+
before(:all) do
|
3
|
+
create_test_db
|
4
|
+
@test_class = client.post('classes', name: 'context_test')[:ref]
|
5
|
+
end
|
6
|
+
|
7
|
+
after(:all) do
|
8
|
+
destroy_test_db
|
9
|
+
end
|
10
|
+
|
11
|
+
around do |ex|
|
12
|
+
# Ensure context is not shared between tests
|
13
|
+
Fauna::Context.reset
|
14
|
+
ex.run
|
15
|
+
Fauna::Context.reset
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'REST methods' do
|
19
|
+
around do |ex|
|
20
|
+
stubs = Faraday::Adapter::Test::Stubs.new
|
21
|
+
[:get, :post, :put, :patch, :delete].each do |method|
|
22
|
+
stubs.send(method, '/tests/context') do |env|
|
23
|
+
[200, {}, { resource: env.method.to_s.upcase }.to_json]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Fauna::Context.block(Fauna::Client.new(adapter: [:test, stubs])) do
|
28
|
+
ex.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe '#get' do
|
33
|
+
it 'performs GET request' do
|
34
|
+
expect(Fauna::Context.get('tests/context')).to eq('GET')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#post' do
|
39
|
+
it 'performs POST request' do
|
40
|
+
expect(Fauna::Context.post('tests/context')).to eq('POST')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#put' do
|
45
|
+
it 'performs PUT request' do
|
46
|
+
expect(Fauna::Context.put('tests/context')).to eq('PUT')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#patch' do
|
51
|
+
it 'performs PATCH request' do
|
52
|
+
expect(Fauna::Context.patch('tests/context')).to eq('PATCH')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#delete' do
|
57
|
+
it 'performs DELETE request' do
|
58
|
+
expect(Fauna::Context.delete('tests/context')).to eq('DELETE')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#query' do
|
64
|
+
it 'performs query' do
|
65
|
+
Fauna::Context.block(client) do
|
66
|
+
expect(Fauna::Context.query { add 1, 1 }).to eq(2)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#push' do
|
72
|
+
it 'pushes new client' do
|
73
|
+
new = Fauna::Client.new
|
74
|
+
|
75
|
+
Fauna::Context.push(new)
|
76
|
+
expect(Fauna::Context.client).to be(new)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#pop' do
|
81
|
+
it 'pops active client' do
|
82
|
+
outer = Fauna::Client.new
|
83
|
+
inner = Fauna::Client.new
|
84
|
+
|
85
|
+
Fauna::Context.push(outer)
|
86
|
+
Fauna::Context.push(inner)
|
87
|
+
|
88
|
+
expect(Fauna::Context.pop).to be(inner)
|
89
|
+
expect(Fauna::Context.client).to be(outer)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe '#reset' do
|
94
|
+
it 'resets stack' do
|
95
|
+
initial = Fauna::Client.new
|
96
|
+
outer = Fauna::Client.new
|
97
|
+
inner = Fauna::Client.new
|
98
|
+
|
99
|
+
Fauna::Context.push(initial)
|
100
|
+
Fauna::Context.push(outer)
|
101
|
+
Fauna::Context.push(inner)
|
102
|
+
|
103
|
+
Fauna::Context.reset
|
104
|
+
expect { Fauna::Context.client }.to raise_error(Fauna::NoContextError)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe '#block' do
|
109
|
+
it 'changes client within block' do
|
110
|
+
outer = Fauna::Client.new
|
111
|
+
inner = Fauna::Client.new
|
112
|
+
Fauna::Context.push(outer)
|
113
|
+
|
114
|
+
expect(Fauna::Context.client).to be(outer)
|
115
|
+
Fauna::Context.block(inner) do
|
116
|
+
expect(Fauna::Context.client).to be(inner)
|
117
|
+
end
|
118
|
+
expect(Fauna::Context.client).to be(outer)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|