Skip to content

add new generator and helpers to return values for boolean pointers #637

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

Merged
merged 5 commits into from
Oct 14, 2021

Conversation

kim-tsao
Copy link
Contributor

@kim-tsao kim-tsao commented Oct 6, 2021

What does this PR do?:

Adds new helper functions to return values for the various boolean pointer properties. The primary consumer will be the library parser which will use these helpers to set the default values on the flattened devfile obj so clients can avoid encountering nil values.

Which issue(s) this PR fixes:

#615

PR acceptance criteria:

Testing and documentation do not need to be complete in order for this PR to be approved. We just need to ensure tracking issues are opened.

  • Open new test/doc issues under the devfile/api repo
  • Check each criteria if:
  • There is a separate tracking issue. Add the issue link under the criteria
    or
  • test/doc updates are made as part of this PR
  • If unchecked, explain why it's not needed
  • Unit/Functional tests

Testing will be done as part of changes to the Library parser

Marker help has been added

  • Client Impact

Clients can retrieve the default boolean values with the helper functions or via the library parser once the changes are in place

How to test changes / Special notes to the reviewer:

@kim-tsao kim-tsao marked this pull request as ready for review October 6, 2021 18:54

// Generator generates helper functions that are used to return values for boolean pointer fields.
//
// The helper function takes as a parameter, the `devfile:default:generate` annotated type and returns the value of the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be?

Suggested change
// The helper function takes as a parameter, the `devfile:default:generate` annotated type and returns the value of the
// The helper function takes as a parameter, the `devfile:default:value` annotated type and returns the value of the

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking up on this. It should be devfile:helper:generate. I changed the marker name last minute to better reflect what the action should be

Copy link
Contributor

@amisevsk amisevsk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a generator is the right approach in general here -- it's a lot of code (that could have bugs of its own) to replace writing out 6-7 functions explicitly.

func IsDefault(in *CommandGroup) bool {
  return getBoolOrDefault(in.IsDefault, false)
}
// etc...

func getBoolOrDefault(input *bool, defaultVal bool) bool {
  if input != nil {
    return *input
  }
  return defaultVal
}

We could also consider attaching the functions to the specific types, in order to avoid populating the v1alpha2 namespace with helpers, e.g.

func (c *ExecCommand) HotReloadCapable() bool {
  return getBoolOrDefault(c.HotReloadCapable, false)
}

