-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[db-macros] support for children without names in lookup_resource!
#7508
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,10 @@ pub struct Input { | |
ancestors: Vec<String>, | ||
/// unordered list of resources that are direct children of this resource | ||
/// (e.g., for a Project, these would include "Instance" and "Disk") | ||
// N.B. that this is no longer used by the the parent's resource codegen, | ||
// but it may be again in future. Therefore, continue parsing it so that | ||
// the resource definitions don't change, but allow it to be unused. | ||
#[allow(dead_code)] | ||
children: Vec<String>, | ||
/// whether lookup by name is supported (usually within the parent collection) | ||
lookup_by_name: bool, | ||
|
@@ -127,11 +131,6 @@ pub struct Config { | |
/// [`authz_silo`, `authz_project`]) | ||
path_authz_names: Vec<syn::Ident>, | ||
|
||
// Child resources | ||
/// list of names of child resources, in the same form and with the same | ||
/// assumptions as [`Input::name`] (i.e., typically PascalCase) | ||
child_resources: Vec<String>, | ||
|
||
// Parent resource, if any | ||
/// Information about the parent resource, if any | ||
parent: Option<Resource>, | ||
|
@@ -160,7 +159,6 @@ impl Config { | |
.collect(); | ||
path_authz_names.push(resource.authz_name.clone()); | ||
|
||
let child_resources = input.children; | ||
let parent = input.ancestors.last().map(|s| Resource::for_name(s)); | ||
let silo_restricted = !input.visible_outside_silo | ||
&& input.ancestors.iter().any(|s| s == "Silo"); | ||
|
@@ -177,7 +175,6 @@ impl Config { | |
path_types, | ||
path_authz_names, | ||
parent, | ||
child_resources, | ||
lookup_by_name: input.lookup_by_name, | ||
primary_key_columns, | ||
soft_deletes: input.soft_deletes, | ||
|
@@ -221,22 +218,22 @@ pub fn lookup_resource( | |
let resource_name = &config.resource.name; | ||
let the_basics = generate_struct(&config); | ||
let misc_helpers = generate_misc_helpers(&config); | ||
let child_selectors = generate_child_selectors(&config); | ||
let child_selector = generate_child_selector(&config); | ||
let lookup_methods = generate_lookup_methods(&config); | ||
let database_functions = generate_database_functions(&config); | ||
|
||
Ok(quote! { | ||
#the_basics | ||
|
||
impl<'a> #resource_name<'a> { | ||
#child_selectors | ||
|
||
#lookup_methods | ||
|
||
#misc_helpers | ||
|
||
#database_functions | ||
} | ||
|
||
#child_selector | ||
}) | ||
} | ||
|
||
|
@@ -287,63 +284,70 @@ fn generate_struct(config: &Config) -> TokenStream { | |
} | ||
} | ||
|
||
/// Generates the child selectors for this resource | ||
/// Generates the child selector for this resource's parent | ||
/// | ||
/// For example, for the "Project" resource with child resources "Instance" and | ||
/// "Disk", this will generate the `Project::instance_name()` and | ||
/// `Project::disk_name()` functions. | ||
fn generate_child_selectors(config: &Config) -> TokenStream { | ||
let child_resource_types: Vec<_> = | ||
config.child_resources.iter().map(|c| format_ident!("{}", c)).collect(); | ||
let child_selector_fn_names: Vec<_> = config | ||
.child_resources | ||
.iter() | ||
.map(|c| format_ident!("{}_name", heck::AsSnakeCase(c).to_string())) | ||
.collect(); | ||
let child_selector_fn_names_owned: Vec<_> = config | ||
.child_resources | ||
.iter() | ||
.map(|c| { | ||
format_ident!("{}_name_owned", heck::AsSnakeCase(c).to_string()) | ||
}) | ||
.collect(); | ||
let child_selector_fn_docs: Vec<_> = config | ||
.child_resources | ||
.iter() | ||
.map(|child| { | ||
format!( | ||
"Select a resource of type {} within this {}, \ | ||
identified by its name", | ||
child, config.resource.name, | ||
) | ||
}) | ||
.collect(); | ||
/// For example, for the "Instance" resource with parent resource "Project", | ||
/// this will generate the `Project::instance_name()` and | ||
/// `Project::instance_name_owned()` functions. | ||
/// | ||
/// This is generated by the child resource codegen, rather than by the parent | ||
/// resource codegen, since such functions are only generated for resources with | ||
/// `lookup_by_name = true`. Whether or not this is enabled for the child | ||
/// resource is not known when generating the parent resource code, so it's | ||
/// performed by the child instead. | ||
fn generate_child_selector(config: &Config) -> TokenStream { | ||
// If this resource cannot be looked up by name, we don't need to generate | ||
// child selectors on the parent resource. | ||
if !config.lookup_by_name { | ||
return quote! {}; | ||
} | ||
Comment on lines
+301
to
+303
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To confirm: This is like, the "functional crux" of the change, the rest is moving the generation from "generated in parent declaration" to "generated in child declaration", which is more of a refactor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's correct. Skipping this generation when the child resource can only be looked up by ID is necessary to stop generating code that won't compile for such resources. Moving around where that code gets generated is only necessary because otherwise, we wouldn't have the information about whether the child is looked up by name. |
||
|
||
// The child selector is generated for the parent resource type. If there | ||
// isn't one, nothing to do here. | ||
let Some(ref parent) = config.parent else { return quote! {} }; | ||
|
||
let parent_resource_type = &parent.name; | ||
let child_selector_fn_name = format_ident!( | ||
"{}_name", | ||
heck::AsSnakeCase(config.resource.name.to_string()).to_string() | ||
); | ||
|
||
let child_selector_fn_name_owned = format_ident!( | ||
"{}_name_owned", | ||
heck::AsSnakeCase(config.resource.name.to_string()).to_string() | ||
); | ||
let child_resource_type = &config.resource.name; | ||
|
||
let child_selector_fn_docs = format!( | ||
"Select a resource of type {child_resource_type} within this \ | ||
{parent_resource_type}, identified by its name", | ||
); | ||
|
||
quote! { | ||
#( | ||
impl<'a> #parent_resource_type<'a> { | ||
#[doc = #child_selector_fn_docs] | ||
pub fn #child_selector_fn_names<'b, 'c>( | ||
pub fn #child_selector_fn_name<'b, 'c>( | ||
self, | ||
name: &'b Name | ||
) -> #child_resource_types<'c> | ||
) -> #child_resource_type<'c> | ||
where | ||
'a: 'c, | ||
'b: 'c, | ||
{ | ||
#child_resource_types::Name(self, name) | ||
#child_resource_type::Name(self, name) | ||
} | ||
|
||
#[doc = #child_selector_fn_docs] | ||
pub fn #child_selector_fn_names_owned<'c>( | ||
pub fn #child_selector_fn_name_owned<'c>( | ||
self, | ||
name: Name, | ||
) -> #child_resource_types<'c> | ||
) -> #child_resource_type<'c> | ||
where | ||
'a: 'c, | ||
{ | ||
#child_resource_types::OwnedName(self, name) | ||
#child_resource_type::OwnedName(self, name) | ||
} | ||
)* | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I'll defer to @davepacheco here but I wouldn't be opposed to removing this as an input.
(We could always re-add it later if necessary, but it seems nice to only need to specify the parent, not the parent + children)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm, I'd also be fine with removing it. My thinking was that, presently, the resource-definition file has lists of children in it, and we could remove that, but if we wanted to later add codegen to the parents that depended on knowing all the children, we'd have to add that information again. I felt like that could potentially be a minor hassle: if we add more child resources, we wouldn't be able to just check out the code from before the lists were removed, and we'd instead have to manually reconstruct them. But...maybe that isn't actually a big deal, IDK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove this if we're ignoring it. We can always go back in the Git history, but in the meantime, having it here but ignored seems confusing to readers (especially if the values supplied wind up drifting from what would be correct, which seems likely).
Is this the only thing
children
was used for? (If so, why would it not work to leave outWebhookSecret
from the list of children ofWebhookReceiver
?)If we can get away with this, it does seem better. I imagine I just didn't think of it when I first did this work. It's always been less than ideal that you had to specify both parent and children!