You're using a programming language. Wouldn't it make more sense to take advantage of that? How about:
Match has_for_of_from = Regex.Match(input, @"\b(for|of|from)\b");
Match has_good_thing = Regex.Match(input, @"\b(?:good|thing|item)s?\b");
Match has_id = Regex.Match(input, @"\bid\b");
if (has_for_of_from && not (has_good_thing && has_id)) {
// error about missing items/id
}
if (has_good_thing && has_id && not has_for_from) {
// error about missing for/of/from
}
I moved the optional s
outside the alternation (making it easier to read but still doing the same thing). I wasn't sure from the description whether the logic is exactly what you wanted (should that first conditional say has_for && not (has_good || has_id)
?), but I'm sure you can figure it out from here.
You'd need a pretty messy regex to do this in just one match, and it wouldn't necessarily suggest which condition triggered the issue, so you won't have an informative error message.
Disclaimer: I do not know c# or .net, so my syntax may be slightly off.