nupp.serde.adapters

These builders give a model library that owns its schema adapters over its own nupp.serde.Member type. See Serialization bindings for how model bindings fit beside declaration bindings.

A declaration binding from nupp.serde.binding covers ordinary records. A library that generates types from Smithy, OpenAPI, or a binary schema needs its own member identities instead, so its codec policy can read shape identifiers, XML namespaces, or field numbers off them.

local serde = require("nupp.serde")
local adapters = require("nupp.serde.adapters")
local json = require("nupp.serde.json")
local plain = json.codec(json.memberNamePolicy)

-- The model's own member type. A real library would carry its schema here.
local record Member is serde.Member
    @readonly name: string
    @readonly index: integer?
end

local record Book
    title: string
    tags: {string}
end

-- Reading builds a Book through an exclusive draft that is closed either way.
local record Draft is nupp.Closeable
    title: string?
    tags: {string}?
    function close(takes self): nil
    end
end
local record Construction is adapters.Construction<Book, Draft>
    function begin(self): Draft
        return new Draft()
    end
    function finish(self, takes draft: Draft): Book
        local owned: Draft = draft
        local book = new Book(title = assert(owned.title, "title is required"), tags = owned.tags or {})
        owned:close()
        return book
    end
end

local title = new Member(name = "title")
local tags = new Member(name = "tags")
local book = adapters.structure(new Member(name = "Book"), {
    adapters.field<Book, Draft, string, Member>(
        title,
        function(borrows value: Book): string borrows (value)
            return value.title
        end,
        function(exclusive draft: Draft, value: string): nil
            draft.title = value
        end,
        adapters.text(title),
        true,
        false
    ),
    adapters.field<Book, Draft, {string}, Member>(
        tags,
        function(borrows value: Book): {string} borrows (value)
            return value.tags
        end,
        function(exclusive draft: Draft, value: {string}): nil
            draft.tags = value
        end,
        adapters.list(tags, new Member(name = "tag"), adapters.text(new Member(name = "tag"))),
        false,
        true
    )
}, new Construction())
local books: serde.Binding<Book> = new serde.Bound<Book, Member>(adapter = book)

local bytes = plain:encode(books, new Book(title = "Dune", tags = {"sf"}))
assert(bytes == '{"title":"Dune","tags":["sf"]}')
assert(plain:decode(books, '{"title":"Emma"}').title == "Emma")

Every builder takes the member the value sits at, so the same scalar adapter type serves a top-level value, a structure field, and a list element. Wrap the finished adapter in nupp.serde.Bound to pass it to a codec.

Module contents

Types

TypeKindDescription
BorrowGettypeReads a field by borrowing it from the value.
ConstructioninterfaceHow a structure opens and finishes the draft its fields are read into.
ConversioninterfaceThe projection convert applies: project on write, restore on read.
CopyGettypeReads a field by copying it out of the value.
FieldinterfaceA field built by field, with the operations a codec needs to plan it.
FieldConsumerinterfaceVisits one field of a structure with its value type recovered.
GettypeA field reader, borrowed or copied.
HastypeDecides whether a field is written for a value.
IndexedValuerecordOne record of a Layout, read and written through its slots.
LayoutinterfaceA record whose fields are defined at runtime, for a model that loads its schema rather than generating Nupp types...
SettypeStores a decoded field value into a structure's draft.
SlotrecordA typed field of a Layout.
StructureConsumerinterfaceVisits a structure adapter's fields and construction.

Functions

FunctionKindDescription
atomfunctionA model-defined nupp.serde.Atom, passed through uninterpreted.
bigIntegerfunctionAn integer of unbounded size, carried as its decimal text.
booleanfunctionA boolean.
borrowedGetfunctionWraps a borrowing field reader.
bytesfunctionBinary data.
convertfunctionConverts between an application value and one an adapter already handles.
copiedGetfunctionWraps a copying field reader, for a field computed rather than stored.
decimalfunctionAn exact decimal, as a nupp.serde.Decimal.
documentfunctionA document-valued member: a value with no schema of its own, read and written through a document adapter such as...
fieldfunctionOne field of a structure.
floatfunctionA floating-point number written at a fixed width.
inspectfunctionOpens a structure adapter to a consumer, for a codec that plans its own field layout, such as one that assigns...
integerfunctionAn integer of a fixed width and signedness, as a nupp.serde.Integer.
layoutfunctionStarts an empty Layout.
listfunctionA list of values that share one element adapter.
structurefunctionA structure: named fields written in order, read in any order, and constructed through an exclusive draft.
textfunctionA UTF-8 string.
timestampfunctionAn instant, as a nupp.serde.Timestamp.

Types#

BorrowGettype#

type BorrowGet<T, V is nupp.Copyable> = function(borrows value: T): V borrows (value)

Reads a field by borrowing it from the value.

Type parameters

NameDescription
T
V

Constructioninterface#

interface Construction<T, S is nupp.Closeable> ...

How a structure opens and finishes the draft its fields are read into.

The draft is exclusive and Closeable, so a failed read drops it rather than leaking a half-built value.

Type parameters

NameDescription
T
S

Members

NameKindDescription
beginmethodOpens an empty draft for one read.
finishmethodBuilds the value from a draft the fields have been read into.

beginmethod#

begin: function(self): S

Opens an empty draft for one read.

Arguments
NameTypeDescription
?self
Returns
TypeDescription
S

the draft the fields' setters write into

finishmethod#

finish: function(self, takes state: S): T

Builds the value from a draft the fields have been read into.

The structure adapter does not check required or hasDefault itself, so finish is where a field that never arrived is reported or given its default. The draft is transferred to finish, which drops it.

Arguments
NameTypeDescription
?self
takes stateS

the draft, after every field present in the input was set

Returns
TypeDescription
T

the value

Conversioninterface#

interface Conversion<T, V is nupp.Copyable> ...

The projection convert applies: project on write, restore on read.

Type parameters

NameDescription
T
V

Members

NameKindDescription
projectmethodReturns the value the base adapter writes in place of an application value.
restoremethodBuilds the application value from one the base adapter read.

projectmethod#

project: function(self, borrows value: T): V

Returns the value the base adapter writes in place of an application value.

Arguments
NameTypeDescription
?self
borrows valueT

the application value being written

Returns
TypeDescription
V

the projected value

restoremethod#

restore: function(self, value: V): T

Builds the application value from one the base adapter read.

Arguments
NameTypeDescription
?self
valueV

the value the base adapter read

Returns
TypeDescription
T

the application value

CopyGettype#

type CopyGet<T, V is nupp.Copyable> = function(borrows value: T): V

Reads a field by copying it out of the value.

Type parameters

NameDescription
T
V

Fieldinterface#

interface Field<T, S is nupp.Closeable, M is core.Member> is core.Child<M> ...

A field built by field, with the operations a codec needs to plan it.

Type parameters

NameDescription
T
S
M

Members

NameKindDescription
writemethodWrites the field of a value, or nothing when the value leaves it out.
readmethodReads the field's value and stores it into the draft.
acceptmethodHands the field's parts to a consumer with the field's value type recovered.

writemethod#

write: function(self, borrows source: T, exclusive writer: core.Writer<M>): nil

Writes the field of a value, or nothing when the value leaves it out.

The structure adapter asks writer:include first and calls this only for a member the writer includes.

Arguments
NameTypeDescription
?self
borrows sourceT

the value the field is read from

exclusive writercore.Writer<M>

the writer the field is written to

Returns
TypeDescription
nil

readmethod#

read: function(self, exclusive state: S, exclusive reader: core.Reader<M>): nil

Reads the field's value and stores it into the draft.

Arguments
NameTypeDescription
?self
exclusive stateS

the draft being constructed

exclusive readercore.Reader<M>

the reader positioned at the field's value

Returns
TypeDescription
nil

acceptmethod#

accept: function<R>(self, consumer: FieldConsumer<T, S, M, R>): R

Hands the field's parts to a consumer with the field's value type recovered.

Arguments
NameTypeDescription
?self
consumerFieldConsumer<T, S, M, R>

what to apply to the field

Returns
TypeDescription
R

the consumer's result

FieldConsumerinterface#

interface FieldConsumer<T, S is nupp.Closeable, M is core.Member, R> ...

Visits one field of a structure with its value type recovered.

Type parameters

NameDescription
T
S
M
R

Members

NameKindDescription
applymethodReceives one field's parts with its value type V in scope.

applymethod#

apply: function<V is nupp.Copyable>(
    self,
    member: M,
    has: Has<T>,
    get: Get<T, V>,
    set: Set<S, V>,
    adapter: core.Adapter<V, M>,
    fallback: (function(): V)?
): R

Receives one field's parts with its value type V in scope.

Arguments
NameTypeDescription
?self
memberM

the member the field sits at

hasHas<T>

decides whether a value writes the field; always true when the field was built without present

getGet<T, V>

reads the field from a value

setSet<S, V>

stores a decoded value into the draft

adaptercore.Adapter<V, M>

the field value's adapter

fallback(function(): V)?

supplies the field's default, or nil when the field carries none of its own

Returns
TypeDescription
R

the consumer's result

Gettype#

type Get<T, V is nupp.Copyable> = BorrowedGet<T, V> | CopiedGet<T, V>

A field reader, borrowed or copied.

Type parameters

NameDescription
T
V

Hastype#

type Has<T> = function(borrows value: T): boolean

Decides whether a field is written for a value.

Type parameters

NameDescription
T

IndexedValuerecord#

record IndexedValue ...

One record of a Layout, read and written through its slots.

Members

NameKindDescription
ownerfield
valuesfield
presentfield
hasmethodReturns whether a slot holds a value, explicit null included.
getmethodReturns the value a slot holds.
setmethodStores a value under a slot and marks it present.
removemethodClears a slot, making it absent.

ownerfield#

owner: table
@private@readonly

valuesfield#

values: {[integer]: unknown}
@private@readonly

presentfield#

present: {[integer]: boolean}
@private@readonly

hasmethod#

has: function has<V is nupp.Copyable>(self, slot: Slot<V>): boolean

Returns whether a slot holds a value, explicit null included.

A slot whose adapter reads null holds nil and still counts as present, so has tells an explicit null from an absent field where get does not.

Arguments
NameTypeDescription
selfany
slotSlot<V>

the slot to test

Returns
TypeDescription
boolean

whether the slot is present

Raises
TypeCondition
string

when the slot belongs to another layout

getmethod#

get: function get<V is nupp.Copyable>(self, slot: Slot<V>): V?

Returns the value a slot holds.

A default is not applied here. Decoding stores it when the field is absent, but a value made by the layout's value holds nothing until it is set.

Arguments
NameTypeDescription
selfany
slotSlot<V>

the slot to read

Returns
TypeDescription
V?

the value, or nil when the slot is absent or explicitly null

Raises
TypeCondition
string

when the slot belongs to another layout

setmethod#

set: function set<V is nupp.Copyable>(self, slot: Slot<V>, value: V): nil

Stores a value under a slot and marks it present.

Arguments
NameTypeDescription
selfany
slotSlot<V>

the slot to write

valueV

the value, which is nil for an explicit null

Returns
TypeDescription
nil
Raises
TypeCondition
string

when the slot belongs to another layout

removemethod#

remove: function remove<V is nupp.Copyable>(self, slot: Slot<V>): nil

Clears a slot, making it absent.

Arguments
NameTypeDescription
selfany
slotSlot<V>

the slot to clear

Returns
TypeDescription
nil
Raises
TypeCondition
string

when the slot belongs to another layout

Layoutinterface#

interface Layout<M is core.Member> ...

A record whose fields are defined at runtime, for a model that loads its schema rather than generating Nupp types from it.

Each field is a typed Slot the layout hands out. A value keeps absent and explicitly null fields apart, so a document-shaped protocol can round trip both.

local serde = require("nupp.serde")
local adapters = require("nupp.serde.adapters")
local json = require("nupp.serde.json")
local plain = json.codec(json.memberNamePolicy)
local record Member is serde.Member
    @readonly name: string
    @readonly index: integer?
end
local title = new Member(name = "title")
local pages = new Member(name = "pages")
local layout = adapters.layout(new Member(name = "Book"))
local titleSlot = layout:add(title, adapters.text(title), true)
local pagesSlot = layout:addDefault(pages, adapters.float(pages, 64), function(): number return 0 end)
local books = layout:binding()

local book = plain:decode(books, '{"title":"Dune"}')
assert(book:get(titleSlot) == "Dune" and book:get(pagesSlot) == 0)
book:set(pagesSlot, 412)
assert(plain:encode(books, book) == '{"title":"Dune","pages":412}')

Type parameters

NameDescription
M

Members

NameKindDescription
addmethodAdds a field with no default.
addDefaultmethodAdds a field that falls back to a default when absent.
valuemethodMakes an empty value of this layout, with every field absent.
bindingmethodReturns the binding that reads and writes this layout's values.

addmethod#

add: function<V is nupp.Copyable>(self, member: M, adapter: core.Adapter<V, M>, required: boolean): Slot<V>

Adds a field with no default.

A required field that is absent fails both directions: decoding raises once the input is read, and encoding raises at the field. An optional field that is absent is left out of the output and stays absent on decode.

Arguments
NameTypeDescription
?self
memberM

the member the field sits at

adaptercore.Adapter<V, M>

the field value's adapter, which must describe member

requiredboolean

whether the field must be present

Returns
TypeDescription
Slot<V>

the field's slot

Raises
TypeCondition
string

when the layout is sealed, the adapter describes another member, or the member is already a field

addDefaultmethod#

addDefault: function<V is nupp.Copyable>(
    self,
    member: M,
    adapter: core.Adapter<V, M>,
    default: function(): V
): Slot<V>

Adds a field that falls back to a default when absent.

Decoding stores the default in a value that lacks the field, so it reads as present. Encoding writes the default for an absent field without storing it.

Arguments
NameTypeDescription
?self
memberM

the member the field sits at

adaptercore.Adapter<V, M>

the field value's adapter, which must describe member

defaultfunction(): V

makes the field's default, called each time one is needed

Returns
TypeDescription
Slot<V>

the field's slot

Raises
TypeCondition
string

when the layout is sealed, the adapter describes another member, or the member is already a field

valuemethod#

value: function(self): Value

Makes an empty value of this layout, with every field absent.

Arguments
NameTypeDescription
?self
Returns
TypeDescription
Value

the value

bindingmethod#

binding: function(self): core.Binding<Value>

Returns the binding that reads and writes this layout's values.

The first call seals the layout, so no field can be added after it, and every call returns the same binding. Fields are written in the order they were added.

Arguments
NameTypeDescription
?self
Returns
TypeDescription
core.Binding<Value>

the binding

Settype#

type Set<S, V is nupp.Copyable> = function(exclusive state: S, value: V): nil

Stores a decoded field value into a structure's draft.

Type parameters

NameDescription
S
V

Slotrecord#

record Slot<V is nupp.Copyable> ...

A typed field of a Layout.

Type parameters

NameDescription
V

Members

NameKindDescription
ownerfield
positionfield
valueTypefield

ownerfield#

owner: table
@private@readonly

positionfield#

position: integer
@private@readonly

valueTypefield#

valueType: (function(value: V): V)?
@private@readonly

StructureConsumerinterface#

interface StructureConsumer<T, M is core.Member, R> ...

Visits a structure adapter's fields and construction. See inspect.

Type parameters

NameDescription
T
M
R

Members

NameKindDescription
applymethodReceives a structure adapter's parts with its draft type S in scope.

applymethod#

apply: function<S is nupp.Closeable>(
    self,
    member: M,
    fields: const{Field<T, S, M>},
    construction: Construction<T, S>,
    unknown: core.UnknownConsumer<S>,
    additional: core.Members<M, T>?
): R

Receives a structure adapter's parts with its draft type S in scope.

Arguments
NameTypeDescription
?self
memberM

the member the structure sits at

fieldsconst{Field<T, S, M>}

the fields, in write order

constructionConstruction<T, S>

how the structure opens and finishes its draft

unknowncore.UnknownConsumer<S>

what the structure does with undeclared wire fields; the rejecting consumer when the adapter was built without one

additionalcore.Members<M, T>?

the extra members written after the fields, or nil

Returns
TypeDescription
R

the consumer's result

Functions#

atomfunction#

function atom<M is core.Member>(member: M): core.Adapter<core.Atom, M>

A model-defined nupp.serde.Atom, passed through uninterpreted.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<core.Atom, M>

bigIntegerfunction#

function bigInteger<M is core.Member>(member: M): core.Adapter<string, M>

An integer of unbounded size, carried as its decimal text.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<string, M>

booleanfunction#

function boolean<M is core.Member>(member: M): core.Adapter<boolean, M>

A boolean.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<boolean, M>

borrowedGetfunction#

function borrowedGet<T, V is nupp.Copyable>(read: BorrowGet<T, V>): Get<T, V>

Wraps a borrowing field reader.

Type parameters

NameDescription
T
V

Arguments

NameTypeDescription
readBorrowGet<T, V>

Returns

TypeDescription
Get<T, V>

bytesfunction#

function bytes<M is core.Member>(member: M): core.Adapter<string, M>

Binary data. JSON writes it as base64.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<string, M>

convertfunction#

function convert<T, V is nupp.Copyable, M is core.Member>(base: core.Adapter<V, M>, conversion: Conversion<T, V>): core.Adapter<T, M>

Converts between an application value and one an adapter already handles.

local serde = require("nupp.serde")
local adapters = require("nupp.serde.adapters")
local json = require("nupp.serde.json")
local plain = json.codec(json.memberNamePolicy)
local record Member is serde.Member
    @readonly name: string
    @readonly index: integer?
end
local record Celsius
    degrees: number
end
local record Degrees is adapters.Conversion<Celsius, number>
    function project(self, borrows value: Celsius): number
        return value.degrees
    end
    function restore(self, value: number): Celsius
        return new Celsius(degrees = value)
    end
end
local member = new Member(name = "temperature")
local temperature: serde.Binding<Celsius> = new serde.Bound<Celsius, Member>(
    adapter = adapters.convert(adapters.float(member, 64), new Degrees())
)
assert(plain:encode(temperature, new Celsius(degrees = 21.5)) == "21.5")

Type parameters

NameDescription
T
V
M

Arguments

NameTypeDescription
basecore.Adapter<V, M>
conversionConversion<T, V>

Returns

TypeDescription
core.Adapter<T, M>

copiedGetfunction#

function copiedGet<T, V is nupp.Copyable>(read: CopyGet<T, V>): Get<T, V>

Wraps a copying field reader, for a field computed rather than stored.

Type parameters

NameDescription
T
V

Arguments

NameTypeDescription
readCopyGet<T, V>

Returns

TypeDescription
Get<T, V>

decimalfunction#

function decimal<M is core.Member>(member: M): core.Adapter<core.Decimal, M>

An exact decimal, as a nupp.serde.Decimal.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<core.Decimal, M>

documentfunction#

function document<D, M is core.Member>(member: M, adapter: core.DocumentAdapter<D>): core.Adapter<D, M>

A document-valued member: a value with no schema of its own, read and written through a document adapter such as nupp.serde.document.adapter.

local serde = require("nupp.serde")
local adapters = require("nupp.serde.adapters")
local document = require("nupp.serde.document")
local json = require("nupp.serde.json")
local plain = json.codec(json.memberNamePolicy)
local record Member is serde.Member
    @readonly name: string
    @readonly index: integer?
end
local payload: serde.Binding<document.Document> = new serde.Bound<document.Document, Member>(
    adapter = adapters.document(new Member(name = "payload"), document.adapter())
)
assert(assert(plain:decode(payload, '{"any":[1,true]}'):get("any")):size() == 2)

Type parameters

NameDescription
D
M

Arguments

NameTypeDescription
memberM
adaptercore.DocumentAdapter<D>

Returns

TypeDescription
core.Adapter<D, M>

fieldfunction#

function field<T, S is nupp.Closeable, V is nupp.Copyable, M is core.Member>(member: M, get: BorrowGet<T, V>, set: Set<S, V>, adapter: core.Adapter<V, M>, required: boolean, hasDefault: boolean, present: Has<T>?): Field<T, S, M>

One field of a structure.

get borrows the field from the value on write and set stores a decoded one into the draft on read. A required field must be present when reading; hasDefault tells codecs the construction supplies one, so the field may be absent. present, when given, decides per value whether the field is written at all.

Type parameters

NameDescription
T
S
V
M

Arguments

NameTypeDescription
memberM
getBorrowGet<T, V>
setSet<S, V>
adaptercore.Adapter<V, M>
requiredboolean
hasDefaultboolean
presentHas<T>?

Returns

TypeDescription
Field<T, S, M>

floatfunction#

function float<M is core.Member>(member: M, width: 32 | 64): core.Adapter<number, M>

A floating-point number written at a fixed width.

A 32-bit float is rounded on write, so a codec that keeps widths, such as nupp.serde.native, restores the value it would have had in a float32.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM
width32 | 64

Returns

TypeDescription
core.Adapter<number, M>

inspectfunction#

function inspect<T, M is core.Member, R>(selected: core.Adapter<T, M>, consumer: Consumer<T, M, R>): R?

Opens a structure adapter to a consumer, for a codec that plans its own field layout, such as one that assigns binary field numbers.

Type parameters

NameDescription
T
M
R

Arguments

NameTypeDescription
selectedcore.Adapter<T, M>
consumerConsumer<T, M, R>

Returns

TypeDescription
R?

integerfunction#

function integer<M is core.Member>(member: M, width: 8 | 16 | 32 | 64, signed: boolean): core.Adapter<core.Integer, M>

An integer of a fixed width and signedness, as a nupp.serde.Integer.

The codec checks the range on both sides, so a 64-bit value round-trips exactly on every target. Use convert to expose it as a plain integer.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM
width8 | 16 | 32 | 64
signedboolean

Returns

TypeDescription
core.Adapter<core.Integer, M>

layoutfunction#

function layout<M is core.Member>(member: M): Layout<M>

Starts an empty Layout.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
Layout<M>

listfunction#

function list<V is nupp.Copyable, M is core.Member>(member: M, element: M, adapter: core.Adapter<V, M>): core.Adapter<{V}, M>

A list of values that share one element adapter.

Type parameters

NameDescription
V
M

Arguments

NameTypeDescription
memberM
elementM
adaptercore.Adapter<V, M>

Returns

TypeDescription
core.Adapter<{V}, M>

structurefunction#

function structure<T, S is nupp.Closeable, M is core.Member>(member: M, fields: const{Field<T, S, M>}, construction: Construction<T, S>, unknown: core.UnknownConsumer<S>?, additional: core.Members<M, T>?, deferredConstruction: boolean?): core.Adapter<T, M>

A structure: named fields written in order, read in any order, and constructed through an exclusive draft.

Reading calls construction:begin(), applies each field's setter to the draft as the codec meets it, then hands the draft to construction:finish(). A wire field the model does not declare is passed to unknown, which rejects it when nil. additional emits extra members after the declared ones, such as preserved unknown fields.

Type parameters

NameDescription
T
S
M

Arguments

NameTypeDescription
memberM
fieldsconst{Field<T, S, M>}
constructionConstruction<T, S>
unknowncore.UnknownConsumer<S>?
additionalcore.Members<M, T>?
deferredConstructionboolean?

Returns

TypeDescription
core.Adapter<T, M>

textfunction#

function text<M is core.Member>(member: M): core.Adapter<string, M>

A UTF-8 string.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<string, M>

timestampfunction#

function timestamp<M is core.Member>(member: M): core.Adapter<core.Timestamp, M>

An instant, as a nupp.serde.Timestamp. The codec policy chooses its text or numeric form.

Type parameters

NameDescription
M

Arguments

NameTypeDescription
memberM

Returns

TypeDescription
core.Adapter<core.Timestamp, M>