Noise Ser/  de
1 Serialization & Deserialization
1.1 Records
:
define-record
record-out
record-info?
1.2 Enumerations
define-enum
enum-out
enum-info?
1.3 Field Types
field-type?
Bool
Bytes
Float32
Float64
Int16
Int32
Varint
UInt16
UInt32
UVarint
String
Symbol
Delay
Hash  Table
Listof
Optional
String  Convertible
2 Backends
define-rpc
define-callout
serve
get-rpc-checksum
3 Callouts
callout-box?
make-callout-box
callout-box-install!
9.2.0.2

Noise Ser/de🔗ℹ

Bogdan Popa <bogdan@defn.io>

This is a companion package to Noise that provides utilities for serializing and deserializing data structures between Swift and Racket.

1 Serialization & Deserialization🔗ℹ

 (require noise/serde) package: noise-serde-lib

1.1 Records🔗ℹ

A record is a data structure that is shared between Swift and Racket. In both languages, they are represented by structs. Use the raco command noise-serde-codegen to generate Swift definitions for records reachable from a given root module.

syntax

:

syntax

(define-record name
  field ...)
 
name = id
  | (id : protocol-id ...+)
     
field = [field-id : field-type field-option ...]
  | [(field-id default-expr) : field-type field-option ...]
     
field-option = #:mutable
  | #:contract field-ctc-expr
 
  field-type : (or/c field-type? enum-info? record-info?)
Defines a record called name with the given set of fields. Records are backed by structs and generate smart constructors that take a keyword argument for every field. Smart constructors are named by prepending make- to the record name and bound at phase level 0. Record names must be unique across all modules.

When a field is #:mutable, it uses var as its introducer in Swift instead of let. The option currently has no effect on the generated Racket code.

Examples:
> (define-record (Human : Equatable Hashable)
    [name : String]
    [age : UVarint #:contract (integer-in 0 125)]
    [(likes-pizza? #t) : Bool])
> (make-Human
   #:name "Bogdan"
   #:age 30)

(Human "Bogdan" 30 #t)

> Human

#<procedure:Human>

Changed in version 0.3 of package noise-serde-lib: Added the #:mutable field option.
Changed in version 0.8: Added protocol support.

syntax

(record-out id)

Exports the bindings associated with a record id.

procedure

(record-info? v)  boolean?

  v : any/c
Returns #t when v is a value containing runtime information about a record.

1.2 Enumerations🔗ℹ

An enumeration is a tagged union of product types. In Racket, enum variants are represented by individual structs that inherit from a common base. In Swift, they are represented using regular enums.

syntax

(define-enum name
  [variant-name variant-field ...] ...+)
 
name = id
  | (id : protocol-id ...+)
     
variant-field = {field-id : field-type}
 
  field-type : (or/c field-type? enum-info? record-info?)
Defines an enumeration called name with the given set of variants. Enumeration names must be unique across all modules. In Swift, the variant-names and field-ids are converted to camel case.

Examples:
> (define-enum Result
    [ok]
    [err {message : String}])
> (Result? (Result.ok))

#t

> (Result? (Result.err "example"))

#t

> Result

#<enum-info>

Changed in version 0.8 of package noise-serde-lib: Added protocol support.

syntax

(enum-out id)

Exports the bindings associated with an enum id.

procedure

(enum-info? v)  boolean?

  v : any/c
Returns #t when v is a value containing runtime information about an enumeration.

1.3 Field Types🔗ℹ

Field types control how individual values are serialized and deserialized.

procedure

(field-type? v)  boolean?

  v : any/c
Returns #t when v is a field type.

Field types for primitive values.

The Varint and UVarint field types serialize signed and unsigned integer values, respectively, using a variable-length encoding. In Swift, these values are represented by Int64 and UInt64.

syntax

(Delay t-expr)

 
  t-expr : (or/c field-type? enum-info? record-info?)
A constructor for a field type that delays the execution of t-expr until one of its methods is called. Use this to implement mutually-recursive data types.

A constructor for field types that represent hash tables composed of k keys and v values. In Swift, these values are represented by Dictionary values parameterized over the Swift representations of the k and v types, respectively.

When k is an enum or a record type, the enum or record must be extended to implement the Hashable protocol in Swift.

procedure

(Listof t)  field-type?

  t : (or/c field-type? enum-info? record-info?)
A constructor for field types that represent lists composed of field-type values. In Swift, these values are represented by arrays of the subtype.

procedure

(Optional t)  field-type?

  t : (or/c field-type? enum-info? record-info?)
A constructor for optional field types.

procedure

(StringConvertible string-> ->string)  field-type?

  string-> : (-> string? any/c)
  ->string : (-> any/c string?)
Like String, but converts the value to a string using ->string before sending it to a client and converts strings to values using string-> when receiving data from a client.

2 Backends🔗ℹ

 (require noise/backend) package: noise-serde-lib

The noise/backend module has an internal handler registry that is used to map remote procedure call ids to handler procedures.

syntax

(define-rpc (id arg ... maybe-response-type)
  body ...+)
 
arg = [arg-label arg-id : arg-type-expr]
     
maybe-response-type = 
  | : response-type-expr
 
  arg-type-expr : (or/c field-type? enum-info? record-info?)
  response-type-expr : (or/c field-type? enum-info? record-info?)
Defines a procedure named id and registers an RPC handler for it in the handler registry. RPC ids must be unique across all modules. The procedure is automatically provided in a submodule of the enclosing module named rpc.

The noise-serde-codegen command automatically generates Swift code to handle calling these procedures. In Swift, the RPC id, arg-labels and arg-ids are converted to camel case. The arg-labels have no meaning in Racket.

Examples:
> (define-rpc (do-nothing)
    (void))
> (do-nothing)
> (define-rpc (get-human-name [of h : Human] : String)
    (Human-name h))
> (get-human-name (make-Human #:name "Bogdan" #:age 30))

"Bogdan"

syntax

(define-callout (id arg ...))

 
arg = [arg-id : arg-type-expr]
 
  arg-type-expr : (or/c field-type? enum-info? record-info?)
Defines a foreign procedure named id. Callout ids must be unique across all modules.

The noise-serde-codegen command automatically generates Swift code to handle installing Swift procedures for each callout.

Example:
> (define-callout (hello-cb [h : Human]))

In Racket, the above example binds a procedure named hello that may be called with a Human value in order to execute a Swift procedure. In Swift, the generated backend will contain a procedure with the following signature:

installCallback(helloCb: @escaping (Human) -> Void) -> Future<String, Void>

That procedure can be used to install a callback from the Swift side. Executing a callout on the Racket side before it’s been installed from the Swift side raises an exception.

Currently, the buffer used to decode data sent back to Swift via a callout is limited to 8KiB, so avoid sending large payloads. If necessary, have the Swift side call a regular RPC when it receives a callout instead.

procedure

(serve in-fd out-fd)  (-> void?)

  in-fd : exact-integer?
  out-fd : exact-integer?
Converts the file descriptors represented by in-fd and out-fd to an input port and an output port, respectively, then spawns a thread that reads requests from the input port in the form of records. Request handlers are defined using define-rpc. Handlers are run in their own threads and multiple requests may be handled concurrently.

Returns a procedure that stops the server when applied.

procedure

(get-rpc-checksum)  (-> string?)

Returns a SHA1 checksum in hex format representing the hashed names of all defined RPCs at the moment the procedure is called. No new RPCs may be defined after this procedure is called.

You can use this procedure to ensure that a generated backend file matches the compiled application.

Example:
> (get-rpc-checksum)

"a68de490719a92d38e7bab29d7418518030c055c"

Added in version 0.12 of package noise-serde-lib.

3 Callouts🔗ℹ

 (require noise/unsafe/callout) package: noise-serde-lib

The noise/unsafe/callout module provides a facility for converting function pointer addresses to callable Racket procedures via the FFI. A callout box is a two-element struct containing a C function type and an optional Racket procedure of that type. Callout boxes start out empty and must be filled via a call to callout-box-install!. Callout boxes are themselves callable once filled.

Warning: these operations are inherently unsafe and you must take care that the function pointers installed in a box are kept immobile during the lifetime of that box.

procedure

(callout-box? v)  boolean?

  v : any/c
Returns #t when v is a callout box.

procedure

(make-callout-box fun-type)  callout-box?

  fun-type : ctype?
Returns a callout box with the given function type.

procedure

(callout-box-install! b p)  void?

  b : callout-box?
  p : exact-integer?
Installs the function pointer located at address p in b.