-
Notifications
You must be signed in to change notification settings - Fork 203
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
Conversation
ef458d4
to
02bfb2a
Compare
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
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.
This looks great - I really like the split-out type- and property converter.
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) |
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.
Maybe this? It seems a bit cleaner than constructing result and then assigning typeConverter.
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), | |
} | |
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.
Agreed. I've oscilated a bit on the style for initializing interrelated members, I think this works better.
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 | ||
} |
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.
These methods are simple enough that I think the visitor would be clearer with them inline in NewFunctionInjector.
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.
They are simple functions, but giving them names makes the intentions super clear when the TypeVisitorBuilder
is used. #wontfix
// 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 | ||
} |
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.
Same with this.
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 think there's benefit in giving intention revealing names. #wontfix
} | ||
|
||
func (f *StorageTypeFactory) process() error { | ||
err := f.pendingStorageConversion.Process(f.createStorageVariant) |
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'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.
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.
Agree: it wasn't clear to me if the queue type was actually needed here or not (I think not?)
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.
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?
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 |
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.
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.
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'm sure I've seen this - can change it if not idiomatic
} | ||
|
||
func (f *StorageTypeFactory) process() error { | ||
err := f.pendingStorageConversion.Process(f.createStorageVariant) |
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.
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) |
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.
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?
83b43f0
to
49ebcfe
Compare
49ebcfe
to
da79cc3
Compare
da79cc3
to
52c3aaf
Compare
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.
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 |
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.
namespace
-> package
(I think that's the appropriate Golang terminology?)
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.
Fixed.
@@ -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 |
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.
// definitions containing the results of the transfomration, or possibly an error | |
// definitions containing the results of the transformation, or possibly an error |
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.
Missed this fix - included in my current branch instead.
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.
LGTM!
Co-authored-by: Matthew Christopher <matthchr@microsoft.com>
MC> A few more minor suggestions - last main big topic is the queues + processing which I think Sorry, I missed responding to those comments - I've removed the use of queues completely. |
a2f32fa
to
5435b85
Compare
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:
How does this PR make you feel:
#headingintherightdirection
If applicable: