Skip to content
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

Create storage conversion helper functions #1495

Merged
merged 63 commits into from
May 27, 2021

Conversation

theunrepentantgeek
Copy link
Member

What this PR does / why we need it:

Create storage variants of all resources and complex object types, injecting helper conversion functions for conversion back and forth. These helper functions will be used to build the actual conversions in a later PR.

Special notes for your reviewer:

The crux of operation is the StorageTypeFactory (see storage_type_factory.go) which uses some distinct utility classes to make the required changes.

Also includes:

  • Some improvements to debugger output for various types
  • Use of new abstractions PropertyContainer and FunctionContainer
  • Improvements to field & parameter names, and other code gardening

How does this PR make you feel:
#headingintherightdirection

If applicable:

  • this PR contains documentation
  • this PR contains tests

@codecov-commenter
Copy link

codecov-commenter commented May 24, 2021

Codecov Report

Merging #1495 (e28774c) into master (7a9f076) will decrease coverage by 0.85%.
The diff coverage is 23.21%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1495      +/-   ##
==========================================
- Coverage   63.77%   62.91%   -0.86%     
==========================================
  Files         169      174       +5     
  Lines       11267    11497     +230     
==========================================
+ Hits         7185     7233      +48     
- Misses       3433     3611     +178     
- Partials      649      653       +4     
Impacted Files Coverage Δ
hack/generator/pkg/astmodel/known_locals_set.go 100.00% <ø> (ø)
...ck/generator/pkg/astmodel/package_reference_set.go 94.73% <0.00%> (-5.27%) ⬇️
hack/generator/pkg/astmodel/resource_type.go 71.00% <0.00%> (-0.30%) ⬇️
...enerator/pkg/astmodel/storage_package_reference.go 90.00% <0.00%> (+20.00%) ⬆️
hack/generator/pkg/astmodel/type_visitor.go 51.61% <0.00%> (ø)
hack/generator/pkg/astmodel/types.go 56.97% <0.00%> (-5.05%) ⬇️
hack/generator/pkg/astmodel/validated_type.go 81.01% <0.00%> (ø)
...rator/pkg/codegen/pipeline_create_storage_types.go 10.34% <0.00%> (+7.18%) ⬆️
...rator/pkg/codegen/pipeline_mark_storage_version.go 60.46% <0.00%> (-6.21%) ⬇️
...generator/pkg/codegen/storage/function_injector.go 0.00% <0.00%> (ø)
... and 20 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7a9f076...e28774c. Read the comment docs.

@theunrepentantgeek theunrepentantgeek self-assigned this May 24, 2021
@theunrepentantgeek theunrepentantgeek added this to the codegen-alpha-0 milestone May 24, 2021
Copy link
Member

@babbageclunk babbageclunk left a comment

Choose a reason for hiding this comment

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

This looks great - I really like the split-out type- and property converter.

Comment on lines 30 to 42
result := &StorageTypeFactory{
service: service,
types: make(astmodel.Types),
pendingStorageConversion: astmodel.MakeTypeNameQueue(),
pendingConversionInjection: astmodel.MakeTypeNameQueue(),
pendingMarkAsHubVersion: astmodel.MakeTypeNameQueue(),
idFactory: idFactory,
conversionMap: make(map[astmodel.PackageReference]astmodel.PackageReference),
functionInjector: NewFunctionInjector(),
resourceHubMarker: NewHubVersionMarker(),
}

result.typeConverter = NewTypeConverter(result.types)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this? It seems a bit cleaner than constructing result and then assigning typeConverter.

