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

[RND-696] .NET routing/validation #338

Merged
merged 8 commits into from
Jan 16, 2024
Merged

[RND-696] .NET routing/validation #338

merged 8 commits into from
Jan 16, 2024

Conversation

bradbanister
Copy link
Contributor

No description provided.

@bradbanister
Copy link
Contributor Author

This version does endpoint validation. Document validation still in progress.

@bradbanister
Copy link
Contributor Author

bradbanister commented Jan 11, 2024

Got schema validation working.

Edit

The NJsonSchema library, does not provide enough information for total overpost pruning. It does provide enough information to prune extra properties at the top level of a document but not, for example, extra properties in an array object. Don't know if this is good enough.

@bradbanister
Copy link
Contributor Author

The JsonSchema.Net library from json-everything does provide information for total overpost pruning. I demonstrated this with error messages showing the path to JSON elements that need pruning, separate from other schema validation errors.

@bradbanister bradbanister marked this pull request as ready for review January 12, 2024 17:52
@bradbanister bradbanister reopened this Jan 12, 2024
.AndThen(DocumentValidation);

return frontendResponse ?? new(StatusCode: 200, Body: $"Success: {finalRequestModel.ResourceInfo.ToString()}");
// return frontendResponse ?? new(StatusCode: 500, Body: "FrontendResponse was null");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this comment be removed now?

{
if (str.Length == 0) return str;
if (str.Length == 1) return str.ToLower();
return str[0..1].ToLower() + str[1..];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although not much of a difference, should the left side be str[0].ToLower() for clarity?

Copy link
Contributor Author

@bradbanister bradbanister Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly, C# wants str[0] to be a Char type., while the [0..1] range remains a string with the ToLower method.


Match? match = pathExpression.Match(path);

if (match == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should go single line here

/**
* Validates JSON document shape
*/
public static async Task<MiddlewareModel> DocumentValidation(MiddlewareModel middlewareModel)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got a warning when building the solution because of this function being async but not having any awaits:

warning CS1998: This async method lacks 'await' operators and will run synchronously.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the 3rd-party libs I tried wanted await. Current one doesn't but I'm going to "wait" on changing this. There will other awaits in the middleware with the DB stuff but removing this one first cascades errors in several places.

Copy link
Contributor

@andonyns andonyns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple comments

@bradbanister bradbanister requested a review from a team as a code owner January 15, 2024 19:23
public static JObject LoadApiSchemaFromFile()
{
// Hardcoded and synchronous way to read the API Schema file
return JObject.Parse(File.ReadAllText($"{AppContext.BaseDirectory}/DataStandard-5.0.0-pre.1.json"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long-term, we'll want to try System.Text.Json instead of Newtonsoft.Json. The former is a much higher performance rewrite, by the same creator but now directly employed by Microsoft. There are a few known limitations where Newtonsoft has continued to be better, but hopefully we won't run into those.

* as a decapitalized MetaEd entity name. However, there are exceptions, for example descriptors have a
* "Descriptor" suffix on their endpoint name.
*/
public record struct EndpointName(string Value);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have read about records, but first time I've actually seen them. Seems like a good use.

Comment on lines +3 to +7
/**
* A string type branded as an EndpointName, which is the name of an API resource endpoint. Typically, this is the same
* as a decapitalized MetaEd entity name. However, there are exceptions, for example descriptors have a
* "Descriptor" suffix on their endpoint name.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the eventual implementation, we'll want to use C# "xml" doc strings

Suggested change
/**
* A string type branded as an EndpointName, which is the name of an API resource endpoint. Typically, this is the same
* as a decapitalized MetaEd entity name. However, there are exceptions, for example descriptors have a
* "Descriptor" suffix on their endpoint name.
*/
/// <summary>
/// A string type branded as an EndpointName, which is the name of an API resource endpoint.
/// Typically, this is the same as a decapitalized MetaEd entity name. However, there are
/// exceptions, for example descriptors have a "Descriptor" suffix on their endpoint name.
/// </summary>

public static JObject? FindProjectSchema(JObject apiSchema, ProjectNamespace projectNamespace)
{
JToken? projectSchema = apiSchema.SelectToken($"$.projectSchemas.{projectNamespace.Value}");
return (JObject?)projectSchema;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good article on tips for improving this performance: https://medium.com/@mbearz/strange-c-tricks-4-just-some-json-tricks-aa74ed02463e

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad to see this already present. Making nullability an opt-in instead of always-on is a great new-ish feature in C#.

namespace Meadowlark.Net.Core;


public static class FrontendFacade
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these static classes will need to become instance classes so that they can implement interfaces and thus be used in dependency injection... thereby facilitating mocking in unit tests.

.SendTo(LoadApiSchema)
.AndThen(ParsePath)
.AndThen(EndpointValidation)
.AndThen(DocumentValidation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these same steps are taken for all requests, we may want to implement them as ASP.NET Core middleware. The pipeline approach above looks good, but there may be some advantages to using the middleware. One advantage might be greater familiarity for veteran C# developers.


public static class ParsePathMiddleware
{
private static string Decapitalize(string str)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to make this an extension method in the future, if it might be needed elsewhere in the code base.

// /ed-fi/Sections
// /ed-fi/Sections/
// /ed-fi/Sections/idValue
Regex pathExpression = new(@"\/(?<projectNamespace>[^/]+)\/(?<endpointName>[^/]+)(\/|$)((?<documentUuid>[^/]*$))?");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For full implementation, we'll want this regex to be compiled and defined in a lazy static property.


namespace Meadowlark.Net.Frontend.MinimalAPI
{
public static class CrudHandler
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to call this a Controller, to help new people who are searching around for the Controllers.

This particular controller will work with two different API definitions: Descriptors and Resources. For now, "Crud" seems as good a unifying name as anything 😆 .

@stephenfuqua stephenfuqua merged commit 2e141a1 into main Jan 16, 2024
15 checks passed
@stephenfuqua stephenfuqua deleted the RND-696 branch January 16, 2024 16:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants