mirror of
https://github.com/gvx/bitser.git
synced 2024-11-24 06:54:20 +00:00
Add initial version of the extension API
This commit is contained in:
parent
1d3ebe04cf
commit
1fc6c6171c
29
EXTENSION_API.md
Normal file
29
EXTENSION_API.md
Normal file
@ -0,0 +1,29 @@
|
||||
This document lays out the API provided for extension authors by bitser.
|
||||
|
||||
An extension object is a table that contains at least the following keys:
|
||||
|
||||
* `"bitser-type"`: a string, like `"userdata"`. The extension will only be
|
||||
used to serialize a value `v` if `type(v)` equals the value of this key.
|
||||
A value of this type is called a "potential match" in the rest of this
|
||||
document. Note that this type does not need to be natively supported by
|
||||
bitser.
|
||||
* `"bitser-match"`: a function that takes a single argument and returns a
|
||||
boolean. The extension will only be used if this function returns `true`.
|
||||
It does not need to check `type(v)`, as it will only be called for potential
|
||||
matches.
|
||||
* `"bitser-dump"`: a function that takes a single potential match as argument
|
||||
and returns a single value that bitser is able to serialize (either natively
|
||||
or through an extension).
|
||||
* `"bitser-load"`: a function that takes a single argument that is a
|
||||
deserialized copy of a value previously returned by the dump function, that
|
||||
returns a potential match.
|
||||
|
||||
All other keys will be ignored, but string keys that start with `"bitser-"`
|
||||
are reserved for future versions of this API.
|
||||
|
||||
Extension authors SHOULD NOT call `bitser.registerExtension`. This should be
|
||||
left to extension users, so they may choose an ID that does not conflict with
|
||||
other extensions they may be using.
|
||||
|
||||
The matching function SHOULD be highly performant, as it is called for every
|
||||
potential match that is to be serialized.
|
30
USAGE.md
30
USAGE.md
@ -10,8 +10,10 @@
|
||||
* [`bitser.includeMetatables`](#includeMetatables)
|
||||
* [`bitser.register`](#register)
|
||||
* [`bitser.registerClass`](#registerclass)
|
||||
* [`bitser.registerExtension`](#registerextension)
|
||||
* [`bitser.unregister`](#unregister)
|
||||
* [`bitser.unregisterClass`](#unregisterclass)
|
||||
* [`bitser.unregisterExtension`](#unregisterextension)
|
||||
* [`bitser.reserveBuffer`](#reservebuffer)
|
||||
* [`bitser.clearBuffer`](#clearbuffer)
|
||||
|
||||
@ -202,6 +204,24 @@ Returns the registered class as a convenience.
|
||||
|
||||
See also: [`bitser.unregisterClass`](#unregisterclass).
|
||||
|
||||
|
||||
## registerExtension
|
||||
|
||||
```lua
|
||||
bitser.registerExtension(extension_id, extension)
|
||||
```
|
||||
|
||||
Registers the extension `extension` and give it the identifier `extension_id`. This is a way to allow 3rd party libraries to
|
||||
extend the functionality of bitser, for example to be able to serialize exotic data, or to allow certain optimisations.
|
||||
|
||||
To implement your own bitser extensions, see
|
||||
[EXTENSION_API.md](EXTENSION_API.md).
|
||||
|
||||
The name `extension_id` must not conflict with that of other extensions you're
|
||||
using. Strings are recommended, but other primitive types will work.
|
||||
|
||||
See also: [`bitser.unregisterExtension`](#unregisterextension).
|
||||
|
||||
## unregister
|
||||
|
||||
```lua
|
||||
@ -223,6 +243,16 @@ which is useful in a context where you don't have a reference to the class you w
|
||||
|
||||
See also: [`bitser.registerClass`](#registerclass).
|
||||
|
||||
## unregisterExtension
|
||||
|
||||
```lua
|
||||
bitser.unregister(extension_id)
|
||||
```
|
||||
|
||||
Deregisters the previously registered extension with the id `extension_id`.
|
||||
|
||||
See also: [`bitser.registerExtension`](#registerextension).
|
||||
|
||||
## reserveBuffer
|
||||
|
||||
```lua
|
||||
|
33
bitser.lua
33
bitser.lua
@ -143,6 +143,12 @@ local class_registry = {}
|
||||
local class_name_registry = {}
|
||||
local classkey_registry = {}
|
||||
local class_deserialize_registry = {}
|
||||
local extension_registry = {}
|
||||
local extensions_by_type = {}
|
||||
local EXTENSION_TYPE_KEY = 'bitser-type'
|
||||
local EXTENSION_MATCH_KEY = 'bitser-match'
|
||||
local EXTENSION_LOAD_KEY = 'bitser-load'
|
||||
local EXTENSION_DUMP_KEY = 'bitser-dump'
|
||||
|
||||
local serialize_value
|
||||
|
||||
@ -276,6 +282,17 @@ serialize_value = function(value, seen)
|
||||
end
|
||||
return
|
||||
end
|
||||
if not disable_extensions and extensions_by_type[t] then
|
||||
for extension_id, extension in pairs(extensions_by_type[t]) do
|
||||
if extension[EXTENSION_MATCH_KEY](value) then
|
||||
-- extension
|
||||
Buffer_write_byte(254)
|
||||
serialize_value(extension_id, seen)
|
||||
serialize_value(extension[EXTENSION_DUMP_KEY](value), seen)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
(types[t] or
|
||||
error("cannot serialize type " .. t)
|
||||
)(value, seen)
|
||||
@ -388,6 +405,10 @@ local function deserialize_value(seen)
|
||||
local read_into = ffi.typeof('$[1]', ctype)()
|
||||
Buffer_read_raw(read_into, len)
|
||||
return ctype(read_into[0])
|
||||
elseif t == 254 then
|
||||
--extension
|
||||
local extension_id = deserialize_value(seen)
|
||||
return extension_registry[extension_id][EXTENSION_LOAD_KEY](deserialize_value(seen))
|
||||
else
|
||||
error("unsupported serialized type " .. t)
|
||||
end
|
||||
@ -493,4 +514,16 @@ end, unregisterClass = function(name)
|
||||
classkey_registry[name] = nil
|
||||
class_deserialize_registry[name] = nil
|
||||
class_registry[name] = nil
|
||||
end, registerExtension = function(extension_id, extension)
|
||||
assert(not extension_registry[extension_id], 'extension with id ' .. extension_id .. ' already registered')
|
||||
local ty = extension[EXTENSION_TYPE_KEY]
|
||||
assert(type(ty) == 'string' and type(extension[EXTENSION_MATCH_KEY]) == 'function' and type(extension[EXTENSION_LOAD_KEY]) == 'function' and type(extension[EXTENSION_DUMP_KEY]) == 'function', 'not a valid extension')
|
||||
extension_registry[extension_id] = extension
|
||||
if not extensions_by_type[ty] then
|
||||
extensions_by_type[ty] = {}
|
||||
end
|
||||
extensions_by_type[ty][extension_id] = extension
|
||||
end, unregisterExtension = function(extension_id)
|
||||
extensions_by_type[extension_registry[extension_id][EXTENSION_TYPE_KEY]][extension_id] = nil
|
||||
extension_registry[extension_id] = nil
|
||||
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
|
||||
|
@ -274,7 +274,6 @@ describe("bitser", function()
|
||||
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
|
||||
end)
|
||||
it("cannot deserialize values from unassigned type bytes", function()
|
||||
assert.has_error(function() bitser.loads("\254") end, "unsupported serialized type 254")
|
||||
assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
|
||||
end)
|
||||
it("can load from raw data", function()
|
||||
@ -348,4 +347,20 @@ describe("bitser", function()
|
||||
assert.is_nil(serdeser(t).bar)
|
||||
bitser.includeMetatables(true) -- revert back to default for potential other tests
|
||||
end)
|
||||
it("provides a simple extension mechanism", function()
|
||||
local MATCH_CALLS = 0
|
||||
local LOAD_CALLS = 0
|
||||
local DUMP_CALLS = 0
|
||||
bitser.registerExtension('test', {
|
||||
['bitser-type'] = 'number',
|
||||
['bitser-match'] = function(value) MATCH_CALLS = MATCH_CALLS + 1; return value > 0 end,
|
||||
['bitser-load'] = function(value) LOAD_CALLS = LOAD_CALLS + 1; return tonumber(value) end,
|
||||
['bitser-dump'] = function(value) DUMP_CALLS = DUMP_CALLS + 1; return tostring(value) end })
|
||||
local t = {1.0, -1.0, 0., -1/0, 'strings should not match'}
|
||||
test_serdeser(t)
|
||||
assert.are.same(4, MATCH_CALLS)
|
||||
assert.are.same(1, LOAD_CALLS)
|
||||
assert.are.same(1, DUMP_CALLS)
|
||||
bitser.unregisterExtension('test')
|
||||
end)
|
||||
end)
|
||||
|
Loading…
Reference in New Issue
Block a user