-
Notifications
You must be signed in to change notification settings - Fork 281
Alternative Validation implementation #163
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
Conversation
…the product codes
| var validator = new OpenApiValidator(); | ||
| validator.Visit(components); | ||
| // Act | ||
| bool result = components.Validate(out errors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, the developer should create an object of OpenApiValidator and check the validator.Errors?
Why do you think the three line codes is better than the one line code call? components.Validate(out errors)?
@PerthCharern what do you think?
| /// <summary> | ||
| /// The interface for visitor. | ||
| /// </summary> | ||
| internal interface IVisitor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I rename IVistor as IValidator can help to understand?
| /// </summary> | ||
| /// <param name="doc"></param> | ||
| /// <param name="doc">OpenApiDocument to be walked</param> | ||
| public void Walk(OpenApiDocument doc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's my concern. Does the customer only walk starting from OpenApiDocument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look at the tests, you can Visit an element directly. If we wanted to expose Walk entry points for sub-graphs we could do that too. But then in your next comment you suggest that OpenApiValidator should be internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling Visit is not the same as calling Walk though. Visit doesn't recursively walk into the children, right? I think it's still nice to provide a Walk-like feature at every level of the subgraph.
In reply to: 158121892 [](ancestors = 158121892)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure it would be nice, but exposing functionality publicly that people can take a dependency on before we have any real experience with how well the validation works seems risky to me. Why don't we get the validation working as an internal feature and once we are comfortable with it, then consider exposing more. Unless we have a concrete usecase, I would rather not increase our support surface area.
In reply to: 158124882 [](ancestors = 158124882,158121892)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One possible use case I can envision:
- Define a large reusable component (say, schema) in
a separate file and refer to it using Reference. User wants to make sure this component is defined correctly by running a validation on this component (schema), which is technically not in a document.
In reply to: 158125996 [](ancestors = 158125996,158115415)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only minor problem we have with that at the moment is we don't support reading anything other than a complete document via our public interface. We will need to do that internally when pulling in external references.
In reply to: 158127399 [](ancestors = 158127399,158125996,158115415)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right. This subgraph-level discussion seems to be a recurring theme (writer, reader, and now walker).
I agree with you on this:
"It is worth noting that is much easier to add more granularity in a future release than to take it away."
So I think this level of exposure is fine as is as a beginning point.
On that note, there might be value in adding the Walk methods for all leaf nodes. In case we would like to flip the internal/public switch in the future, it will be a simple string change.
In reply to: 158128738 [](ancestors = 158128738,158127399,158125996,158115415)
| /// <summary> | ||
| /// Class containing dispatchers to execute validation rules on for Open API document. | ||
| /// </summary> | ||
| public class OpenApiValidator : OpenApiVisitorBase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally, the OpenApiValidator should be internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it needs to be public in this implementation though. The user needs to instantiate OpenApiWalker with new OpenApiValidator() as an argument to perform validation.
In reply to: 158116144 [](ancestors = 158116144)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@PerthCharern Sorry, Yes. In Darrel PR, it's public. But I mean it should be internal if we have a OpenApiValidator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how we want to incorporate the Validator. We could make it an internal implementation detail, or we could allow users to initiate it directly via the Validator visitor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we can decide what are the use cases for validating fragments of the OpenApiDocument then we can decide what portions of the traversal algorithm we should expose as public. It is worth noting that is much easier to add more granularity in a future release than to take it away.
|
|
||
| _visitor.Visit(doc as IOpenApiExtensible); | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It took me a bit to understand how to distinguish the case when we simply call .Visit and the case where we need to have WalkXXX private method.
Would it be easier to read if we always create WalkXXX method for every OpenApiXXX and its collection properties? If it's something simple without further inner object like OpenApiInfo, then sure, WalkInfo will simply be a wrapper around _visitor.Visit(info).
My concern is that with the current implementation, in case someone mistakenly uses .Visit instead of Walk in certain places, it will be very difficult to spot.
If we always create a WalkXXX, the pattern for each WalkXXX is also always consistent, with one Visit call and a bunch of following Walk calls.
public void WalkObject(OpenApiObject object)
{
// Visit self
_visitor.Visit(object);
// Walk the rest
WalkProperty1(object.Property1);
WalkMapProperty2(object.MapProperty2);
}
public void WalkProperty1(OpenApiProperty1Type property1)
{
// Visit self
_visitor.Visit(property1);
// Walk the rest
WalkProperty11(property1.Property11);
WalkProperty12(property1.Property12);
WalkProperty13(property1.Property13);
}
public void WalkMapProperty2(IDictionary<string, Property2Type> property2Map)
{
// Visit self
_visitor.Visit(property2Map);
// Walk the rest
foreach( var property2 in property2Map.Values )
{
WalkProperty2(property2);
}
}This will also address Sam's concern about having other starting point for Walking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Walk methods are just helper methods for grouping visits. Think of Walk methods as non-leaf nodes of the traversal tree and Visit as the leaf nodes. If you want to have a Walk for every leaf I suppose we could.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's a good thing to have. Say, if customer wants to only walk his OpenApiComponents, having a WalkComponents will enable that feature.
In reply to: 158121152 [](ancestors = 158121152)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean they only want to Validate the components, but not validate anything else? Or do you mean they want to only apply a visitor to the components? If so, they can create a class that derives from the VisitorBase, create an overload of the Visit(OpenApiComponent ) method and then use the regular walk.
In reply to: 158121661 [](ancestors = 158121661,158121152)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the former. Given that the implementation consistency concern is relatively minor, this boils down to the same issue as Sam's.
We can just discuss in the other thread in OpenApiWalker.cs to avoid repetition.
In reply to: 158126857 [](ancestors = 158126857,158121661,158121152)
|
Ok, @xuzhg @PerthCharern I hear your concerns. How about we meet in the middle? I will create Walk methods for every type that Visits the object and all child objects. But for the moment, we keep those methods internal. At least until we a) have specific case that requires us to make them public and b) we have gained a bit of confidence in the effectiveness of the approach. I will make these changes and then we can make the final call as to whether we want take my approach of having all the visiting/walking methods in a single class, or Sam's approach of having a seperate class for each visitor. Does that seem reasonable? |
|
Yes, I didn't see this comment, so I just proposed the same thing in the thread. :) This sounds good to me. |
| /// Execute validation rules against an <see cref="OpenApiSchema"/> | ||
| /// </summary> | ||
| /// <param name="item">The object to be validated</param> | ||
| public override void Visit(OpenApiSchema item) => Validate(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
item [](start = 49, length = 4)
schema
| /// Execute validation rules against an <see cref="OpenApiEncoding"/> | ||
| /// </summary> | ||
| /// <param name="item">The object to be validated</param> | ||
| public override void Visit(OpenApiEncoding item) => Validate(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
item [](start = 51, length = 4)
encoding
| { | ||
| _visitor.Visit(tag); | ||
| _visitor.Visit(tag.ExternalDocs); | ||
| _visitor.Visit(tag as IOpenApiExtensible); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be Walk, right?
| { | ||
| _visitor.Visit(variable); | ||
| } | ||
| _visitor.Visit(server as IOpenApiExtensible); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be Walk, right?
| _visitor.Visit(pathItem); | ||
|
|
||
| Walk(pathItem.Operations); | ||
| _visitor.Visit(pathItem as IOpenApiExtensible); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be Walk, right?
| } | ||
| } | ||
|
|
||
| _visitor.Visit(response as IOpenApiExtensible); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These should be Walk, right?
| { | ||
| foreach (var item in encoding.Values) | ||
| { | ||
| _visitor.Visit(item); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_visitor.Visit(item); [](start = 14, length = 23)
Walk?
PerthCharern
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
![]()
|
I prefer this pattern over the current implementation in #152 since it separates the logic of walking and validating. One concern that Sam raised earlier about the call pattern still remains unsolved: In #152, the call pattern is simply: But here, it's a bit more complicated and less intuitive: As a customer, I would not like to repeat the three lines over and over. It also takes away the meaning of validation from the call line itself (i.e. it's easy to understand that What do you think about creating extension method for IOpenApiElement that takes care of calling the three lines? |
|
@PerthCharern I have created the extension method, but needed to do a giant type switch to dispatch the walker. Should push that change soon. |
|
@PerthCharern The Validate method isn't quite the same as @xuzhg proposed. I changed it to this way because the visitor (in this case OpenApiValidator) is responsible for the state during the walking of the subgraph. I don't really like the idea of passing external state (an existing list of errors) into the visitor. A consumer can always aggregate errors themselves. If someone wants to aggregate errors across different parts of the graph, then they might as well create their own custom walker. Calling Validate and getting a list of errors back seems like a simpler interface for the case when someone wants to validate a subgraph. Thoughts? |
|
I'm okay with either design. I don't think Sam's design was originally meant for aggregating errors. His design kinda resembles what we have in the reader (diagnostic passed as out param). We don't really need that here since there's only one return object, and I don't foresee us having more "outputs" for Validate. In reply to: 355116009 [](ancestors = 355116009) |
| /// <typeparam name="T"></typeparam> | ||
| /// <param name="element"></param> | ||
| /// <returns></returns> | ||
| public static IEnumerable<ValidationError> Validate(this IOpenApiElement element) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, for the customer/developer, he will check:
- The return is not null
- The return has elements.
OpenApiDocument doc = new OpenApiDocument();
var errors = doc.Validate();
if (errors == null || !errors.Any())
{
// validation passed.
}
else
{
// validation failed.
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking errors.Any() should be sufficient because returning IEnumerable as null is just evil. I will add documentation to convey the fact that the function will never return null.
|
@darrelmiller Are you waiting for anything from me or Sam? I think the overall structure looks good. We can help fill in the validation details in future PRs once this is checked in. |
This uses the Rules from @xuzhg 's PR but my Validator and Walker. The tests that @xuzhg created have been updated to use the Validator instead.
There are still some parts of the DOM graph that are not yet being visited. Walker tests will need to be implemented.
What I believe are the advantages of this approach:
It is almost 1000 LOC less :-)