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

Refactor V2/NuGetServerAPICalls to use object-oriented query/filter builder #1645

Conversation

sean-r-williams
Copy link
Contributor

@sean-r-williams sean-r-williams commented Apr 24, 2024

PR Summary

This PR replaces the existing NuGet v2 API call-building mechanism based on string manipulation (as used in members of V2ServerAPICalls and NuGetServerAPICalls) with a separate pair of classes (NuGetV2QueryBuilder and NuGetV2FilterBuilder) that construct a query-string by aggregating user-supplied criteria.

Resolves #1643.

PR Context

The current implementation of V2 API calling classes builds API call URLs (including parameters) by directly performing string-manipulation on the URL itself. This has several drawbacks:

  • Performance: In the CLR, strings are immutable. Calling += on a string means a completely new string is created (with the suffix appended), and the prior string remains in memory until GC.
    • NuGetV2FilterBuilder uses StringBuilder to avoid costly reallocation of large strings.
  • Sanitization: OData filters, being URL query parameters, must be URL-encoded to prevent unexpected behavior. Operating on the URL directly means each relevant method has to URL-encode, which adds boilerplate or security risks if methods mis-encode.
    • NuGetV2QueryBuilder uses a derivative of System.Collections.Specialized.NameValueCollection to automatically URL-encode any configured parameters.
    • This does not include user input sanitization at the OData level - see below.
  • Flexibility: Different NuGet server implementations have different API call requirements, as seen by the quirk flag system used today. Different combinations of these quirk flags have caused manual combination of filter clauses to misbehave (Unable to install packages with dependencies from Artifactory PSGallery mirror (NuGet v2) #1633) when a clause previously expected to be "always present" is no longer present.
    • NuGetV2FilterBuilder joins filter clauses together after the full set is available, so there's no duplicated/inappropriately-placed joining operators.

There are some specific limitations in the current implementation:

  • I had to add a project reference to System.Web to build against TFM net472, as we need System.Web.HttpUtility. To my knowledge, System.Web should be in the GAC on .NET 4.5 and above, and PowerShell 7 already has System.Web.HttpUtility.dll linked as-is.
    I'm not an expert in MSBuild, however, so if there's a better way to ensure HttpUtility is available I'm all ears.
  • NuGetV2FilterBuilder doesn't ensure that filter clauses are properly escaping OData-relevant syntax (parentheses, quotes, etc.).
    I wanted to focus namely on the aggregation of clauses versus the clauses themselves in the first pass - now that we have purpose-built classes for these, it would be significantly easier to expose an interface for filter clauses (INuGetV2FilterCriterion?) with some inheritance for unary-/binary-operator criteria.

PR Checklist

@sean-r-williams sean-r-williams force-pushed the sean-r-williams/1643_nuget-v2-query-builder branch from 43359fe to 52477bc Compare April 24, 2024 12:27
@sean-r-williams
Copy link
Contributor Author

@microsoft-github-policy-service agree company="Bungie, Inc."

@sean-r-williams sean-r-williams changed the title Refactor V2/NuGetServerAPICalls to use object-oriented query/filter builder WIP: Refactor V2/NuGetServerAPICalls to use object-oriented query/filter builder Apr 24, 2024
@sean-r-williams sean-r-williams changed the title WIP: Refactor V2/NuGetServerAPICalls to use object-oriented query/filter builder Refactor V2/NuGetServerAPICalls to use object-oriented query/filter builder Apr 24, 2024
@anamnavi
Copy link
Member

/azp run PowerShell.PSResourceGet

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@anamnavi
Copy link
Member

@sean-r-williams thanks for creating this PR, it looks really thorough and the context was well explained :) The team will take a deeper look into it an get back by this week with any suggestions/questions/follow up.

@sean-r-williams
Copy link
Contributor Author

Thanks @anamnavi! It looks like CodeQL is falling for some reason, but I'm not authorized to view the workflow logs. I have a feeling this might be due to the new dependency on System.Web, but I'm unsure.

If someone from your team is able to summarize the errors coming back from CodeQL, I might be able to resolve the errors myself.

@sean-r-williams
Copy link
Contributor Author

Scratch that, it sems I forgot to add a file when I renamed a method. If someone could re-run Azure Pipelines, that'd be appreciated.

@anamnavi
Copy link
Member

/azp run PowerShell.PSResourceGet

Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@anamnavi
Copy link
Member

no worries! if that doesn't work I can take a look at the CodeQL errors/see if it has to do with things on our end.

@sean-r-williams
Copy link
Contributor Author

@sean-r-williams thanks for creating this PR, it looks really thorough and the context was well explained :) The team will take a deeper look into it an get back by this week with any suggestions/questions/follow up.

@anamnavi Gentle reminder on this - the current implementation of v2 API calls blocks dependency installation on Artifactory.

I'd like to make sure either this PR or #1644 are included in 1.0.5 (or whatever the next patch or minor release happens to be), and that said release is made available in the near future.

/// </param>
internal NuGetV2QueryBuilder(Dictionary<string, string> parameters) : this()
{
AdditionalParameters = new Dictionary<string, string>(parameters);
Copy link
Member

Choose a reason for hiding this comment

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

Can we have AdditionalParameters = parameters to save on the new allocation of Dictionary?

src/code/V2ServerAPICalls.cs Outdated Show resolved Hide resolved
@alerickson alerickson merged commit 666b680 into PowerShell:master May 9, 2024
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants