Ctrl K
ComunidadGuías…

vlib/json2/decode_sumtype.v

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
module json2

import time

struct SumtypeTimeValue {
	typ   string @[json: '_type'; required]
	value i64    @[required]
}

fn sumtype_variant_name(type_name string) string {
	return type_name.all_after_last('.')
}

// struct_variant_tag returns the `_type` of a struct sum type variant of type `T`: the
// struct's own name, also for an alias of it (`Foo` for `type Alias = Foo`), like the
// removed `json` module wrote it.
fn struct_variant_tag[T]() string {
	return sumtype_variant_name(typeof($zero(T.unaliased_typ)).name)
}

// option_payload_tag returns the `_type` of an option variant of a struct or a time.
fn option_payload_tag[P](_ ?P) string {
	return struct_variant_tag[P]()
}

fn (mut decoder Decoder) decode_selected_sumtype[T](mut value T) ! {
	type_idx := value.type_idx()
	$for variant in T.variants {
		// Keep the narrowing in its own condition for generic variant inference.
		if mut value is variant {
			if type_idx == variant.typ {
				$if variant.typ !is $option && variant.typ is time.Time {
					decoder.decode_sumtype_time(mut value)!
				} $else {
					decoder.decode_value(mut value)!
				}
				return
			}
		}
	}
	decoder.decode_error('could not decode resolved sumtype (should not happen)')!
}

// check_element_type_valid reports whether the value at `value_idx` in values_info
// has the shape of `element`.
fn (mut decoder Decoder) check_element_type_valid[T](element T, value_idx int) bool {
	if !decoder.has_value(value_idx) {
		$if element is $array || element is $map {
			return false
		}
		return true
	}
	value_kind := decoder.values_info[value_idx].value_kind

	$if element is $sumtype { // this will always match the first sumtype array/map
		return true
	}
	$if element is $option {
		// A `none` element is `null`, or `{}` as the removed module wrote it; a set one
		// has the shape of its payload.
		if value_kind == .null || decoder.is_empty_object(value_idx) {
			return true
		}
		return decoder.check_option_element_valid(element, value_idx)
	}

	match value_kind {
		.string {
			$if element is string {
				return true
			} $else $if element is time.Time {
				return true
			} $else $if element is StringDecoder {
				return true
			}
		}
		.number {
			$if element is $float {
				return true
			} $else $if element is $int {
				return true
			} $else $if element is $enum {
				return true
			} $else $if element is NumberDecoder {
				return true
			}
		}
		.boolean {
			$if element is bool {
				return true
			} $else $if element is BooleanDecoder {
				return true
			}
		}
		.null {
			$if element is $option {
				return true
			} $else $if element is NullDecoder {
				return true
			}
		}
		.array {
			$if element is $array {
				return decoder.check_array_type_valid(element, value_idx + 1)
			}
		}
		.object {
			$if element is $map {
				// The first value of the object, after its first key.
				return decoder.check_map_type_valid(element, value_idx + 2)
			} $else $if element is $struct {
				return decoder.check_struct_type_valid(element, value_idx)
			}
		}
	}

	return false
}

fn (mut decoder Decoder) check_option_element_valid[P](_ ?P, value_idx int) bool {
	return decoder.check_element_type_valid(P{}, value_idx)
}

// is_empty_object reports whether the value at `value_idx` is an object without
// members (`{}`).
fn (decoder &Decoder) is_empty_object(value_idx int) bool {
	value_info := decoder.values_info[value_idx]
	if value_info.value_kind != .object {
		return false
	}
	end := value_info.position + value_info.length
	return !decoder.has_value(value_idx + 1) || decoder.values_info[value_idx + 1].position >= end
}

fn get_array_element_type[T](_arr []T) T {
	return T{}
}

fn (mut decoder Decoder) check_array_type_valid[T](arr []T, value_idx int) bool {
	element := get_array_element_type(arr)
	return decoder.check_element_type_valid(element, value_idx)
}

fn get_map_element_type[U, V](_m map[U]V) V {
	return V{}
}

fn (mut decoder Decoder) check_map_type_valid[T](m T, value_idx int) bool {
	element := get_map_element_type(m)
	return decoder.check_element_type_valid(element, value_idx)
}

// get_sumtype_type_field_idx returns the index in values_info of the value of the
// `_type` key of the object at `value_idx`, or no_value_idx without such a key.
@[direct_array_access; markused]
fn (mut decoder Decoder) get_sumtype_type_field_idx(value_idx int) int {
	if !decoder.has_value(value_idx) || decoder.values_info[value_idx].value_kind != .object {
		return no_value_idx
	}
	// Look at the object's own keys only: a nested value (such as a field holding
	// another sum type, which is encoded before the outer `_type`) can have a
	// `_type` of its own.
	object_info := decoder.values_info[value_idx]
	object_end := object_info.position + object_info.length
	values_len := decoder.values_info.len
	mut key_idx := value_idx + 1
	for key_idx < values_len && decoder.values_info[key_idx].position < object_end {
		key_value_idx := key_idx + 1
		if key_value_idx >= values_len {
			break
		}
		// A key spelled with escapes (`"_\u0074ype"`) is `_type` after unescaping.
		if decoder.json_key_matches(decoder.values_info[key_idx], '_type') or { false } {
			return key_value_idx
		}
		// Skip the value, with everything nested in it.
		key_idx = decoder.next_value(key_value_idx)
	}
	return no_value_idx
}

@[markused]
fn (mut decoder Decoder) sumtype_type_field_matches(type_field_idx int, expected string) bool {
	if !decoder.has_value(type_field_idx) {
		return false
	}
	value_info := decoder.values_info[type_field_idx]
	if value_info.value_kind != .string {
		return false
	}
	body := decoder.json[value_info.position + 1..value_info.position + value_info.length - 1]
	if body.index_u8(`\\`) != -1 {
		// An escaped discriminator (`"Hum\u0061n"`) names the variant after unescaping.
		decoded := decoder.decode_string_value(value_info) or { return false }
		return decoded == expected
	}
	return body == expected
}

fn (mut decoder Decoder) check_sumtype_type_valid[T](value T, value_idx int) bool {
	type_field_idx := decoder.get_sumtype_type_field_idx(value_idx)
	// An alias of a struct is tagged with its own name, or with the struct's name.
	return decoder.sumtype_type_field_matches(type_field_idx,
		sumtype_variant_name(typeof(value).name))
		|| decoder.sumtype_type_field_matches(type_field_idx, struct_variant_tag[T]())
}

fn (mut decoder Decoder) check_struct_type_valid[T](s T, value_idx int) bool {
	return decoder.check_sumtype_type_valid(s, value_idx)
}

// resolve_sumtype_from_type_field selects the struct (or time.Time) variant named by
// the object's `_type` field. The name is matched before anything is constructed:
// building a variant runs its field defaults, which may have side effects, so only
// the selected variant may be built.
fn (mut decoder Decoder) resolve_sumtype_from_type_field[T](mut val T) !bool {
	type_field_idx := decoder.get_sumtype_type_field_idx(decoder.current_idx)
	if type_field_idx == no_value_idx {
		return false
	}
	mut has_discriminated_variant := false
	// A variant is tagged with its payload's name, like in the removed `json` module:
	// the struct's name for an alias of a struct (`Foo` for `type Alias = Foo`), and
	// `Time` for a time or an alias of it. The variant's own name is accepted too, and
	// is tried first, so `Foo | Alias` tells both apart.
	for base_names in [false, true] {
		$for v in T.variants {
			$if v.typ is $option {
				// An option of a struct or a time (`?Foo`, `?time.Time`) is tagged with
				// its payload's name.
				option_value := $zero(v.typ)
				if option_payload_fit(option_value, .object) > 0 {
					has_discriminated_variant = true
					name := if base_names {
						option_payload_tag(option_value)
					} else {
						sumtype_variant_name(typeof(v.typ).name.trim_left('?'))
					}
					if decoder.sumtype_type_field_matches(type_field_idx, name) {
						val = T(v)
						return true
					}
				}
			} $else $if v.typ is $struct {
				has_discriminated_variant = true
				name := if base_names {
					sumtype_variant_name(typeof(v.typ.unaliased_typ).name)
				} else {
					sumtype_variant_name(typeof(v.typ).name)
				}
				if decoder.sumtype_type_field_matches(type_field_idx, name) {
					val = T(v)
					return true
				}
			}
		}
	}
	if !has_discriminated_variant {
		return false
	}
	decoder.decode_error('could not resolve sumtype `${T.name}` from "_type" field')!
	return false
}

@[markused]
fn (mut decoder Decoder) decode_sumtype_time(mut val time.Time) ! {
	mut wrapper := SumtypeTimeValue{
		typ:   ''
		value: 0
	}
	decoder.decode_value(mut wrapper)!
	val = time.unix(wrapper.value)
}

fn (mut decoder Decoder) init_sumtype_by_value_kind[T](mut val T, value_info ValueInfo) ! {
	mut failed_struct := false
	mut struct_variant_count := 0

	// For a string, number or boolean, a variant of the plain type wins over an alias
	// of it (`MyString | string` selects `string`), like in the removed `json` module:
	// aliases are only tried after all other variants.
	match value_info.value_kind {
		.string {
			for aliases in [false, true] {
				$for v in T.variants {
					mut is_alias := false
					$if v.typ is $alias {
						is_alias = true
					}
					if is_alias == aliases {
						$if v.typ is string {
							val = T(v)
							return
						} $else $if v.typ is time.Time {
							val = T(v)
							return
						} $else $if v.typ is StringDecoder {
							val = T(v)
							return
						}
					}
				}
			}
		}
		.number {
			for aliases in [false, true] {
				$for v in T.variants {
					mut is_alias := false
					$if v.typ is $alias {
						is_alias = true
					}
					if is_alias == aliases {
						$if v.typ is $float {
							val = T(v)
							return
						} $else $if v.typ is $int {
							val = T(v)
							return
						} $else $if v.typ is $enum {
							val = T(v)
							return
						} $else $if v.typ is NumberDecoder {
							val = T(v)
							return
						}
					}
				}
			}
		}
		.boolean {
			for aliases in [false, true] {
				$for v in T.variants {
					mut is_alias := false
					$if v.typ is $alias {
						is_alias = true
					}
					if is_alias == aliases {
						$if v.typ is bool {
							val = T(v)
							return
						} $else $if v.typ is BooleanDecoder {
							val = T(v)
							return
						}
					}
				}
			}
		}
		.null {
			$for v in T.variants {
				$if v.typ is $option {
					val = T(v)
					return
				} $else $if v.typ is NullDecoder {
					val = T(v)
					return
				}
			}
		}
		.array {
			$for v in T.variants {
				$if v.typ is $array {
					candidate := $zero(v.typ)
					if decoder.check_element_type_valid(candidate, decoder.current_idx) {
						val = T(candidate)
						return
					}
				}
			}
		}
		.object {
			if decoder.resolve_sumtype_from_type_field(mut val)! {
				return
			}
			$for v in T.variants {
				$if v.typ is $map {
					candidate := $zero(v.typ)
					if decoder.check_element_type_valid(candidate, decoder.current_idx) {
						val = T(candidate)
						return
					}
				} $else $if v.typ is $struct {
					// Without a `_type` field no struct variant matches by name; the
					// only one that can be selected is built below.
					struct_variant_count++
					failed_struct = true
				}
			}
		}
	}

	if failed_struct {
		// If there is only one struct variant and no explicit `_type` key,
		// the object shape is already unambiguous.
		if struct_variant_count == 1
			&& decoder.get_sumtype_type_field_idx(decoder.current_idx) == no_value_idx {
			$for v in T.variants {
				$if v.typ is $struct {
					val = T(v)
				}
			}
			return
		}
		decoder.decode_error('could not resolve sumtype `${T.name}`, missing "_type" field?')!
	}
	// A value no other variant takes goes to an option variant, whose payload has to
	// decode it (`5` for `?int | string`), like in the removed `json` module: first one
	// whose payload takes the JSON value as it is (`?string` for a string), then one
	// that converts it (`?rune` for a string), then any.
	if value_info.value_kind != .null {
		for min_fit in [2, 1, 0] {
			$for v in T.variants {
				$if v.typ is $option {
					if option_payload_fit($zero(v.typ), value_info.value_kind) >= min_fit {
						val = T(v)
						return
					}
				}
			}
		}
	}

	decoder.decode_error('could not resolve sumtype `${T.name}`, got ${value_info.value_kind}.')!
}

// option_payload_fit reports how the payload of an option (`?int`) takes a JSON value
// of `kind`: 2 when it is of that kind (`?string` for a string), 1 when it converts it
// (`?rune` or `?time.Time` for a string), and 0 when it does not take it. Aliases are
// unwrapped, so `?Text` takes a string for `type Text = string`.
fn option_payload_fit[P](_ ?P, kind ValueKind) int {
	$if P.unaliased_typ is time.Time {
		return if kind == .string || kind == .object { 1 } else { 0 }
	} $else $if P.unaliased_typ is string {
		return if kind == .string { 2 } else { 0 }
	} $else $if P.unaliased_typ is bool {
		return if kind == .boolean { 2 } else { 0 }
	} $else $if P.unaliased_typ is rune {
		// A rune is written as a string, and a number is taken as its code point.
		return if kind == .string || kind == .number { 1 } else { 0 }
	} $else $if P.unaliased_typ is $int || P.unaliased_typ is $float {
		return if kind == .number { 2 } else { 0 }
	} $else $if P.unaliased_typ is $enum {
		return if kind == .string || kind == .number { 1 } else { 0 }
	} $else $if P.unaliased_typ is $array || P.unaliased_typ is $array_fixed {
		return if kind == .array { 2 } else { 0 }
	} $else {
		return if kind == .object { 2 } else { 0 }
	}
}

fn (mut decoder Decoder) decode_sumtype[T](mut val T) ! {
	value_info := decoder.current_value()

	decoder.init_sumtype_by_value_kind(mut val, value_info)!

	decoder.decode_selected_sumtype(mut val)!
}