oxidecomputer/typify

Better generation of newtype-uuid types?

sunshowers opened this issue · 0 comments

At Oxide we're moving towards adding type safety to our UUID usage via the newtype-uuid crate. The handling of typed UUIDs in progenitor results in some friction, and @davepacheco mentioned a possibility that progenitor/typify gets smarter about handling these types.

(The rest of this issue is going to be based on this test, which produces the outputs shown here.)

newtype-uuid implements JsonSchema, and the type TypedUuid<MyKind> turns into TypedUuidForMyKind. This gets reflected in the JSON schema as a new type with the UUID format.

When turned back into Rust code with typify, this becomes a newtype wrapper, with conversions to and from Uuid. This is not a horrendously bad outcome, but it has a couple of issues:

  • This type is its own thing and doesn't hook into the typed UUID infrastructure at all -- we'd manually have to write From impls each way.
  • The code automatically generates conversions from TypedUuidForMyKind to Uuid and back -- that's something we're trying to be more explicit about in newtype-uuid with the GenericUuid trait.

The outcome with a replace directive is ideal. But as we add more typed UUID kinds, we'll have to remember to add corresponding replace directives for them to all of their corresponding clients. That's a bit unfortunate, and I'm wondering if we can do better.

Questions

One challenge is that while we know where TypedUuid lives, we don't know where MyKind lives in general. (In omicron it'll always live in omicron-uuid-kinds.) So I'm wondering whether some notion of replace patterns based on regexes might help. For example:

replace_patterns = {
    ^TypedUuidFor([A-Za-z][A-Za-z0-9]*)$ = ::newtype_uuid::TypedUuid<::omicron_uuid_kinds::\\1>,
}

Is this, or something like it, worth implementing?

Could newtype-uuid help out here somehow by providing hints?