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
145a9ba8
Commit
145a9ba8
authored
1 year ago
by
Georgy Moshkin
Browse files
Options
Downloads
Patches
Plain Diff
feat: Introspection::set_field_from_yaml
parent
dbbf1937
No related branches found
No related tags found
1 merge request
!900
Gmoshkin/config.yaml cli consistent naming
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
pico_proc_macro/src/lib.rs
+266
-8
266 additions, 8 deletions
pico_proc_macro/src/lib.rs
src/config.rs
+1
-0
1 addition, 0 deletions
src/config.rs
src/introspection.rs
+269
-0
269 additions, 0 deletions
src/introspection.rs
src/lib.rs
+1
-0
1 addition, 0 deletions
src/lib.rs
with
537 additions
and
8 deletions
pico_proc_macro/src/lib.rs
+
266
−
8
View file @
145a9ba8
use
quote
::
quote
;
macro_rules!
unwrap_or_compile_error
{
(
$expr:expr
)
=>
{
match
$expr
{
Ok
(
v
)
=>
v
,
Err
(
e
)
=>
{
return
e
.to_compile_error
()
.into
();
}
}
};
}
#[allow(clippy::single_match)]
#[proc_macro_derive(Introspection)]
#[proc_macro_derive(Introspection
,
attributes(introspection)
)]
pub
fn
derive_introspection
(
input
:
proc_macro
::
TokenStream
)
->
proc_macro
::
TokenStream
{
let
input
=
syn
::
parse_macro_input!
(
input
as
syn
::
DeriveInput
);
let
name
=
&
input
.ident
;
let
args
=
unwrap_or_compile_error!
(
Args
::
from_attributes
(
input
.attrs
));
let
mut
context
=
Context
{
args
,
fields
:
vec!
[],
};
let
mut
field_names
=
vec!
[];
match
&
input
.data
{
syn
::
Data
::
Struct
(
ds
)
=>
match
&
ds
.fields
{
match
input
.data
{
syn
::
Data
::
Struct
(
ds
)
=>
match
ds
.fields
{
syn
::
Fields
::
Named
(
fs
)
=>
{
for
field
in
&
fs
.named
{
let
field_name
=
field
.ident
.as_ref
()
.unwrap
();
field_names
.push
(
field_name
.to_string
());
for
mut
field
in
fs
.named
{
let
attrs
=
std
::
mem
::
take
(
&
mut
field
.attrs
);
let
attrs
=
unwrap_or_compile_error!
(
FieldAttrs
::
from_attributes
(
attrs
));
if
attrs
.ignore
{
continue
;
}
let
name
=
field_name
(
&
field
);
field_names
.push
(
name
.clone
());
context
.fields
.push
(
FieldInfo
{
name
,
ident
:
field
.ident
.clone
()
.expect
(
"Fields::Named has fields with names"
),
attrs
,
field
,
});
}
}
_
=>
{}
...
...
@@ -20,12 +52,238 @@ pub fn derive_introspection(input: proc_macro::TokenStream) -> proc_macro::Token
_
=>
{}
}
let
body_for_set_field_from_yaml
=
generate_body_for_set_field_from_yaml
(
&
context
);
let
crate_
=
&
context
.args.crate_
;
quote!
{
impl
#
name
{
pub
const
FIELD_NAMES
:
&
'static
[
&
'static
str
]
=
&
[
#[automatically_derived]
impl
#
crate_
::
introspection
::
Introspection
for
#
name
{
const
FIELD_NAMES
:
&
'static
[
&
'static
str
]
=
&
[
#
(
#
field_names
,
)
*
];
fn
set_field_from_yaml
(
&
mut
self
,
path
:
&
str
,
yaml
:
&
str
)
->
::
std
::
result
::
Result
<
(),
#
crate_
::
introspection
::
IntrospectionError
>
{
use
#
crate_
::
introspection
::
IntrospectionError
;
#
body_for_set_field_from_yaml
}
}
}
.into
()
}
fn
generate_body_for_set_field_from_yaml
(
context
:
&
Context
)
->
proc_macro2
::
TokenStream
{
let
mut
set_non_nestable
=
quote!
{};
let
mut
set_nestable
=
quote!
{};
let
mut
error_if_nestable
=
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 assigning to a non-nestable field
set_non_nestable
.extend
(
quote!
{
#
name
=>
{
match
serde_yaml
::
from_str
(
yaml
)
{
Ok
(
v
)
=>
{
self
.#
ident
=
v
;
return
Ok
(());
}
Err
(
error
)
=>
return
Err
(
IntrospectionError
::
SerdeYaml
{
field
:
path
.into
(),
error
}),
}
}
});
}
else
{
// Handle assigning to a nested sub-field
set_nestable
.extend
(
quote!
{
#
name
=>
{
return
self
.#
ident
.set_field_from_yaml
(
tail
,
yaml
)
.map_err
(|
e
|
e
.with_prepended_prefix
(
head
));
}
});
// Handle if trying to assign to field marked with `#[introspection(nested)]`
// This is not currently supported, all of it's subfields must be assigned individually
error_if_nestable
.extend
(
quote!
{
#
name
=>
return
Err
(
IntrospectionError
::
AssignToNested
{
field
:
path
.into
(),
example
:
#
Type
::
FIELD_NAMES
.get
(
0
)
.unwrap_or
(
&
"<actually there's no fields in this struct :(>"
),
}),
})
}
}
// 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
.extend
(
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
#
set_nestable
_
=>
{
return
Err
(
IntrospectionError
::
NoSuchField
{
parent
:
""
.into
(),
field
:
head
.into
(),
expected
:
Self
::
FIELD_NAMES
,
});
}
}
}
None
=>
{
match
path
{
#
set_non_nestable
#
error_if_nestable
_
=>
{
return
Err
(
IntrospectionError
::
NoSuchField
{
parent
:
""
.into
(),
field
:
path
.into
(),
expected
:
Self
::
FIELD_NAMES
,
});
}
}
}
}
}
}
struct
Context
{
fields
:
Vec
<
FieldInfo
>
,
args
:
Args
,
}
struct
FieldInfo
{
name
:
String
,
ident
:
syn
::
Ident
,
attrs
:
FieldAttrs
,
#[allow(unused)]
field
:
syn
::
Field
,
}
struct
Args
{
crate_
:
syn
::
Path
,
}
impl
Args
{
fn
from_attributes
(
attrs
:
Vec
<
syn
::
Attribute
>
)
->
Result
<
Self
,
syn
::
Error
>
{
let
mut
result
=
Self
{
crate_
:
syn
::
parse2
(
quote!
(
crate
))
.unwrap
(),
};
for
attr
in
attrs
{
if
!
attr
.path
.is_ident
(
"introspection"
)
{
continue
;
}
let
meta
:
PathKeyValue
=
attr
.parse_args
()
?
;
if
meta
.key
.is_ident
(
"crate"
)
{
result
.crate_
=
meta
.value
;
}
}
Ok
(
result
)
}
}
struct
FieldAttrs
{
ignore
:
bool
,
nested
:
bool
,
}
impl
FieldAttrs
{
fn
from_attributes
(
attrs
:
Vec
<
syn
::
Attribute
>
)
->
Result
<
Self
,
syn
::
Error
>
{
let
mut
result
=
Self
{
ignore
:
false
,
nested
:
false
,
};
for
attr
in
attrs
{
if
!
attr
.path
.is_ident
(
"introspection"
)
{
continue
;
}
attr
.parse_args_with
(|
input
:
syn
::
parse
::
ParseStream
|
{
// `input` is a stream of those tokens right there
// `#[introspection(foo, bar, ...)]`
// ^^^^^^^^^^^^^
while
!
input
.is_empty
()
{
let
ident
=
input
.parse
::
<
syn
::
Ident
>
()
?
;
if
ident
==
"ignore"
{
result
.ignore
=
true
;
}
else
if
ident
==
"nested"
{
result
.nested
=
true
;
}
else
{
return
Err
(
syn
::
Error
::
new
(
ident
.span
(),
format!
(
"unknown attribute argument `{ident}`, expected one of `ignore`, `nested`"
),
));
}
if
!
input
.is_empty
()
{
input
.parse
::
<
syn
::
Token!
[,]
>
()
?
;
}
}
Ok
(())
})
?
;
}
Ok
(
result
)
}
}
fn
field_name
(
field
:
&
syn
::
Field
)
->
String
{
// TODO: consider using `quote::format_ident!` instead
let
mut
name
=
field
.ident
.as_ref
()
.unwrap
()
.to_string
();
if
name
.starts_with
(
"r#"
)
{
// Remove 2 leading characters
name
.remove
(
0
);
name
.remove
(
0
);
}
name
}
#[derive(Debug)]
struct
PathKeyValue
{
key
:
syn
::
Path
,
#[allow(unused)]
eq_token
:
syn
::
Token!
[
=
],
value
:
syn
::
Path
,
}
impl
syn
::
parse
::
Parse
for
PathKeyValue
{
fn
parse
(
input
:
syn
::
parse
::
ParseStream
)
->
Result
<
Self
,
syn
::
Error
>
{
Ok
(
Self
{
key
:
input
.parse
()
?
,
eq_token
:
input
.parse
()
?
,
value
:
input
.parse
()
?
,
})
}
}
This diff is collapsed.
Click to expand it.
src/config.rs
+
1
−
0
View file @
145a9ba8
...
...
@@ -2,6 +2,7 @@ use crate::address::Address;
use
crate
::
cli
::
args
;
use
crate
::
failure_domain
::
FailureDomain
;
use
crate
::
instance
::
InstanceId
;
use
crate
::
introspection
::
Introspection
;
use
crate
::
replicaset
::
ReplicasetId
;
use
crate
::
storage
;
use
crate
::
tier
::
Tier
;
...
...
This diff is collapsed.
Click to expand it.
src/introspection.rs
0 → 100644
+
269
−
0
View file @
145a9ba8
//! This module contains items intended to help with runtime introspection of
//! rust types. Currently it's mainly used for the [`PicodataConfig`] struct
//! to simplify the management of configuration parameters and relatetd stuff.
//!
//! The main functionality is implemented via the [`Introspection`] trait and
//! the corresponding derive macro. This trait currently facilitates the ability
//! to set and get fields of the struct via a path known at runtime. Also it
//! supports some basic struct field information.
//!
//! [`PicodataConfig`]: crate::config::PicodataConfig
pub
use
pico_proc_macro
::
Introspection
;
pub
trait
Introspection
{
const
FIELD_NAMES
:
&
'static
[
&
'static
str
];
/// Assign field of `self` described by `path` to value parsed from a given
/// `yaml` expression.
///
/// When using the `#[derive(Introspection)]` derive macro the implementation
/// uses the `serde_yaml` to decode values from yaml. This may change in the
/// future.
///
/// # Examples:
/// ```
/// use picodata::introspection::Introspection;
///
/// #[derive(Introspection, Default)]
/// #[introspection(crate = picodata)]
/// struct MyStruct {
/// number: i32,
/// text: String,
/// #[introspection(nested)]
/// nested: NestedStruct,
/// }
///
/// #[derive(Introspection, Default)]
/// #[introspection(crate = picodata)]
/// struct NestedStruct {
/// sub_field: f32,
/// }
///
/// let mut s = MyStruct::default();
/// s.set_field_from_yaml("number", "420").unwrap();
/// s.set_field_from_yaml("text", "hello world").unwrap();
/// s.set_field_from_yaml("nested.sub_field", "3.14").unwrap();
/// ```
fn
set_field_from_yaml
(
&
mut
self
,
path
:
&
str
,
yaml
:
&
str
)
->
Result
<
(),
IntrospectionError
>
;
}
#[derive(Debug,
thiserror::Error)]
pub
enum
IntrospectionError
{
#[error(
"{}"
,
Self::no_such_field_error_message(
.
parent,
.
field,
.
expected))]
NoSuchField
{
parent
:
String
,
field
:
String
,
expected
:
&
'static
[
&
'static
str
],
},
#[error(
"incorrect value for field '{field}': {error}"
)]
SerdeYaml
{
field
:
String
,
error
:
serde_yaml
::
Error
,
},
#[error(
"{expected} '{path}'"
)]
InvalidPath
{
path
:
String
,
expected
:
&
'static
str
,
},
#[error(
"field '{field}' has no nested sub-fields"
)]
NotNestable
{
field
:
String
},
#[error(
"field '{field}' cannot be assigned directly, must choose a sub-field (for example '{field}.{example}')"
)]
AssignToNested
{
field
:
String
,
example
:
&
'static
str
,
},
}
impl
IntrospectionError
{
fn
no_such_field_error_message
(
parent
:
&
str
,
field
:
&
str
,
expected
:
&
[
&
str
])
->
String
{
let
mut
res
=
String
::
with_capacity
(
128
);
if
!
parent
.is_empty
()
{
_
=
write!
(
&
mut
res
,
"{parent}: "
);
}
use
std
::
fmt
::
Write
;
_
=
write!
(
&
mut
res
,
"unknown field `{field}`"
);
let
mut
fields
=
expected
.iter
();
if
let
Some
(
first
)
=
fields
.next
()
{
_
=
write!
(
&
mut
res
,
", expected one of `{first}`"
);
for
next
in
fields
{
_
=
write!
(
&
mut
res
,
", `{next}`"
);
}
}
else
{
_
=
write!
(
&
mut
res
,
", there are no fields at all"
);
}
res
}
pub
fn
prepend_prefix
(
&
mut
self
,
prefix
:
&
str
)
{
match
self
{
Self
::
NotNestable
{
field
}
=>
{
*
field
=
format!
(
"{prefix}.{field}"
);
}
Self
::
InvalidPath
{
path
,
..
}
=>
{
*
path
=
format!
(
"{prefix}.{path}"
);
}
Self
::
NoSuchField
{
parent
,
..
}
=>
{
if
parent
.is_empty
()
{
*
parent
=
prefix
.into
();
}
else
{
*
parent
=
format!
(
"{prefix}.{parent}"
);
}
}
Self
::
SerdeYaml
{
field
,
..
}
=>
{
*
field
=
format!
(
"{prefix}.{field}"
);
}
Self
::
AssignToNested
{
field
,
..
}
=>
{
*
field
=
format!
(
"{prefix}.{field}"
);
}
}
}
#[inline(always)]
pub
fn
with_prepended_prefix
(
mut
self
,
prefix
:
&
str
)
->
Self
{
self
.prepend_prefix
(
prefix
);
self
}
}
#[cfg(test)]
mod
test
{
use
super
::
*
;
#[test]
fn
derive_set_field_from_yaml
()
{
#[derive(Default,
Debug,
Introspection)]
struct
S
{
x
:
i32
,
y
:
f32
,
s
:
String
,
v
:
Vec
<
String
>
,
#[introspection(nested)]
r
#
struct
:
Nested
,
#[introspection(ignore)]
ignored
:
serde_yaml
::
Value
,
}
#[derive(Default,
Debug,
Introspection)]
struct
Nested
{
a
:
String
,
b
:
i64
,
#[introspection(nested)]
empty
:
Empty
,
}
#[derive(Default,
Debug,
Introspection)]
struct
Empty
{}
let
mut
s
=
S
::
default
();
//
// Check error cases
//
let
e
=
s
.set_field_from_yaml
(
"a"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"unknown field `a`, expected one of `x`, `y`, `s`, `v`, `struct`"
);
let
e
=
s
.set_field_from_yaml
(
".x"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"expected a field name before '.x'"
);
let
e
=
s
.set_field_from_yaml
(
"&-*%?!"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"unknown field `&-*%?!`, expected one of `x`, `y`, `s`, `v`, `struct`"
);
let
e
=
s
.set_field_from_yaml
(
"x.foo.bar"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"field 'x' has no nested sub-fields"
);
let
e
=
s
.set_field_from_yaml
(
"struct"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"field 'struct' cannot be assigned directly, must choose a sub-field (for example 'struct.a')"
);
let
e
=
s
.set_field_from_yaml
(
"struct.empty"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"field 'struct.empty' cannot be assigned directly, must choose a sub-field (for example 'struct.empty.<actually there's no fields in this struct :(>')"
);
let
e
=
s
.set_field_from_yaml
(
"struct.foo"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"struct: unknown field `foo`, expected one of `a`, `b`, `empty`"
);
let
e
=
s
.set_field_from_yaml
(
"struct.a.bar"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"field 'struct.a' has no nested sub-fields"
);
let
e
=
s
.set_field_from_yaml
(
"struct.a.."
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"expected a field name after 'struct.a.'"
);
let
e
=
s
.set_field_from_yaml
(
"x."
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"expected a field name after 'x.'"
);
let
e
=
s
.set_field_from_yaml
(
"x"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"incorrect value for field 'x': invalid type: string
\"
foo
\"
, expected i32"
);
let
e
=
s
.set_field_from_yaml
(
"x"
,
"'420'"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"incorrect value for field 'x': invalid type: string
\"
420
\"
, expected i32"
);
let
e
=
s
.set_field_from_yaml
(
"x"
,
"'420'"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"incorrect value for field 'x': invalid type: string
\"
420
\"
, expected i32"
);
let
e
=
s
.set_field_from_yaml
(
"ignored"
,
"foo"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"unknown field `ignored`, expected one of `x`, `y`, `s`, `v`, `struct`"
);
assert_eq!
(
s
.ignored
,
serde_yaml
::
Value
::
default
());
let
e
=
s
.set_field_from_yaml
(
"struct.empty.foo"
,
"bar"
)
.unwrap_err
();
assert_eq!
(
e
.to_string
(),
"struct.empty: unknown field `foo`, there are no fields at all"
);
//
// Check success cases
//
s
.set_field_from_yaml
(
"v"
,
"[1, 2, 3]"
)
.unwrap
();
assert_eq!
(
&
s
.v
,
&
[
"1"
,
"2"
,
"3"
]);
s
.set_field_from_yaml
(
"v"
,
"['foo',
\"
bar
\"
, baz]"
)
.unwrap
();
assert_eq!
(
&
s
.v
,
&
[
"foo"
,
"bar"
,
"baz"
]);
s
.set_field_from_yaml
(
"x"
,
"420"
)
.unwrap
();
assert_eq!
(
s
.x
,
420
);
s
.set_field_from_yaml
(
"y"
,
"13"
)
.unwrap
();
assert_eq!
(
s
.y
,
13.0
);
s
.set_field_from_yaml
(
"y"
,
"13.37"
)
.unwrap
();
assert_eq!
(
s
.y
,
13.37
);
s
.set_field_from_yaml
(
"s"
,
"13.37"
)
.unwrap
();
assert_eq!
(
s
.s
,
"13.37"
);
s
.set_field_from_yaml
(
"s"
,
"foo bar"
)
.unwrap
();
assert_eq!
(
s
.s
,
"foo bar"
);
s
.set_field_from_yaml
(
"s"
,
"'foo bar'"
)
.unwrap
();
assert_eq!
(
s
.s
,
"foo bar"
);
s
.set_field_from_yaml
(
" struct . a "
,
"aaaa"
)
.unwrap
();
assert_eq!
(
s
.r
#
struct
.a
,
"aaaa"
);
s
.set_field_from_yaml
(
"struct.b"
,
" 0xbbbb "
)
.unwrap
();
assert_eq!
(
s
.r
#
struct
.b
,
0xbbbb
);
}
}
This diff is collapsed.
Click to expand it.
src/lib.rs
+
1
−
0
View file @
145a9ba8
...
...
@@ -47,6 +47,7 @@ pub mod governor;
pub
mod
http_server
;
pub
mod
info
;
pub
mod
instance
;
pub
mod
introspection
;
pub
mod
ipc
;
pub
mod
kvcell
;
pub
mod
r
#
loop
;
...
...
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