mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-28 18:30:06 +00:00
encoding/cbor: fully support marshal/unmarshal of unions
This commit is contained in:
@@ -506,8 +506,32 @@ marshal_into_encoder :: proc(e: Encoder, v: any) -> (err: Marshal_Error) {
|
||||
if v.data == nil || tag <= 0 {
|
||||
return _encode_nil(e.writer)
|
||||
}
|
||||
|
||||
id := info.variants[tag-1].id
|
||||
return marshal_into(e, any{v.data, id})
|
||||
if len(info.variants) == 1 {
|
||||
id := info.variants[tag-1].id
|
||||
return marshal_into(e, any{v.data, id})
|
||||
}
|
||||
|
||||
// Encode a non-nil multi-variant union as the `TAG_OBJECT_TYPE`.
|
||||
// Which is a tag of an array, where the first element is the textual id/type of the object
|
||||
// that follows it.
|
||||
|
||||
err_conv(_encode_u16(e, TAG_OBJECT_TYPE, .Tag)) or_return
|
||||
_encode_u8(e.writer, 2, .Array) or_return
|
||||
|
||||
vti := reflect.union_variant_type_info(v)
|
||||
#partial switch vt in vti.variant {
|
||||
case reflect.Type_Info_Named:
|
||||
err_conv(_encode_text(e, vt.name)) or_return
|
||||
case:
|
||||
builder := strings.builder_make(context.temp_allocator) or_return
|
||||
defer strings.builder_destroy(&builder)
|
||||
reflect.write_type(&builder, vti)
|
||||
err_conv(_encode_text(e, strings.to_string(builder))) or_return
|
||||
}
|
||||
|
||||
return marshal_into(e, any{v.data, vti.id})
|
||||
|
||||
case runtime.Type_Info_Enum:
|
||||
return marshal_into(e, any{v.data, info.base.id})
|
||||
|
||||
@@ -38,6 +38,15 @@ TAG_BASE64_ID :: "base64"
|
||||
// given content is definitely CBOR.
|
||||
TAG_SELF_DESCRIBED_CBOR :: 55799
|
||||
|
||||
// A tag that is used to assign a textual type to the object following it.
|
||||
// The tag's value must be an array of 2 items, where the first is text (describing the following type)
|
||||
// and the second is any valid CBOR value.
|
||||
//
|
||||
// See the registration: https://datatracker.ietf.org/doc/draft-rundgren-cotx/05/
|
||||
//
|
||||
// We use this in Odin to marshal and unmarshal unions.
|
||||
TAG_OBJECT_TYPE :: 1010
|
||||
|
||||
// A tag implementation that handles marshals and unmarshals for the tag it is registered on.
|
||||
Tag_Implementation :: struct {
|
||||
data: rawptr,
|
||||
|
||||
@@ -8,9 +8,6 @@ import "core:runtime"
|
||||
import "core:strings"
|
||||
import "core:unicode/utf8"
|
||||
|
||||
// `strings` is only used in poly procs, but -vet thinks it is fully unused.
|
||||
_ :: strings
|
||||
|
||||
/*
|
||||
Unmarshals the given CBOR into the given pointer using reflection.
|
||||
Types that require allocation are allocated using the given allocator.
|
||||
@@ -79,7 +76,7 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E
|
||||
dst = err_conv(decode(r, hdr)) or_return
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
switch hdr {
|
||||
case .U8:
|
||||
decoded := _decode_u8(r) or_return
|
||||
@@ -275,10 +272,12 @@ _unmarshal_value :: proc(r: io.Reader, v: any, hdr: Header) -> (err: Unmarshal_E
|
||||
}
|
||||
|
||||
nr := err_conv(_decode_tag_nr(r, add)) or_return
|
||||
|
||||
|
||||
// Custom tag implementations.
|
||||
if impl, ok := _tag_implementations_nr[nr]; ok {
|
||||
return impl->unmarshal(r, nr, v)
|
||||
} else if nr == TAG_OBJECT_TYPE {
|
||||
return _unmarshal_union(r, v, ti, hdr)
|
||||
} else {
|
||||
// Discard the tag info and unmarshal as its value.
|
||||
return _unmarshal_value(r, v, _decode_header(r) or_return)
|
||||
@@ -717,6 +716,73 @@ _unmarshal_map :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header
|
||||
}
|
||||
}
|
||||
|
||||
// Unmarshal into a union, based on the `TAG_OBJECT_TYPE` tag of the spec, it denotes a tag which
|
||||
// contains an array of exactly two elements, the first is a textual representation of the following
|
||||
// CBOR value's type.
|
||||
_unmarshal_union :: proc(r: io.Reader, v: any, ti: ^reflect.Type_Info, hdr: Header) -> (err: Unmarshal_Error) {
|
||||
#partial switch t in ti.variant {
|
||||
case reflect.Type_Info_Union:
|
||||
idhdr: Header
|
||||
target_name: string
|
||||
{
|
||||
vhdr := _decode_header(r) or_return
|
||||
vmaj, vadd := _header_split(vhdr)
|
||||
if vmaj != .Array {
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
n_items, unknown := err_conv(_decode_container_length(r, vadd)) or_return
|
||||
if unknown || n_items != 2 {
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
idhdr = _decode_header(r) or_return
|
||||
idmaj, idadd := _header_split(idhdr)
|
||||
if idmaj != .Text {
|
||||
return .Bad_Tag_Value
|
||||
}
|
||||
|
||||
context.allocator = context.temp_allocator
|
||||
target_name = err_conv(_decode_text(r, idadd)) or_return
|
||||
}
|
||||
defer delete(target_name, context.temp_allocator)
|
||||
|
||||
for variant, i in t.variants {
|
||||
tag := i64(i)
|
||||
if !t.no_nil {
|
||||
tag += 1
|
||||
}
|
||||
|
||||
#partial switch vti in variant.variant {
|
||||
case reflect.Type_Info_Named:
|
||||
if vti.name == target_name {
|
||||
reflect.set_union_variant_raw_tag(v, tag)
|
||||
return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return)
|
||||
}
|
||||
|
||||
case:
|
||||
builder := strings.builder_make(context.temp_allocator)
|
||||
defer strings.builder_destroy(&builder)
|
||||
|
||||
reflect.write_type(&builder, variant)
|
||||
variant_name := strings.to_string(builder)
|
||||
|
||||
if variant_name == target_name {
|
||||
reflect.set_union_variant_raw_tag(v, tag)
|
||||
return _unmarshal_value(r, any{v.data, variant.id}, _decode_header(r) or_return)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No variant matched.
|
||||
return _unsupported(v, idhdr)
|
||||
|
||||
case:
|
||||
// Not a union.
|
||||
return _unsupported(v, hdr)
|
||||
}
|
||||
}
|
||||
|
||||
_assign_int :: proc(val: any, i: $T) -> bool {
|
||||
v := reflect.any_core(val)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user