lardawge-rfm 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/rfm/commands/database.rb +96 -0
- data/lib/rfm/commands/field_control.rb +50 -0
- data/lib/rfm/commands/layout.rb +275 -0
- data/lib/rfm/commands/script.rb +18 -0
- data/lib/rfm/commands/server.rb +388 -0
- data/lib/rfm/error.rb +256 -0
- data/lib/rfm/factory.rb +84 -0
- data/lib/rfm/result.rb +446 -0
- data/lib/rfm/utility.rb +12 -0
- data/lib/rfm.rb +16 -7
- data/test/{rfm_test_errors.rb → errors_test.rb} +3 -2
- metadata +14 -11
- data/lib/rfm_command.rb +0 -790
- data/lib/rfm_error.rb +0 -252
- data/lib/rfm_factory.rb +0 -82
- data/lib/rfm_result.rb +0 -437
- data/lib/rfm_util.rb +0 -10
- data/test/rfm_tester.rb +0 -2
@@ -0,0 +1,388 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'cgi'
|
3
|
+
module Rfm
|
4
|
+
# This class represents a single FileMaker server. It is initialized with basic
|
5
|
+
# connection information, including the hostname, port number, and default database
|
6
|
+
# account name and password.
|
7
|
+
#
|
8
|
+
# Note: The host and port number refer to the FileMaker Web Publishing Engine, which
|
9
|
+
# must be installed and configured in order to use RFM. It may not actually be running
|
10
|
+
# on the same server computer as FileMaker Server itself. See your FileMaker Server
|
11
|
+
# or FileMaker Server Advanced documentation for information about configuring a Web
|
12
|
+
# Publishing Engine.
|
13
|
+
#
|
14
|
+
# =Accessing Databases
|
15
|
+
#
|
16
|
+
# Typically, you access a Database object from the Server like this:
|
17
|
+
#
|
18
|
+
# myDatabase = myServer["Customers"]
|
19
|
+
#
|
20
|
+
# This code gets the Database object representing the Customers object.
|
21
|
+
#
|
22
|
+
# Note: RFM does not talk to the server when you retrieve a database object in this way. Instead, it
|
23
|
+
# simply assumes you know what you're talking about. If the database you specify does not exist, you
|
24
|
+
# will get no error at this point. Instead, you'll get an error when you use the Layout object you get
|
25
|
+
# from this database. This makes debugging a little less convenient, but it would introduce too much
|
26
|
+
# overhead to hit the server at this point.
|
27
|
+
#
|
28
|
+
# The Server object has a +db+ attribute that provides alternate access to Database objects. It acts
|
29
|
+
# like a hash of Database objects, one for each accessible database on the server. So, for example, you
|
30
|
+
# can do this if you want to print out a list of all databses on the server:
|
31
|
+
#
|
32
|
+
# myServer.db.each {|database|
|
33
|
+
# puts database.name
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# The Server::db attribute is actually a DbFactory object, although it subclasses hash, so it should work
|
37
|
+
# in all the ways you expect. Note, though, that it is completely empty until the first time you attempt
|
38
|
+
# to access its elements. At that (lazy) point, it hits FileMaker, loads in the list of databases, and
|
39
|
+
# constructs a Database object for each one. In other words, it incurrs no overhead until you use it.
|
40
|
+
#
|
41
|
+
# =Attributes
|
42
|
+
#
|
43
|
+
# In addition to the +db+ attribute, Server has a few other useful attributes:
|
44
|
+
#
|
45
|
+
# * *host_name* is the host name this server points to
|
46
|
+
# * *port* is the port number this server communicates on
|
47
|
+
# * *state* is a hash of all server options used to initialize this server
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
# The Database object represents a single FileMaker Pro database. When you retrieve a Database
|
52
|
+
# object from a server, its account name and password are set to the account name and password you
|
53
|
+
# used when initializing the Server object. You can override this of course:
|
54
|
+
#
|
55
|
+
# myDatabase = myServer["Customers"]
|
56
|
+
# myDatabase.account_name = "foo"
|
57
|
+
# myDatabase.password = "bar"
|
58
|
+
#
|
59
|
+
# =Accessing Layouts
|
60
|
+
#
|
61
|
+
# All interaction with FileMaker happens through a Layout object. You can get a Layout object
|
62
|
+
# from the Database object like this:
|
63
|
+
#
|
64
|
+
# myLayout = myDatabase["Details"]
|
65
|
+
#
|
66
|
+
# This code gets the Layout object representing the layout called Details in the database.
|
67
|
+
#
|
68
|
+
# Note: RFM does not talk to the server when you retrieve a Layout object in this way. Instead, it
|
69
|
+
# simply assumes you know what you're talking about. If the layout you specify does not exist, you
|
70
|
+
# will get no error at this point. Instead, you'll get an error when you use the Layout object methods
|
71
|
+
# to talk to FileMaker. This makes debugging a little less convenient, but it would introduce too much
|
72
|
+
# overhead to hit the server at this point.
|
73
|
+
#
|
74
|
+
# The Database object has a +layout+ attribute that provides alternate access to Layout objects. It acts
|
75
|
+
# like a hash of Layout objects, one for each accessible layout in the database. So, for example, you
|
76
|
+
# can do this if you want to print out a list of all layouts:
|
77
|
+
#
|
78
|
+
# myDatabase.layout.each {|layout|
|
79
|
+
# puts layout.name
|
80
|
+
# }
|
81
|
+
#
|
82
|
+
# The Database::layout attribute is actually a LayoutFactory object, although it subclasses hash, so it
|
83
|
+
# should work in all the ways you expect. Note, though, that it is completely empty until the first time
|
84
|
+
# you attempt to access its elements. At that (lazy) point, it hits FileMaker, loads in the list of layouts,
|
85
|
+
# and constructs a Layout object for each one. In other words, it incurrs no overhead until you use it.
|
86
|
+
#
|
87
|
+
# =Accessing Scripts
|
88
|
+
#
|
89
|
+
# If for some reason you need to enumerate the scripts in a database, you can do so:
|
90
|
+
#
|
91
|
+
# myDatabase.script.each {|script|
|
92
|
+
# puts script.name
|
93
|
+
# }
|
94
|
+
#
|
95
|
+
# The Database::script attribute is actually a ScriptFactory object, although it subclasses hash, so it
|
96
|
+
# should work in all the ways you expect. Note, though, that it is completely empty until the first time
|
97
|
+
# you attempt to access its elements. At that (lazy) point, it hits FileMaker, loads in the list of scripts,
|
98
|
+
# and constructs a Script object for each one. In other words, it incurrs no overhead until you use it.
|
99
|
+
#
|
100
|
+
# Note: You don't need a Script object to _run_ a script (see the Layout object instead).
|
101
|
+
#
|
102
|
+
# =Attributes
|
103
|
+
#
|
104
|
+
# In addition to the +layout+ attribute, Server has a few other useful attributes:
|
105
|
+
#
|
106
|
+
# * *server* is the Server object this database comes from
|
107
|
+
# * *name* is the name of this database
|
108
|
+
# * *state* is a hash of all server options used to initialize this server
|
109
|
+
class Server
|
110
|
+
#
|
111
|
+
# To create a Server object, you typically need at least a host name:
|
112
|
+
#
|
113
|
+
# myServer = Rfm::Server.new({:host => 'my.host.com'})
|
114
|
+
#
|
115
|
+
# Several other options are supported:
|
116
|
+
#
|
117
|
+
# * *host* the hostname of the Web Publishing Engine (WPE) server (defaults to 'localhost')
|
118
|
+
#
|
119
|
+
# * *port* the port number the WPE is listening no (defaults to 80 unless *ssl* +true+ which sets it to 443)
|
120
|
+
#
|
121
|
+
# * *account_name* the default account name to log in to databases with (you can also supply a
|
122
|
+
# account name on a per-database basis if necessary)
|
123
|
+
#
|
124
|
+
# * *password* the default password to log in to databases with (you can also supplly a password
|
125
|
+
# on a per-databases basis if necessary)
|
126
|
+
#
|
127
|
+
# * *log_actions* when +true+, RFM logs all action URLs that are sent to FileMaker server to stderr
|
128
|
+
# (defaults to +false+)
|
129
|
+
#
|
130
|
+
# * *log_responses* when +true+, RFM logs all raw XML responses (including headers) from FileMaker to
|
131
|
+
# stderr (defaults to +false+)
|
132
|
+
#
|
133
|
+
# * *warn_on_redirect* normally, RFM prints a warning to stderr if the Web Publishing Engine redirects
|
134
|
+
# (this can usually be fixed by using a different host name, which speeds things up); if you *don't*
|
135
|
+
# want this warning printed, set +warn_on_redirect+ to +true+
|
136
|
+
#
|
137
|
+
# * *raise_on_401* although RFM raises error when FileMaker returns error responses, it typically
|
138
|
+
# ignores FileMaker's 401 error (no records found) and returns an empty record set instead; if you
|
139
|
+
# prefer a raised error when a find produces no errors, set this option to +true+
|
140
|
+
#
|
141
|
+
#SSL Options (SSL AND CERTIFICATE VERIFICATION ARE ON BY DEFAULT):
|
142
|
+
#
|
143
|
+
# * *ssl* +false+ if you want to turn SSL (HTTPS) off when connecting to connect to FileMaker (default is +true+)
|
144
|
+
#
|
145
|
+
# If you are using SSL and want to verify the certificate use the following options:
|
146
|
+
#
|
147
|
+
# * *root_cert* +false+ if you do not want to verify your SSL session (default is +true+).
|
148
|
+
# You will want to turn this off if you are using a self signed certificate and do not have a certificate authority cert file.
|
149
|
+
# If you choose this option you will need to provide a cert *root_cert_name* and *root_cert_path* (if not in root directory).
|
150
|
+
#
|
151
|
+
# * *root_cert_name* name of pem file for certificate verification (Root cert from certificate authority who issued certificate.
|
152
|
+
# If self signed certificate do not use this option!!). You can download the entire bundle of CA Root Certificates
|
153
|
+
# from http://curl.haxx.se/ca/cacert.pem. Place the pem file in config directory.
|
154
|
+
#
|
155
|
+
# * *root_cert_path* path to cert file. (defaults to '/' if no path given)
|
156
|
+
#
|
157
|
+
#Configuration Examples:
|
158
|
+
#
|
159
|
+
# Example to turn off SSL:
|
160
|
+
#
|
161
|
+
# myServer = Rfm::Server.new({
|
162
|
+
# :host => 'localhost',
|
163
|
+
# :account_name => 'sample',
|
164
|
+
# :password => '12345',
|
165
|
+
# :ssl => false
|
166
|
+
# })
|
167
|
+
#
|
168
|
+
# Example using SSL without *root_cert*:
|
169
|
+
#
|
170
|
+
# myServer = Rfm::Server.new({
|
171
|
+
# :host => 'localhost',
|
172
|
+
# :account_name => 'sample',
|
173
|
+
# :password => '12345',
|
174
|
+
# :root_cert => false
|
175
|
+
# })
|
176
|
+
#
|
177
|
+
# Example using SSL with *root_cert* at file root:
|
178
|
+
#
|
179
|
+
# myServer = Rfm::Server.new({
|
180
|
+
# :host => 'localhost',
|
181
|
+
# :account_name => 'sample',
|
182
|
+
# :password => '12345',
|
183
|
+
# :root_cert_name => 'example.pem'
|
184
|
+
# })
|
185
|
+
#
|
186
|
+
# Example using SSL with *root_cert* specifying *root_cert_path*:
|
187
|
+
#
|
188
|
+
# myServer = Rfm::Server.new({
|
189
|
+
# :host => 'localhost',
|
190
|
+
# :account_name => 'sample',
|
191
|
+
# :password => '12345',
|
192
|
+
# :root_cert_name => 'example.pem'
|
193
|
+
# :root_cert_path => '/usr/cert_file/'
|
194
|
+
# })
|
195
|
+
|
196
|
+
def initialize(options)
|
197
|
+
@state = {
|
198
|
+
:host => 'localhost',
|
199
|
+
:port => 80,
|
200
|
+
:ssl => true,
|
201
|
+
:root_cert => true,
|
202
|
+
:root_cert_name => '',
|
203
|
+
:root_cert_path => '/',
|
204
|
+
:account_name => '',
|
205
|
+
:password => '',
|
206
|
+
:log_actions => false,
|
207
|
+
:log_responses => false,
|
208
|
+
:warn_on_redirect => true,
|
209
|
+
:raise_on_401 => false
|
210
|
+
}.merge(options)
|
211
|
+
|
212
|
+
@state.freeze
|
213
|
+
|
214
|
+
@host_name = @state[:host]
|
215
|
+
@scheme = @state[:ssl] ? "https" : "http"
|
216
|
+
@port = @state[:ssl] && options[:port].nil? ? 443 : @state[:port]
|
217
|
+
|
218
|
+
@db = Rfm::Factory::DbFactory.new(self)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Access the database object representing a database on the server. For example:
|
222
|
+
#
|
223
|
+
# myServer['Customers']
|
224
|
+
#
|
225
|
+
# would return a Database object representing the _Customers_
|
226
|
+
# database on the server.
|
227
|
+
#
|
228
|
+
# Note: RFM never talks to the server until you perform an action. The database object
|
229
|
+
# returned is created on the fly and assumed to refer to a valid database, but you will
|
230
|
+
# get no error at this point if the database you access doesn't exist. Instead, you'll
|
231
|
+
# receive an error when you actually try to perform some action on a layout from this
|
232
|
+
# database.
|
233
|
+
def [](dbname)
|
234
|
+
self.db[dbname]
|
235
|
+
end
|
236
|
+
|
237
|
+
attr_reader :db, :host_name, :port, :scheme, :state
|
238
|
+
|
239
|
+
# Performs a raw FileMaker action. You will generally not call this method directly, but it
|
240
|
+
# is exposed in case you need to do something "under the hood."
|
241
|
+
#
|
242
|
+
# The +action+ parameter is any valid FileMaker web url action. For example, +-find+, +-finadny+ etc.
|
243
|
+
#
|
244
|
+
# The +args+ parameter is a hash of arguments to be included in the action url. It will be serialized
|
245
|
+
# and url-encoded appropriately.
|
246
|
+
#
|
247
|
+
# The +options+ parameter is a hash of RFM-specific options, which correspond to the more esoteric
|
248
|
+
# FileMaker URL parameters. They are exposed separately because they can also be passed into
|
249
|
+
# various methods on the Layout object, which is a much more typical way of sending an action to
|
250
|
+
# FileMaker.
|
251
|
+
#
|
252
|
+
# This method returns the Net::HTTP response object representing the response from FileMaker.
|
253
|
+
#
|
254
|
+
# For example, if you wanted to send a raw command to FileMaker to find the first 20 people in the
|
255
|
+
# "Customers" database whose first name is "Bill" you might do this:
|
256
|
+
#
|
257
|
+
# response = myServer.do_action(
|
258
|
+
# '-find',
|
259
|
+
# {
|
260
|
+
# "-db" => "Customers",
|
261
|
+
# "-lay" => "Details",
|
262
|
+
# "First Name" => "Bill"
|
263
|
+
# },
|
264
|
+
# { :max_records => 20 }
|
265
|
+
# )
|
266
|
+
def do_action(account_name, password, action, args, options = {})
|
267
|
+
post = args.merge(expand_options(options)).merge({action => ''})
|
268
|
+
http_fetch(@host_name, @port, "/fmi/xml/fmresultset.xml", account_name, password, post)
|
269
|
+
end
|
270
|
+
|
271
|
+
def load_layout(layout)
|
272
|
+
post = {'-db' => layout.db.name, '-lay' => layout.name, '-view' => ''}
|
273
|
+
http_fetch(@host_name, @port, "/fmi/xml/FMPXMLLAYOUT.xml", layout.db.account_name, layout.db.password, post)
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def http_fetch(host_name, port, path, account_name, password, post_data, limit=10)
|
279
|
+
raise Rfm::Error::CommunicationError.new("While trying to reach the Web Publishing Engine, RFM was redirected too many times.") if limit == 0
|
280
|
+
|
281
|
+
if @state[:log_actions] == true
|
282
|
+
qs = post_data.collect{|key,val| "#{CGI::escape(key.to_s)}=#{CGI::escape(val.to_s)}"}.join("&")
|
283
|
+
warn "#{@scheme}://#{@host_name}:#{@port}#{path}?#{qs}"
|
284
|
+
end
|
285
|
+
|
286
|
+
request = Net::HTTP::Post.new(path)
|
287
|
+
request.basic_auth(account_name, password)
|
288
|
+
request.set_form_data(post_data)
|
289
|
+
|
290
|
+
response = Net::HTTP.new(host_name, port)
|
291
|
+
|
292
|
+
if @state[:ssl]
|
293
|
+
response.use_ssl = true
|
294
|
+
if @state[:root_cert]
|
295
|
+
response.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
296
|
+
response.ca_file = File.join(@state[:root_cert_path], @state[:root_cert_name])
|
297
|
+
else
|
298
|
+
response.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
response = response.start { |http| http.request(request) }
|
303
|
+
|
304
|
+
if @state[:log_responses] == true
|
305
|
+
response.to_hash.each { |key, value| warn "#{key}: #{value}" }
|
306
|
+
warn response.body
|
307
|
+
end
|
308
|
+
|
309
|
+
case response
|
310
|
+
when Net::HTTPSuccess
|
311
|
+
response
|
312
|
+
when Net::HTTPRedirection
|
313
|
+
if @state[:warn_on_redirect]
|
314
|
+
warn "The web server redirected to " + response['location'] +
|
315
|
+
". You should revise your connection hostname or fix your server configuration if possible to improve performance."
|
316
|
+
end
|
317
|
+
newloc = URI.parse(response['location'])
|
318
|
+
http_fetch(newloc.host, newloc.port, newloc.request_uri, account_name, password, post_data, limit - 1)
|
319
|
+
when Net::HTTPUnauthorized
|
320
|
+
msg = "The account name (#{account_name}) or password provided is not correct (or the account doesn't have the fmxml extended privilege)."
|
321
|
+
raise Rfm::Error::AuthenticationError.new(msg)
|
322
|
+
when Net::HTTPNotFound
|
323
|
+
msg = "Could not talk to FileMaker because the Web Publishing Engine is not responding (server returned 404)."
|
324
|
+
raise Rfm::Error::CommunicationError.new(msg)
|
325
|
+
else
|
326
|
+
msg = "Unexpected response from server: #{result.code} (#{result.class.to_s}). Unable to communicate with the Web Publishing Engine."
|
327
|
+
raise Rfm::Error::CommunicationError.new(msg)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def expand_options(options)
|
332
|
+
result = {}
|
333
|
+
options.each do |key,value|
|
334
|
+
case key
|
335
|
+
when :max_records
|
336
|
+
result['-max'] = value
|
337
|
+
when :skip_records
|
338
|
+
result['-skip'] = value
|
339
|
+
when :sort_field
|
340
|
+
if value.kind_of? Array
|
341
|
+
raise Rfm::Error::ParameterError.new(":sort_field can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
342
|
+
value.each_index { |i| result["-sortfield.#{i+1}"] = value[i] }
|
343
|
+
else
|
344
|
+
result["-sortfield.1"] = value
|
345
|
+
end
|
346
|
+
when :sort_order
|
347
|
+
if value.kind_of? Array
|
348
|
+
raise Rfm::Error::ParameterError.new(":sort_order can have at most 9 fields, but you passed an array with #{value.size} elements.") if value.size > 9
|
349
|
+
value.each_index { |i| result["-sortorder.#{i+1}"] = value[i] }
|
350
|
+
else
|
351
|
+
result["-sortorder.1"] = value
|
352
|
+
end
|
353
|
+
when :post_script
|
354
|
+
if value.class == Array
|
355
|
+
result['-script'] = value[0]
|
356
|
+
result['-script.param'] = value[1]
|
357
|
+
else
|
358
|
+
result['-script'] = value
|
359
|
+
end
|
360
|
+
when :pre_find_script
|
361
|
+
if value.class == Array
|
362
|
+
result['-script.prefind'] = value[0]
|
363
|
+
result['-script.prefind.param'] = value[1]
|
364
|
+
else
|
365
|
+
result['-script.presort'] = value
|
366
|
+
end
|
367
|
+
when :pre_sort_script
|
368
|
+
if value.class == Array
|
369
|
+
result['-script.presort'] = value[0]
|
370
|
+
result['-script.presort.param'] = value[1]
|
371
|
+
else
|
372
|
+
result['-script.presort'] = value
|
373
|
+
end
|
374
|
+
when :response_layout
|
375
|
+
result['-lay.response'] = value
|
376
|
+
when :logical_operator
|
377
|
+
result['-lop'] = value
|
378
|
+
when :modification_id
|
379
|
+
result['-modid'] = value
|
380
|
+
else
|
381
|
+
raise Rfm::Error::ParameterError.new("Invalid option: #{key} (are you using a string instead of a symbol?)")
|
382
|
+
end
|
383
|
+
end
|
384
|
+
return result
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
end
|
data/lib/rfm/error.rb
ADDED
@@ -0,0 +1,256 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# These classes wrap the filemaker error codes. FileMakerError is the base class of this hierarchy.
|
4
|
+
#
|
5
|
+
# One could get a FileMakerError by doing:
|
6
|
+
# err = Rfm::Error::FileMakerError.getError(102)
|
7
|
+
#
|
8
|
+
# The above code would return a FieldMissingError instance. Your could use this instance to raise that appropriate
|
9
|
+
# exception:
|
10
|
+
#
|
11
|
+
# raise err
|
12
|
+
#
|
13
|
+
# You could access the specific error code by accessing:
|
14
|
+
#
|
15
|
+
# err.code
|
16
|
+
#
|
17
|
+
# Author:: Mufaddal Khumri
|
18
|
+
# Copyright:: Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumri
|
19
|
+
# License:: See MIT-LICENSE for details
|
20
|
+
module Rfm
|
21
|
+
module Error
|
22
|
+
|
23
|
+
class RfmError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
class CommunicationError < RfmError
|
27
|
+
end
|
28
|
+
|
29
|
+
class ParameterError < RfmError
|
30
|
+
end
|
31
|
+
|
32
|
+
class AuthenticationError < RfmError
|
33
|
+
end
|
34
|
+
|
35
|
+
# Base class for all FileMaker errors
|
36
|
+
class FileMakerError < RfmError
|
37
|
+
attr_accessor :code
|
38
|
+
|
39
|
+
# Default filemaker error message map
|
40
|
+
@default_messages = {}
|
41
|
+
class << self; attr_reader :default_messages; end
|
42
|
+
|
43
|
+
|
44
|
+
# This method instantiates and returns the appropriate FileMakerError object depending on the error code passed to it. It
|
45
|
+
# also accepts an optional message.
|
46
|
+
def self.getError(code, message = nil)
|
47
|
+
if @default_messages == nil or @default_messages.size == 0
|
48
|
+
(0..99).each{|i| @default_messages[i] = 'SystemError occurred.'}
|
49
|
+
(100..199).each{|i| @default_messages[i] = 'MissingError occurred.'}
|
50
|
+
@default_messages[102] = 'FieldMissingError occurred.'
|
51
|
+
@default_messages[104] = 'ScriptMissingError occurred.'
|
52
|
+
@default_messages[105] = 'LayoutMissingError occurred.'
|
53
|
+
@default_messages[106] = 'TableMissingError occurred.'
|
54
|
+
(200..299).each{|i| @default_messages[i] = 'SecurityError occurred.'}
|
55
|
+
@default_messages[200] = 'RecordAccessDeniedError occurred.'
|
56
|
+
@default_messages[201] = 'FieldCannotBeModifiedError occurred.'
|
57
|
+
@default_messages[202] = 'FieldAccessIsDeniedError occurred.'
|
58
|
+
(300..399).each{|i| @default_messages[i] = 'ConcurrencyError occurred.'}
|
59
|
+
@default_messages[301] = 'RecordInUseError occurred.'
|
60
|
+
@default_messages[302] = 'TableInUseError occurred.'
|
61
|
+
@default_messages[306] = 'RecordModIdDoesNotMatchError occurred.'
|
62
|
+
(400..499).each{|i| @default_messages[i] = 'GeneralError occurred.'}
|
63
|
+
@default_messages[401] = 'NoRecordsFoundError occurred.'
|
64
|
+
(500..599).each{|i| @default_messages[i] = 'ValidationError occurred.'}
|
65
|
+
@default_messages[500] = 'DateValidationError occurred.'
|
66
|
+
@default_messages[501] = 'TimeValidationError occurred.'
|
67
|
+
@default_messages[502] = 'NumberValidationError occurred.'
|
68
|
+
@default_messages[503] = 'RangeValidationError occurred.'
|
69
|
+
@default_messages[504] = 'UniqueValidationError occurred.'
|
70
|
+
@default_messages[505] = 'ExistingValidationError occurred.'
|
71
|
+
@default_messages[506] = 'ValueListValidationError occurred.'
|
72
|
+
@default_messages[507] = 'ValidationCalculationError occurred.'
|
73
|
+
@default_messages[508] = 'InvalidFindModeValueError occurred.'
|
74
|
+
@default_messages[511] = 'MaximumCharactersValidationError occurred.'
|
75
|
+
(800..899).each{|i| @default_messages[i] = 'FileError occurred.'}
|
76
|
+
@default_messages[802] = 'UnableToOpenFileError occurred.'
|
77
|
+
end
|
78
|
+
|
79
|
+
message = @default_messages[code] if message == nil || message.strip == ''
|
80
|
+
message += " (FileMaker Error ##{code})"
|
81
|
+
|
82
|
+
if 0 <= code and code <= 99
|
83
|
+
err = SystemError.new(message)
|
84
|
+
elsif 100 <= code and code <= 199
|
85
|
+
if code == 101
|
86
|
+
err = RecordMissingError.new(message)
|
87
|
+
elsif code == 102
|
88
|
+
err = FieldMissingError.new(message)
|
89
|
+
elsif code == 104
|
90
|
+
err = ScriptMissingError.new(message)
|
91
|
+
elsif code == 105
|
92
|
+
err = LayoutMissingError.new(message)
|
93
|
+
elsif code == 106
|
94
|
+
err = TableMissingError.new(message)
|
95
|
+
else
|
96
|
+
err = MissingError.new(message)
|
97
|
+
end
|
98
|
+
elsif 200 <= code and code <= 299
|
99
|
+
if code == 200
|
100
|
+
err = RecordAccessDeniedError.new(message)
|
101
|
+
elsif code == 201
|
102
|
+
err = FieldCannotBeModifiedError.new(message)
|
103
|
+
elsif code == 202
|
104
|
+
err = FieldAccessIsDeniedError.new(message)
|
105
|
+
else
|
106
|
+
err = SecurityError.new(message)
|
107
|
+
end
|
108
|
+
elsif 300 <= code and code <= 399
|
109
|
+
if code == 301
|
110
|
+
err = RecordInUseError.new(message)
|
111
|
+
elsif code == 302
|
112
|
+
err = TableInUseError.new(message)
|
113
|
+
elsif code == 306
|
114
|
+
err = RecordModIdDoesNotMatchError.new(message)
|
115
|
+
else
|
116
|
+
err = ConcurrencyError.new(message)
|
117
|
+
end
|
118
|
+
elsif 400 <= code and code <= 499
|
119
|
+
if code == 401
|
120
|
+
err = NoRecordsFoundError.new(message)
|
121
|
+
else
|
122
|
+
err = GeneralError.new(message)
|
123
|
+
end
|
124
|
+
elsif 500 <= code and code <= 599
|
125
|
+
if code == 500
|
126
|
+
err = DateValidationError.new(message)
|
127
|
+
elsif code == 501
|
128
|
+
err = TimeValidationError.new(message)
|
129
|
+
elsif code == 502
|
130
|
+
err = NumberValidationError.new(message)
|
131
|
+
elsif code == 503
|
132
|
+
err = RangeValidationError.new(message)
|
133
|
+
elsif code == 504
|
134
|
+
err = UniqueValidationError.new(message)
|
135
|
+
elsif code == 505
|
136
|
+
err = ExistingValidationError.new(message)
|
137
|
+
elsif code == 506
|
138
|
+
err = ValueListValidationError.new(message)
|
139
|
+
elsif code == 507
|
140
|
+
err = ValidationCalculationError.new(message)
|
141
|
+
elsif code == 508
|
142
|
+
err = InvalidFindModeValueError.new(message)
|
143
|
+
elsif code == 511
|
144
|
+
err = MaximumCharactersValidationError.new(message)
|
145
|
+
else
|
146
|
+
err = ValidationError.new(message)
|
147
|
+
end
|
148
|
+
elsif 800 <= code and code <= 899
|
149
|
+
if code == 802
|
150
|
+
err = UnableToOpenFileError.new(message)
|
151
|
+
else
|
152
|
+
err = FileError.new(message)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
# called for code == -1 or any other code not handled above.
|
156
|
+
err = UnknownError.new(message)
|
157
|
+
end
|
158
|
+
err.code = code
|
159
|
+
return err
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
class UnknownError < FileMakerError
|
164
|
+
end
|
165
|
+
|
166
|
+
class SystemError < FileMakerError
|
167
|
+
end
|
168
|
+
|
169
|
+
class MissingError < FileMakerError
|
170
|
+
end
|
171
|
+
|
172
|
+
class RecordMissingError < MissingError
|
173
|
+
end
|
174
|
+
|
175
|
+
class FieldMissingError < MissingError
|
176
|
+
end
|
177
|
+
|
178
|
+
class ScriptMissingError < MissingError
|
179
|
+
end
|
180
|
+
|
181
|
+
class LayoutMissingError < MissingError
|
182
|
+
end
|
183
|
+
|
184
|
+
class TableMissingError < MissingError
|
185
|
+
end
|
186
|
+
|
187
|
+
class SecurityError < FileMakerError
|
188
|
+
end
|
189
|
+
|
190
|
+
class RecordAccessDeniedError < SecurityError
|
191
|
+
end
|
192
|
+
|
193
|
+
class FieldCannotBeModifiedError < SecurityError
|
194
|
+
end
|
195
|
+
|
196
|
+
class FieldAccessIsDeniedError < SecurityError
|
197
|
+
end
|
198
|
+
|
199
|
+
class ConcurrencyError < FileMakerError
|
200
|
+
end
|
201
|
+
|
202
|
+
class RecordInUseError < ConcurrencyError
|
203
|
+
end
|
204
|
+
|
205
|
+
class TableInUseError < ConcurrencyError
|
206
|
+
end
|
207
|
+
|
208
|
+
class RecordModIdDoesNotMatchError < ConcurrencyError
|
209
|
+
end
|
210
|
+
|
211
|
+
class GeneralError < FileMakerError
|
212
|
+
end
|
213
|
+
|
214
|
+
class NoRecordsFoundError < GeneralError
|
215
|
+
end
|
216
|
+
|
217
|
+
class ValidationError < FileMakerError
|
218
|
+
end
|
219
|
+
|
220
|
+
class DateValidationError < ValidationError
|
221
|
+
end
|
222
|
+
|
223
|
+
class TimeValidationError < ValidationError
|
224
|
+
end
|
225
|
+
|
226
|
+
class NumberValidationError < ValidationError
|
227
|
+
end
|
228
|
+
|
229
|
+
class RangeValidationError < ValidationError
|
230
|
+
end
|
231
|
+
|
232
|
+
class UniqueValidationError < ValidationError
|
233
|
+
end
|
234
|
+
|
235
|
+
class ExistingValidationError < ValidationError
|
236
|
+
end
|
237
|
+
|
238
|
+
class ValueListValidationError < ValidationError
|
239
|
+
end
|
240
|
+
|
241
|
+
class ValidationCalculationError < ValidationError
|
242
|
+
end
|
243
|
+
|
244
|
+
class InvalidFindModeValueError < ValidationError
|
245
|
+
end
|
246
|
+
|
247
|
+
class MaximumCharactersValidationError < ValidationError
|
248
|
+
end
|
249
|
+
|
250
|
+
class FileError < FileMakerError
|
251
|
+
end
|
252
|
+
|
253
|
+
class UnableToOpenFileError < FileError
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|