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

Constraint trait for mutually exclusive structure members #786

Open
vcschapp opened this issue Apr 29, 2021 · 4 comments
Open

Constraint trait for mutually exclusive structure members #786

vcschapp opened this issue Apr 29, 2021 · 4 comments
Labels
feature-request A feature should be added or improved.

Comments

@vcschapp
Copy link

vcschapp commented Apr 29, 2021

My team is using Smithy extensively for API modelling and getting some great results!

One gap we are finding is in the constraint traits. The ones that exist are pretty great, however they are not capturing some repetitive use cases we have. One such use case is:

  • Mutually exclusive structure fields. This is the simpler use case. A structure has two fields, Foo and Bar. Either one may be specified, but both cannot be specified together. We would like a constraint trait to assist with this.

My team owns our own code generator for our server side code, so we translate the Smithy model into code ourselves. If these constraints are implemented into the language, we can take it from there by updating our code generator to auto-generate the code from the constraints that we're currently writing by hand.

Examples

Alternative 1

// Each array within @exclusiveMembers provides a set of fields that are mutually
// exclusive with one another.
//
// When validating the model, Smithy ensures that (a) the referenced members exist;
// and (b) there are no silly cycles that would prevent a member from ever being set.
@exclusiveMembers(
    ["Foo", "Bar"],
    ["Ham", "Eggs"]
)
structure S {
    Foo: MyString,
    Bar: MyString,
    Ham: MyString,
    Eggs: StrMyString
}

Alternative 2

// The value of @excludes is either a single string naming a field that the annotated
// field is mutually exclusive with, or a list of strings naming a set of fields that the
// annotated field is mutually exclusive with. This is a reciprocal annotation so if one
// member names another member, the other member must also name the first one.
//
// When validating the model, Smithy ensures that (a) the referenced fields exist in
// the structure; (b) there are no bad cycles/impossible states; and (c) that reciprocity
// is done so that if A excludes B, then B excludes A.
structure T {
    @excludes(["Bar", "Ham"])
    Foo: MyString,

    @excludes("Foo")
    Bar: MyString,

    @excludes(["Eggs", "Foo"])
    Ham: MyString,

    @excludes("Ham")
    Eggs: MyString
}

Tracking: SMITHY-438

@vcschapp vcschapp changed the title Constraint trait for mutually exclusive fields Constraint trait for mutually exclusive structure members Apr 29, 2021
@mtdowling mtdowling added the feature-request A feature should be added or improved. label May 3, 2021
@mtdowling
Copy link
Member

Mutually exclusive structure members is something we've been kicking around for a while, and I think it would have a lot of value. To achieve this today, service owners need to implement validation in code manually. A trait coupled with code generated validation would be useful.

At first glance, I like the second approach because it means we can do things like use selectors if needed to find members that are used as part of an exclusive list. I also think the idea of forcing the exclusion to be reciprocal to help with readability too.

I think we'd need to ensure that members that are part of an exclusion list couldn't be marked as required if that's impossible. For example, the following is impossible:

structure T {
    @required
    @excludes(["Bar", "Ham"])
    Foo: MyString,

    @required
    @excludes(["Foo"])
    Bar: MyString,

    @required
    @excludes(["Eggs", "Foo"])
    Ham: MyString,

    @required
    @excludes(["Ham"])
    Eggs: MyString
}

@vcschapp
Copy link
Author

vcschapp commented May 23, 2021

Yes absolutely agreed: @required would have to be prevented.

However, there is one subtlety here: What if, within a set of exclusive fields, there is a requirement that at least one is set? (The constraint "At least one of A or B, but not both, must be set" is not uncommon in my experience.)

That gets a bit more complicated if there is more than one field involved. Again I can think of two approaches here, one with a trait on the structure and one with a trait on the members...

Alterative 1

@requireOneOf("Foo", "Bar", "Ham")
structure U {
    @excludes(["Bar", "Ham"])
    Foo: MyString,

    @excludes("Foo")
    Bar: MyString,

    @excludes(["Eggs", "Foo"])
    Ham: MyString,

    @excludes("Ham")
    Eggs: MyString
}

Alternative 2

structure U {
    @excludes(["Bar", "Ham"])
    @required({"Unless": { "Or": ["Bar", "Ham"] }})
    Foo: MyString,

    @excludes("Foo")
    Bar: MyString,

    @excludes(["Eggs", "Foo"])
    Ham: MyString,

    @excludes("Ham")
    Eggs: MyString
}

@MTShannon
Copy link

Has this been added? Think it would still be useful

@mtdowling
Copy link
Member

No it hasn't been added. Something with as little conditional logic and as simple as possible is necessary for this to be considered. We won't expand the scope of the @required trait for this (so alternative 2 listed above is out).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved.
Projects
None yet
Development

No branches or pull requests

3 participants