Suggested change
result := &StorageTypeFactory{
service: service,
types: make(astmodel.Types),
pendingStorageConversion: astmodel.MakeTypeNameQueue(),
pendingConversionInjection: astmodel.MakeTypeNameQueue(),
pendingMarkAsHubVersion: astmodel.MakeTypeNameQueue(),
idFactory: idFactory,
conversionMap: make(map[astmodel.PackageReference]astmodel.PackageReference),
functionInjector: NewFunctionInjector(),
resourceHubMarker: NewHubVersionMarker(),
}
result.typeConverter = NewTypeConverter(result.types)
types := make(astmodel.Types)
result := &StorageTypeFactory{
service: service,
types: types,
pendingStorageConversion: astmodel.MakeTypeNameQueue(),
pendingConversionInjection: astmodel.MakeTypeNameQueue(),
pendingMarkAsHubVersion: astmodel.MakeTypeNameQueue(),
idFactory: idFactory,
conversionMap: make(map[astmodel.PackageReference]astmodel.PackageReference),
functionInjector: NewFunctionInjector(),
resourceHubMarker: NewHubVersionMarker(),
typeConverter: NewTypeConverter(types),
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. I've oscilated a bit on the style for initializing interrelated members, I think this works better.

Comment on lines +34 to +47
func (_ *FunctionInjector) injectFunctionIntoObject(
_ *astmodel.TypeVisitor, ot *astmodel.ObjectType, ctx interface{}) (astmodel.Type, error) {
fn := ctx.(astmodel.Function)
return ot.WithFunction(fn), nil
}

// injectFunctionIntoResource takes the function provided as a context and includes it on the
// provided resource type
func (_ *FunctionInjector) injectFunctionIntoResource(
_ *astmodel.TypeVisitor, rt *astmodel.ResourceType, ctx interface{}) (astmodel.Type, error) {
fn := ctx.(astmodel.Function)
return rt.WithFunction(fn), nil
}
Copy link
Member

Choose a reason for hiding this comment

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

These methods are simple enough that I think the visitor would be clearer with them inline in NewFunctionInjector.

Copy link
Member Author

@theunrepentantgeek theunrepentantgeek May 25, 2021

Choose a reason for hiding this comment

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

They are simple functions, but giving them names makes the intentions super clear when the TypeVisitorBuilder is used. #wontfix

Comment on lines 26 to 36
// MarkAsStorageVersion mark the supplied type definition as the storage version
func (m *HubVersionMarker) MarkAsStorageVersion(def astmodel.TypeDefinition) (astmodel.TypeDefinition, error) {
return m.visitor.VisitDefinition(def, nil)
}

// markResourceAsStorageVersion marks the supplied resource as the canonical hub (storage) version
func (m *HubVersionMarker) markResourceAsStorageVersion(
_ *astmodel.TypeVisitor, rt *astmodel.ResourceType, _ interface{}) (astmodel.Type, error) {
return rt.MarkAsStorageVersion(), nil
}
Copy link
Member

Choose a reason for hiding this comment

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

Same with this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think there's benefit in giving intention revealing names. #wontfix

}

func (f *StorageTypeFactory) process() error {
err := f.pendingStorageConversion.Process(f.createStorageVariant)
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure the queues are buying you much here - each one is only populated from one place, and they're processed one after the other, so they could have just been slices. That said, they don't really hurt either. I was just expecting the population logic to be more complicated.

Copy link
Member

Choose a reason for hiding this comment

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

Agree: it wasn't clear to me if the queue type was actually needed here or not (I think not?)

Copy link
Member

Choose a reason for hiding this comment

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

Would the whole relationship between types and pendingStorageConversion be simpler if instead you just did away with pendingStorageConversion entirely, had Add add to f.Types, and then had a result astmodel.Types which was produced by process?

So:

    inputTypes astmodel.Types // input types to be processed by process() - this is filled out by Add

The set of types that are pendingStorageConversion then is just all inputTypes? So you loop over that and call createStorageVariant for each one, except now that you're not doing it using the queue-processing function you have a bit more flexibility in return type, so you could just have createStorageVariant return the new TypeDefinition it created, which you could store in a local result astmodel.Types variable inside process()? This code ends up looking a lot more like a mini-pipeline stage (in that it's dealing with sets of types?)

I feel like the separation of the set of input types and the set of output types helps reason about things like createStorageVariant more easily, as that operates on f.types, but if f.types is simultanously being modified to include the result of added storage variants there's more confusion in "what exactly is in f.types" than there has to be?

hack/generator/pkg/astbuilder/builder.go Show resolved Hide resolved
hack/generator/pkg/astmodel/std_references.go Outdated Show resolved Hide resolved
hack/generator/pkg/astmodel/storage_conversion_function.go Outdated Show resolved Hide resolved
hack/generator/pkg/codegen/storage/property_converter.go Outdated Show resolved Hide resolved
pendingStorageConversion astmodel.TypeNameQueue // Queue of types that need storage variants created for them
pendingConversionInjection astmodel.TypeNameQueue // Queue of types that need conversion functions injected
pendingMarkAsHubVersion astmodel.TypeNameQueue // Queue of types that need to be flagged as the hub storage version
idFactory astmodel.IdentifierFactory // Factory for creating identifiers
Copy link
Member

Choose a reason for hiding this comment

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

minor, style: Is the description in a tabbed-over string a standard golang style? (@babbageclunk knows maybe?)

It reads pretty cleanly here, but also isn't a "golang-style" comment as it's not starting with the name of the variable it refers to?

Mostly just wondering for my own knowledge.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm sure I've seen this - can change it if not idiomatic

}

func (f *StorageTypeFactory) process() error {
err := f.pendingStorageConversion.Process(f.createStorageVariant)
Copy link
Member

Choose a reason for hiding this comment

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

Agree: it wasn't clear to me if the queue type was actually needed here or not (I think not?)

}

func (f *StorageTypeFactory) process() error {
err := f.pendingStorageConversion.Process(f.createStorageVariant)
Copy link
Member

Choose a reason for hiding this comment

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

Would the whole relationship between types and pendingStorageConversion be simpler if instead you just did away with pendingStorageConversion entirely, had Add add to f.Types, and then had a result astmodel.Types which was produced by process?

So:

    inputTypes astmodel.Types // input types to be processed by process() - this is filled out by Add

The set of types that are pendingStorageConversion then is just all inputTypes? So you loop over that and call createStorageVariant for each one, except now that you're not doing it using the queue-processing function you have a bit more flexibility in return type, so you could just have createStorageVariant return the new TypeDefinition it created, which you could store in a local result astmodel.Types variable inside process()? This code ends up looking a lot more like a mini-pipeline stage (in that it's dealing with sets of types?)

I feel like the separation of the set of input types and the set of output types helps reason about things like createStorageVariant more easily, as that operates on f.types, but if f.types is simultanously being modified to include the result of added storage variants there's more confusion in "what exactly is in f.types" than there has to be?

hack/generator/pkg/codegen/storage/type_converter.go Outdated Show resolved Hide resolved
@theunrepentantgeek theunrepentantgeek force-pushed the feature/storage-conversions branch from 83b43f0 to 49ebcfe Compare May 25, 2021 23:34
@theunrepentantgeek theunrepentantgeek force-pushed the feature/storage-conversions branch from 49ebcfe to da79cc3 Compare May 26, 2021 00:12
@theunrepentantgeek theunrepentantgeek force-pushed the feature/storage-conversions branch from da79cc3 to 52c3aaf Compare May 26, 2021 00:18
Copy link
Member

@matthchr matthchr left a comment

Choose a reason for hiding this comment

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

A few more minor suggestions - last main big topic is the queues + processing which I think @babbageclunk and I had comments on. (Not necessarily agitating for a specific change there but you hadn't responded to those comments yet so unsure if you're planning changes there or not).

}

// shortCircuitNamesOfSimpleTypes redirects or replaces TypeNames
// o If a TypeName points into an API namespace, it is redirected into the appropriate storage namespace
Copy link
Member

Choose a reason for hiding this comment

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

namespace -> package (I think that's the appropriate Golang terminology?)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

hack/generator/pkg/codegen/storage/storage_type_factory.go Outdated Show resolved Hide resolved
@@ -204,3 +203,22 @@ func (types Types) ResolveResourceStatusDefinition(
// preserve outer spec name
return resourceStatusDef.WithName(statusName), nil
}

// Process applies a func to transform all members of this set of type definitions, returning a new set of type
// definitions containing the results of the transfomration, or possibly an error
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// definitions containing the results of the transfomration, or possibly an error
// definitions containing the results of the transformation, or possibly an error

Copy link
Member Author

Choose a reason for hiding this comment

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

Missed this fix - included in my current branch instead.

Copy link
Member

@matthchr matthchr left a comment

Choose a reason for hiding this comment

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

LGTM!

theunrepentantgeek and others added 2 commits May 27, 2021 07:50
@theunrepentantgeek
Copy link
Member Author

MC> A few more minor suggestions - last main big topic is the queues + processing which I think
MC> @babbageclunk and I had comments on.

Sorry, I missed responding to those comments - I've removed the use of queues completely.

@theunrepentantgeek theunrepentantgeek merged commit 7a6e709 into master May 27, 2021
@theunrepentantgeek theunrepentantgeek deleted the feature/storage-conversions branch May 27, 2021 01:45
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.

4 participants