cloudform icon indicating copy to clipboard operation
cloudform copied to clipboard

Increase type safety

Open wolverian opened this issue 8 years ago • 5 comments

Feel free to reject this as something out of cloudform's scope:

It'd be great if cf.Fn.Ref() calls failed to compile if the referenced resource doesn't exist in the template.

I have an example project that deploys an ECS cluster and a service with cloudform: https://github.com/wolverian/ts-ecs-example. From writing it (and from using cloudform in another, private project), one of the biggest pain points I've experienced is making sure I give the correct string values to cf.Fn.Ref() and cf.Fn.GetAtt().

I don't have a concrete suggestion how to implement this, but any ideas are welcome.

wolverian avatar Apr 18 '18 09:04 wolverian

Yes, I've been thinking about a solution for this and although I haven't published anything yet, I have some ideas. I need to weight whether the API complexity added is worth it. I'll elaborate soon.

By now, what I do is to use a dictionary object with all the resource names defined and reference these names through that object only. See const Resource in the example. But yes, this is only a convention, not a type safety measure.

NOtherDev avatar Apr 18 '18 10:04 NOtherDev

By now, what I do is to use a dictionary object with all the resource names defined and reference these names through that object only. See const Resource in the example. But yes, this is only a convention, not a type safety measure.

That’s a good pattern, thanks!

After a bit of pondering, I think there has to be a type-level link between Ref and Template so that they can both be parametrised to accept only resources of the same type (where “same type” here is a type level restriction like keyof MyResources).

wolverian avatar Apr 18 '18 10:04 wolverian

Yes, although requiring the API user to always specify that MyResources generic parameter would kill the usefulness for me.

cloudform<MyResources>({
  Resources: {
    x: {
      Ref: Fn.Ref<MyResources>('x')
    }
  }
})

I'm now thinking about exposing that type-safe Ref via the typed callback function like this:

const MyResources = {
  x: 'x'
}

cloudform<typeof MyResources>(context: (Context<typeof MyResources>) => ({
   x: {
      Ref: context.Ref('x')
    }
}))

or maybe better by passing this MyResources definition in:

// cloudform<T>(resourcesDefinition: T, templateBuilder: (context: Context<T>) => Template)

cloudform(MyResources, context => ({
   x: {
      Ref: context.Ref('x')
    }
}))

WDYT?

NOtherDev avatar Apr 18 '18 18:04 NOtherDev

LGTM. I think I prefer the latter approach, as it looks simpler. I'll play around with this approach a bit and see how it works. I'll report if I bump into problems. I'd be happy to contribute a PR later, though it's fine if you implement this before I get around to it. 👍

wolverian avatar Apr 19 '18 08:04 wolverian

I strongly recommend to add a linter step (we use cfn-lint) over the CloudForm result for additional validation. It's not perfect, but this can grab not only resource reference issues, but some other errors too.

fhewitt avatar Jun 11 '18 17:06 fhewitt