Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
P
picodata
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Container Registry
Model registry
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
core
picodata
Commits
7bfd042f
Commit
7bfd042f
authored
1 year ago
by
Georgy Moshkin
Browse files
Options
Downloads
Patches
Plain Diff
feat: #[introspection(config_default = ".")] + Introspection::get_field_default_value_as_rmpv
parent
7563bf31
No related branches found
Branches containing commit
No related tags found
Tags containing commit
1 merge request
!913
feat: pico.config() now shows sources for provided parameters
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
pico_proc_macro/src/lib.rs
+147
-5
147 additions, 5 deletions
pico_proc_macro/src/lib.rs
src/config.rs
+1
-0
1 addition, 0 deletions
src/config.rs
src/introspection.rs
+150
-0
150 additions, 0 deletions
src/introspection.rs
with
298 additions
and
5 deletions
pico_proc_macro/src/lib.rs
+
147
−
5
View file @
7bfd042f
...
...
@@ -66,6 +66,9 @@ pub fn derive_introspection(input: proc_macro::TokenStream) -> proc_macro::Token
let
body_for_get_field_as_rmpv
=
generate_body_for_get_field_as_rmpv
(
&
context
);
let
body_for_get_field_default_value_as_rmpv
=
generate_body_for_get_field_default_value_as_rmpv
(
&
context
);
let
crate_
=
&
context
.args.crate_
;
quote!
{
#[automatically_derived]
...
...
@@ -88,6 +91,11 @@ pub fn derive_introspection(input: proc_macro::TokenStream) -> proc_macro::Token
use
#
crate_
::
introspection
::
IntrospectionError
;
#
body_for_get_field_as_rmpv
}
fn
get_field_default_value_as_rmpv
(
&
self
,
path
:
&
str
)
->
Result
<
Option
<
#
crate_
::
introspection
::
RmpvValue
>
,
#
crate_
::
introspection
::
IntrospectionError
>
{
use
#
crate_
::
introspection
::
IntrospectionError
;
#
body_for_get_field_default_value_as_rmpv
}
}
}
.into
()
...
...
@@ -347,6 +355,133 @@ fn generate_body_for_get_field_as_rmpv(context: &Context) -> proc_macro2::TokenS
}
}
fn
generate_body_for_get_field_default_value_as_rmpv
(
context
:
&
Context
,
)
->
proc_macro2
::
TokenStream
{
let
crate_
=
&
context
.args.crate_
;
let
mut
default_for_non_nestable
=
quote!
{};
let
mut
default_for_whole_nestable
=
quote!
{};
let
mut
default_for_nested_subfield
=
quote!
{};
let
mut
non_nestable_names
=
vec!
[];
for
field
in
&
context
.fields
{
let
name
=
&
field
.name
;
let
ident
=
&
field
.ident
;
#[allow(non_snake_case)]
let
Type
=
&
field
.field.ty
;
if
!
field
.attrs.nested
{
non_nestable_names
.push
(
name
);
// Handle getting default for a non-nestable field
if
let
Some
(
default
)
=
&
field
.attrs.config_default
{
default_for_non_nestable
.extend
(
quote!
{
#
name
=>
{
match
#
crate_
::
introspection
::
to_rmpv_value
(
&
(
#
default
))
{
Err
(
e
)
=>
{
return
Err
(
IntrospectionError
::
ToRmpvValue
{
field
:
path
.into
(),
details
:
e
});
}
Ok
(
value
)
=>
return
Ok
(
Some
(
value
)),
}
}
});
}
else
{
default_for_non_nestable
.extend
(
quote!
{
#
name
=>
{
return
Ok
(
None
);
}
});
}
}
else
{
// Handle getting a field marked with `#[introspection(nested)]`.
default_for_whole_nestable
.extend
(
quote!
{
#
name
=>
{
use
#
crate_
::
introspection
::
RmpvValue
;
let
field_infos
=
#
Type
::
FIELD_INFOS
;
let
mut
fields
=
Vec
::
with_capacity
(
field_infos
.len
());
for
field
in
field_infos
{
let
value
=
self
.#
ident
.get_field_default_value_as_rmpv
(
field
.name
)
.map_err
(|
e
|
e
.with_prepended_prefix
(
#
name
))
?
;
let
Some
(
value
)
=
value
else
{
continue
;
};
fields
.push
((
RmpvValue
::
from
(
field
.name
),
value
));
}
if
fields
.is_empty
()
{
return
Ok
(
None
);
}
return
Ok
(
Some
(
RmpvValue
::
Map
(
fields
)));
}
});
// Handle getting a nested field
default_for_nested_subfield
.extend
(
quote!
{
#
name
=>
{
return
self
.#
ident
.get_field_default_value_as_rmpv
(
tail
)
.map_err
(|
e
|
e
.with_prepended_prefix
(
head
));
}
});
}
}
// Handle if a nested path is specified for non-nestable field
let
mut
error_if_non_nestable
=
quote!
{};
if
!
non_nestable_names
.is_empty
()
{
error_if_non_nestable
=
quote!
{
#
(
#
non_nestable_names
)|
*
=>
{
return
Err
(
IntrospectionError
::
NotNestable
{
field
:
head
.into
()
})
}
};
}
// Actual generated body:
quote!
{
match
path
.split_once
(
'.'
)
{
Some
((
head
,
tail
))
=>
{
let
head
=
head
.trim
();
if
head
.is_empty
()
{
return
Err
(
IntrospectionError
::
InvalidPath
{
expected
:
"expected a field name before"
,
path
:
format!
(
".{tail}"
),
})
}
let
tail
=
tail
.trim
();
if
!
tail
.chars
()
.next
()
.map_or
(
false
,
char
::
is_alphabetic
)
{
return
Err
(
IntrospectionError
::
InvalidPath
{
expected
:
"expected a field name after"
,
path
:
format!
(
"{head}."
),
})
}
match
head
{
#
error_if_non_nestable
#
default_for_nested_subfield
_
=>
{
return
Err
(
IntrospectionError
::
NoSuchField
{
parent
:
""
.into
(),
field
:
head
.into
(),
expected
:
Self
::
FIELD_INFOS
,
});
}
}
}
None
=>
{
match
path
{
#
default_for_non_nestable
#
default_for_whole_nestable
_
=>
{
return
Err
(
IntrospectionError
::
NoSuchField
{
parent
:
""
.into
(),
field
:
path
.into
(),
expected
:
Self
::
FIELD_INFOS
,
});
}
}
}
}
}
}
struct
Context
{
fields
:
Vec
<
FieldInfo
>
,
args
:
Args
,
...
...
@@ -385,17 +520,16 @@ impl Args {
}
}
#[derive(Default)]
struct
FieldAttrs
{
ignore
:
bool
,
nested
:
bool
,
config_default
:
Option
<
syn
::
Expr
>
,
}
impl
FieldAttrs
{
fn
from_attributes
(
attrs
:
Vec
<
syn
::
Attribute
>
)
->
Result
<
Self
,
syn
::
Error
>
{
let
mut
result
=
Self
{
ignore
:
false
,
nested
:
false
,
};
let
mut
result
=
Self
::
default
();
for
attr
in
attrs
{
if
!
attr
.path
.is_ident
(
"introspection"
)
{
...
...
@@ -412,10 +546,18 @@ impl FieldAttrs {
result
.ignore
=
true
;
}
else
if
ident
==
"nested"
{
result
.nested
=
true
;
}
else
if
ident
==
"config_default"
{
if
result
.config_default
.is_some
()
{
return
Err
(
syn
::
Error
::
new
(
ident
.span
(),
"duplicate `config_default` specified"
));
}
input
.parse
::
<
syn
::
Token!
[
=
]
>
()
?
;
result
.config_default
=
Some
(
input
.parse
::
<
syn
::
Expr
>
()
?
);
}
else
{
return
Err
(
syn
::
Error
::
new
(
ident
.span
(),
format!
(
"unknown attribute argument `{ident}`, expected one of `ignore`, `nested`"
),
format!
(
"unknown attribute argument `{ident}`, expected one of `ignore`, `nested`
, `config_default`
"
),
));
}
...
...
This diff is collapsed.
Click to expand it.
src/config.rs
+
1
−
0
View file @
7bfd042f
...
...
@@ -711,6 +711,7 @@ impl ClusterConfig {
#[derive(PartialEq,
Default,
Debug,
Clone,
serde::Deserialize,
serde::Serialize,
Introspection)]
pub
struct
InstanceConfig
{
#[introspection(config_default
=
"."
)]
pub
data_dir
:
Option
<
String
>
,
pub
service_password_file
:
Option
<
String
>
,
pub
config_file
:
Option
<
String
>
,
...
...
This diff is collapsed.
Click to expand it.
src/introspection.rs
+
150
−
0
View file @
7bfd042f
...
...
@@ -130,6 +130,67 @@ pub trait Introspection {
/// assert_eq!(s.get_field_as_rmpv("nested.sub_field").unwrap(), Value::from(2.71));
/// ```
fn
get_field_as_rmpv
(
&
self
,
path
:
&
str
)
->
Result
<
rmpv
::
Value
,
IntrospectionError
>
;
/// Get a value which was specified via the `#[introspection(config_default = expr)]`
/// attribute converted to a [`rmpv::Value`].
///
/// Returns `Ok(None)` if `config_default` attribute wasn't provided for
/// given field.
///
/// Note that the value is not type checked, instead it is immediately
/// converted to a rmpv::Value, so it's the user's responsibility to make
/// sure the resulting value has the appropriate type. This however makes it
/// a bit simpler to specify values for nested types like `Option<String>`,
/// etc.
///
/// The `expr` in `#[introspection(config_default = expr)]` may contain
/// references to `self`, which allows for default values of some parameters
/// to be dependent on values of other parameters
/// (see [`InstanceConfig::advertise_address`] for example).
///
/// Also note that this function doesn't do any special checks to see if the
/// values are set or not in the actual struct. See what happens in
/// [`PicodataConfig::set_defaults_explicitly`] for a real-life example.
///
/// When using the `#[derive(Introspection)]` derive macro the implementation
/// converts the value to msgpack using `rmp_serde` and then decodes that
/// msgpack into a `rmpv::Value`. This is sub-optimal with respect to performance,
/// but for our use cases this is fine.
///
/// # Examples:
/// ```
/// use picodata::introspection::Introspection;
/// use rmpv::Value;
///
/// #[derive(Introspection, Default)]
/// #[introspection(crate = picodata)]
/// struct MyStruct {
/// #[introspection(config_default = 69105)]
/// number: i32,
/// #[introspection(config_default = format!("there's {} leaves in the pile", self.number))]
/// text: String,
/// doesnt_have_default: bool,
/// }
///
/// let mut s = MyStruct {
/// number: 2,
/// ..Default::default()
/// };
///
/// assert_eq!(s.get_field_default_value_as_rmpv("number").unwrap(), Some(Value::from(69105)));
///
/// // Note here the actual value of `s.number` is used, not the config_default one. --------V
/// assert_eq!(s.get_field_default_value_as_rmpv("text").unwrap(), Some(Value::from("there's 2 leaves in the pile")));
///
/// assert_eq!(s.get_field_default_value_as_rmpv("doesnt_have_default").unwrap(), None);
/// ```
///
/// [`InstanceConfig::advertise_address`]: crate::config::InstanceConfig::advertise_address
/// [`PicodataConfig::set_defaults_explicitly`]: crate::config::PicodataConfig::set_defaults_explicitly
fn
get_field_default_value_as_rmpv
(
&
self
,
path
:
&
str
,
)
->
Result
<
Option
<
rmpv
::
Value
>
,
IntrospectionError
>
;
}
/// Information about a single struct field. This is the type which is stored
...
...
@@ -322,8 +383,11 @@ mod test {
#[derive(Default,
Debug,
Introspection,
PartialEq)]
struct
S
{
x
:
i32
,
#[introspection(config_default
=
self
.
x
as
f32
*
0.5
)]
y
:
f32
,
#[introspection(config_default
=
"this is a &str but it still works"
)]
s
:
String
,
#[introspection(config_default
=
&[
"this"
,
"also"
,
"works"
]
)]
v
:
Vec
<
String
>
,
#[introspection(nested)]
r
#
struct
:
Nested
,
...
...
@@ -334,7 +398,9 @@ mod test {
#[derive(Default,
Debug,
Introspection,
PartialEq)]
struct
Nested
{
#[introspection(config_default
=
"nested of course works"
)]
a
:
String
,
#[introspection(config_default
=
format
!
(
"{}, but type safety is missing unfortunately"
,
self
.
a))]
b
:
i64
,
#[introspection(nested)]
empty
:
Empty
,
...
...
@@ -661,4 +727,88 @@ mod test {
assert_eq!
(
leaf_field_paths
::
<
Nested
>
(),
&
[
"a"
,
"b"
,
"empty"
]);
}
#[test]
fn
get_field_default_value_as_rmpv
()
{
let
s
=
S
{
x
:
999
,
y
:
8.88
,
s
:
"S"
.into
(),
v
:
vec!
[
"V0"
.into
(),
"V1"
.into
(),
"V2"
.into
()],
r
#
struct
:
Nested
{
a
:
"self.a which was set explicitly"
.into
(),
b
:
0xb
,
empty
:
Empty
{},
},
ignored
:
serde_yaml
::
Value
::
default
(),
};
//
// Error cases are mostly covered in tests above
//
let
e
=
s
.get_field_default_value_as_rmpv
(
"struct.no_such_field"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"struct: unknown field `no_such_field`, expected one of `a`, `b`, `empty`"
);
let
e
=
s
.get_field_default_value_as_rmpv
(
"ignored"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
);
//
// Success cases
//
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"x"
)
.unwrap
(),
None
);
// Remember, it's `self.x * 0.5`
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"y"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
F32
(
499.5
))
);
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"s"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
from
(
"this is a &str but it still works"
))
);
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"v"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
Array
(
vec!
[
rmpv
::
Value
::
from
(
"this"
),
rmpv
::
Value
::
from
(
"also"
),
rmpv
::
Value
::
from
(
"works"
),
]))
);
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"struct.a"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
from
(
"nested of course works"
))
);
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"struct.b"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
from
(
"self.a which was set explicitly, but type safety is missing unfortunately"
))
);
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"struct.empty"
)
.unwrap
(),
None
);
// For some reason we even allow getting the default for the whole subsection.
#[rustfmt::skip]
assert_eq!
(
s
.get_field_default_value_as_rmpv
(
"struct"
)
.unwrap
(),
Some
(
rmpv
::
Value
::
Map
(
vec!
[
(
rmpv
::
Value
::
from
(
"a"
),
rmpv
::
Value
::
from
(
"nested of course works"
)),
(
rmpv
::
Value
::
from
(
"b"
),
rmpv
::
Value
::
from
(
"self.a which was set explicitly, but type safety is missing unfortunately"
)),
])),
);
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment