// Default-value validation (upstream defaults.rs, minus code emission).
///|
/// How a default value can be produced.
pub(all) enum DefaultKind {
/// The type's natural default (null, [], {}, false, 0, "").
Intrinsic
/// Needs a bespoke function.
Specific
/// Covered by a shared generic function.
Generic(DefaultImpl)
} derive(Debug)
///|
pub let std_num_nonzero_prefix : String = "::std::num::NonZero"
///|
fn TypeEntry::check_defaults(
self : TypeEntry,
space : TypeSpace,
) -> Unit raise TypifyError {
match self.details {
Enum({ default: Some(d), .. })
| Struct({ default: Some(d), .. })
| Newtype({ default: Some(d), .. }) =>
if self.validate_value(space, d) is Generic(f) {
space.add_default_impl(f)
}
_ => ()
}
match self.details {
Struct({ properties, .. }) =>
for prop in properties {
check_property_defaults(prop, space)
}
Enum({ variants, .. }) =>
for variant in variants {
if variant.details is Struct(props) {
for prop in props {
check_property_defaults(prop, space)
}
}
}
_ => ()
}
}
///|
fn check_property_defaults(
prop : StructProperty,
space : TypeSpace,
) -> Unit raise TypifyError {
if prop.state is Default(value) {
let entry = space.entry_unchecked(prop.type_id)
if entry.validate_value(space, value) is Generic(f) {
space.add_default_impl(f)
}
}
}
///|
/// Check that a value is a valid instance of this type, and classify how a
/// default of that value can be produced.
pub fn TypeEntry::validate_value(
self : TypeEntry,
space : TypeSpace,
value : @serde_json.Value,
) -> DefaultKind raise TypifyError {
let or_invalid = (k : DefaultKind?) => {
match k {
Some(k) => k
None => raise InvalidValue
}
}
match self.details {
Enum({ tag_type, variants, .. }) =>
match tag_type {
External =>
or_invalid(validate_default_for_external_enum(space, variants, value))
Internal(tag~) =>
or_invalid(
validate_default_for_internal_enum(space, variants, value, tag),
)
Adjacent(tag~, content~) =>
or_invalid(
validate_default_for_adjacent_enum(
space, variants, value, tag, content,
),
)
Untagged =>
or_invalid(validate_default_for_untagged_enum(space, variants, value))
}
Struct({ properties, .. }) =>
or_invalid(validate_default_struct_props(properties, space, value))
Newtype({ type_id, .. }) => {
ignore(validate_type_id(type_id, space, value))
Specific
}
Option(type_id) =>
if value is Null {
Intrinsic
} else {
ignore(validate_type_id(type_id, space, value))
Specific
}
Box(type_id) => validate_type_id(type_id, space, value)
Vec(type_id) =>
match value {
Array([]) => Intrinsic
Array(items) => {
let entry = space.entry_unchecked(type_id)
for item in items {
ignore(entry.validate_value(space, item))
}
Specific
}
_ => raise InvalidValue
}
Map(key_id, value_id) =>
match value {
Object(m) if m.is_empty() => Intrinsic
Object(m) => {
let key_ty = space.entry_unchecked(key_id)
let value_ty = space.entry_unchecked(value_id)
for k, v in m {
ignore(key_ty.validate_value(space, String(k)))
ignore(value_ty.validate_value(space, v))
}
Specific
}
_ => raise InvalidValue
}
Set(type_id) =>
match value {
Array([]) => Intrinsic
Array(items) => {
let entry = space.entry_unchecked(type_id)
for i, item in items {
for j in (i + 1).. raise InvalidValue
}
Tuple(ids) => or_invalid(validate_default_tuple(ids, space, value))
Array(type_id, length) => {
guard value is Array(arr) else { raise InvalidValue }
if arr.length() != length {
raise InvalidValue
}
let entry = space.entry_unchecked(type_id)
for item in arr {
ignore(entry.validate_value(space, item))
}
Specific
}
Unit => if value is Null { Intrinsic } else { raise InvalidValue }
Native(_) => Specific
JsonValue => Specific
Boolean =>
match value {
Bool(false) => Intrinsic
Bool(true) => Generic(Boolean)
_ => raise InvalidValue
}
Integer(itype) =>
match (value.as_u64(), value.as_i64()) {
(None, None) => raise InvalidValue
(Some(0UL), _) => Intrinsic
(Some(_), _) =>
if itype.has_prefix(std_num_nonzero_prefix) {
Generic(NZU64)
} else {
Generic(U64)
}
(_, Some(_)) => Generic(I64)
}
Float(_) =>
match value.as_f64() {
Some(v) => if v == 0.0 { Intrinsic } else { Generic(I64) }
None => raise InvalidValue
}
String => if value is String("") { Intrinsic } else { Specific }
Reference(_) => raise panic_with("internal error: entered unreachable code")
}
}
///|
/// `validate_type_id(..).ok()` that still propagates panics.
fn validate_ok(
type_id : TypeId,
space : TypeSpace,
value : @serde_json.Value,
) -> DefaultKind? raise TypifyError {
ok(() => validate_type_id(type_id, space, value))
}
///|
fn validate_default_for_external_enum(
space : TypeSpace,
variants : Array[Variant],
value : @serde_json.Value,
) -> DefaultKind? raise TypifyError {
if value.as_str() is Some(simple_name) {
guard variants.search_by(v => v.raw_name == simple_name) is Some(i) else {
return None
}
return if variants[i].details is Simple { Some(Specific) } else { None }
}
guard value.as_object() is Some(map) else { return None }
if map.length() != 1 {
return None
}
guard map.first() is Some((name, inner)) else { return None }
guard variants.search_by(v => v.raw_name == name) is Some(i) else {
return None
}
match variants[i].details {
Simple => None
Item(type_id) => validate_ok(type_id, space, inner)
Tuple(tup) => validate_default_tuple(tup, space, inner)
Struct(props) => validate_default_struct_props(props, space, inner)
}
}
///|
fn validate_default_for_internal_enum(
space : TypeSpace,
variants : Array[Variant],
value : @serde_json.Value,
tag : String,
) -> DefaultKind? raise TypifyError {
guard value.as_object() is Some(map) else { return None }
guard map.get(tag).bind(v => v.as_str()) is Some(name) else { return None }
guard variants.search_by(v => v.raw_name == name) is Some(i) else {
return None
}
match variants[i].details {
Simple => Some(Specific)
Struct(props) => {
let inner = map.copy()
ignore(inner.remove(tag))
validate_default_struct_props(props, space, Object(inner))
}
Item(_) | Tuple(_) =>
raise panic_with("internal error: entered unreachable code")
}
}
///|
fn validate_default_for_adjacent_enum(
space : TypeSpace,
variants : Array[Variant],
value : @serde_json.Value,
tag : String,
content : String,
) -> DefaultKind? raise TypifyError {
guard value.as_object() is Some(map) else { return None }
let tag_value = map.get(tag).bind(v => v.as_str())
let content_value = map.get(content)
let (tag_value, content_value) = match
(map.length(), tag_value, content_value) {
(1, Some(t), None) => (t, None)
(2, Some(t), Some(c)) => (t, Some(c))
_ => return None
}
guard variants.search_by(v => v.raw_name == tag_value) is Some(i) else {
return None
}
match (variants[i].details, content_value) {
(Simple, None) => Some(Specific)
(Tuple(tup), Some(c)) => validate_default_tuple(tup, space, c)
(Struct(props), Some(c)) => validate_default_struct_props(props, space, c)
_ => None
}
}
///|
fn validate_default_for_untagged_enum(
space : TypeSpace,
variants : Array[Variant],
value : @serde_json.Value,
) -> DefaultKind? raise TypifyError {
for variant in variants {
let result = match variant.details {
Simple => if value is Null { Some(Specific) } else { None }
Item(type_id) => validate_ok(type_id, space, value)
Tuple(tup) => validate_default_tuple(tup, space, value)
Struct(props) => validate_default_struct_props(props, space, value)
}
if result is Some(_) {
return result
}
}
None
}
///|
fn validate_type_id(
type_id : TypeId,
space : TypeSpace,
value : @serde_json.Value,
) -> DefaultKind raise TypifyError {
space.entry_unchecked(type_id).validate_value(space, value)
}
///|
fn validate_default_tuple(
types : Array[TypeId],
space : TypeSpace,
value : @serde_json.Value,
) -> DefaultKind? raise TypifyError {
guard value.as_array() is Some(arr) else { return None }
if arr.length() != types.length() {
return None
}
for i, type_id in types {
if validate_ok(type_id, space, arr[i]) is None {
return None
}
}
Some(Specific)
}
///|
fn validate_default_struct_props(
properties : Array[StructProperty],
space : TypeSpace,
value : @serde_json.Value,
) -> DefaultKind? raise TypifyError {
guard value.as_object() is Some(map) else { return None }
let named : @collections.StrMap[(TypeId, Bool)] = @collections.StrMap::new()
let unnamed : Array[TypeId] = []
for prop in properties {
for p in all_props(prop, space) {
match p.0 {
Some(name) => named.set(name, (p.1, p.2))
None => unnamed.push(p.1)
}
}
}
for name, default_value in map {
match named.get(name) {
Some((type_id, _)) =>
if validate_ok(type_id, space, default_value) is None {
return None
}
None => {
let mut any = false
for type_id in unnamed {
if validate_ok(type_id, space, default_value) is Some(_) {
any = true
break
}
}
if !any {
return None
}
}
}
}
for name, info in named {
if info.1 && !map.contains(name) {
return None
}
}
Some(Specific)
}
///|
/// All (possibly flattened) properties: (name, type, required).
fn all_props(
prop : StructProperty,
space : TypeSpace,
) -> Array[(String?, TypeId, Bool)] raise TypifyError {
let maybe_name = match prop.rename {
NoRename => Some(prop.name)
Rename(r) => Some(r)
Flatten => None
}
if maybe_name is Some(name) {
return [(Some(name), prop.type_id, prop.state is Required)]
}
let entry = space.entry_unchecked(prop.type_id)
let (properties, all_required) = match entry.details {
Struct({ properties, .. }) => (properties, !(prop.state is Optional))
Option(inner) =>
match space.entry_unchecked(inner).details {
Struct({ properties, .. }) => (properties, false)
_ => raise panic_with("internal error: entered unreachable code")
}
Map(_, value_id) => return [(None, value_id, false)]
_ => raise panic_with("internal error: entered unreachable code")
}
let out = []
for p in properties {
for x in all_props(p, space) {
out.push((x.0, x.1, x.2 && all_required))
}
}
out
}