yax-fauna 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ module Fauna
2
+ # Example observer that can be used for debugging
3
+ module ClientLogger
4
+ ##
5
+ # Lambda that can be the +observer+ for a Client.
6
+ # Will call the passed block on a string representation of each RequestResult.
7
+ #
8
+ # Example:
9
+ #
10
+ # logger = ClientLogger.logger do |str|
11
+ # puts str
12
+ # end
13
+ # Client.new observer: logger, ...
14
+ def self.logger
15
+ lambda do |request_result|
16
+ yield show_request_result(request_result)
17
+ end
18
+ end
19
+
20
+ # Translates a RequestResult to a string suitable for logging.
21
+ def self.show_request_result(request_result)
22
+ rr = request_result
23
+ logged = ''
24
+
25
+ logged << "Fauna #{rr.method.to_s.upcase} /#{rr.path}#{query_string_for_logging(rr.query)}\n"
26
+ logged << " Credentials: #{rr.auth}\n"
27
+ if rr.request_content
28
+ logged << " Request JSON: #{indent(FaunaJson.to_json_pretty(rr.request_content))}\n"
29
+ end
30
+ logged << " Response headers: #{indent(FaunaJson.to_json_pretty(rr.response_headers))}\n"
31
+ logged << " Response JSON: #{indent(FaunaJson.to_json_pretty(rr.response_content))}\n"
32
+ logged << " Response (#{rr.status_code}): Network latency #{(rr.time_taken * 1000).to_i}ms"
33
+
34
+ logged
35
+ end
36
+
37
+ def self.indent(str) # :nodoc:
38
+ indent_str = ' '
39
+ str.split("\n").join("\n" + indent_str)
40
+ end
41
+
42
+ def self.query_string_for_logging(query) # :nodoc:
43
+ return unless query && !query.empty?
44
+
45
+ '?' + query.collect do |k, v|
46
+ "#{k}=#{v}"
47
+ end.join('&')
48
+ end
49
+
50
+ private_class_method :indent, :query_string_for_logging
51
+ end
52
+ end
@@ -0,0 +1,81 @@
1
+ module Fauna
2
+ ##
3
+ # Error raised when the context is used without a client being set.
4
+ class NoContextError < RuntimeError; end
5
+
6
+ ##
7
+ # The client context wrapper.
8
+ #
9
+ # Used for accessing the client without directly passing around the client instance.
10
+ # Context is scoped to the current thread.
11
+ class Context
12
+ ##
13
+ # Returns a context block with the given client.
14
+ #
15
+ # +client+:: Client to use for the context block.
16
+ def self.block(client)
17
+ push(client)
18
+ yield
19
+ ensure
20
+ pop
21
+ end
22
+
23
+ ##
24
+ # Adds a client to the current context.
25
+ #
26
+ # +client+:: Client to add to the current context.
27
+ def self.push(client)
28
+ stack.push(client)
29
+ end
30
+
31
+ ##
32
+ # Removes the last client context from the stack and returns it.
33
+ def self.pop
34
+ stack.pop
35
+ end
36
+
37
+ ##
38
+ # Resets the current client context, removing all the clients from the stack.
39
+ def self.reset
40
+ stack.clear
41
+ end
42
+
43
+ ##
44
+ # Issues a query to FaunaDB with the current client context.
45
+ #
46
+ # Queries are built via the Query helpers. See {FaunaDB Query API}[https://fauna.com/documentation/queries]
47
+ # for information on constructing queries.
48
+ #
49
+ # +expression+:: A query expression
50
+ #
51
+ # :category: Client Methods
52
+ def self.query(expression = nil, &expr_block)
53
+ client.query(expression, &expr_block)
54
+ end
55
+
56
+ ##
57
+ # Creates a Fauna::Page for paging/iterating over a set with the current client context.
58
+ #
59
+ # +set+:: A set query to paginate over.
60
+ # +params+:: A list of parameters to pass to {paginate}[https://fauna.com/documentation/queries#read_functions-paginate_set].
61
+ # +fauna_map+:: Optional block to wrap the generated paginate query with. The block will be run in a query context.
62
+ # The paginate query will be passed into the block as an argument.
63
+ def self.paginate(set, params = {}, &fauna_map)
64
+ client.paginate(set, params, &fauna_map)
65
+ end
66
+
67
+ ##
68
+ # Returns the current context's client, or if there is none, raises NoContextError.
69
+ def self.client
70
+ stack.last || fail(NoContextError, 'You must be within a Fauna::Context.block to perform operations.')
71
+ end
72
+
73
+ class << self
74
+ private
75
+
76
+ def stack
77
+ Thread.current[:fauna_context_stack] ||= []
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ module Fauna
2
+ module Deprecate
3
+ ##
4
+ # Deprecates a method
5
+ #
6
+ # class AClass
7
+ # extend Fauna::Deprecate
8
+ #
9
+ # def method
10
+ # end
11
+ #
12
+ # deprecate :method, :new_method
13
+ #
14
+ # def new_method
15
+ # end
16
+ # end
17
+ #
18
+ # +name+:: The method name to be deprecated
19
+ # +replacement+:: The new method that should be used instead
20
+ def deprecate(name, replacement)
21
+ old_name = "deprecated_#{name}"
22
+ alias_method old_name, name
23
+ define_method name do |*args, &block|
24
+ warn "Method #{name} called from #{Gem.location_of_caller.join(':')} is deprecated. Use #{replacement} instead"
25
+ self.__send__ old_name, *args, &block
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,235 @@
1
+ module Fauna
2
+ ##
3
+ # Error for when an object passed into a query cannot be serialized.
4
+ # Objects that are not native to the query language should implement +to_h+/+to_hash+.
5
+ class SerializationError < RuntimeError
6
+ def initialize(obj) # :nodoc:
7
+ super("Object #{obj.inspect} is not serializable")
8
+ @object = obj
9
+ end
10
+
11
+ # The object that could not be serialized.
12
+ attr_reader :object
13
+ end
14
+
15
+ ##
16
+ # Error for when the server returns an unexpected kind of response.
17
+ class UnexpectedError < RuntimeError
18
+ # RequestResult for the request that caused this error.
19
+ attr_reader :request_result
20
+
21
+ def initialize(description, request_result) # :nodoc:
22
+ super(description)
23
+ @request_result = request_result
24
+ end
25
+
26
+ def self.get_or_raise(request_result, hash, key) # :nodoc:
27
+ unless hash.is_a? Hash and hash.key? key
28
+ fail UnexpectedError.new("Response JSON does not contain expected key #{key}", request_result)
29
+ end
30
+ hash[key]
31
+ end
32
+ end
33
+
34
+ ##
35
+ # Error returned by the FaunaDB server.
36
+ # For documentation of error types, see the docs[https://fauna.com/documentation#errors].
37
+ class FaunaError < RuntimeError
38
+ # List of ErrorData objects returned by the server.
39
+ attr_reader :errors
40
+
41
+ # RequestResult for the request that caused this error.
42
+ attr_reader :request_result
43
+
44
+ ##
45
+ # Raises the associated error from a RequestResult based on the status code.
46
+ #
47
+ # Returns +nil+ for 2xx status codes
48
+ def self.raise_for_status_code(request_result)
49
+ case request_result.status_code
50
+ when 200..299
51
+
52
+ when 400
53
+ fail BadRequest.new(request_result)
54
+ when 401
55
+ fail Unauthorized.new(request_result)
56
+ when 403
57
+ fail PermissionDenied.new(request_result)
58
+ when 404
59
+ fail NotFound.new(request_result)
60
+ when 405
61
+ fail MethodNotAllowed.new(request_result)
62
+ when 500
63
+ fail InternalError.new(request_result)
64
+ when 502
65
+ fail UnavailableError.new(request_result)
66
+ when 503
67
+ fail UnavailableError.new(request_result)
68
+ when 504
69
+ fail UnavailableError.new(request_result)
70
+ else
71
+ fail UnexpectedError.new('Unexpected status code.', request_result)
72
+ end
73
+ end
74
+
75
+ # Creates a new error from a given RequestResult or Exception.
76
+ def initialize(request_result)
77
+ message = nil
78
+
79
+ if request_result.is_a? RequestResult
80
+ @request_result = request_result
81
+
82
+ begin
83
+ if request_result.response_content.nil?
84
+ fail UnexpectedError.new('Invalid JSON.', request_result)
85
+ end
86
+
87
+ errors_raw = UnexpectedError.get_or_raise request_result, request_result.response_content, :errors
88
+ @errors = catch :invalid_response do
89
+ throw :invalid_response unless errors_raw.is_a? Array
90
+ errors_raw.map { |error| ErrorData.from_hash(error) }
91
+ end
92
+
93
+ if @errors.nil?
94
+ fail UnexpectedError.new('Error data has an unexpected format.', request_result)
95
+ elsif @errors.empty?
96
+ fail UnexpectedError.new('Error data returned was blank.', request_result)
97
+ end
98
+
99
+ message = @errors.map do |error|
100
+ msg = 'Error'
101
+ msg += " at #{error.position}" unless error.position.nil?
102
+ msg += ": #{error.code} - #{error.description}"
103
+
104
+ unless error.failures.nil?
105
+ msg += ' (' + error.failures.map do |failure|
106
+ "Failure at #{failure.field}: #{failure.code} - #{failure.description}"
107
+ end.join(' ') + ')'
108
+ end
109
+
110
+ msg
111
+ end.join(' ')
112
+ rescue UnexpectedError => e
113
+ unless self.is_a?(UnavailableError) && [502, 503, 504].include?(request_result.status_code)
114
+ raise e
115
+ end
116
+
117
+ message = request_result.response_raw
118
+ end
119
+ elsif request_result.is_a? Exception
120
+ message = request_result.class.name
121
+ unless request_result.message.nil?
122
+ message += ": #{request_result.message}"
123
+ end
124
+ end
125
+
126
+ super(message)
127
+ end
128
+ end
129
+
130
+ # An exception thrown if FaunaDB cannot evaluate a query.
131
+ class BadRequest < FaunaError; end
132
+
133
+ # An exception thrown if FaunaDB responds with an HTTP 401.
134
+ class Unauthorized < FaunaError; end
135
+
136
+ # An exception thrown if FaunaDB responds with an HTTP 403.
137
+ class PermissionDenied < FaunaError; end
138
+
139
+ # An exception thrown if FaunaDB responds with an HTTP 404 for non-query endpoints.
140
+ class NotFound < FaunaError; end
141
+
142
+ # An exception thrown if FaunaDB responds with an HTTP 405.
143
+ class MethodNotAllowed < FaunaError; end
144
+
145
+ ##
146
+ # An exception thrown if FaunaDB responds with an HTTP 500. Such errors represent an internal
147
+ # failure within the database.
148
+ class InternalError < FaunaError; end
149
+
150
+ # An exception thrown if FaunaDB responds with an HTTP 502, 503, or 504.
151
+ class UnavailableError < FaunaError; end
152
+
153
+ # Data for one error returned by the server.
154
+ class ErrorData
155
+ ##
156
+ # Error code.
157
+ #
158
+ # Reference: {FaunaDB Error codes}[https://fauna.com/documentation#errors]
159
+ attr_reader :code
160
+ # Error description.
161
+ attr_reader :description
162
+ # Position of the error in a query. May be +nil+.
163
+ attr_reader :position
164
+ # List of Failure objects returned by the server. +nil+ except for <code>validation failed</code> errors.
165
+ attr_reader :failures
166
+
167
+ def self.from_hash(hash) # :nodoc:
168
+ code = ErrorHelpers.get_or_throw hash, :code
169
+ description = ErrorHelpers.get_or_throw hash, :description
170
+ position = ErrorHelpers.map_position hash[:position]
171
+ failures = hash[:failures].map(&Failure.method(:from_hash)) unless hash[:failures].nil?
172
+ ErrorData.new code, description, position, failures
173
+ end
174
+
175
+ def initialize(code, description, position, failures) # :nodoc:
176
+ @code = code
177
+ @description = description
178
+ @position = position
179
+ @failures = failures
180
+ end
181
+
182
+ def inspect # :nodoc:
183
+ "ErrorData(#{code.inspect}, #{description.inspect}, #{position.inspect}, #{failures.inspect})"
184
+ end
185
+ end
186
+
187
+ ##
188
+ # Part of ErrorData.
189
+ # For more information, see the {docs}[https://fauna.com/documentation#errors-invalid_data].
190
+ class Failure
191
+ # Failure code.
192
+ attr_reader :code
193
+ # Failure description.
194
+ attr_reader :description
195
+ # Field of the failure in the instance.
196
+ attr_reader :field
197
+
198
+ def self.from_hash(hash) # :nodoc:
199
+ Failure.new(
200
+ ErrorHelpers.get_or_throw(hash, :code),
201
+ ErrorHelpers.get_or_throw(hash, :description),
202
+ ErrorHelpers.map_position(hash[:field]),
203
+ )
204
+ end
205
+
206
+ def initialize(code, description, field) # :nodoc:
207
+ @code = code
208
+ @description = description
209
+ @field = field
210
+ end
211
+
212
+ def inspect # :nodoc:
213
+ "Failure(#{code.inspect}, #{description.inspect}, #{field.inspect})"
214
+ end
215
+ end
216
+
217
+ module ErrorHelpers # :nodoc:
218
+ def self.map_position(position)
219
+ unless position.nil?
220
+ position.map do |part|
221
+ if part.is_a? String
222
+ part.to_sym
223
+ else
224
+ part
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ def self.get_or_throw(hash, key)
231
+ throw :invalid_response unless hash.is_a? Hash and hash.key? key
232
+ hash[key]
233
+ end
234
+ end
235
+ end
@@ -0,0 +1,99 @@
1
+ module Fauna
2
+ module FaunaJson # :nodoc:
3
+ @@serializable_types = [String, Numeric, TrueClass, FalseClass, NilClass, Hash, Array, Symbol, Time, Date, Fauna::Ref, Fauna::SetRef, Fauna::Bytes, Fauna::QueryV, Fauna::Query::Expr]
4
+
5
+ def self.serializable_types
6
+ @@serializable_types
7
+ end
8
+
9
+ def self.to_json(value)
10
+ serialize(value).to_json
11
+ end
12
+
13
+ def self.to_json_pretty(value)
14
+ JSON.pretty_generate serialize(value)
15
+ end
16
+
17
+ def self.deserialize(obj)
18
+ if obj.is_a?(Hash)
19
+ if obj.key? :@ref
20
+ ref = obj[:@ref]
21
+ id = ref[:id]
22
+
23
+ if !ref.key?(:class) && !ref.key?(:database)
24
+ Native.from_name(id)
25
+ else
26
+ cls = self.deserialize(ref[:class])
27
+ db = self.deserialize(ref[:database])
28
+ Ref.new(id, cls, db)
29
+ end
30
+ elsif obj.key? :@set
31
+ SetRef.new deserialize(obj[:@set])
32
+ elsif obj.key? :@obj
33
+ deserialize(obj[:@obj])
34
+ elsif obj.key? :@ts
35
+ Time.iso8601 obj[:@ts]
36
+ elsif obj.key? :@date
37
+ Date.iso8601 obj[:@date]
38
+ elsif obj.key? :@bytes
39
+ Bytes.from_base64 obj[:@bytes]
40
+ elsif obj.key? :@query
41
+ QueryV.new deserialize(obj[:@query])
42
+ else
43
+ Hash[obj.collect { |k, v| [k, deserialize(v)] }]
44
+ end
45
+ elsif obj.is_a?(Array)
46
+ obj.collect { |val| deserialize(val) }
47
+ else
48
+ obj
49
+ end
50
+ end
51
+
52
+ def self.json_load(body)
53
+ JSON.load body, nil, max_nesting: false, symbolize_names: true, create_additions: false
54
+ end
55
+
56
+ def self.json_load_or_nil(body)
57
+ json_load body
58
+ rescue JSON::ParserError
59
+ nil
60
+ end
61
+
62
+ def self.serialize(value)
63
+ # Handle primitives
64
+ if [String, Numeric, TrueClass, FalseClass, NilClass].any? { |type| value.is_a? type }
65
+ value
66
+ elsif value.is_a? Hash
67
+ Hash[value.collect { |k, v| [k, serialize(v)] }]
68
+ elsif value.is_a? Array
69
+ value.collect { |val| serialize(val) }
70
+ elsif value.is_a? Symbol
71
+ value.to_s
72
+ # Natively supported types
73
+ elsif value.is_a? Time
74
+ # 9 means: include nanoseconds in encoding
75
+ { :@ts => value.iso8601(9) }
76
+ elsif value.is_a? Date
77
+ { :@date => value.iso8601 }
78
+ # Fauna native types
79
+ elsif value.is_a? Ref
80
+ ref = { id: value.id }
81
+ ref[:class] = value.class_ unless value.class_.nil?
82
+ ref[:database] = value.database unless value.database.nil?
83
+ { :@ref => serialize(ref) }
84
+ elsif value.is_a? SetRef
85
+ { :@set => serialize(value.value) }
86
+ elsif value.is_a? Bytes
87
+ { :@bytes => value.to_base64 }
88
+ elsif value.is_a? QueryV
89
+ { :@query => serialize(value.value) }
90
+ # Query expression wrapper
91
+ elsif value.is_a? Query::Expr
92
+ serialize(value.raw)
93
+ # Everything else is rejected
94
+ else
95
+ fail SerializationError.new(value)
96
+ end
97
+ end
98
+ end
99
+ end