-
Notifications
You must be signed in to change notification settings - Fork 1
/
Router.cls
605 lines (537 loc) · 26.4 KB
/
Router.cls
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
Class Frontier.UnitTest.Router Extends Frontier.Router
{
XData UrlMap [ XMLNamespace = "http://github.com/rfns/frontier" ]
{
<Routes>
<Route Url="/public/single" Method="GET" Call="TestGETSingleStaticFile" />
<Route Url="/public/(.*)" Strict="false" Method="GET" Call="TestGETStaticFile"/>
<Route Url="/public/custom/(.*)" Strict="false" Method="GET" Call="TestGETStaticFileWithCustomConfig"/>
<Route Url="/upload/multi" Method="POST" Call="TestPOSTMultipartFileUpload"/>
<Route Url="/upload" Method="POST" Call="TestPOSTSingleFileUpload"/>
<Route Url="/route-params/:class" Method="GET" Call="TestGETRouteParams"/>
<Route Url="/route-params/:class/students/:student" Method="GET" Call="TestGETRouteParamsWhereId"/>
<Route Url="/query-params" Method="GET" Call="TestGETOneQueryParameter"/>
<Route Url="/rest-params" Method="GET" Call="TestGETRestParametersSum"/>
<Route Url="/sql/dynamic" Method="GET" Call="TestGETDynamicSQLResult"/>
<Route Url="/sql/query" Method="GET" Call="TestGETQuerySQLResult"/>
<Route Url="/sql/inline-query" Method="GET" Call="TestGETInlineQueryBuilder" />
<Route Url="/raw" Method="GET" Call="TestGETRawMode"/>
<Route Url="/shared-data" Method="GET" Call="TestGETData"/>
<Route Url="/stream" Method="GET" Call="TestGETStream"/>
<Route Url="/alias" Method="GET" Call="TestGETAliasedQueryParameter"/>
<Route Url="/payload/single-object" Method="POST" Call="TestPOSTObjectPayloadSingle"/>
<Route Url="/payload/object-with-query-params" Method="POST" Call="TestPOSTObjectPayloadQueryParams"/>
<Route Url="/payload/single-array" Method="POST" Call="TestPOSTArrayPayloadSingle"/>
<Route Url="/payload/array-with-query-params" Method="POST" Call="TestPOSTArrayPayloadQueryParams"/>
<Route Url="/payload/invalid" Method="POST" Call="TestPOSTInvalidPayload"/>
<Route Url="/mixed/object" Method="GET" Call="TestGETMixedDynamicObject"/>
<Route Url="/mixed/array" Method="GET" Call="TestGETMixedDynamicArray"/>
<Route Url="/mixed/rest" Method="POST" Call="TestPOSTMixedRestParametersSum"/>
<Route Url="/unmarshal" Method="POST" Call="TestPOSTUnmarshalToClass"/>
<Route Url="/unmarshal/:class" Method="PUT" Call="TestPUTUnmarshalToClass"/>
<Route Url="/user-info" Method="GET" Call="TestGETSessionUserInfo"/>
<Route Url="/sign-in" Method="POST" UseAuth="false" Call="SignIn" />
<Route Url="/errors/siblings" Method="GET" Call="TestGETMultipleSiblingErrors"/>
<Route Url="/arguments/no-weak-typed" Method="GET" Call="TestGETNoWeakTypedArgs"/>
<Route Url="/methods/no-weak-typed" Method="GET" Call="TestGETNoWeakTypedMethods" />
<Route Url="/custom-auth" Method="GET" AuthStrategy="BasicSystemLogin" Call="TestGETCustomAuth" />
<Map Prefix="/map/A/:param1" Forward="Frontier.UnitTest.Router.Map.A" />
<Map Prefix="/map/B/:param1/:param2" Forward="Frontier.UnitTest.Router.Map.B" />
<Map Prefix="/map/C" Forward="Frontier.UnitTest.Router.Map.C" />
<Map Prefix="/map/D/:paramA/fixed/:paramB" Forward="Frontier.UnitTest.Router.Map.D"/>
<Map Prefix="/map/E/:namedParam" Forward="Frontier.UnitTest.Router.Map.E"/>
</Routes>
}
/// Use this method to share data between methods.
ClassMethod OnDataSet(data As %DynamicObject) As %Status
{
/// This 'data' object is shared between all methods. Accessible using %frontier.Data.
set data.Message = "This 'Message' is shared between all methods."
set data.Workspace = ##class(%File).NormalizeFilename(##class(Port.Configuration).GetWorkspace("frontier"))
return $$$OK
}
/// This method is reserved for setting up configurations.
ClassMethod OnSetup() As %Status
{
//set %frontier.Parameters.DIRECTWRITE = 1
// Add reporters to notify the developers about ongoing errors.
// This one creates a new entry into the Frontier.Reporter.Log table.
set logReporter = ##class(Frontier.Reporter.Log).%New({
// We want to log any abnormal error.
"include": [($$$CacheError), ($$$InvalidUsernameOrPassword)]
})
$$$QuitOnError(%frontier.ReporterManager.AddReporter(logReporter))
// Prettifies JSON by indenting it based on two spaces.
// Leave it as 0 for inline output.
set %frontier.Parameters.INDENTSIZE = 2
// Defines how deep should the object be serialized.
// WARNING: The higher the value the longer will be the wait, which could cause a Gateway Timeout.
set %frontier.Parameters.MAXIMUMDEPTH = 7
set cookieStrategy = ##class(Frontier.Authentication.CookieStrategy).%New({
"secret": "shhh",
"validator": ($classname()_":ValidateUsingCookie")
})
// Asks the user for a Basic + Base64(username:password) encoded Authorization header.
set basicStrategy = ##class(Frontier.Authentication.BasicStrategy).%New({
"realm": "tests",
"validator": ($classname()_":ValidateCredentials")
})
set systemBasicStrategy = ##class(Frontier.Authentication.BasicStrategy).%New({
"realm": "system",
"name": "BasicSystemLogin",
"validator": ($classname()_":ValidateSystemCredentials")
})
// This uses the cookie generated by the /sign-in route to validate the access.
$$$QuitOnError(%frontier.AuthenticationManager.AddStrategy(cookieStrategy))
// This provides a way to check if the current user is authenticated using Basic scheme.
$$$QuitOnError(%frontier.AuthenticationManager.AddStrategy(basicStrategy))
$$$QuitOnError(%frontier.AuthenticationManager.AddStrategy(systemBasicStrategy))
set %frontier.Response.GzipOutput = 1
do %frontier.CORS.Default()
return $$$OK
}
/// Whenever a parameter is typed of %Persistent and a valid id is provided, the parameter will be instantiated.
/// Call example:
/// curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/route-params/6'
/// {"Plate":"O5397","Students":[{"Name":"Drabek,Peter T.","__id__":"20"}],"__id__":"6"}
ClassMethod TestGETRouteParams(class As Frontier.UnitTest.Fixtures.Class) As %Status
{
return class
}
/// This follows the same principle as the TestGETRouteParams example, however WHEREID allows the developer to search
/// for the id using alternative sources other than relying straight to route parameters.
/// It is useful for avoiding the object id from being exposed.
/// The format is close to a SQL where, however supporting :placeholders.
/// Placeholders are checked using the following order:
/// :name -> %frontier.RouteParameters("name") (obligatory)
/// :name -> %frontier.User.name (optional)
/// :name -> %frontier.Data.name (optional)
/// This example brings the route parameter class's value along with the route parameter value from student.
/// The result is /route-params/1/students/2 instead of /route/params/1/students/1||2.
/// Query parameters are also supported and are fetched if the parameter name matches the URL query name.
/// Although <Route /> doesn't define the parameter itself. It's defined by putting it into the method's signature.
ClassMethod TestGETRouteParamsWhereId(class As Frontier.UnitTest.Fixtures.Class, student As Frontier.UnitTest.Fixtures.Student) As %Status
{
return student
}
/// Query parameters with default value are considered optional, otherwise they become obligatory.
/// Call example:
/// curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/query-params?msg=hello'
/// {result":"hello"}
ClassMethod TestGETOneQueryParameter(msg As %String) As %String
{
return msg
}
/// Rest parameters are sequential query parameters. They can have flexible size and have the format parameterN.
/// This request takes that number of parameters and sum them.
/// Rest parameters are always optional.
/// Call example:
/// curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/rest-params?n1=10&n2=20&n3=30'
ClassMethod TestGETRestParametersSum(n... As %String) As %Integer
{
set sum = 0
for i=1:1:n set sum = sum + n(i)
return sum
}
/// Dynamic SQL queries can be returned as long as the Frontier SQL API is used.
/// For SQL, the usage of Prepare() from frontier.SQL API is required.
/// Call example:
/// curl -H "Content-Type: application/json" 'http://localhost:57772/api/frontier/test/sql/dynamic?page=1&rows=5'
ClassMethod TestGETDynamicSQLResult(page As %Integer = 1, rows As %Integer = 5) As Frontier.SQL.Provider
{
set offset = (page * rows) - (rows - 1)
set limit = page * rows
return %frontier.SQL.Prepare(
"SELECT *, %VID as Index FROM (SELECT * FROM FRONTIER_UNITTEST_FIXTURES.STUDENT) WHERE %VID BETWEEN ? AND ?"
).Parameters(offset, limit).Mode(2)
}
/// The same applies for named (cached) SQL queries. Notice that they can be called by providing
/// the class:queryname instead of a dynamic SQL.
/// curl -H "Content-Type: application/json" 'http://localhost:57772/api/frontier/test/sql/query?page=1&rows=5'
ClassMethod TestGETQuerySQLResult(page As %Integer = 1, rows As %Integer = 5) As Frontier.SQL.Provider
{
set offset = (page * rows) - (rows - 1)
set limit = page * rows
return %frontier.SQL.Prepare("Frontier.UnitTest.Fixtures.Student:PaginatedStudents").Parameters(offset, limit).Mode(2)
}
/// This method captures parameter "filter" as a source to build the custom query.
/// You need to provide the target class which resolves to the SQL Table using the For method.
/// The filter format can be divided into two types:
/// Simple: fieldnameA_operation:value;fieldnameB_operation:value
/// Composited: _or:[fieldnameA_operation:value;fieldNameA_operation:value]
/// Supported operations:
/// field_eq:value field = value
/// field_neq:value field <> value
/// field_gt:value field > value
/// field_gte:value field >= value
/// field_lt:value field < value
/// field_lte:value field <= value
/// field_sw:value field %STARTSWITH value
/// field_nsw:value field NOT %STARTSWITH value
/// field_ct:value field [ value
/// field_nct:value field '[ value
/// field_lk:%value% field LIKE '%value%'
/// field_nlk:%value% field NOT LIKE '%value%'
/// field_il:v,a,l,u,e field %INLIST($lfs('v,a,l,u,e'))
/// field_nil:v,a,l,u,e field NOT %INLIST($lfs('v,a,l,u,e'))
/// field_is:null field IS NULL
/// field_isn:null field IS NOT NULL
/// field_bt:0,5 field BETWEEN 0 and 5
/// field_nbt:0,5 field NOT BETWEEN 0 and 5
/// field_in:v,a,l,u,e field IN('v','a','l','u','e')
/// field_nin:v,a,l,u,e field NOT IN('v','a','l','u','e')
/// field:value is translate to field_eq:value
/// OrderBy can be used to sort the fields, the syntax is the same as you would use with SQL.
/// Fields can be used to select the fields you want include in the results.
ClassMethod TestGETInlineQueryBuilder(filter As %String, page As %String = "", limit As %String = "", orderBy As %String = "", groupBy As %String = "") As Frontier.SQL.Provider
{
set builder = %frontier.SQL.InlineQueryBuilder("Frontier.UnitTest.Fixtures.Student")
if page '= "" && (limit '= "") do builder.Pagination(page, limit)
if orderBy '= "" do builder.OrderBy(orderBy)
if groupBy '= "" do builder.GroupBy(groupBy)
if filter '= "" do builder.Filter(filter)
return builder.Build().Provide()
}
/// There can be cases where the response should not be a JSON.
/// This method shows how to output a text in plain format.
/// Call example:
/// curl -H "Content-Type: application/json" http://localhost:57772/api/frontier/test/raw
ClassMethod TestGETRawMode() As %String
{
do %frontier.Raw()
return "hello raw response"
}
/// This method uses the data object provided on Setup method.
/// The idea is to demonstrate how to access shared data.
/// Call example:
/// /// curl -H "Content-Type: application/json" 'localhost:57772/api/frontier/test/shared_data'
ClassMethod TestGETData() As %DynamicObject
{
return %frontier.Data
}
/// This method demonstrates how %Stream.Object based instances are treated.
/// Call example:
/// curl -H "Content-Type: application/json" 'http://localhost:57772/api/frontier/test/stream'
ClassMethod TestGETStream() As %Stream.Object
{
set stream = ##class(%Stream.GlobalCharacter).%New()
do stream.Write("This line is from a stream.")
return stream
}
/// If the request is a POST/PUT/PATCH method, it can receive a payload that's represented by a parameter typed of %DynamicObject instances.
/// Call example:
/// curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' 'http://localhost:57772/api/frontier/test/payload/single-object'
ClassMethod TestPOSTObjectPayloadSingle(payload As %DynamicObject) As %DynamicObject
{
return payload
}
/// Request can have a payload along with a query parameter.
/// Call example:
/// curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' 'http://localhost:57772/api/frontier/test/payload/object-with-query-params?msg=hello'
ClassMethod TestPOSTObjectPayloadQueryParams(msg As %String = "", payload As %DynamicObject) As %Status
{
return {
"msg": (msg),
"payload": (payload)
}
}
/// Payloads can also be an array.
/// Call example:
/// curl -H "Content-Type: application/json" -X POST -d '[{"username":"xyz","password":"xyz"}]' 'http://localhost:57772/api/frontier/test/payload/single-array'
ClassMethod TestPOSTArrayPayloadSingle(payload As %DynamicArray) As %DynamicObject
{
return payload
}
/// Requests with array payloads also support query parameters.
/// Call example:
/// curl -H "Content-Type: application/json" -X POST -d '[{"username":"xyz","password":"xyz"}]' 'http://localhost:57772/api/frontier/test/payload/array-with-query-params?msg=hello'
ClassMethod TestPOSTArrayPayloadQueryParams(payload As %DynamicArray, msg As %String = "") As %DynamicArray
{
return [ (msg), (payload) ]
}
/// This demonstrates payload validation, as browsers can receive one payload per request
/// a classmethod that expects more than one is considered invalid and will throw an exception.
/// curl -H "Content-Type: application/json" -X POST -d '[{"username":"xyz","password":"xyz"}]' 'http://localhost:57772/api/frontier/test/payload/invalid'
ClassMethod TestPOSTInvalidPayload(payloadA As %DynamicArray, payloadB As %DynamicObject) As %DynamicArray
{
return payloadA
}
/// Normally %Dynamic instances cannot serialize childrens that aren't dynamic as well. (%ToJSON would thrown an exception).
/// Frontier fixes it by marshalling irregularities to %Dynamic instances beforehand and making them compatible.
/// Call example:
/// curl -H "Content-Type: application/json" http://localhost:57772/api/frontier/test/mixed/object?class=1
ClassMethod TestGETMixedDynamicObject(class As Frontier.UnitTest.Fixtures.Class) As %DynamicObject
{
return {
"class": (class)
}
}
/// Also supported for %DynamicArray.
/// Call example:
/// curl -H "Content-Type: application/json" http://localhost:57772/api/frontier/test/mixed/array?class=1
ClassMethod TestGETMixedDynamicArray(class As Frontier.UnitTest.Fixtures.Class) As %DynamicArray
{
return [(class)]
}
/// It's possible to mix multiple parameter types as you could when calling a method.
/// The example below illustrates the usage of a method that receives a 'msg' query parameter along with a payload and a rest parameter.
/// Call example:
/// curl -H "Content-Type: application/json" -X POST -d '{"username":"xyz","password":"xyz"}' 'http://localhost:57772/api/frontier/test/mixed/rest?n1=10&n2=20&msg=hello'
ClassMethod TestPOSTMixedRestParametersSum(msg As %String, data As %DynamicObject, n... As %Integer) As %DynamicObject
{
set parameters = []
set sum = 0
for i=1:1:n set sum = sum + n(i) do parameters.%Push(n(i))
return {
"sum": (sum),
"data": (data),
"params": (parameters),
"msg": (msg)
}
return sum
}
/// For compability with existing clients that use query parameters with symbols. For such cases
/// ALIAS can be used to make it compatible. If the expected alias is not found then Frontier will
/// fall back to using the original argument name instead.
ClassMethod TestGETAliasedQueryParameter(msg As %String(ALIAS="aliased_key")) As %String
{
return msg
}
/// It's also possible to save from a payload directly to a %Persistent instance as long as the payload
/// has the same format as the %Persistent class. Just define the the parameter UNMARSHALL to 1.
/// curl -H "Content-Type: application/json" -X POST -d '{"Plate": "R-2948","Students": [{"Name": "Rubens","BirthDate": "04/21/1970","SomeValue": 0}]}' 'http://localhost:57772/api/frontier/unmarshal'
ClassMethod TestPOSTUnmarshalToClass(class As Frontier.UnitTest.Fixtures.Class(UNMARSHAL=1)) As Frontier.UnitTest.Fixtures.Student
{
$$$ThrowOnError(class.%Save())
return {
"ok": 1,
"__id__": (class.%Id())
}
}
/// You can also edit the entity just by providing the property "__id__" along with the new payload data. If you want the id to be read from the URL,
/// add a new parameter and map the unmarshaller to use it as id by specifying the WHEREID. If WHEREID is not
/// found, the id will be searched inside the payload. Note that when the id is not found, an exception will be thrown regarding the
/// incorrect PUT usage.
/// curl -H "Content-Type: application/json" -X POST -d '{"Plate": "R-2948","Students": [{"Name": "Rubens","BirthDate": "04/21/1970","SomeValue": 0}]}' 'http://localhost:57772/api/frontier/unmarshall/15'
ClassMethod TestPUTUnmarshalToClass(classId As %String, class As Frontier.UnitTest.Fixtures.Class(UNMARSHAL=1,WHEREID="ID = :classId")) As Frontier.UnitTest.Fixtures.Student
{
$$$ThrowOnError(class.%Save())
return {
"ok": 1,
"__id__": (class.%Id())
}
}
/// This method depends on the authorization header sent by the client and validated with the validator method.
/// The validator method is also responsible for defining the user object.
/// Call example:
/// curl -H "Authorization: Basic Zm9vOmJhcg" 'http://localhost:57772/api/frontier/test/user'
ClassMethod TestGETSessionUserInfo() As %Status
{
return %frontier.User
}
/// Serves all files from the path defined on Data.Workspace (this project's folder).
/// This is the fastest way to implement a file server.
/// In order to use it, you must provide A non-Strict route that allows custom regular expressions like this one:
/// <Route Url="/public/?(.*)?" Strict="false" Method="GET" Call="TestFileServer"/>
/// That regular expression means 'put everything after public or public/ into %frontier.Matches'.
/// E.g.: /public/my/path/has/this/file.txt resolves into /<root>/my/path/has/this/file.txt
/// Keep in mind that this won't create a virtual host. So if you're serving files like HTML that link to other files,
/// you must provide the path relative to the server's virtual host instead of relating it to the Route path.
ClassMethod TestGETStaticFile() As %Stream.Object
{
return %frontier.Files.ServeFrom(%frontier.Data.Workspace)
}
/// This actually does the same than TestGETStaticFile.
/// Using Serve allows to provide advanced cache configuration and extension recognition.
/// The settings are:
///
/// root - Provides the path where files are located.
///
/// file - If root is not provided, a 'file' is expected to be defined. Which points to a file instead of a directory.
///
/// index - Indicates which files to search for and serve as default if no specific file path was provided.
///
/// cache - How the cache should behave.
///
/// charset - Defines which charset to be apply while reading the device and also sets the charset parameter for the response.
///
/// cache.configuration - A string containing the Cache-Control header configuration. Eg: "max-age=120, public".
/// cacheignore - A array indicating which extensions should not be cached.
///
/// extensions - An Object containing how to the server should handle files matching the extension.
///
/// The file server attempts to discover how to handle the extension.
/// But defaults to 'application/octet-stream' if the extension is unknown. The 'extension' object provides a way to handle them.
/// Each extension object should be keyed by its own extension identifier along with a child object.
///
/// <extension> - This is the keyed object, all configurations inside it will affect the <extension> exclusively.
/// <extension>.disposition - This will set the Content-Disposition header.
/// <extension>.disposition = "attachment" will force the browser to download the file instead of opening it.
/// <extension>.disposition = "inline" will force the browser to open the file.
/// <extension>.mimeType - Sets the Content-Type to the specified string.
/// <extension>.binary - Informs the file server to handle the file as binary. This disables charset conversion.
/// <extension>.charset - If not specified, this assumes the global 'charset' configuration. This setting exists to differ the charset by file extension.
ClassMethod TestGETStaticFileWithCustomConfig() As %Stream.Object
{
return %frontier.Files.Serve({
"root": (%frontier.Data.Workspace),
"index": "index.html index.htm",
"extensions": {
"md": {
"disposition": "inline",
"mimeType": "text/markdown",
"binary": false
}
}
})
}
/// If you need to provide access to a single file without exposing the path where it actually is, then
/// you can do so by using the ServeFile instead, this method takes an absolute path to a file restricting
/// the access to anything else.
ClassMethod TestGETSingleStaticFile() As %Stream.Object
{
return %frontier.Files.ServeFile(%frontier.Data.Workspace_"/README.md", "utf-8")
}
/// This method implements a full-sized configuration on how to handle multipart uploads.
ClassMethod TestPOSTMultipartFileUpload() As %Status
{
set location = %frontier.Data.Workspace_"/fixtures/uploads/multiple"
set destination = (location_"/:KEY/:FILE_NAME:EXTENSION")
// 512 KB
set maxFileSize = (1024**2/0.5)
return %frontier.Files.Upload({
"hooks": {
"onItemUploadSuccess": "Frontier.UnitTest.Router:WriteResultToProcessGlobal",
"onItemUploadError": "Frontier.UnitTest.Router:WriteErrorToProcessGlobal",
"onComplete": "Frontier.UnitTest.Router:WriteResultSummaryToProcessGlobal"
},
"filters": {
"verbose": true,
"maxFileSize": { "value": (maxFileSize), "errorTemplate": "O arquivo excedeu o limite de :VALUE bytes." },
"extensions": ["cls", "md", "txt", "pdf"]
},
"destinations": {
"file_a": { "path": (destination) },
"file_b": { "path": (destination) },
"file_c": { "path": (destination), "optional": true },
"file_d": { "path": (destination), "optional": true },
"files": {
"path": (location_"/files/:INDEX/:FILE_NAME:EXTENSION"),
"slots": 3,
"filters": { "maxFileSize": 500000000 }
}
}
})
}
/// This is an alternative format for handling uploads that aren't form-data/multipart.
/// This format should be used to handle simple file uploads.
/// The uploader changes to this mode if "destination" is passed instead of "destinations".
/// With a few placeholder exceptions (FILE_NAME, EXTENSION), all other features can also be applied here.
/// Since only the file is sent straight to %request.Content, there's no way to know the
/// file name, which means that its name should defined by the implementation.
ClassMethod TestPOSTSingleFileUpload() As %Status
{
return %frontier.Files.Upload({
"filters": {
"verbose": true,
"extensions": ["txt"]
},
"hooks": {
"onComplete": "Frontier.UnitTest.Router:WriteResultSummaryToProcessGlobal"
},
"destination": (%frontier.Data.Workspace_"/fixtures/uploads/single/file.txt")
})
}
ClassMethod WriteResultToProcessGlobal(key As %String, index As %String, destination As %String, filePath As %String) As %Status
{
set ^||Frontier("success", key) = $lb(destination, filePath)
return $$$OK
}
ClassMethod WriteResultSummaryToProcessGlobal(summary As %DynamicArray) As %Status
{
set ^||Frontier("summary") = summary.%ToJSON()
return $$$OK
}
ClassMethod WriteErrorToProcessGlobal(key As %String, index As %String, destination As %String, filePath As %String, sc As %Status) As %Status
{
set ^||Frontier("error", key, index) = $lb(sc, key, destination, filePath)
return $$$OK
}
/// Errors can be put together. Either by simblings or childrens.
ClassMethod TestGETMultipleSiblingErrors() As %Status
{
set sc1 = $$$ERROR($$$GeneralError, "First")
set sc2 = $$$ERROR($$$GeneralError, "Second")
set sc3 = $$$ERROR($$$GeneralError, "Third")
set sc3 = $$$EMBEDSC(sc3, $$$ERROR($$$QueryNameTooLong, "Child"))
set sc1 = $$$ADDSC(sc1, sc2)
set sc1 = $$$ADDSC(sc1, sc3)
$$$ThrowOnError(sc1)
}
/// This will thrown an error. Because Frontier doesn't allow arguments without type.
ClassMethod TestGETNoWeakTypedArgs(a As %String = "", b = "", c) As %Status
{
return $$$OK
}
/// Neither it allows methods without return type.
ClassMethod TestGETNoWeakTypedMethods()
{
return $$$OK
}
/// This method can be retrieved only if the user logs with the Caché credentials.
/// The purpose is to show that custom authentication strategies can be used exclusively.
ClassMethod TestGETCustomAuth() As %String
{
return "works"
}
ClassMethod ValidateCredentials(login As %String, password As %String, found As %Boolean = 1, Output httpStatus As %String, Output user As %DynamicObject) As %Status
{
set useScope = 0
if login = "foo" && (password = "bar") set found = 1
if login = "admin" && (password = "admin") set found = 1 set useScope = 1
if found set httpStatus = ..#HTTP200OK
else set httpStatus = ..#HTTP403FORBIDDEN
set user = {
"login": (login)
}
if useScope {
set user.scope = "admin not_enough"
}
return $$$OK
}
ClassMethod ValidateSystemCredentials(login As %String, password As %String, Output found As %Boolean = 1, Output httpStatus As %String, Output user As %DynamicObject) As %Status
{
set found = $System.Security.Login(login, password)
if 'found {
set httpStatus = ..#HTTP403FORBIDDEN
return $$$ERROR($$$InvalidUsernameOrPassword)
}
if '$System.Security.CheckUserPermission(login, "%Development", "USE") {
set httpStatus = ..#HTTP403FORBIDDEN
return $$$ERROR($$$RequiresRoleForConnection, "%Development")
}
return $$$OK
}
ClassMethod ValidateUsingCookie(value As %String, Output found = 0, Output httpStatus, Output user) As %Status
{
set data = $zcvt($System.Encryption.Base64Decode(value), "I", "UTF8")
set login = $listget(data, 1)
if login = "foo" set password = "bar"
set exp = +$listget(data, 2)
if exp < $h {
set found = 0
set httpStatus = "401 Forbidden"
return $$$OK
}
return ..ValidateCredentials(login, password, .found, .httpStatus, .user)
}
ClassMethod SignIn() As %String
{
do %frontier.Raw()
set exp = $piece($h, ",") +30_","_$piece($h, ",", 2)
set credential = $translate($System.Encryption.Base64Encode($zcvt($lb("foo", exp), "O", "UTF8")), "=/+$", "")
set expires = ##class(%CSP.StreamServer).ToHTTPDate(exp)
do %frontier.Response.SetCookie("__fc", ##class(Frontier.Security.CookieSignature).Sign(credential, "shhh"), expires, %frontier.Request.Application,,,1)
return ""
}
}