Comment on lines 118 to 126
defaultVal := helper.defaultVal
if fName != "MountSources" {
buf.WriteString(helperFunction + `
return ` + defaultVal + ` }`)
} else {
buf.WriteString(helperFunction + `else {
if DedicatedPod(in) { return false }
return ` + defaultVal + ` }}`)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's appropriate to have field name-specific functionality in the generator -- it should do what it says for every instance of the marker.

Handling for mountSources should probably be an explicit function instead of a generated one.

Copy link
Contributor Author

@kim-tsao kim-tsao Oct 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to set a conditional expression in the marker value for mountSources but ran into problems so I resorted to plan B, which was to handle this in the code (yes, it's a bit hackish). I can create this function manually for now.

@kim-tsao
Copy link
Contributor Author

kim-tsao commented Oct 8, 2021

I'm not sure a generator is the right approach in general here -- it's a lot of code (that could have bugs of its own) to replace writing out 6-7 functions explicitly.

@amisevsk, I went with the generator approach because the k8s structs are the "single source of truth" and I wanted the default values to be extracted from this source. If we ever wanted to change the values in the future or introduce new boolean pointer fields, it can be done in one place and kept in sync.

func IsDefault(in *CommandGroup) bool {
  return getBoolOrDefault(in.IsDefault, false)
}
// etc...

func getBoolOrDefault(input *bool, defaultVal bool) bool {
  if input != nil {
    return *input
  }
  return defaultVal
}

We could also consider attaching the functions to the specific types, in order to avoid populating the v1alpha2 namespace with helpers, e.g.

func (c *ExecCommand) HotReloadCapable() bool {
  return getBoolOrDefault(c.HotReloadCapable, false)
}

I thought having the containing struct type as the receiver makes this a "getter". I originally had this in place but you were not in favour of using getters: #545 (comment)

@amisevsk
Copy link
Contributor

I thought having the containing struct type as the receiver makes this a "getter". I originally had this in place but you were not in favour of using getters: #545 (comment)

I am contradicting myself, and I do apologize for that -- sometimes seeing it written out in code makes me realize I wasn't thinking through it clearly 😄. I'm fine with either approach (and honestly am not sure what I was thinking in that comment). The benefit, in my eyes, of implementing it as a method would be autocomplete would suggest to direct field access (returning a *bool) and also the function (returning a bool).

I think your reasoning for using generators makes sense, my main concern is that the tag should be straightforward (if I put this here, generate a helper method of some sort to return my default value). I'm 👍 with using it for all fields save mountSources, where I think we should figure out something better.

@kim-tsao
Copy link
Contributor Author

I thought having the containing struct type as the receiver makes this a "getter". I originally had this in place but you were not in favour of using getters: #545 (comment)

I am contradicting myself, and I do apologize for that -- sometimes seeing it written out in code makes me realize I wasn't thinking through it clearly 😄. I'm fine with either approach (and honestly am not sure what I was thinking in that comment). The benefit, in my eyes, of implementing it as a method would be autocomplete would suggest to direct field access (returning a *bool) and also the function (returning a bool).

I think your reasoning for using generators makes sense, my main concern is that the tag should be straightforward (if I put this here, generate a helper method of some sort to return my default value). I'm 👍 with using it for all fields save mountSources, where I think we should figure out something better.

I'm in favour of using getters but we are bending the rules a bit because the fields should be private but we need to them to be public in order for deserialization to work properly. This will result in a name conflict between the field and method names (if we adopt the Go guidance to assign the capitalized field name as the method name) which unfortunately, leaves me with no choice but to prefix the methods with Get.

@amisevsk
Copy link
Contributor

This will result in a name conflict between the field and method names (if we adopt the Go guidance to assign the capitalized field name as the method name) which unfortunately, leaves me with no choice but to prefix the methods with Get.

I hadn't thought about that -- good point. Sorry for all the needless delay in this case :(

Copy link
Contributor

@amisevsk amisevsk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, a couple of optional suggestions.

Comment on lines 114 to 118
getterMethod := `
// Get` + fName + ` returns the value of the boolean property. If unset, it's the default value specified in the devfile:default:value marker` + `
func (in *` + cmd.Name + `) Get` + fName + `() bool {
return getBoolOrDefault(in.` + fName + `,` + defaultVal + `)}`
buf.WriteString(getterMethod)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider using fmt.Sprintf here, but it's optional.

Suggested change
getterMethod := `
// Get` + fName + ` returns the value of the boolean property. If unset, it's the default value specified in the devfile:default:value marker` + `
func (in *` + cmd.Name + `) Get` + fName + `() bool {
return getBoolOrDefault(in.` + fName + `,` + defaultVal + `)}`
buf.WriteString(getterMethod)
fmt.Sprintf(`
// Get %[1]s returns the value of the boolean property. If unset, it's the default value specified in the devfile:default:value marker
func (in *%[2]s) Get%[1]s() bool {
return getBoolOrDefault(in.%[1]s, %[3]s)
}`, fName, cmd.Name, defaultVal)
buf.WriteString(getterMethod)

(looks like markdown formatting is broken by including a backtick in the sample in the sample :/ )

func (in *Endpoint) GetSecure() bool {
return getBoolOrDefault(in.Secure, false)
}
func getBoolOrDefault(input *bool, defaultVal bool) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be ideal to have a newline before this function, so that the generated file is unchanged by executing go fmt ./...

@openshift-ci
Copy link

openshift-ci bot commented Oct 14, 2021

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: amisevsk, kim-tsao

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@kim-tsao
Copy link
Contributor Author

This will result in a name conflict between the field and method names (if we adopt the Go guidance to assign the capitalized field name as the method name) which unfortunately, leaves me with no choice but to prefix the methods with Get.

I hadn't thought about that -- good point. Sorry for all the needless delay in this case :(

No worries, it's good to hash out the details. Thanks for the thorough review as always!

@openshift-ci
Copy link

openshift-ci bot commented Oct 14, 2021

New changes are detected. LGTM label has been removed.

@openshift-ci openshift-ci bot removed the lgtm label Oct 14, 2021
@kim-tsao kim-tsao merged commit 9d4037b into devfile:main Oct 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants