Framing and JSON, pure.
The GIMP plugin's wire protocol is asymmetric, and both directions are captured here so that neither is buried behind an I/O call where no generator can reach it.
REQUEST one JSON object followed by a newline. The plugin accumulates until the buffer parses, so the newline is a courtesy rather than a delimiter, but the reference client sends it and the plugin's own logging assumes it.
RESPONSE one JSON object and NOTHING else. No length prefix, no
terminator, no newline. parses as JSON IS the frame boundary,
which is why complete-frame? exists as a named, tested predicate
instead of a try/catch buried in a read loop.
One deliberate strengthening over the reference clients: a frame is complete only when the buffer parses as a JSON OBJECT. Both Python clients accept any parse, so a response truncated at a point where the prefix happens to be valid JSON on its own gets treated as a whole message and the rest of the real answer is read as the head of the NEXT one. Requiring an object costs nothing (every plugin answer is an object) and removes the failure mode.
Framing and JSON, pure.
The GIMP plugin's wire protocol is asymmetric, and both directions are
captured here so that neither is buried behind an I/O call where no
generator can reach it.
REQUEST one JSON object followed by a newline. The plugin accumulates
until the buffer parses, so the newline is a courtesy rather than
a delimiter, but the reference client sends it and the plugin's
own logging assumes it.
RESPONSE one JSON object and NOTHING else. No length prefix, no
terminator, no newline. `parses as JSON` IS the frame boundary,
which is why `complete-frame?` exists as a named, tested predicate
instead of a try/catch buried in a read loop.
One deliberate strengthening over the reference clients: a frame is complete
only when the buffer parses as a JSON OBJECT. Both Python clients accept any
parse, so a response truncated at a point where the prefix happens to be
valid JSON on its own gets treated as a whole message and the rest of the
real answer is read as the head of the NEXT one. Requiring an object costs
nothing (every plugin answer is an object) and removes the failure mode.(complete-frame? buffer)True when buffer holds a whole response object.
This is the read loop's only stopping condition, so it carries the whole
framing contract. nil and the empty string are never complete.
True when `buffer` holds a whole response object. This is the read loop's only stopping condition, so it carries the whole framing contract. `nil` and the empty string are never complete.
(decode buffer)One response frame to a RawResponse.
Three distinct failures, kept distinct because they call for different actions: unparseable means the frame was truncated or the socket desynced, not-an-object means something other than the plugin answered, and malformed means the plugin answered in a shape this contract does not know.
One response frame to a `RawResponse`. Three distinct failures, kept distinct because they call for different actions: unparseable means the frame was truncated or the socket desynced, not-an-object means something other than the plugin answered, and malformed means the plugin answered in a shape this contract does not know.
(encode command)GimpCommand to one complete request frame.
Params are written verbatim, nulls included. The reference client sends
"layer_name": null for an omitted optional and the plugin reads it back
with .get, so an encoder that helpfully dropped nil keys would be sending
a different message than every other client of this plugin.
`GimpCommand` to one complete request frame. Params are written verbatim, nulls included. The reference client sends `"layer_name": null` for an omitted optional and the plugin reads it back with `.get`, so an encoder that helpfully dropped nil keys would be sending a different message than every other client of this plugin.
Appended to every request frame. See the ns docstring: a courtesy, not a delimiter.
Appended to every request frame. See the ns docstring: a courtesy, not a delimiter.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |