yax-fauna 3.0.1

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.
@@ -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