introduce ParseResult.GetValue<T>(string name)#2083
Conversation
|
|
||
| if (!argument.HasDefaultValue && argument.Arity.MinimumNumberOfValues > 0) | ||
| { | ||
| argumentResult.AddError(LocalizationResources.RequiredArgumentMissing(argumentResult)); |
There was a problem hiding this comment.
this fixes the issue I've mentioned in #2082 (comment) : when a required argument is missing, we report the error for the ArgumentResult, not for CommandResult
| => RootCommandResult.GetValue(option); | ||
|
|
||
| /// <summary> | ||
| /// Gets the parsed or default value for the specified symbol name, in the context of parsed command. |
There was a problem hiding this comment.
this is important: the implementation provides only values for the parsed command, not for the entire symbol tree.
1374d11 to
8a0bb9c
Compare
| => parseResult.GetValue<T>(argument.Name); | ||
|
|
||
| [Fact] | ||
| public void In_case_of_argument_name_conflict_the_value_which_belongs_to_the_last_parsed_command_is_returned() |
There was a problem hiding this comment.
A few additional test cases come to mind:
- What happens when an option and an argument name are the same...
- at the same level of the tree?
- at different levels of the tree?
- What happens when symbols with the same name exist at different levels of the tree and the inner one is not provided? Does it fall back to the outer one's value?
- Does this vary depending on whether the inner one has a default value defined?
- How do customer parsers behave when...
- ...parsing an inner symbol
-xand looking up a value for-xwhen there's an outer symbol-x? - ...parsing an outer symbol
-xand looking up a value for-xwhen there's an inner symbol-x? - ...parsing symbol
-aand looking up a value for symbol-x...- ...which is above it in the tree
- ...which is below it in the tree
- ...parsing an inner symbol
I have opinions on what some of these behaviors should be but I'll wait so I don't anchor anyone.
There was a problem hiding this comment.
- What happens when an option and an argument name are the same at the same level of the tree?
We should throw.
- What happens when an option and an argument name are the same at different levels of the tree?
The new methods provides only values for the parsed command (parseResult.CommandResult), not for the entire symbol tree.
- What happens when symbols with the same name exist at different levels of the tree and the inner one is not provided? Does it fall back to the outer one's value?
The outer value will never be returned, only inner if provided or has default.
How do customer parsers behave when...
Such scenario is not supported by design (nobody asked for it), the method is exposed only for ParseResult, not for SymbolResult. It simplifies the design a lot (the cache can be populated just once).
|
|
||
| getRequired | ||
| .Should() | ||
| .Throw<InvalidOperationException>() |
There was a problem hiding this comment.
ArgumentException might be more appropriate here, though I could make an argument for InvalidOperationException if the name exists on an unparsed branch of the larger grammar, e.g. under a subcommand that wasn't present in the current result.
| getConflicted | ||
| .Should() | ||
| .Throw<NotSupportedException>() | ||
| .Where(ex => ex.Message == $"More than one symbol uses name \"{sameName}\" for command \"{command.Name}\"."); |
There was a problem hiding this comment.
I feel like we should be detecting this at parser configuration time, e.g. under ThrowIfInvalid or earlier.
There was a problem hiding this comment.
The name is immutable now, so we should be able to throw at the moment when given symbol is being added to the Command. We should discuss when is the right moment for throwing at our next design meeting.
| parseResult.GetValue<int>(sameName).Should().Be(456); | ||
|
|
||
| parseResult = command.Parse($"outer 123"); | ||
| parseResult.GetValue<int>(sameName).Should().Be(123); |
There was a problem hiding this comment.
Not to reopen the old late binding can of worms, but are there tests for what happens in various cases if GetValue<T> is called for Option<U>?
There was a problem hiding this comment.
Great catch, I've added the missing tests and fixed the discovered issue.
| SymbolResultTree.TryGetValue(Command.Arguments[i], out SymbolResult? parsedResult); | ||
| cache.Add(Command.Arguments[i].Name, parsedResult); | ||
| } | ||
| Populate(cache, Command.Arguments); |
There was a problem hiding this comment.
If I'm understanding this correctly, a call to Populate could be triggered by a custom parser before the complete command line has been parsed. Will symbols parsed afterward be findable by name?
There was a problem hiding this comment.
A custom parser gets access to SymbolResult-derived type, it does not provide a reference to ParseResult. The CommandResult.GetValue<T>(string name) is internal, so it's also impossible to call outside of S.CL before the parsing has been finished. I can move this method to ParseResult itself to avoid confusion.
Edit: I've moved the implementation for ParseResult.
There was a problem hiding this comment.
it's also impossible to call outside of S.CL before the parsing has been finished
The specific call I was wondering about is calls to ArgumentResult from within a custom parse delegate. This happens before the ParseResult is ready.
There was a problem hiding this comment.
With my latest changes it's impossible
* add test coverage for casting T to U, fix discovered issue * throw ArgumentException instead InvalidOperationException when no symbol is found for given name * move the method from CommandResult to ParseResult to make it clear that it's available only after parsing has finished
0e6425f to
82f24bd
Compare
fixes #2052