From 3c0358cce78c7722cfadeabe12a86d34f6dc1ea4 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Sun, 6 Oct 2024 16:15:04 +0800 Subject: [PATCH] toml: add reflect/decode struct default value support (#22412) --- vlib/toml/any.v | 331 ++++++++++++++------------- vlib/toml/tests/default_value_test.v | 85 +++++++ vlib/toml/toml.v | 5 +- 3 files changed, 256 insertions(+), 165 deletions(-) create mode 100644 vlib/toml/tests/default_value_test.v diff --git a/vlib/toml/any.v b/vlib/toml/any.v index 41b9c93b51..9fb49088ba 100644 --- a/vlib/toml/any.v +++ b/vlib/toml/any.v @@ -330,181 +330,186 @@ pub fn (a Any) reflect[T]() T { mut reflected := T{} $for field in T.fields { mut toml_field_name := field.name + mut skip := false // Remapping of field names, for example: // TOML: 'assert = "ok"' // V: User { asrt string @[toml: 'assert'] } // User.asrt == 'ok' for attr in field.attrs { + if attr == 'skip' { + skip = true + break + } if attr.starts_with('toml:') { toml_field_name = attr.all_after(':').trim_space() } } - - $if field.typ is string { - reflected.$(field.name) = a.value(toml_field_name).default_to('').string() - } $else $if field.typ is bool { - reflected.$(field.name) = a.value(toml_field_name).default_to(false).bool() - } $else $if field.typ is int { - reflected.$(field.name) = a.value(toml_field_name).default_to(0).int() - } $else $if field.typ is f32 { - reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f32() - } $else $if field.typ is f64 { - reflected.$(field.name) = a.value(toml_field_name).default_to(0.0).f64() - } $else $if field.typ is i64 { - reflected.$(field.name) = a.value(toml_field_name).default_to(0).i64() - } $else $if field.typ is u64 { - reflected.$(field.name) = a.value(toml_field_name).default_to(0).u64() - } $else $if field.typ is Any { - reflected.$(field.name) = a.value(toml_field_name) - } $else $if field.typ is DateTime { - dt := DateTime{'0000-00-00T00:00:00.000'} - reflected.$(field.name) = a.value(toml_field_name).default_to(dt).datetime() - } $else $if field.typ is Date { - da := Date{'0000-00-00'} - reflected.$(field.name) = a.value(toml_field_name).default_to(da).date() - } $else $if field.typ is Time { - t := Time{'00:00:00.000'} - reflected.$(field.name) = a.value(toml_field_name).default_to(t).time() - } - // Arrays of primitive types - $else $if field.typ is []string { - any_array := a.value(toml_field_name).array() - reflected.$(field.name) = any_array.as_strings() - } $else $if field.typ is []bool { - any_array := a.value(toml_field_name).array() - mut arr := []bool{cap: any_array.len} - for any_value in any_array { - arr << any_value.bool() + value := a.value(toml_field_name) + // only set the field's value when value != null and !skip, else field got it's default value + if !skip && value != null { + $if field.typ is string { + reflected.$(field.name) = value.string() + } $else $if field.typ is bool { + reflected.$(field.name) = value.bool() + } $else $if field.typ is int { + reflected.$(field.name) = value.int() + } $else $if field.typ is f32 { + reflected.$(field.name) = value.f32() + } $else $if field.typ is f64 { + reflected.$(field.name) = value.f64() + } $else $if field.typ is i64 { + reflected.$(field.name) = value.i64() + } $else $if field.typ is u64 { + reflected.$(field.name) = value.u64() + } $else $if field.typ is Any { + reflected.$(field.name) = value + } $else $if field.typ is DateTime { + reflected.$(field.name) = value.datetime() + } $else $if field.typ is Date { + reflected.$(field.name) = value.date() + } $else $if field.typ is Time { + reflected.$(field.name) = value.time() } - reflected.$(field.name) = arr - } $else $if field.typ is []int { - any_array := a.value(toml_field_name).array() - mut arr := []int{cap: any_array.len} - for any_value in any_array { - arr << any_value.int() + // Arrays of primitive types + $else $if field.typ is []string { + any_array := value.array() + reflected.$(field.name) = any_array.as_strings() + } $else $if field.typ is []bool { + any_array := value.array() + mut arr := []bool{cap: any_array.len} + for any_value in any_array { + arr << any_value.bool() + } + reflected.$(field.name) = arr + } $else $if field.typ is []int { + any_array := value.array() + mut arr := []int{cap: any_array.len} + for any_value in any_array { + arr << any_value.int() + } + reflected.$(field.name) = arr + } $else $if field.typ is []f32 { + any_array := value.array() + mut arr := []f32{cap: any_array.len} + for any_value in any_array { + arr << any_value.f32() + } + reflected.$(field.name) = arr + } $else $if field.typ is []f64 { + any_array := value.array() + mut arr := []f64{cap: any_array.len} + for any_value in any_array { + arr << any_value.f64() + } + reflected.$(field.name) = arr + } $else $if field.typ is []i64 { + any_array := value.array() + mut arr := []i64{cap: any_array.len} + for any_value in any_array { + arr << any_value.i64() + } + reflected.$(field.name) = arr + } $else $if field.typ is []u64 { + any_array := value.array() + mut arr := []u64{cap: any_array.len} + for any_value in any_array { + arr << any_value.u64() + } + reflected.$(field.name) = arr + } $else $if field.typ is []Any { + reflected.$(field.name) = value.array() + } $else $if field.typ is []DateTime { + any_array := value.array() + mut arr := []DateTime{cap: any_array.len} + for any_value in any_array { + arr << any_value.datetime() + } + reflected.$(field.name) = arr + } $else $if field.typ is []Date { + any_array := value.array() + mut arr := []Date{cap: any_array.len} + for any_value in any_array { + arr << any_value.date() + } + reflected.$(field.name) = arr + } $else $if field.typ is []Time { + any_array := value.array() + mut arr := []Time{cap: any_array.len} + for any_value in any_array { + arr << any_value.time() + } + reflected.$(field.name) = arr } - reflected.$(field.name) = arr - } $else $if field.typ is []f32 { - any_array := a.value(toml_field_name).array() - mut arr := []f32{cap: any_array.len} - for any_value in any_array { - arr << any_value.f32() + // String key maps of primitive types + $else $if field.typ is map[string]string { + any_map := value.as_map() + reflected.$(field.name) = any_map.as_strings() + } $else $if field.typ is map[string]bool { + any_map := value.as_map() + mut type_map := map[string]bool{} + for k, any_value in any_map { + type_map[k] = any_value.bool() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]int { + any_map := value.as_map() + mut type_map := map[string]int{} + for k, any_value in any_map { + type_map[k] = any_value.int() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]f32 { + any_map := value.as_map() + mut type_map := map[string]f32{} + for k, any_value in any_map { + type_map[k] = any_value.f32() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]f64 { + any_map := value.as_map() + mut type_map := map[string]f64{} + for k, any_value in any_map { + type_map[k] = any_value.f64() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]i64 { + any_map := value.as_map() + mut type_map := map[string]i64{} + for k, any_value in any_map { + type_map[k] = any_value.i64() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]u64 { + any_map := value.as_map() + mut type_map := map[string]u64{} + for k, any_value in any_map { + type_map[k] = any_value.u64() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]Any { + reflected.$(field.name) = value.as_map() + } $else $if field.typ is map[string]DateTime { + any_map := value.as_map() + mut type_map := map[string]DateTime{} + for k, any_value in any_map { + type_map[k] = any_value.datetime() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]Date { + any_map := value.as_map() + mut type_map := map[string]Date{} + for k, any_value in any_map { + type_map[k] = any_value.date() + } + reflected.$(field.name) = type_map.clone() + } $else $if field.typ is map[string]Time { + any_map := value.as_map() + mut type_map := map[string]Time{} + for k, any_value in any_map { + type_map[k] = any_value.time() + } + reflected.$(field.name) = type_map.clone() } - reflected.$(field.name) = arr - } $else $if field.typ is []f64 { - any_array := a.value(toml_field_name).array() - mut arr := []f64{cap: any_array.len} - for any_value in any_array { - arr << any_value.f64() - } - reflected.$(field.name) = arr - } $else $if field.typ is []i64 { - any_array := a.value(toml_field_name).array() - mut arr := []i64{cap: any_array.len} - for any_value in any_array { - arr << any_value.i64() - } - reflected.$(field.name) = arr - } $else $if field.typ is []u64 { - any_array := a.value(toml_field_name).array() - mut arr := []u64{cap: any_array.len} - for any_value in any_array { - arr << any_value.u64() - } - reflected.$(field.name) = arr - } $else $if field.typ is []Any { - reflected.$(field.name) = a.value(toml_field_name).array() - } $else $if field.typ is []DateTime { - any_array := a.value(toml_field_name).array() - mut arr := []DateTime{cap: any_array.len} - for any_value in any_array { - arr << any_value.datetime() - } - reflected.$(field.name) = arr - } $else $if field.typ is []Date { - any_array := a.value(toml_field_name).array() - mut arr := []Date{cap: any_array.len} - for any_value in any_array { - arr << any_value.date() - } - reflected.$(field.name) = arr - } $else $if field.typ is []Time { - any_array := a.value(toml_field_name).array() - mut arr := []Time{cap: any_array.len} - for any_value in any_array { - arr << any_value.time() - } - reflected.$(field.name) = arr - } - // String key maps of primitive types - $else $if field.typ is map[string]string { - any_map := a.value(toml_field_name).as_map() - reflected.$(field.name) = any_map.as_strings() - } $else $if field.typ is map[string]bool { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]bool{} - for k, any_value in any_map { - type_map[k] = any_value.bool() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]int { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]int{} - for k, any_value in any_map { - type_map[k] = any_value.int() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]f32 { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]f32{} - for k, any_value in any_map { - type_map[k] = any_value.f32() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]f64 { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]f64{} - for k, any_value in any_map { - type_map[k] = any_value.f64() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]i64 { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]i64{} - for k, any_value in any_map { - type_map[k] = any_value.i64() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]u64 { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]u64{} - for k, any_value in any_map { - type_map[k] = any_value.u64() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]Any { - reflected.$(field.name) = a.value(toml_field_name).as_map() - } $else $if field.typ is map[string]DateTime { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]DateTime{} - for k, any_value in any_map { - type_map[k] = any_value.datetime() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]Date { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]Date{} - for k, any_value in any_map { - type_map[k] = any_value.date() - } - reflected.$(field.name) = type_map.clone() - } $else $if field.typ is map[string]Time { - any_map := a.value(toml_field_name).as_map() - mut type_map := map[string]Time{} - for k, any_value in any_map { - type_map[k] = any_value.time() - } - reflected.$(field.name) = type_map.clone() } } return reflected diff --git a/vlib/toml/tests/default_value_test.v b/vlib/toml/tests/default_value_test.v new file mode 100644 index 0000000000..5c3995bb9e --- /dev/null +++ b/vlib/toml/tests/default_value_test.v @@ -0,0 +1,85 @@ +import toml + +const toml_text = '# This TOML can reflect/decode to a struct +val_string = "test" +val_bool = false +val_int = 456 +val_i64 = 4567 +val_u64 = 45678 +val_f32 = 200.2 +val_f64 = 2000.2 +val_datetime = 2024-10-05 09:10:20.000 +val_date = 2099-09-09 +val_time = 22:22:22.222 +' + +const toml_all_default_text = '# This TOML can reflect/decode to a struct all with default values' + +struct Test { + val_string string = 'abcd' + val_bool bool = true + val_int int = 123 + val_i64 i64 = 1234 + val_u64 u64 = 12345 + val_f32 f32 = 100.1 + val_f64 f64 = 1000.1 + val_datetime toml.DateTime = toml.DateTime{'1980-07-11 21:23:42.123'} + val_date toml.Date = toml.Date{'1977-07-07'} + val_time toml.Time = toml.Time{'11:11:11.111'} +} + +fn test_reflect_default_values() { + toml_has_values := toml.parse_text(toml_text) or { panic(err) } + test_has_values := toml_has_values.reflect[Test]() + + assert test_has_values.val_string == 'test' + assert test_has_values.val_bool == false + assert test_has_values.val_int == 456 + assert test_has_values.val_i64 == 4567 + assert test_has_values.val_u64 == 45678 + assert test_has_values.val_f32 == 200.2 + assert test_has_values.val_f64 == 2000.2 + assert test_has_values.val_datetime == toml.DateTime{'2024-10-05 09:10:20.000'} + assert test_has_values.val_date == toml.Date{'2099-09-09'} + assert test_has_values.val_time == toml.Time{'22:22:22.222'} + + toml_all_default_values := toml.parse_text(toml_all_default_text) or { panic(err) } + test_all_default_values := toml_all_default_values.reflect[Test]() + + assert test_all_default_values.val_string == 'abcd' + assert test_all_default_values.val_bool == true + assert test_all_default_values.val_int == 123 + assert test_all_default_values.val_i64 == 1234 + assert test_all_default_values.val_u64 == 12345 + assert test_all_default_values.val_f32 == 100.1 + assert test_all_default_values.val_f64 == 1000.1 + assert test_all_default_values.val_datetime == toml.DateTime{'1980-07-11 21:23:42.123'} + assert test_all_default_values.val_date == toml.Date{'1977-07-07'} + assert test_all_default_values.val_time == toml.Time{'11:11:11.111'} +} + +fn test_decode_struct_default_values() { + test_has_values := toml.decode[Test](toml_text) or { panic(err) } + assert test_has_values.val_string == 'test' + assert test_has_values.val_bool == false + assert test_has_values.val_int == 456 + assert test_has_values.val_i64 == 4567 + assert test_has_values.val_u64 == 45678 + assert test_has_values.val_f32 == 200.2 + assert test_has_values.val_f64 == 2000.2 + assert test_has_values.val_datetime == toml.DateTime{'2024-10-05 09:10:20.000'} + assert test_has_values.val_date == toml.Date{'2099-09-09'} + assert test_has_values.val_time == toml.Time{'22:22:22.222'} + + test_all_default_values := toml.decode[Test](toml_all_default_text) or { panic(err) } + assert test_all_default_values.val_string == 'abcd' + assert test_all_default_values.val_bool == true + assert test_all_default_values.val_int == 123 + assert test_all_default_values.val_i64 == 1234 + assert test_all_default_values.val_u64 == 12345 + assert test_all_default_values.val_f32 == 100.1 + assert test_all_default_values.val_f64 == 1000.1 + assert test_all_default_values.val_datetime == toml.DateTime{'1980-07-11 21:23:42.123'} + assert test_all_default_values.val_date == toml.Date{'1977-07-07'} + assert test_all_default_values.val_time == toml.Time{'11:11:11.111'} +} diff --git a/vlib/toml/toml.v b/vlib/toml/toml.v index 51ffa45276..65a4b91cf0 100644 --- a/vlib/toml/toml.v +++ b/vlib/toml/toml.v @@ -44,8 +44,9 @@ fn decode_struct[T](doc Any, mut typ T) { field_name = attr.all_after(':').trim_space() } } - if !skip { - value := doc.value(field_name) + value := doc.value(field_name) + // only set the field's value when value != null and !skip, else field got it's default value + if !skip && value != null { $if field.is_enum { typ.$(field.name) = value.int() } $else $if field.typ is string {