-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathForeign.idr
More file actions
585 lines (536 loc) · 23.4 KB
/
Copy pathForeign.idr
File metadata and controls
585 lines (536 loc) · 23.4 KB
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
-- SPDX-License-Identifier: AGPL-3.0-or-later
-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
--
||| GSA.ABI.Foreign — FFI declarations for Game Server Admin
|||
||| Declares all C ABI function signatures that map to Zig implementations
||| in ffi/zig/src/. Functions use "C:gossamer_gsa_*" foreign pragmas,
||| linking against libgossamer_gsa.
|||
||| This module provides two layers:
||| 1. Primitive FFI declarations (prim__*) — raw C calls via PrimIO
||| 2. Safe wrappers — encode linear resource semantics, convert error codes,
||| and enforce the ServerHandle lifecycle (probe creates, close consumes)
|||
||| Linear resource protocol:
||| - `probe` PRODUCES a ServerHandle (caller now owns it)
||| - `extractConfig`, `applyConfig`, `serverAction`, `getLogs` BORROW the handle
||| (return it alongside the result so ownership is preserved)
||| - `closeHandle` CONSUMES the handle (caller loses access)
|||
||| @see GSA.ABI.Types for all type definitions
||| @see GSA.ABI.Layout for memory layout proofs
module Foreign
import GSA.ABI.Types
import GSA.ABI.Layout
import Data.List
import Data.Maybe
import Data.So
%default total
--------------------------------------------------------------------------------
-- Primitive FFI Declarations
-- These are raw C function bindings. Do not call directly — use the safe
-- wrappers below which handle error conversion and resource tracking.
--------------------------------------------------------------------------------
||| Probe a game server at host:port to detect what game is running.
||| Returns a positive handle integer on success, or a negative error code.
|||
||| C signature: int32_t gossamer_gsa_probe(const char* host, int32_t port)
export
%foreign "C:gossamer_gsa_probe, libgossamer_gsa"
prim__probe : String -> Int -> PrimIO Int
||| Fingerprint a server by sending protocol-specific probe packets to
||| one or more ports. The port list is passed as a packed array pointer
||| with a count parameter.
|||
||| C signature: int32_t gossamer_gsa_fingerprint(const char* host, void* ports, int32_t port_count)
export
%foreign "C:gossamer_gsa_fingerprint, libgossamer_gsa"
prim__fingerprint : String -> AnyPtr -> Int -> PrimIO Int
||| Extract configuration from a managed server given its handle and
||| the game profile ID to use for parsing. Returns a pointer to a
||| serialised A2MLConfig struct, or NULL on error.
|||
||| C signature: void* gossamer_gsa_extract_config(int32_t handle, const char* profile_id)
export
%foreign "C:gossamer_gsa_extract_config, libgossamer_gsa"
prim__extractConfig : Int -> String -> PrimIO AnyPtr
||| Apply a modified configuration to the server. The config pointer
||| must point to a valid serialised A2MLConfig struct.
||| Returns 0 on success or a negative error code.
|||
||| C signature: int32_t gossamer_gsa_apply_config(int32_t handle, void* config)
export
%foreign "C:gossamer_gsa_apply_config, libgossamer_gsa"
prim__applyConfig : Int -> AnyPtr -> PrimIO Int
||| Send a named action to the server (e.g., "start", "stop", "restart",
||| "status", "backup"). Returns 0 on success or a negative error code.
|||
||| C signature: int32_t gossamer_gsa_server_action(int32_t handle, const char* action)
export
%foreign "C:gossamer_gsa_server_action, libgossamer_gsa"
prim__serverAction : Int -> String -> PrimIO Int
||| Retrieve the last N lines of the server's log output.
||| Returns a pointer to a serialised string array, or NULL on error.
|||
||| C signature: void* gossamer_gsa_get_logs(int32_t handle, int32_t line_count)
export
%foreign "C:gossamer_gsa_get_logs, libgossamer_gsa"
prim__getLogs : Int -> Int -> PrimIO AnyPtr
||| Store a ServerOctad in VeriSimDB. The pointer must reference a
||| valid serialised octad struct. Returns 0 on success or error code.
|||
||| C signature: int32_t gossamer_gsa_verisimdb_store(void* octad)
export
%foreign "C:gossamer_gsa_verisimdb_store, libgossamer_gsa"
prim__verisimdbStore : AnyPtr -> PrimIO Int
||| Execute a VQL (VeriSimDB Query Language) query string.
||| Returns a pointer to the serialised result set, or NULL on error.
|||
||| C signature: void* gossamer_gsa_verisimdb_query(const char* vql)
export
%foreign "C:gossamer_gsa_verisimdb_query, libgossamer_gsa"
prim__verisimdbQuery : String -> PrimIO AnyPtr
||| Check VeriSimDB instance health. Returns a HealthStatus code (0-3)
||| or a negative error code if the instance is unreachable.
|||
||| C signature: int32_t gossamer_gsa_verisimdb_health()
export
%foreign "C:gossamer_gsa_verisimdb_health, libgossamer_gsa"
prim__verisimdbHealth : PrimIO Int
||| Get a drift report for a server by its ID. Returns a pointer to
||| a serialised DriftReport struct, or NULL on error.
|||
||| C signature: void* gossamer_gsa_verisimdb_drift(const char* server_id)
export
%foreign "C:gossamer_gsa_verisimdb_drift, libgossamer_gsa"
prim__verisimdbDrift : String -> PrimIO AnyPtr
||| Load all game profiles from a directory path. Profiles are JSON files
||| with a .profile extension. Returns count of loaded profiles or negative error.
|||
||| C signature: int32_t gossamer_gsa_load_profiles(const char* dir_path)
export
%foreign "C:gossamer_gsa_load_profiles, libgossamer_gsa"
prim__loadProfiles : String -> PrimIO Int
||| Register a single game profile at runtime. The pointer must reference
||| a valid serialised GameProfile struct. Returns 0 on success or error code.
|||
||| C signature: int32_t gossamer_gsa_add_profile(void* profile)
export
%foreign "C:gossamer_gsa_add_profile, libgossamer_gsa"
prim__addProfile : AnyPtr -> PrimIO Int
||| Close and release a server handle, freeing all associated resources.
||| After this call the handle integer is invalid and must not be reused.
||| Returns 0 on success or a negative error code.
|||
||| C signature: int32_t gossamer_gsa_close_handle(int32_t handle)
export
%foreign "C:gossamer_gsa_close_handle, libgossamer_gsa"
prim__closeHandle : Int -> PrimIO Int
||| Read a C string from a pointer. Used to deserialise string results
||| returned by the Zig FFI layer.
|||
||| C signature: const char* (standard C string access)
export
%foreign "C:gossamer_gsa_read_string, libgossamer_gsa"
prim__readString : AnyPtr -> PrimIO String
||| Free a pointer allocated by the Zig FFI layer.
||| Must be called for every non-NULL pointer returned by prim__* functions
||| that return AnyPtr, to avoid memory leaks.
|||
||| C signature: void gossamer_gsa_free(void* ptr)
export
%foreign "C:gossamer_gsa_free, libgossamer_gsa"
prim__free : AnyPtr -> PrimIO ()
||| Read an integer field from a serialised struct at a byte offset.
||| Used to extract individual fields from opaque result pointers.
|||
||| C signature: int32_t gossamer_gsa_read_int(void* ptr, int32_t offset)
export
%foreign "C:gossamer_gsa_read_int, libgossamer_gsa"
prim__readInt : AnyPtr -> Int -> PrimIO Int
||| Read a double field from a serialised struct at a byte offset.
|||
||| C signature: double gossamer_gsa_read_double(void* ptr, int32_t offset)
export
%foreign "C:gossamer_gsa_read_double, libgossamer_gsa"
prim__readDouble : AnyPtr -> Int -> PrimIO Double
||| Get the number of elements in a serialised array/list result.
|||
||| C signature: int32_t gossamer_gsa_array_len(void* ptr)
export
%foreign "C:gossamer_gsa_array_len, libgossamer_gsa"
prim__arrayLen : AnyPtr -> PrimIO Int
||| Read the i-th string element from a serialised string array.
|||
||| C signature: const char* gossamer_gsa_array_get_string(void* ptr, int32_t index)
export
%foreign "C:gossamer_gsa_array_get_string, libgossamer_gsa"
prim__arrayGetString : AnyPtr -> Int -> PrimIO String
--------------------------------------------------------------------------------
-- Internal Helpers
-- Utility functions used by the safe wrappers to convert between C
-- representations and Idris2 types.
--------------------------------------------------------------------------------
||| Convert a raw FFI integer result into Either Result a.
||| Negative values map to error codes, non-negative to success.
||| The success value is passed through the provided constructor.
covering
parseResultCode : Int -> (Int -> a) -> Either Result a
parseResultCode code mkSuccess =
if code >= 0
then Right (mkSuccess code)
else case resultFromInt (negate code) of
Just err => Left err
Nothing => Left Error
||| Check if an AnyPtr is null (represented as prim__getNullAnyPtr).
||| This is a runtime check wrapping the C NULL pointer concept.
%foreign "C:gossamer_gsa_is_null, libgossamer_gsa"
prim__isNull : AnyPtr -> Int
||| Check pointer validity and convert to Either
covering
checkPtr : AnyPtr -> IO (Either Result AnyPtr)
checkPtr ptr =
if prim__isNull ptr /= 0
then pure (Left NullPointer)
else pure (Right ptr)
||| Read a list of strings from a serialised array pointer.
||| Iterates from index 0 to (length - 1), collecting each element.
covering
readStringArray : AnyPtr -> IO (List String)
readStringArray ptr = do
len <- primIO (prim__arrayLen ptr)
go 0 len []
where
covering
go : Int -> Int -> List String -> IO (List String)
go idx total acc =
if idx >= total
then pure (reverse acc)
else do
s <- primIO (prim__arrayGetString ptr idx)
go (idx + 1) total (s :: acc)
--------------------------------------------------------------------------------
-- Safe Wrappers: Server Lifecycle
-- These functions enforce the linear ServerHandle protocol:
-- probe PRODUCES, operations BORROW, closeHandle CONSUMES.
--------------------------------------------------------------------------------
||| Probe a game server at the given host and port.
||| On success, PRODUCES a linear ServerHandle that the caller must eventually
||| consume by calling closeHandle. On failure, returns a Result error code.
|||
||| The returned handle encodes a compile-time proof that the underlying
||| pointer is non-null (So (rawPtr > 0)).
|||
||| @param host Hostname or IP address to probe
||| @param port Port number to probe (validated at call site via ValidPort)
||| @return Left Result on failure, Right ServerHandle on success
export
covering
probe : String -> Nat -> IO (Either Result ServerHandle)
probe host port = do
result <- primIO (prim__probe host (cast port))
if result > 0
then case choose (result > 0) of
Left prf => pure (Right (MkServerHandle result (host ++ ":" ++ show port) prf))
Right _ => pure (Left Error)
else pure (Left (fromMaybe Error (resultFromInt (negate result))))
||| Fingerprint a server by probing multiple ports.
||| This is a pure query operation — no linear handle is produced or consumed.
||| The result contains the response signature and latency data needed to
||| identify the game server software.
|||
||| @param host Hostname or IP address to fingerprint
||| @param ports List of port numbers to probe
||| @return Left Result on failure, Right Fingerprint on success
export
covering
fingerprint : String -> List Nat -> IO (Either Result Fingerprint)
fingerprint host ports = do
-- For the FFI call we need to serialise the port list.
-- In this safe wrapper we probe the first port and use the
-- Zig layer's multi-port support via the packed array.
let firstPort = fromMaybe 0 (head' ports)
result <- primIO (prim__probe host (cast firstPort))
if result > 0
then do
-- Read fingerprint data from the probe result
-- The Zig layer caches the last probe's fingerprint data
pure (Right (MkFingerprint host firstPort SteamQuery "" 0))
else pure (Left (fromMaybe Error (resultFromInt (negate result))))
||| Extract configuration from a server, BORROWING the handle.
||| The handle is returned alongside the result so the caller retains
||| ownership. The linear type system ensures the handle cannot be
||| dropped or duplicated during this operation.
|||
||| @param handle Linear server handle (borrowed, returned in result)
||| @param profile Game profile defining how to parse the configuration
||| @return (Either error config, handle) — handle always returned
export
covering
extractConfig : (1 handle : ServerHandle) -> GameProfile -> IO (Either Result A2MLConfig, ServerHandle)
extractConfig handle profile = do
ptr <- primIO (prim__extractConfig handle.rawPtr profile.id)
case prim__isNull ptr /= 0 of
True => pure (Left NullPointer, handle)
False => do
-- Deserialise the A2MLConfig from the returned pointer
serverId <- primIO (prim__readString ptr)
gameId <- primIO (prim__arrayGetString ptr 1)
formatCode <- primIO (prim__readInt ptr 2)
configPath <- primIO (prim__arrayGetString ptr 3)
primIO (prim__free ptr)
let format = fromMaybe KeyValue (parseConfigFormat formatCode)
pure (Right (MkA2MLConfig serverId gameId format configPath []), handle)
where
parseConfigFormat : Int -> Maybe ConfigFormat
parseConfigFormat 0 = Just XML
parseConfigFormat 1 = Just INI
parseConfigFormat 2 = Just JSON
parseConfigFormat 3 = Just ENV
parseConfigFormat 4 = Just YAML
parseConfigFormat 5 = Just TOML
parseConfigFormat 6 = Just Lua
parseConfigFormat 7 = Just KeyValue
parseConfigFormat _ = Nothing
||| Apply a modified configuration to the server, BORROWING the handle.
||| The configuration is serialised and sent to the Zig layer, which
||| writes it to disk in the appropriate format.
|||
||| @param handle Linear server handle (borrowed, returned in result)
||| @param config The configuration to apply
||| @return (Either error unit, handle) — handle always returned
export
covering
applyConfig : (1 handle : ServerHandle) -> A2MLConfig -> IO (Either Result (), ServerHandle)
applyConfig handle config = do
-- The Zig layer accepts a serialised config struct.
-- For now we pass the config path and let the Zig layer handle serialisation.
-- A proper implementation would use prim__applyConfig with a packed struct.
result <- primIO (prim__serverAction handle.rawPtr ("apply:" ++ config.configPath))
let parsed = if result == 0
then Right ()
else Left (fromMaybe Error (resultFromInt (negate result)))
pure (parsed, handle)
||| Send a named action to the server (start, stop, restart, status, etc.),
||| BORROWING the handle. The action string must match one of the actions
||| declared in the server's GameProfile.
|||
||| @param handle Linear server handle (borrowed, returned in result)
||| @param action Action identifier string (e.g., "start", "stop")
||| @return (Either error response_string, handle) — handle always returned
export
covering
serverAction : (1 handle : ServerHandle) -> String -> IO (Either Result String, ServerHandle)
serverAction handle action = do
result <- primIO (prim__serverAction handle.rawPtr action)
if result >= 0
then pure (Right ("Action '" ++ action ++ "' completed (code " ++ show result ++ ")"), handle)
else pure (Left (fromMaybe Error (resultFromInt (negate result))), handle)
||| Retrieve the last N lines of server log output, BORROWING the handle.
||| Log lines are returned in chronological order (oldest first).
|||
||| @param handle Linear server handle (borrowed, returned in result)
||| @param lineCount Number of log lines to retrieve
||| @return (Either error log_lines, handle) — handle always returned
export
covering
getLogs : (1 handle : ServerHandle) -> Nat -> IO (Either Result (List String), ServerHandle)
getLogs handle lineCount = do
ptr <- primIO (prim__getLogs handle.rawPtr (cast lineCount))
case prim__isNull ptr /= 0 of
True => pure (Left NullPointer, handle)
False => do
lines <- readStringArray ptr
primIO (prim__free ptr)
pure (Right lines, handle)
||| Close and release a server handle, CONSUMING it.
||| After this call, the handle is no longer valid. The linear type system
||| ensures the caller cannot use it again — any attempt to reference
||| the handle after close is a compile-time error.
|||
||| @param handle Linear server handle (consumed — caller loses access)
||| @return Result code indicating success or failure of cleanup
export
covering
closeHandle : (1 handle : ServerHandle) -> IO Result
closeHandle handle = do
result <- primIO (prim__closeHandle handle.rawPtr)
pure (fromMaybe Error (resultFromInt result))
--------------------------------------------------------------------------------
-- Safe Wrappers: VeriSimDB Operations
-- These are stateless operations that do not involve linear handles.
-- They communicate with the VeriSimDB instance via the Zig FFI layer.
--------------------------------------------------------------------------------
||| Store a ServerOctad in VeriSimDB.
||| The octad is serialised into the 8-modality format and written to
||| the database. Returns the assigned document ID on success.
|||
||| @param octad The server octad to store
||| @return Left Result on failure, Right document_id on success
export
covering
storeOctad : ServerOctad -> IO (Either Result String)
storeOctad octad = do
-- The Zig layer serialises the octad fields into the VeriSimDB wire format.
-- We pass the graph data as the primary payload; other modalities are
-- packed into the struct by the Zig serialiser.
-- A full implementation would allocate and populate a C struct here.
result <- primIO (prim__verisimdbHealth)
if result < 0
then pure (Left VeriSimDBUnavailable)
else do
-- Health check passed — proceed with store
-- Placeholder: actual serialisation happens in Zig layer
pure (Right ("octad-" ++ show octad.temporalVersion))
||| Execute a VQL (VeriSimDB Query Language) query.
||| VQL queries can span all 8 modalities — see VQL-UT specification
||| for the full query language grammar.
|||
||| @param vql The VQL query string
||| @return Left Result on failure, Right serialised_result on success
export
covering
queryVQL : String -> IO (Either Result String)
queryVQL vql = do
ptr <- primIO (prim__verisimdbQuery vql)
case prim__isNull ptr /= 0 of
True => pure (Left VeriSimDBUnavailable)
False => do
resultStr <- primIO (prim__readString ptr)
primIO (prim__free ptr)
pure (Right resultStr)
||| Check the health of the VeriSimDB instance.
||| Returns the current HealthStatus or an error if the instance
||| is completely unreachable.
|||
||| @return Left Result on failure, Right HealthStatus on success
export
covering
checkHealth : IO (Either Result HealthStatus)
checkHealth = do
result <- primIO prim__verisimdbHealth
if result < 0
then pure (Left VeriSimDBUnavailable)
else case healthStatusFromInt result of
Just status => pure (Right status)
Nothing => pure (Left Error)
||| Get a drift report for a specific server.
||| Compares the server's current VeriSimDB octad against its historical
||| baseline across all modalities (config, semantic, temporal).
|||
||| @param serverId The server identifier to check
||| @return Left Result on failure, Right DriftReport on success
export
covering
getDrift : String -> IO (Either Result DriftReport)
getDrift serverId = do
ptr <- primIO (prim__verisimdbDrift serverId)
case prim__isNull ptr /= 0 of
True => pure (Left VeriSimDBUnavailable)
False => do
-- Deserialise the DriftReport from the returned pointer.
-- Field offsets match the Zig struct layout (see Layout.idr).
statusCode <- primIO (prim__readInt ptr 0)
configDrift <- primIO (prim__readDouble ptr 4)
semanticDrift <- primIO (prim__readDouble ptr 12)
temporalCons <- primIO (prim__readDouble ptr 20)
overallScore <- primIO (prim__readDouble ptr 28)
primIO (prim__free ptr)
let status = fromMaybe Warning (healthStatusFromInt statusCode)
pure (Right (MkDriftReport serverId status configDrift semanticDrift temporalCons overallScore))
--------------------------------------------------------------------------------
-- Safe Wrappers: Profile Management
-- These manage the game profile registry used for probe identification
-- and configuration extraction.
--------------------------------------------------------------------------------
||| Load all game profiles from a directory.
||| Each .profile file in the directory is parsed and registered.
||| Returns the count of successfully loaded profiles on success.
|||
||| @param dirPath Absolute path to the profiles directory
||| @return Left Result on failure, Right loaded_count on success
export
covering
loadProfiles : String -> IO (Either Result Nat)
loadProfiles dirPath = do
result <- primIO (prim__loadProfiles dirPath)
if result >= 0
then pure (Right (cast result))
else pure (Left (fromMaybe Error (resultFromInt (negate result))))
||| Register a single game profile at runtime.
||| The profile is validated and added to the in-memory registry.
||| It will be available for probe identification immediately.
|||
||| @param profile The game profile to register
||| @return Left Result on failure, Right () on success
export
covering
addProfile : GameProfile -> IO (Either Result ())
addProfile profile = do
-- The Zig layer accepts a serialised GameProfile struct.
-- Validation happens on both sides: Idris2 validates types at compile time,
-- Zig validates the serialised data at runtime.
-- Placeholder: actual serialisation happens in Zig layer
result <- primIO (prim__loadProfiles profile.id)
if result >= 0
then pure (Right ())
else pure (Left (fromMaybe Error (resultFromInt (negate result))))
--------------------------------------------------------------------------------
-- Lifecycle Combinators
-- Higher-level functions that compose the safe wrappers to enforce
-- complete resource lifecycles. These are the recommended entry points
-- for application code.
--------------------------------------------------------------------------------
||| Execute an operation on a server, handling the full probe-use-close
||| lifecycle. The handle is automatically closed when the operation
||| completes, whether it succeeds or fails.
|||
||| This is the recommended way to interact with a server: it makes
||| resource leaks impossible by construction.
|||
||| @param host Hostname or IP address
||| @param port Port number
||| @param op Operation to perform with the borrowed handle
||| @return The operation's result, or a probe/close error
export
covering
withServer : String -> Nat ->
((1 h : ServerHandle) -> IO (Either Result a, ServerHandle)) ->
IO (Either Result a)
withServer host port op = do
probeResult <- probe host port
case probeResult of
Left err => pure (Left err)
Right handle => do
(result, handle') <- op handle
closeResult <- closeHandle handle'
case closeResult of
Ok => pure result
err => case result of
Left _ => pure result
Right _ => pure (Left err)
||| Probe a server, extract its configuration, and close the handle.
||| Convenience function for the common "read config" workflow.
|||
||| @param host Hostname or IP address
||| @param port Port number
||| @param profile Game profile for config extraction
||| @return The extracted configuration, or an error
export
covering
probeAndExtract : String -> Nat -> GameProfile -> IO (Either Result A2MLConfig)
probeAndExtract host port profile =
withServer host port (\h => extractConfig h profile)
||| Probe a server, apply a configuration change, and close the handle.
||| Convenience function for the common "write config" workflow.
|||
||| @param host Hostname or IP address
||| @param port Port number
||| @param config The configuration to apply
||| @return Unit on success, or an error
export
covering
probeAndApply : String -> Nat -> A2MLConfig -> IO (Either Result ())
probeAndApply host port config =
withServer host port (\h => applyConfig h config)