Switching search to generate a single query per request, and adding unit tests (#73)
This commit is contained in:
Родитель
b50c734f40
Коммит
3043f8c1aa
|
@ -0,0 +1 @@
|
|||
manifests.json filter=lfs diff=lfs merge=lfs -text
|
|
@ -352,9 +352,12 @@ MigrationBackup/
|
|||
# Ignore JetBrains Files
|
||||
src/.idea/
|
||||
|
||||
# Exlude Function Settings
|
||||
# Exclude Function Settings
|
||||
src/WinGet.RestSource.Functions/local.settings.json
|
||||
|
||||
# Exclude local test settings
|
||||
test.runsettings.json
|
||||
|
||||
# Exclude Documentation XML
|
||||
src/*/Microsoft.WinGet.RestSource*.Documentation.xml
|
||||
|
||||
|
|
26
README.md
26
README.md
|
@ -1,5 +1,18 @@
|
|||
# Welcome to the winget-cli-restsource repository
|
||||
|
||||
## Building the client
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* [Git Large File Storage (LFS)](https://git-lfs.github.com/)
|
||||
* [Visual Studio 2019](https://visualstudio.microsoft.com/downloads/)
|
||||
* The following workloads:
|
||||
* .NET desktop development
|
||||
* Azure development
|
||||
* ASP<area>.NET and web development
|
||||
|
||||
Open `src\WinGet.RestSource.sln` in Visual Studio and build. We currently only build using the solution; command line methods of building a VS solution should work as well.
|
||||
|
||||
## Running locally
|
||||
|
||||
The REST functions can be run locally, but to use winget with them, the functions must be run using HTTPS, this is pre-configured by the `launchSettings.json` file.
|
||||
|
@ -16,6 +29,19 @@ The REST functions can be run locally, but to use winget with them, the function
|
|||
|
||||
Your commands to winget will now use your locally running REST instance as the primary source.
|
||||
|
||||
## Running Unit Tests
|
||||
|
||||
Running unit tests are a great way to ensure that functionality is preserved across major changes. You can run these tests in Visual Studio Test Explorer.
|
||||
|
||||
### Testing Prerequisites
|
||||
|
||||
* Install the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21)
|
||||
* Copy the `WinGet.RestSource.UnitTest\Test.runsettings.template.json` template configuration to `Test.runsettings.json`
|
||||
* The defaults should work for your local Cosmos DB emulator instance. You can change the configuration to point to a Cosmos DB instance in Azure instead.
|
||||
* Alternatively, all of the test configuration properties can be set as environment variables. This is useful for overriding properties in an ADO build.
|
||||
|
||||
In Visual Studio, run the tests from the menu with Test > Run All Tests
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to agree to a
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
# Template helper to restore, build, and publish
|
||||
|
||||
steps:
|
||||
|
||||
# Checkout repo with lfs enabled
|
||||
- checkout: self
|
||||
lfs: "true"
|
||||
|
||||
## Restore
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Restore'
|
||||
|
|
|
@ -7,6 +7,15 @@ parameters:
|
|||
source: ''
|
||||
|
||||
steps:
|
||||
- powershell: |
|
||||
# Copy and rename Test.runsettings.template.json to Test.runsettings.json to be used as test config
|
||||
copy "$(Build.SourcesDirectory)\src\WinGet.RestSource.UnitTest\Test.runsettings.template.json" "${{ parameters.source }}\Test.runsettings.json"
|
||||
|
||||
# Launch Cosmos DB emulator
|
||||
Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
|
||||
Start-CosmosDbEmulator
|
||||
displayName: "Setup test pre-requisites"
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: 'Copy Files: ${{ parameters.name }}'
|
||||
inputs:
|
||||
|
@ -25,6 +34,7 @@ steps:
|
|||
codeCoverageEnabled: true
|
||||
platform: '$(BuildPlatform)'
|
||||
configuration: '$(BuildConfiguration)'
|
||||
overrideTestrunParameters: '-CosmosAccountEndpoint "$(CosmosDbEmulator.Endpoint)"'
|
||||
condition: succeeded()
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
|
|
|
@ -1,33 +1,33 @@
|
|||
# Copyright (c) Microsoft Corporation. All rights reserved
|
||||
# CI pipeline for winget-cli-restsource
|
||||
|
||||
# Branches that trigger a build on commit
|
||||
trigger:
|
||||
- main
|
||||
|
||||
# PR triggers
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
paths:
|
||||
include:
|
||||
- pipelines/*
|
||||
- src/*
|
||||
|
||||
jobs:
|
||||
- job: 'BuildTestPublish'
|
||||
displayName: 'Build, Publish & Test'
|
||||
timeoutInMinutes: 60
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
variables:
|
||||
BuildConfiguration: 'release'
|
||||
BuildPlatform: 'Any CPU'
|
||||
|
||||
steps:
|
||||
# Restore and Build
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved
|
||||
# CI pipeline for winget-cli-restsource
|
||||
|
||||
# Branches that trigger a build on commit
|
||||
trigger:
|
||||
- main
|
||||
|
||||
# PR triggers
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
paths:
|
||||
include:
|
||||
- pipelines/*
|
||||
- src/*
|
||||
|
||||
jobs:
|
||||
- job: 'BuildTestPublish'
|
||||
displayName: 'Build, Publish & Test'
|
||||
timeoutInMinutes: 60
|
||||
pool:
|
||||
vmImage: windows-2019
|
||||
demands:
|
||||
- msbuild
|
||||
- visualstudio
|
||||
variables:
|
||||
BuildConfiguration: 'release'
|
||||
BuildPlatform: 'Any CPU'
|
||||
|
||||
steps:
|
||||
# Restore and Build
|
||||
- template: templates/restore-build-publish-test.yml
|
|
@ -213,7 +213,7 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
// Parse Headers
|
||||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
|
||||
installers = await this.dataStore.GetInstallers(packageIdentifier, packageVersion, installerIdentifier, null);
|
||||
installers = await this.dataStore.GetInstallers(packageIdentifier, packageVersion, installerIdentifier);
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
|
|
|
@ -210,7 +210,7 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
// Parse Headers
|
||||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
|
||||
locales = await this.dataStore.GetLocales(packageIdentifier, packageVersion, packageLocale, null);
|
||||
locales = await this.dataStore.GetLocales(packageIdentifier, packageVersion, packageLocale);
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
|
|
|
@ -60,12 +60,13 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
{
|
||||
// Parse Headers
|
||||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
string continuationToken = headers.GetValueOrDefault(HeaderConstants.ContinuationToken);
|
||||
|
||||
// Get Manifest Search Request and Validate.
|
||||
ManifestSearchRequest manifestSearch = await Parser.StreamParser<ManifestSearchRequest>(req.Body, log);
|
||||
ApiDataValidator.Validate(manifestSearch);
|
||||
|
||||
manifestSearchResponse = await this.dataStore.SearchPackageManifests(manifestSearch, headers, req.Query);
|
||||
manifestSearchResponse = await this.dataStore.SearchPackageManifests(manifestSearch, continuationToken);
|
||||
|
||||
unsupportedFields = UnsupportedAndRequiredFieldsHelper.GetUnsupportedPackageMatchFieldsFromSearchRequest(manifestSearch, ApiConstants.UnsupportedPackageMatchFields);
|
||||
requiredFields = UnsupportedAndRequiredFieldsHelper.GetRequiredPackageMatchFieldsFromSearchRequest(manifestSearch, ApiConstants.RequiredPackageMatchFields);
|
||||
|
|
|
@ -191,7 +191,7 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
|
||||
// Fetch Results
|
||||
packages = await this.dataStore.GetPackages(packageIdentifier, req.Query);
|
||||
packages = await this.dataStore.GetPackages(packageIdentifier, req.Query[QueryConstants.ContinuationToken]);
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
|
|
|
@ -189,8 +189,21 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
// Parse Headers
|
||||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
|
||||
string continuationToken = null;
|
||||
string versionFilter = null;
|
||||
string channelFilter = null;
|
||||
string marketFilter = null;
|
||||
|
||||
// Schema supports query parameters only when PackageIdentifier is specified.
|
||||
manifests = await this.dataStore.GetPackageManifests(packageIdentifier, string.IsNullOrWhiteSpace(packageIdentifier) ? null : req.Query);
|
||||
if (!string.IsNullOrWhiteSpace(packageIdentifier))
|
||||
{
|
||||
continuationToken = req.Query[QueryConstants.ContinuationToken];
|
||||
versionFilter = req.Query[QueryConstants.Version];
|
||||
channelFilter = req.Query[QueryConstants.Channel];
|
||||
marketFilter = req.Query[QueryConstants.Market];
|
||||
}
|
||||
|
||||
manifests = await this.dataStore.GetPackageManifests(packageIdentifier, continuationToken, versionFilter, channelFilter, marketFilter);
|
||||
unsupportedQueryParameters = UnsupportedAndRequiredFieldsHelper.GetUnsupportedQueryParametersFromRequest(req.Query, ApiConstants.UnsupportedQueryParameters);
|
||||
requiredQueryParameters = UnsupportedAndRequiredFieldsHelper.GetRequiredQueryParametersFromRequest(req.Query, ApiConstants.RequiredQueryParameters);
|
||||
}
|
||||
|
|
|
@ -2,65 +2,65 @@
|
|||
// <copyright file="ServerFunctions.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Functions
|
||||
{
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Functions
|
||||
{
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Azure.WebJobs;
|
||||
using Microsoft.Azure.WebJobs.Extensions.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WinGet.RestSource.Cosmos;
|
||||
using Microsoft.WinGet.RestSource.Functions.Common;
|
||||
using Microsoft.WinGet.RestSource.Functions.Common;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants;
|
||||
using Microsoft.WinGet.RestSource.Utils.Exceptions;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the functions for interacting with packages.
|
||||
/// </summary>
|
||||
public class ServerFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerFunctions"/> class.
|
||||
/// </summary>
|
||||
public ServerFunctions()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server Information Get Function.
|
||||
/// This allows us to make Get Server Information.
|
||||
/// </summary>
|
||||
/// <param name="req">HttpRequest.</param>
|
||||
/// <param name="log">ILogger.</param>
|
||||
/// <returns>IActionResult.</returns>
|
||||
[FunctionName(FunctionConstants.InformationGet)]
|
||||
public IActionResult InformationGetAsync(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, FunctionConstants.FunctionGet, Route = "information")]
|
||||
HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// This class contains the functions for interacting with packages.
|
||||
/// </summary>
|
||||
public class ServerFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerFunctions"/> class.
|
||||
/// </summary>
|
||||
public ServerFunctions()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server Information Get Function.
|
||||
/// This allows us to make Get Server Information.
|
||||
/// </summary>
|
||||
/// <param name="req">HttpRequest.</param>
|
||||
/// <param name="log">ILogger.</param>
|
||||
/// <returns>IActionResult.</returns>
|
||||
[FunctionName(FunctionConstants.InformationGet)]
|
||||
public IActionResult InformationGetAsync(
|
||||
[HttpTrigger(AuthorizationLevel.Anonymous, FunctionConstants.FunctionGet, Route = "information")]
|
||||
HttpRequest req,
|
||||
ILogger log)
|
||||
{
|
||||
Information information;
|
||||
try
|
||||
{
|
||||
information = new Information();
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
log.LogError(e.ToString());
|
||||
return ActionResultHelper.ProcessError(e.InternalRestError);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.LogError(e.ToString());
|
||||
return ActionResultHelper.UnhandledError(e);
|
||||
}
|
||||
|
||||
return new ApiObjectResult(new ApiResponse<Information>(information));
|
||||
}
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
information = new Information();
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
log.LogError(e.ToString());
|
||||
return ActionResultHelper.ProcessError(e.InternalRestError);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.LogError(e.ToString());
|
||||
return ActionResultHelper.UnhandledError(e);
|
||||
}
|
||||
|
||||
return new ApiObjectResult(new ApiResponse<Information>(information));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,7 +196,7 @@ namespace Microsoft.WinGet.RestSource.Functions
|
|||
// Parse Headers
|
||||
Dictionary<string, string> headers = HeaderProcessor.ToDictionary(req.Headers);
|
||||
|
||||
versions = await this.dataStore.GetVersions(packageIdentifier, packageVersion, null);
|
||||
versions = await this.dataStore.GetVersions(packageIdentifier, packageVersion);
|
||||
}
|
||||
catch (DefaultException e)
|
||||
{
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AzureFunctionsVersion>V3</AzureFunctionsVersion>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
<AssemblyName>Microsoft.WinGet.RestSource.Functions</AssemblyName>
|
||||
<RootNamespace>Microsoft.WinGet.RestSource.Functions</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AzureFunctionsVersion>V3</AzureFunctionsVersion>
|
||||
<Platforms>AnyCPU</Platforms>
|
||||
<AssemblyName>Microsoft.WinGet.RestSource.Functions</AssemblyName>
|
||||
<RootNamespace>Microsoft.WinGet.RestSource.Functions</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DebugType>full</DebugType>
|
||||
|
@ -13,48 +13,49 @@
|
|||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource.Functions\Microsoft.WinGet.RestSource.Functions.Documentation.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<WarningsAsErrors />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="4.0.0-preview2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<WarningsAsErrors />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version, most likely when updating to dotnet 5.0 -->
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.template.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.7" />
|
||||
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="3.0.13" />
|
||||
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.0.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WinGet.RestSource\WinGet.RestSource.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="host.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.template.json">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
<None Update="local.settings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WinGet.RestSource\WinGet.RestSource.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
"CosmosReadWriteKey": "<your CosmosDB read-write account key>",
|
||||
"CosmosDatabase": "<your CosmosDB database>",
|
||||
"CosmosContainer": "<your CosmosDB container>",
|
||||
"ServerIdentifier": "winget-pkgs-restsource-dev"
|
||||
"ServerIdentifier": "winget-cli-restsource-dev"
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version-->
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version, most likely when updating to dotnet 5.0 -->
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.1.2" />
|
||||
<PackageReference Include="Microsoft.WindowsPackageManager.Utils" Version="0.3.4" GeneratePathProperty="true" />
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="Util.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Winget.RestSource.UnitTest.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Test helper utilities.
|
||||
/// </summary>
|
||||
public static class Util
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes a foreach async operation on an enumerable source in which iterations are run in parallel.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the elements in source.</typeparam>
|
||||
/// <param name="source">The enumerable that contains the original data source.</param>
|
||||
/// <param name="body">The async delegate that is invoked once per iteration.</param>
|
||||
/// <param name="maxDegreeOfParallelism">The maximum number of concurrent tasks.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
|
||||
public static async Task AsyncParallelForEach<TSource>(this IEnumerable<TSource> source, Func<TSource, Task> body, int maxDegreeOfParallelism)
|
||||
{
|
||||
var semaphore = new Semaphore(maxDegreeOfParallelism, maxDegreeOfParallelism);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var item in source)
|
||||
{
|
||||
semaphore.WaitOne();
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await body(item);
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphore.Release();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Wait until all are done
|
||||
await Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"CosmosAccountEndpoint": "https://localhost:8081/",
|
||||
|
||||
// These are well-known hardcoded keys used by the emulator, not secrets https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator?tabs=ssl-netstd21#authenticate-requests
|
||||
"CosmosReadOnlyKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
|
||||
"CosmosReadWriteKey": "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
|
||||
|
||||
"CosmosDatabase": "cosmossql-pkgsrest-test",
|
||||
"CosmosContainer": "ManifestsTest",
|
||||
"ServerIdentifier": "winget-cli-restsource-test"
|
||||
}
|
|
@ -0,0 +1,279 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="CosmosDataStoreTests.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.Winget.RestSource.UnitTest.Tests.RestSource.Cosmos
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WinGet.RestSource.Cosmos;
|
||||
using Microsoft.Winget.RestSource.UnitTest.Common;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants.Enumerations;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Arrays = Microsoft.WinGet.RestSource.Utils.Models.Arrays;
|
||||
using Objects = Microsoft.WinGet.RestSource.Utils.Models.Objects;
|
||||
|
||||
/// <summary>
|
||||
/// CosmosDataStore Tests.
|
||||
/// </summary>
|
||||
public class CosmosDataStoreTests : IAsyncLifetime
|
||||
{
|
||||
private const string ManifestsPath = @"Tests\RestSource\Cosmos\manifests.json";
|
||||
private const int ManifestCount = 500;
|
||||
private const int TestDatabaseRUs = 4000;
|
||||
private const string PowerToysPackageIdentifier = "Microsoft.PowerToys";
|
||||
|
||||
private readonly ITestOutputHelper log;
|
||||
private readonly IConfigurationRoot configuration;
|
||||
private readonly CosmosDataStore cosmosDataStore;
|
||||
private IList<CosmosPackageManifest> allTestManifests;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosDataStoreTests"/> class.
|
||||
/// </summary>
|
||||
/// <param name="log">ITestOutputHelper.</param>
|
||||
public CosmosDataStoreTests(ITestOutputHelper log)
|
||||
{
|
||||
this.log = log;
|
||||
|
||||
this.configuration = new ConfigurationBuilder()
|
||||
|
||||
// Defaults specified in the Test.runsettings.json
|
||||
.AddJsonFile("Test.runsettings.json", true)
|
||||
|
||||
// But they can be overridden using environment variables
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
string endpoint = this.configuration[CosmosConnectionConstants.CosmosAccountEndpointSetting] ?? throw new ArgumentNullException();
|
||||
string readOnlyKey = this.configuration[CosmosConnectionConstants.CosmosReadOnlyKeySetting] ?? throw new ArgumentNullException();
|
||||
string readWriteKey = this.configuration[CosmosConnectionConstants.CosmosReadWriteKeySetting] ?? throw new ArgumentNullException();
|
||||
string databaseId = this.configuration[CosmosConnectionConstants.DatabaseNameSetting] ?? throw new ArgumentNullException();
|
||||
string containerId = this.configuration[CosmosConnectionConstants.ContainerNameSetting] ?? throw new ArgumentNullException();
|
||||
|
||||
this.log.WriteLine($"{CosmosConnectionConstants.CosmosAccountEndpointSetting}: {endpoint}");
|
||||
this.log.WriteLine($"{CosmosConnectionConstants.CosmosReadOnlyKeySetting}: {readOnlyKey}");
|
||||
this.log.WriteLine($"{CosmosConnectionConstants.CosmosReadWriteKeySetting}: {readWriteKey}");
|
||||
this.log.WriteLine($"{CosmosConnectionConstants.DatabaseNameSetting}: {databaseId}");
|
||||
this.log.WriteLine($"{CosmosConnectionConstants.ContainerNameSetting}: {containerId}");
|
||||
|
||||
var logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger<CosmosDataStore>();
|
||||
this.cosmosDataStore = new CosmosDataStore(logger, endpoint, readWriteKey, readOnlyKey, databaseId, containerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
string json = System.IO.File.ReadAllText(ManifestsPath);
|
||||
this.allTestManifests = JsonConvert.DeserializeObject<List<CosmosPackageManifest>>(json);
|
||||
|
||||
// Ensure container exists prior to getting count
|
||||
await this.cosmosDataStore.CreateContainer(TestDatabaseRUs);
|
||||
|
||||
int itemCount = await this.cosmosDataStore.Count();
|
||||
if (itemCount == ManifestCount)
|
||||
{
|
||||
this.log.WriteLine($"Test container already contains the expected number of items ({ManifestCount}), no need to re-initialize");
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.WriteLine($"Test container does not contain the expected number of items, re-initializing. Expected: {ManifestCount}, Actual: {itemCount}");
|
||||
await this.cosmosDataStore.DeleteContainer();
|
||||
await this.cosmosDataStore.CreateContainer(TestDatabaseRUs);
|
||||
|
||||
// Only add the first 500 manifests to the database.
|
||||
await this.allTestManifests.Take(ManifestCount).AsyncParallelForEach(manifest => this.cosmosDataStore.AddPackageManifest(manifest), 30);
|
||||
}
|
||||
|
||||
this.log.WriteLine($"Initialization completed in {sw.Elapsed}");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the various CRUD operations exposed by the CosmosDataStore.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
|
||||
[Fact]
|
||||
public async Task CreateUpdateReadDeleteTest()
|
||||
{
|
||||
var addedManifest = this.allTestManifests.Skip(ManifestCount).First();
|
||||
|
||||
var packageManifestsResult = await this.cosmosDataStore.GetPackageManifests(addedManifest.PackageIdentifier);
|
||||
Assert.Empty(packageManifestsResult.Items);
|
||||
|
||||
await this.cosmosDataStore.AddPackageManifest(addedManifest);
|
||||
Assert.Equal(ManifestCount + 1, await this.cosmosDataStore.Count());
|
||||
|
||||
packageManifestsResult = await this.cosmosDataStore.GetPackageManifests(addedManifest.PackageIdentifier);
|
||||
Assert.Equal(addedManifest.PackageIdentifier, packageManifestsResult.Items.Single().PackageIdentifier);
|
||||
|
||||
string updatedName = "BOGUS_NAME";
|
||||
addedManifest.Versions.First().DefaultLocale.PackageName = updatedName;
|
||||
await this.cosmosDataStore.UpdatePackageManifest(addedManifest.PackageIdentifier, addedManifest);
|
||||
|
||||
packageManifestsResult = await this.cosmosDataStore.GetPackageManifests(addedManifest.PackageIdentifier);
|
||||
Assert.Equal(updatedName, packageManifestsResult.Items.Single().Versions.First().DefaultLocale.PackageName);
|
||||
|
||||
await this.cosmosDataStore.DeletePackageManifest(addedManifest.PackageIdentifier);
|
||||
Assert.Equal(ManifestCount, await this.cosmosDataStore.Count());
|
||||
|
||||
await this.cosmosDataStore.AddPackage(addedManifest);
|
||||
Assert.Equal(ManifestCount + 1, await this.cosmosDataStore.Count());
|
||||
|
||||
var packageResult = await this.cosmosDataStore.GetPackages(addedManifest.PackageIdentifier);
|
||||
Assert.Equal(addedManifest.PackageIdentifier, packageResult.Items.Single().PackageIdentifier);
|
||||
|
||||
var addedVersion = addedManifest.Versions.First();
|
||||
await this.cosmosDataStore.AddVersion(addedManifest.PackageIdentifier, addedVersion);
|
||||
await this.cosmosDataStore.AddInstaller(addedManifest.PackageIdentifier, addedVersion.PackageVersion, addedVersion.Installers.First());
|
||||
await this.cosmosDataStore.AddLocale(addedManifest.PackageIdentifier, addedVersion.PackageVersion, addedVersion.DefaultLocale);
|
||||
|
||||
packageManifestsResult = await this.cosmosDataStore.GetPackageManifests(addedManifest.PackageIdentifier);
|
||||
var resultVersion = packageManifestsResult.Items.First().Versions.First();
|
||||
Assert.Equal(addedVersion.PackageVersion, resultVersion.PackageVersion);
|
||||
Assert.Equal(addedVersion.DefaultLocale.PackageName, resultVersion.DefaultLocale.PackageName);
|
||||
Assert.Equal(addedVersion.Installers.First().InstallerUrl, resultVersion.Installers.First().InstallerUrl);
|
||||
|
||||
await this.cosmosDataStore.DeletePackage(addedManifest.PackageIdentifier);
|
||||
Assert.Equal(ManifestCount, await this.cosmosDataStore.Count());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the GetPackage* APIs.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
|
||||
[Fact]
|
||||
public async Task GetPackages()
|
||||
{
|
||||
this.log.WriteLine("Tests that GetPackages returns the expected package.");
|
||||
{
|
||||
var packages = await this.cosmosDataStore.GetPackages(PowerToysPackageIdentifier, null);
|
||||
Assert.NotEqual(0, packages.Items.Count);
|
||||
Assert.Equal(PowerToysPackageIdentifier, packages.Items.First().PackageIdentifier);
|
||||
}
|
||||
|
||||
this.log.WriteLine("Tests that ContinuationToken has an effect for GetPackages.");
|
||||
{
|
||||
var firstPackageSet = await this.cosmosDataStore.GetPackages(null);
|
||||
Assert.Equal(FunctionSettingsConstants.MaxResultsPerPage, firstPackageSet.Items.Count);
|
||||
|
||||
var continuedPackageSet = await this.cosmosDataStore.GetPackages(null, firstPackageSet.ContinuationToken);
|
||||
Assert.Equal(FunctionSettingsConstants.MaxResultsPerPage, continuedPackageSet.Items.Count);
|
||||
Assert.False(firstPackageSet.Items.Intersect(continuedPackageSet.Items).Any());
|
||||
}
|
||||
|
||||
this.log.WriteLine("Tests that GetPackageManifests returns the expected package.");
|
||||
{
|
||||
var packageManifests = await this.cosmosDataStore.GetPackageManifests(PowerToysPackageIdentifier);
|
||||
Assert.NotEqual(0, packageManifests.Items.Count);
|
||||
Assert.Equal(PowerToysPackageIdentifier, packageManifests.Items.First().PackageIdentifier);
|
||||
}
|
||||
|
||||
this.log.WriteLine("Tests that GetPackageManifests returns the expected package and version.");
|
||||
{
|
||||
const string version = "0.37.0";
|
||||
var packageManifests = await this.cosmosDataStore.GetPackageManifests(PowerToysPackageIdentifier, null, version);
|
||||
Assert.NotEqual(0, packageManifests.Items.Count);
|
||||
Assert.Equal(PowerToysPackageIdentifier, packageManifests.Items.First().PackageIdentifier);
|
||||
Assert.Equal(version, packageManifests.Items.First().Versions.Single().PackageVersion);
|
||||
}
|
||||
|
||||
this.log.WriteLine("Tests that ContinuationToken has an effect for GetPackageManifests.");
|
||||
{
|
||||
var firstPackageManifestSet = await this.cosmosDataStore.GetPackageManifests(null);
|
||||
Assert.Equal(FunctionSettingsConstants.MaxResultsPerPage, firstPackageManifestSet.Items.Count);
|
||||
|
||||
var continuedPackageManifestSet = await this.cosmosDataStore.GetPackageManifests(null, firstPackageManifestSet.ContinuationToken);
|
||||
Assert.Equal(FunctionSettingsConstants.MaxResultsPerPage, continuedPackageManifestSet.Items.Count);
|
||||
Assert.False(firstPackageManifestSet.Items.Intersect(continuedPackageManifestSet.Items).Any());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CosmosDataStore correctly handles a search using a search query term.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
|
||||
[Fact]
|
||||
public async Task SearchUsingQuery()
|
||||
{
|
||||
this.log.WriteLine("Tests that SearchPackageManifests returns the expected results when using the Query property.");
|
||||
{
|
||||
await this.TestSearchQuery("PowerToys", MatchType.Exact, PowerToysPackageIdentifier);
|
||||
await this.TestSearchQuery("powertoys", MatchType.CaseInsensitive, PowerToysPackageIdentifier);
|
||||
await this.TestSearchQuery("PowerT", MatchType.StartsWith, PowerToysPackageIdentifier);
|
||||
await this.TestSearchQuery("owerToy", MatchType.Substring, PowerToysPackageIdentifier);
|
||||
}
|
||||
|
||||
this.log.WriteLine("Tests that using ContinuationToken with SearchPackageManifests allows us to retrieve all manifests.");
|
||||
{
|
||||
var allResults = new HashSet<ManifestSearchResponse>();
|
||||
string continuationToken = null;
|
||||
do
|
||||
{
|
||||
var manifestSearchRequest = new ManifestSearchRequest();
|
||||
var result = await this.cosmosDataStore.SearchPackageManifests(manifestSearchRequest, continuationToken);
|
||||
allResults.UnionWith(result.Items);
|
||||
continuationToken = result.ContinuationToken;
|
||||
}
|
||||
while (continuationToken != null);
|
||||
Assert.Equal(ManifestCount, allResults.Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the CosmosDataStore correctly handles a search using filters.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
|
||||
[Fact]
|
||||
public async Task SearchUsingFilter()
|
||||
{
|
||||
await this.TestSearchFilter(PackageMatchFields.PackageName, "PowerToys", MatchType.Exact, PowerToysPackageIdentifier);
|
||||
await this.TestSearchFilter(PackageMatchFields.PackageName, "powertoys", MatchType.CaseInsensitive, PowerToysPackageIdentifier);
|
||||
await this.TestSearchFilter(PackageMatchFields.PackageName, "PowerT", MatchType.StartsWith, PowerToysPackageIdentifier);
|
||||
await this.TestSearchFilter(PackageMatchFields.PackageName, "owerToy", MatchType.Substring, PowerToysPackageIdentifier);
|
||||
}
|
||||
|
||||
private async Task TestSearchQuery(string value, string matchType, string expectedPackageIdentifier)
|
||||
{
|
||||
var manifestSearchRequest = new ManifestSearchRequest();
|
||||
manifestSearchRequest.Query = new Objects.SearchRequestMatch();
|
||||
manifestSearchRequest.Query.KeyWord = value;
|
||||
manifestSearchRequest.Query.MatchType = matchType;
|
||||
var results = await this.cosmosDataStore.SearchPackageManifests(manifestSearchRequest, null);
|
||||
Assert.NotEqual(0, results.Items.Count);
|
||||
Assert.Equal(expectedPackageIdentifier, results.Items.First().PackageIdentifier);
|
||||
}
|
||||
|
||||
private async Task TestSearchFilter(string packageMatchField, string value, string matchType, string expectedPackageIdentifier)
|
||||
{
|
||||
var manifestSearchRequest = new ManifestSearchRequest();
|
||||
manifestSearchRequest.Filters = new Arrays.SearchRequestPackageMatchFilter
|
||||
{
|
||||
new Objects.SearchRequestPackageMatchFilter { PackageMatchField = packageMatchField, RequestMatch = new Objects.SearchRequestMatch { KeyWord = value, MatchType = matchType } },
|
||||
};
|
||||
|
||||
var results = await this.cosmosDataStore.SearchPackageManifests(manifestSearchRequest, null);
|
||||
Assert.Equal(1, results.Items.Count);
|
||||
Assert.Equal(expectedPackageIdentifier, results.Items.Single().PackageIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:58622012b34c49b71db97299f275175d855501412b5953762c7e6bf7c35fde8b
|
||||
size 5439221
|
|
@ -1,46 +1,63 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Microsoft.Winget.RestSource.UnitTest</AssemblyName>
|
||||
<RootNamespace>Microsoft.Winget.RestSource.UnitTest</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Microsoft.Winget.RestSource.UnitTest</AssemblyName>
|
||||
<RootNamespace>Microsoft.Winget.RestSource.UnitTest</RootNamespace>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource.UnitTest\Microsoft.Winget.RestSource.UnitTest.Documentation.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource.UnitTest\Microsoft.Winget.RestSource.UnitTest.Documentation.xml</DocumentationFile>
|
||||
<UserSecretsId>09404207-b3a3-4757-83f3-0a1ec9c39c4a</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource.UnitTest\Microsoft.Winget.RestSource.UnitTest.Documentation.xml</DocumentationFile>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<WarningsAsErrors />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WinGet.RestSource.Functions\WinGet.RestSource.Functions.csproj" />
|
||||
<ProjectReference Include="..\WinGet.RestSource\WinGet.RestSource.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version, most likely when updating to dotnet 5.0 -->
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="xunit" Version="2.4.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="1.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WinGet.RestSource.Functions\WinGet.RestSource.Functions.csproj" />
|
||||
<ProjectReference Include="..\WinGet.RestSource\WinGet.RestSource.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Test.runsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</None>
|
||||
<None Update="Test.runsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Tests\RestSource\Cosmos\manifests.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -34,5 +34,10 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// Gets or sets continuation Token.
|
||||
/// </summary>
|
||||
public string ContinuationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request charge for this request from the Azure Cosmos DB service.
|
||||
/// </summary>
|
||||
public double RequestCharge { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,7 @@
|
|||
|
||||
namespace Microsoft.WinGet.RestSource.Utils.Common
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
|
||||
/// <summary>
|
||||
|
@ -16,6 +14,12 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// </summary>
|
||||
public interface IApiDataStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the count of items in the data store.
|
||||
/// </summary>
|
||||
/// <returns>The number if items in the data store.</returns>
|
||||
Task<int> Count();
|
||||
|
||||
/// <summary>
|
||||
/// This will add a package to the data store.
|
||||
/// </summary>
|
||||
|
@ -42,9 +46,9 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// This will retrieve a set of packages based on a package identifier and a continuation token.
|
||||
/// </summary>
|
||||
/// <param name="packageIdentifier">Package Identifier.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <param name="continuationToken">(Optional) Continuation token.</param>
|
||||
/// <returns>CosmosPage of Package Manifests.</returns>
|
||||
Task<ApiDataPage<Package>> GetPackages(string packageIdentifier, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<Package>> GetPackages(string packageIdentifier, string continuationToken = null);
|
||||
|
||||
/// <summary>
|
||||
/// This will add a version to a package based on the package identifier.
|
||||
|
@ -76,9 +80,8 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// </summary>
|
||||
/// <param name="packageIdentifier">Package Identifier.</param>
|
||||
/// <param name="packageVersion">Package Version.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <returns>CosmosPage of Version.</returns>
|
||||
Task<ApiDataPage<Version>> GetVersions(string packageIdentifier, string packageVersion, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<Version>> GetVersions(string packageIdentifier, string packageVersion);
|
||||
|
||||
/// <summary>
|
||||
/// This will add an installer referencing a package identifier and a package version.
|
||||
|
@ -114,9 +117,8 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// <param name="packageIdentifier">Package Identifier.</param>
|
||||
/// <param name="packageVersion">Package Version.</param>
|
||||
/// <param name="installerIdentifier">Installer Identifier.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <returns>CosmosPage of Installer.</returns>
|
||||
Task<ApiDataPage<Installer>> GetInstallers(string packageIdentifier, string packageVersion, string installerIdentifier, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<Installer>> GetInstallers(string packageIdentifier, string packageVersion, string installerIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// This will add a locale referencing a package identifier and a package version.
|
||||
|
@ -152,9 +154,8 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// <param name="packageIdentifier">Package Identifier.</param>
|
||||
/// <param name="packageVersion">Package Version.</param>
|
||||
/// <param name="packageLocale">Package Locale.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <returns>CosmosPage of Locale.</returns>
|
||||
Task<ApiDataPage<Locale>> GetLocales(string packageIdentifier, string packageVersion, string packageLocale, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<Locale>> GetLocales(string packageIdentifier, string packageVersion, string packageLocale);
|
||||
|
||||
/// <summary>
|
||||
/// Add a Package Manifest.
|
||||
|
@ -182,17 +183,19 @@ namespace Microsoft.WinGet.RestSource.Utils.Common
|
|||
/// Get a Package Manifest based on package identifier and query parameters.
|
||||
/// </summary>
|
||||
/// <param name="packageIdentifier">Package Identifier.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <param name="continuationToken">(Optional) Continuation token.</param>
|
||||
/// <param name="versionFilter">(Optional) Version filter.</param>
|
||||
/// <param name="channelFilter">(Optional) Channel filter.</param>
|
||||
/// <param name="marketFilter">(Optional) Market filter.</param>
|
||||
/// <returns>CosmosPage of Package Manifests.</returns>
|
||||
Task<ApiDataPage<PackageManifest>> GetPackageManifests(string packageIdentifier, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<PackageManifest>> GetPackageManifests(string packageIdentifier, string continuationToken = null, string versionFilter = null, string channelFilter = null, string marketFilter = null);
|
||||
|
||||
/// <summary>
|
||||
/// This will search for manifests based on a manifest search request and a set of query parameters.
|
||||
/// </summary>
|
||||
/// <param name="manifestSearchRequest">Manifest Search Request.</param>
|
||||
/// <param name="headerParameters">Header Parameters.</param>
|
||||
/// <param name="queryParameters">Query Parameters.</param>
|
||||
/// <param name="continuationToken">(Optional) Continuation token.</param>
|
||||
/// <returns>CosmosPage of ManifestSearchResponse.</returns>
|
||||
Task<ApiDataPage<ManifestSearchResponse>> SearchPackageManifests(ManifestSearchRequest manifestSearchRequest, Dictionary<string, string> headerParameters, IQueryCollection queryParameters);
|
||||
Task<ApiDataPage<ManifestSearchResponse>> SearchPackageManifests(ManifestSearchRequest manifestSearchRequest, string continuationToken = null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="PredicateBuilder.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Utils.Common
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
/// <summary>
|
||||
/// Enables the efficient, dynamic composition of query predicates.
|
||||
/// </summary>
|
||||
public static class PredicateBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a predicate that evaluates to true.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expression type.</typeparam>
|
||||
/// <returns>A constant predicate which always returns true.</returns>
|
||||
public static Expression<Func<T, bool>> True<T>() => param => true;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a predicate that evaluates to false.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expression type.</typeparam>
|
||||
/// <returns>A constant predicate which always returns true.</returns>
|
||||
public static Expression<Func<T, bool>> False<T>() => param => false;
|
||||
|
||||
/// <summary>
|
||||
/// Combines the first predicate with the second using the logical "and".
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expression type.</typeparam>
|
||||
/// <param name="first">The base expression.</param>
|
||||
/// <param name="second">The expression to combine onto the base.</param>
|
||||
/// <returns>A new AND-combined predicate.</returns>
|
||||
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
|
||||
{
|
||||
return first.IsStarted() ? first.Compose(second, Expression.AndAlso) : second;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the first predicate with the second using the logical "or".
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expression type.</typeparam>
|
||||
/// <param name="first">The base expression.</param>
|
||||
/// <param name="second">The expression to combine onto the base.</param>
|
||||
/// <returns>A new OR-combined predicate.</returns>
|
||||
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
|
||||
{
|
||||
return first.IsStarted() ? first.Compose(second, Expression.OrElse) : second;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Negates the predicate.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expression type.</typeparam>
|
||||
/// <param name="expression">The base expression.</param>
|
||||
/// <returns>A new negated expression.</returns>
|
||||
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
|
||||
{
|
||||
var negated = Expression.Not(expression.Body);
|
||||
return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the predicate is started.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of expression.</typeparam>
|
||||
/// <param name="expression">Expression to check.</param>
|
||||
/// <returns>Returns true if predicate has been started (not simply constant true or false), false otherwise.</returns>
|
||||
public static bool IsStarted<T>(this Expression<Func<T, bool>> expression)
|
||||
{
|
||||
return !(expression.Body is ConstantExpression expr && expr.Type == typeof(bool));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new predicate with default expression (false).
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of expression.</typeparam>
|
||||
/// <returns>A new default expression.</returns>
|
||||
public static Expression<Func<T, bool>> New<T>()
|
||||
{
|
||||
return param => false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Combines the first expression with the second using the specified merge function.
|
||||
/// </summary>
|
||||
private static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
|
||||
{
|
||||
// zip parameters (map from parameters of second to parameters of first)
|
||||
var map = first.Parameters
|
||||
.Select((f, i) => new { f, s = second.Parameters[i] })
|
||||
.ToDictionary(p => p.s, p => p.f);
|
||||
|
||||
// replace parameters in the second lambda expression with the parameters in the first
|
||||
var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
|
||||
|
||||
// create a merged lambda expression with parameters from the first expression
|
||||
return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
|
||||
}
|
||||
|
||||
private class ParameterRebinder : ExpressionVisitor
|
||||
{
|
||||
private readonly Dictionary<ParameterExpression, ParameterExpression> map;
|
||||
|
||||
private ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
|
||||
{
|
||||
this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
|
||||
}
|
||||
|
||||
public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
|
||||
{
|
||||
return new ParameterRebinder(map).Visit(exp);
|
||||
}
|
||||
|
||||
protected override Expression VisitParameter(ParameterExpression p)
|
||||
{
|
||||
if (this.map.TryGetValue(p, out ParameterExpression replacement))
|
||||
{
|
||||
p = replacement;
|
||||
}
|
||||
|
||||
return base.VisitParameter(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,9 @@ namespace Microsoft.WinGet.RestSource.Utils.Constants.Enumerations
|
|||
/// </summary>
|
||||
public const string Substring = "Substring";
|
||||
|
||||
/*********************************
|
||||
* These are currently unsupported
|
||||
*********************************
|
||||
/// <summary>
|
||||
/// Wildcard.
|
||||
/// </summary>
|
||||
|
@ -45,5 +48,6 @@ namespace Microsoft.WinGet.RestSource.Utils.Constants.Enumerations
|
|||
/// FuzzySubstring.
|
||||
/// </summary>
|
||||
public const string FuzzySubstring = "FuzzySubstring";
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,11 @@ namespace Microsoft.WinGet.RestSource.Utils.Constants.Enumerations
|
|||
/// </summary>
|
||||
public const string ProductCode = "ProductCode";
|
||||
|
||||
/// <summary>
|
||||
/// ShortDescription.
|
||||
/// </summary>
|
||||
public const string ShortDescription = "ShortDescription";
|
||||
|
||||
/// <summary>
|
||||
/// NormalizedPackageNameAndPublisher.
|
||||
/// </summary>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Utils.Models.Schemas
|
||||
{
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Core;
|
||||
using Microsoft.WinGet.RestSource.Utils.Validators;
|
||||
using Microsoft.WinGet.RestSource.Utils.Validators.StringValidators;
|
||||
|
@ -128,7 +128,7 @@ namespace Microsoft.WinGet.RestSource.Utils.Models.Schemas
|
|||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (this.PackageVersion, this.Channel, this.DefaultLocale).GetHashCode();
|
||||
return HashCode.Combine(this.PackageVersion, this.Channel, this.DefaultLocale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,9 +21,12 @@ namespace Microsoft.WinGet.RestSource.Utils.Validators.EnumValidators
|
|||
MatchType.CaseInsensitive,
|
||||
MatchType.StartsWith,
|
||||
MatchType.Substring,
|
||||
|
||||
/* These are currently unsupported
|
||||
MatchType.Wildcard,
|
||||
MatchType.Fuzzy,
|
||||
MatchType.FuzzySubstring,
|
||||
*/
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace Microsoft.WinGet.RestSource.Utils.Validators.EnumValidators
|
|||
/// </summary>
|
||||
public class PackageMatchFieldValidator : ApiEnumValidator
|
||||
{
|
||||
private List<string> enumList = new List<string>
|
||||
private readonly List<string> enumList = new List<string>
|
||||
{
|
||||
PackageMatchFields.PackageIdentifier,
|
||||
PackageMatchFields.PackageName,
|
||||
|
|
|
@ -34,17 +34,16 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version, most likely when updating to dotnet 5.0 -->
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.22.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.WindowsPackageManager.Utils" Version="0.3.4" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.2" />
|
||||
<!-- TODO: Using a preview version of this CosmosDB helper library as there's no stable version yet that supports the v3 CosmosDB SDK.
|
||||
We'll remove the preview dependency as part of the work to update the LINQ-querying logic.
|
||||
-->
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="4.0.0-preview2" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30503.244
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31710.8
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGet.RestSource", "WinGet.RestSource\WinGet.RestSource.csproj", "{15E4BE9B-2891-410A-A042-47FE838B1AEC}"
|
||||
EndProject
|
||||
|
|
|
@ -6,34 +6,26 @@
|
|||
|
||||
namespace Microsoft.WinGet.RestSource.Cosmos
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using LinqKit;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Azure.Cosmos.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WinGet.RestSource.Utils.Common;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants.Enumerations;
|
||||
using Microsoft.WinGet.RestSource.Utils.Exceptions;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Errors;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Objects;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
using Microsoft.WinGet.RestSource.Utils.Validators;
|
||||
using Version = Microsoft.WinGet.RestSource.Utils.Models.Schemas.Version;
|
||||
|
||||
/// <summary>
|
||||
/// Cosmos Data Store.
|
||||
/// </summary>
|
||||
public class CosmosDataStore : IApiDataStore
|
||||
{
|
||||
private const string ShortDescription = "ShortDescription";
|
||||
|
||||
private const int AllElements = -1;
|
||||
|
||||
private readonly ICosmosDatabase cosmosDatabase;
|
||||
|
@ -49,7 +41,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
PackageMatchFields.PackageFamilyName,
|
||||
PackageMatchFields.ProductCode,
|
||||
PackageMatchFields.NormalizedPackageNameAndPublisher,
|
||||
ShortDescription,
|
||||
PackageMatchFields.ShortDescription,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -67,6 +59,33 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
this.log = log;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a container exists, and if it doesn't, create it. This will make a read operation, and if the
|
||||
/// container is not found it will do a create operation.
|
||||
/// </summary>
|
||||
/// <param name="throughput">(Optional) The throughput provisioned for a container in measurement of
|
||||
/// Request Units per second in the Azure Cosmos DB service.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task CreateContainer(int? throughput = null)
|
||||
{
|
||||
await this.cosmosDatabase.CreateContainer(throughput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the Cosmos DB container.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task DeleteContainer()
|
||||
{
|
||||
await this.cosmosDatabase.DeleteContainer();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> Count()
|
||||
{
|
||||
return await this.cosmosDatabase.Count<CosmosPackageManifest>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AddPackage(Package package)
|
||||
{
|
||||
|
@ -81,7 +100,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
};
|
||||
|
||||
ApiDataValidator.Validate(cosmosDocument.Document);
|
||||
await this.cosmosDatabase.Add<CosmosPackageManifest>(cosmosDocument);
|
||||
await this.cosmosDatabase.Add(cosmosDocument);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -90,7 +109,6 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
CosmosDocument<CosmosPackageManifest> cosmosDocument = new CosmosDocument<CosmosPackageManifest>
|
||||
{
|
||||
Id = packageIdentifier,
|
||||
PartitionKey = packageIdentifier,
|
||||
};
|
||||
|
||||
// Delete Document
|
||||
|
@ -113,15 +131,8 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<Package>> GetPackages(string packageIdentifier, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<Package>> GetPackages(string packageIdentifier, string continuationToken = null)
|
||||
{
|
||||
// Process Continuation token
|
||||
string continuationToken = null;
|
||||
if (queryParameters != null)
|
||||
{
|
||||
continuationToken = queryParameters[QueryConstants.ContinuationToken];
|
||||
}
|
||||
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Create query options
|
||||
|
@ -203,17 +214,8 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ApiDataPage<Version>> GetVersions(string packageIdentifier, string packageVersion, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<Version>> GetVersions(string packageIdentifier, string packageVersion)
|
||||
{
|
||||
// Process Continuation token
|
||||
string continuationToken = null;
|
||||
if (queryParameters != null)
|
||||
{
|
||||
continuationToken = queryParameters[QueryConstants.ContinuationToken];
|
||||
}
|
||||
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Fetch Current Package
|
||||
CosmosDocument<CosmosPackageManifest> cosmosDocument =
|
||||
await this.cosmosDatabase.GetByIdAndPartitionKey<CosmosPackageManifest>(packageIdentifier, packageIdentifier);
|
||||
|
@ -271,17 +273,8 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<Installer>> GetInstallers(string packageIdentifier, string packageVersion, string installerIdentifier, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<Installer>> GetInstallers(string packageIdentifier, string packageVersion, string installerIdentifier)
|
||||
{
|
||||
// Process Continuation token
|
||||
string continuationToken = null;
|
||||
if (queryParameters != null)
|
||||
{
|
||||
continuationToken = queryParameters[QueryConstants.ContinuationToken];
|
||||
}
|
||||
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Fetch Current Package
|
||||
CosmosDocument<CosmosPackageManifest> cosmosDocument =
|
||||
await this.cosmosDatabase.GetByIdAndPartitionKey<CosmosPackageManifest>(packageIdentifier, packageIdentifier);
|
||||
|
@ -339,17 +332,8 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<Locale>> GetLocales(string packageIdentifier, string packageVersion, string packageLocale, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<Locale>> GetLocales(string packageIdentifier, string packageVersion, string packageLocale)
|
||||
{
|
||||
// Process Continuation token
|
||||
string continuationToken = null;
|
||||
if (queryParameters != null)
|
||||
{
|
||||
continuationToken = queryParameters[QueryConstants.ContinuationToken];
|
||||
}
|
||||
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Fetch Current Package
|
||||
CosmosDocument<CosmosPackageManifest> cosmosDocument =
|
||||
await this.cosmosDatabase.GetByIdAndPartitionKey<CosmosPackageManifest>(packageIdentifier, packageIdentifier);
|
||||
|
@ -376,15 +360,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
/// <inheritdoc />
|
||||
public async Task DeletePackageManifest(string packageIdentifier)
|
||||
{
|
||||
// Parse Variables
|
||||
CosmosDocument<CosmosPackageManifest> cosmosDocument = new CosmosDocument<CosmosPackageManifest>
|
||||
{
|
||||
Id = packageIdentifier,
|
||||
PartitionKey = packageIdentifier,
|
||||
};
|
||||
|
||||
// Delete Document
|
||||
await this.cosmosDatabase.Delete<CosmosPackageManifest>(cosmosDocument);
|
||||
await this.DeletePackage(packageIdentifier);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -395,31 +371,16 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
{
|
||||
Document = cosmosPackageManifest,
|
||||
Id = packageIdentifier,
|
||||
PartitionKey = packageIdentifier,
|
||||
};
|
||||
await this.cosmosDatabase.Update<CosmosPackageManifest>(cosmosDocument);
|
||||
await this.cosmosDatabase.Update(cosmosDocument);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<PackageManifest>> GetPackageManifests(string packageIdentifier, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<PackageManifest>> GetPackageManifests(string packageIdentifier, string continuationToken = null, string versionFilter = null, string channelFilter = null, string marketFilter = null)
|
||||
{
|
||||
// Note: GetPackageManifests should use query parameters as predicates when querying all package manifests. Currently, query parameters
|
||||
// are only exposed for GetPackageManifests with a PackageIdentifier input. Whenever query parameters are exposed to querying all
|
||||
// package manifests, this method should utilize search predicates to filter on query parameters.
|
||||
|
||||
// Process Continuation token
|
||||
string continuationToken = null;
|
||||
string versionFilter = null;
|
||||
string channelFilter = null;
|
||||
string marketFilter = null;
|
||||
if (queryParameters != null)
|
||||
{
|
||||
continuationToken = queryParameters[QueryConstants.ContinuationToken];
|
||||
versionFilter = queryParameters[QueryConstants.Version];
|
||||
channelFilter = queryParameters[QueryConstants.Channel];
|
||||
marketFilter = queryParameters[QueryConstants.Market];
|
||||
}
|
||||
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Create feed options
|
||||
|
@ -430,7 +391,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
};
|
||||
|
||||
// Get iQueryable
|
||||
IQueryable<PackageManifest> query = this.cosmosDatabase.GetIQueryable<PackageManifest>(feedOptions);
|
||||
IQueryable<PackageManifest> query = this.cosmosDatabase.GetIQueryable<PackageManifest>(feedOptions, continuationToken);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(packageIdentifier))
|
||||
{
|
||||
|
@ -474,19 +435,16 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
// If Markets object is null or markets do not match filter, exclude them from results.
|
||||
if (!string.IsNullOrEmpty(marketFilter))
|
||||
{
|
||||
this.ApplyMarketFilter(apiDataDocument.Items, marketFilter);
|
||||
ApplyMarketFilter(apiDataDocument.Items, marketFilter);
|
||||
}
|
||||
|
||||
return apiDataDocument;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<ManifestSearchResponse>> SearchPackageManifests(ManifestSearchRequest manifestSearchRequest, Dictionary<string, string> headers, IQueryCollection queryParameters)
|
||||
public async Task<ApiDataPage<ManifestSearchResponse>> SearchPackageManifests(ManifestSearchRequest manifestSearchRequest, string continuationToken = null)
|
||||
{
|
||||
// Create Working Set and return
|
||||
ApiDataPage<ManifestSearchResponse> apiDataPage = new ApiDataPage<ManifestSearchResponse>();
|
||||
List<CosmosPackageManifest> manifests = new List<CosmosPackageManifest>();
|
||||
List<ManifestSearchResponse> manifestSearchResponse = new List<ManifestSearchResponse>();
|
||||
continuationToken = continuationToken != null ? StringEncoder.DecodeContinuationToken(continuationToken) : null;
|
||||
|
||||
// Create feed options for inclusion search: -1 so we can get all matches in inclusion, then filter down.
|
||||
QueryRequestOptions feedOptions = new QueryRequestOptions
|
||||
|
@ -495,163 +453,82 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
MaxItemCount = AllElements,
|
||||
};
|
||||
|
||||
if (manifestSearchRequest.FetchAllManifests || (manifestSearchRequest.Inclusions == null && manifestSearchRequest.Query == null))
|
||||
manifestSearchRequest.Inclusions ??= new Utils.Models.Arrays.SearchRequestPackageMatchFilter();
|
||||
manifestSearchRequest.Filters ??= new Utils.Models.Arrays.SearchRequestPackageMatchFilter();
|
||||
|
||||
// Convert Query to inclusions to submit to cosmos
|
||||
if (manifestSearchRequest.Query != null)
|
||||
{
|
||||
IQueryable<CosmosPackageManifest> query = this.cosmosDatabase.GetIQueryable<CosmosPackageManifest>(feedOptions);
|
||||
FeedIterator<CosmosPackageManifest> documentQuery = query.ToFeedIterator();
|
||||
ApiDataPage<CosmosPackageManifest> apiDataDocument = await this.cosmosDatabase.GetByDocumentQuery<CosmosPackageManifest>(documentQuery);
|
||||
manifests.AddRange(apiDataDocument.Items);
|
||||
manifestSearchRequest.Inclusions.AddRange(this.queryList.Select(q => new SearchRequestPackageMatchFilter()
|
||||
{
|
||||
PackageMatchField = q,
|
||||
RequestMatch = manifestSearchRequest.Query,
|
||||
}));
|
||||
}
|
||||
|
||||
// Process inclusions
|
||||
var inclusionsPredicate = new PredicateGenerator();
|
||||
manifestSearchRequest.Inclusions.ForEach(inclusion => AddConditionIfValid(inclusionsPredicate, inclusion, PredicateOperator.Or));
|
||||
|
||||
// Process filters
|
||||
var filtersPredicate = new PredicateGenerator();
|
||||
manifestSearchRequest.Filters.ForEach(filter => AddConditionIfValid(filtersPredicate, filter, PredicateOperator.And));
|
||||
|
||||
IQueryable<CosmosPackageManifest> query = this.cosmosDatabase.GetIQueryable<CosmosPackageManifest>();
|
||||
|
||||
if (inclusionsPredicate.IsStarted() || filtersPredicate.IsStarted())
|
||||
{
|
||||
query = query.AsExpandable();
|
||||
|
||||
if (inclusionsPredicate.IsStarted())
|
||||
{
|
||||
query = query.Where(inclusionsPredicate.Generate(PredicateOperator.Or));
|
||||
}
|
||||
|
||||
if (filtersPredicate.IsStarted())
|
||||
{
|
||||
query = query.Where(filtersPredicate.Generate(PredicateOperator.And));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process Inclusions
|
||||
Utils.Models.Arrays.SearchRequestPackageMatchFilter inclusions = new Utils.Models.Arrays.SearchRequestPackageMatchFilter();
|
||||
if (manifestSearchRequest.Inclusions != null)
|
||||
{
|
||||
inclusions.AddRange(manifestSearchRequest.Inclusions);
|
||||
}
|
||||
|
||||
// Convert Query to inclusions to submit to cosmos
|
||||
if (manifestSearchRequest.Query != null)
|
||||
{
|
||||
inclusions.AddRange(this.queryList.Select(q => new Utils.Models.Objects.SearchRequestPackageMatchFilter()
|
||||
{
|
||||
PackageMatchField = q,
|
||||
RequestMatch = manifestSearchRequest.Query,
|
||||
}));
|
||||
}
|
||||
|
||||
// Submit Inclusions to Cosmos
|
||||
// Due to join limitation on Cosmos - we are submitting each predicate separately.
|
||||
// TODO: Create a more efficient search - but this will suffice for now for a light weight reference.
|
||||
if (inclusions.Count > 0)
|
||||
{
|
||||
List<Task<ApiDataPage<CosmosPackageManifest>>> taskSet = new List<Task<ApiDataPage<CosmosPackageManifest>>>();
|
||||
foreach (string packageMatchField in inclusions.Select(inc => inc.PackageMatchField).Distinct())
|
||||
{
|
||||
// Create Predicate for search
|
||||
ExpressionStarter<CosmosPackageManifest> inclusionPredicate = PredicateBuilder.New<CosmosPackageManifest>();
|
||||
foreach (SearchRequestPackageMatchFilter matchFilter in inclusions.Where(inc => inc.PackageMatchField.Equals(packageMatchField)))
|
||||
{
|
||||
// Some package match fields or types might not be supported by current version of search.
|
||||
// So we will check the supported list before adding any predicates.
|
||||
if (this.IsPackageMatchFieldSupported(matchFilter.PackageMatchField) &&
|
||||
this.IsMatchTypeSupported(matchFilter.RequestMatch.MatchType))
|
||||
{
|
||||
inclusionPredicate.Or(this.QueryPredicate(matchFilter.PackageMatchField, matchFilter.RequestMatch));
|
||||
}
|
||||
}
|
||||
|
||||
// Create Document Query
|
||||
IQueryable<CosmosPackageManifest> query = this.cosmosDatabase.GetIQueryable<CosmosPackageManifest>(feedOptions);
|
||||
query = query.Where(inclusionPredicate);
|
||||
FeedIterator<CosmosPackageManifest> documentQuery = query.ToFeedIterator();
|
||||
|
||||
// Submit Query to Cosmos
|
||||
taskSet.Add(Task.Run(() => this.cosmosDatabase.GetByDocumentQuery(documentQuery)));
|
||||
}
|
||||
|
||||
// Wait for Cosmos Queries to complete
|
||||
await Task.WhenAll(taskSet.ToArray());
|
||||
|
||||
// Process Manifests from Cosmos
|
||||
foreach (Task<ApiDataPage<CosmosPackageManifest>> task in taskSet)
|
||||
{
|
||||
manifests.AddRange(task.Result.Items);
|
||||
}
|
||||
|
||||
manifests = manifests.Distinct().ToList();
|
||||
}
|
||||
query = query.Where(Utils.Common.PredicateBuilder.True<CosmosPackageManifest>());
|
||||
}
|
||||
|
||||
// Process Filters locally
|
||||
if (manifestSearchRequest.Filters != null)
|
||||
{
|
||||
ExpressionStarter<CosmosPackageManifest> filterPredicate = PredicateBuilder.New<CosmosPackageManifest>();
|
||||
foreach (SearchRequestPackageMatchFilter matchFilter in manifestSearchRequest.Filters)
|
||||
{
|
||||
if (this.IsPackageMatchFieldSupported(matchFilter.PackageMatchField) &&
|
||||
this.IsMatchTypeSupported(matchFilter.RequestMatch.MatchType))
|
||||
{
|
||||
filterPredicate.Or(this.QueryPredicate(matchFilter.PackageMatchField, matchFilter.RequestMatch));
|
||||
}
|
||||
}
|
||||
// Submit Query to Cosmos
|
||||
var results = await this.cosmosDatabase.GetByDocumentQuery(query, feedOptions, continuationToken);
|
||||
this.log.LogTrace($"Query used {results.RequestCharge} RUs query: {query}");
|
||||
|
||||
manifests = manifests.Where(filterPredicate).ToList();
|
||||
}
|
||||
|
||||
foreach (PackageManifest manifest in manifests)
|
||||
{
|
||||
foreach (ManifestSearchResponse response in ManifestSearchResponse.GetSearchVersions(manifest))
|
||||
{
|
||||
manifestSearchResponse.Add(response);
|
||||
}
|
||||
}
|
||||
List<ManifestSearchResponse> manifestSearchResponse = results.Items.Distinct().SelectMany(m => ManifestSearchResponse.GetSearchVersions(m)).ToList();
|
||||
|
||||
// Consolidate Results
|
||||
manifestSearchResponse = ManifestSearchResponse.Consolidate(manifestSearchResponse).OrderBy(manifest => manifest.PackageIdentifier).ToList();
|
||||
|
||||
// Process results
|
||||
if (manifestSearchResponse.Count > manifestSearchRequest.MaximumResults && manifestSearchRequest.MaximumResults > 0)
|
||||
{
|
||||
manifestSearchResponse = manifestSearchResponse.GetRange(0, manifestSearchRequest.MaximumResults);
|
||||
}
|
||||
|
||||
int maxPageCount = manifestSearchRequest.MaximumResults < FunctionSettingsConstants.MaxResultsPerPage && manifestSearchRequest.MaximumResults > 0
|
||||
? manifestSearchRequest.MaximumResults
|
||||
: FunctionSettingsConstants.MaxResultsPerPage;
|
||||
|
||||
int totalResults = manifestSearchResponse.Count;
|
||||
if (totalResults > maxPageCount)
|
||||
{
|
||||
// Process Continuation Token
|
||||
ApiContinuationToken token = null;
|
||||
if (headers.ContainsKey(HeaderConstants.ContinuationToken))
|
||||
{
|
||||
token = Parser.StringParser<ApiContinuationToken>(StringEncoder.DecodeContinuationToken(headers[HeaderConstants.ContinuationToken]));
|
||||
}
|
||||
else
|
||||
{
|
||||
token = new ApiContinuationToken()
|
||||
{
|
||||
Index = 0,
|
||||
MaxPageSize = maxPageCount,
|
||||
};
|
||||
}
|
||||
|
||||
// If index miss-match dump results and return no content.
|
||||
if (token.Index > manifestSearchResponse.Count - 1)
|
||||
{
|
||||
manifestSearchResponse = new List<ManifestSearchResponse>();
|
||||
token = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
int elementsRemaining = totalResults - token.Index;
|
||||
int elements = elementsRemaining < token.MaxPageSize ? elementsRemaining : token.MaxPageSize;
|
||||
manifestSearchResponse = manifestSearchResponse.GetRange(token.Index, elements);
|
||||
|
||||
token.Index += elements;
|
||||
if (token.Index == totalResults)
|
||||
{
|
||||
token = null;
|
||||
}
|
||||
}
|
||||
|
||||
apiDataPage.ContinuationToken = token != null ? StringEncoder.EncodeContinuationToken(FormatJSON.None(token)) : null;
|
||||
}
|
||||
|
||||
apiDataPage.Items = ManifestSearchResponse.Consolidate(manifestSearchResponse.ToList());
|
||||
// Create Working Set and return
|
||||
ApiDataPage<ManifestSearchResponse> apiDataPage = new ApiDataPage<ManifestSearchResponse>();
|
||||
apiDataPage.ContinuationToken = results.ContinuationToken != null ? StringEncoder.EncodeContinuationToken(results.ContinuationToken) : null;
|
||||
apiDataPage.Items = manifestSearchResponse;
|
||||
|
||||
return apiDataPage;
|
||||
}
|
||||
|
||||
private static void AddConditionIfValid(PredicateGenerator predicate, SearchRequestPackageMatchFilter condition, PredicateOperator predicateOperator)
|
||||
{
|
||||
// Some package match fields or types might not be supported by current version of search.
|
||||
// So we will check the supported list before adding any predicates.
|
||||
if (IsPackageMatchFieldSupported(condition.PackageMatchField) &&
|
||||
IsMatchTypeSupported(condition.RequestMatch.MatchType))
|
||||
{
|
||||
predicate.AddCondition(condition, predicateOperator);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to apply market filter and return results.
|
||||
/// </summary>
|
||||
/// <param name="packageManifests">Package manifests on which filter must be applied.</param>
|
||||
/// <param name="marketFilter">Market filter value.</param>
|
||||
internal void ApplyMarketFilter(IList<PackageManifest> packageManifests, string marketFilter)
|
||||
private static void ApplyMarketFilter(IList<PackageManifest> packageManifests, string marketFilter)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(marketFilter))
|
||||
{
|
||||
|
@ -697,7 +574,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
}
|
||||
|
||||
private bool IsPackageMatchFieldSupported(string packageMatchField)
|
||||
private static bool IsPackageMatchFieldSupported(string packageMatchField)
|
||||
{
|
||||
bool isPackageMatchFieldSupported = false;
|
||||
|
||||
|
@ -718,7 +595,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
return isPackageMatchFieldSupported;
|
||||
}
|
||||
|
||||
private bool IsMatchTypeSupported(string matchType)
|
||||
private static bool IsMatchTypeSupported(string matchType)
|
||||
{
|
||||
bool isMatchTypeSupported = false;
|
||||
|
||||
|
@ -734,121 +611,5 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
|
||||
return isMatchTypeSupported;
|
||||
}
|
||||
|
||||
private Expression<Func<CosmosPackageManifest, bool>> QueryPredicate(string packageMatchField, SearchRequestMatch requestMatch)
|
||||
{
|
||||
return (packageMatchField, requestMatch.MatchType) switch
|
||||
{
|
||||
(PackageMatchFields.PackageIdentifier, MatchType.Exact) =>
|
||||
manifest => manifest.PackageIdentifier.Equals(requestMatch.KeyWord),
|
||||
|
||||
(PackageMatchFields.PackageIdentifier, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.PackageIdentifier.ToLower().Equals(requestMatch.KeyWord.ToLower()),
|
||||
|
||||
(PackageMatchFields.PackageIdentifier, MatchType.StartsWith) =>
|
||||
manifest => manifest.PackageIdentifier.StartsWith(requestMatch.KeyWord),
|
||||
|
||||
(PackageMatchFields.PackageIdentifier, MatchType.Substring) =>
|
||||
manifest => manifest.PackageIdentifier.Contains(requestMatch.KeyWord),
|
||||
|
||||
(PackageMatchFields.PackageName, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.PackageName.Equals(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.PackageName, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.PackageName.ToLower().Equals(requestMatch.KeyWord.ToLower())),
|
||||
|
||||
(PackageMatchFields.PackageName, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.PackageName.StartsWith(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.PackageName, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.PackageName.Contains(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.Moniker, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.Moniker != null && extended.DefaultLocale.Moniker.Equals(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.Moniker, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.Moniker != null && extended.DefaultLocale.Moniker.ToLower().Equals(requestMatch.KeyWord.ToLower())),
|
||||
|
||||
(PackageMatchFields.Moniker, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.Moniker != null && extended.DefaultLocale.Moniker.StartsWith(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.Moniker, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.DefaultLocale.Moniker != null && extended.DefaultLocale.Moniker.Contains(requestMatch.KeyWord)),
|
||||
|
||||
(PackageMatchFields.Command, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Commands != null && installer.Commands.Any(command => command.Equals(requestMatch.KeyWord)))),
|
||||
|
||||
(PackageMatchFields.Command, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Commands != null && installer.Commands.Any(command => command.ToLower().Equals(requestMatch.KeyWord.ToLower())))),
|
||||
|
||||
(PackageMatchFields.Command, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Commands != null && installer.Commands.Any(command => command.StartsWith(requestMatch.KeyWord)))),
|
||||
|
||||
(PackageMatchFields.Command, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Commands != null && installer.Commands.Any(command => command.Contains(requestMatch.KeyWord)))),
|
||||
|
||||
(PackageMatchFields.Tag, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.Tags != null && extended.DefaultLocale.Tags.Any(tag => tag.Equals(requestMatch.KeyWord))) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.Tags != null && locale.Tags.Any(tag => tag.Equals(requestMatch.KeyWord))))),
|
||||
|
||||
(PackageMatchFields.Tag, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.Tags != null && extended.DefaultLocale.Tags.Any(tag => tag.ToLower().Equals(requestMatch.KeyWord.ToLower()))) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.Tags != null && locale.Tags.Any(tag => tag.ToLower().Equals(requestMatch.KeyWord.ToLower()))))),
|
||||
|
||||
(PackageMatchFields.Tag, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.Tags != null && extended.DefaultLocale.Tags.Any(tag => tag.StartsWith(requestMatch.KeyWord))) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.Tags != null && locale.Tags.Any(tag => tag.StartsWith(requestMatch.KeyWord))))),
|
||||
|
||||
(PackageMatchFields.Tag, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.Tags != null && extended.DefaultLocale.Tags.Any(tag => tag.Contains(requestMatch.KeyWord))) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.Tags != null && locale.Tags.Any(tag => tag.Contains(requestMatch.KeyWord))))),
|
||||
|
||||
(PackageMatchFields.PackageFamilyName, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.PackageFamilyName != null && installer.PackageFamilyName.Equals(requestMatch.KeyWord))),
|
||||
|
||||
(PackageMatchFields.PackageFamilyName, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.PackageFamilyName != null && installer.PackageFamilyName.ToLower().Equals(requestMatch.KeyWord.ToLower()))),
|
||||
|
||||
(PackageMatchFields.PackageFamilyName, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.PackageFamilyName != null && installer.PackageFamilyName.StartsWith(requestMatch.KeyWord))),
|
||||
|
||||
(PackageMatchFields.PackageFamilyName, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.PackageFamilyName != null && installer.PackageFamilyName.Contains(requestMatch.KeyWord))),
|
||||
|
||||
(PackageMatchFields.ProductCode, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.ProductCode != null && installer.ProductCode.Equals(requestMatch.KeyWord))),
|
||||
|
||||
(PackageMatchFields.ProductCode, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.ProductCode != null && installer.ProductCode.ToLower().Equals(requestMatch.KeyWord.ToLower()))),
|
||||
|
||||
(PackageMatchFields.ProductCode, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.ProductCode != null && installer.ProductCode.StartsWith(requestMatch.KeyWord))),
|
||||
|
||||
(PackageMatchFields.ProductCode, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.ProductCode != null && installer.ProductCode.Contains(requestMatch.KeyWord))),
|
||||
|
||||
(ShortDescription, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.ShortDescription.Equals(requestMatch.KeyWord)) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.ShortDescription != null && locale.ShortDescription.Equals(requestMatch.KeyWord)))),
|
||||
|
||||
(ShortDescription, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.ShortDescription.ToLower().Equals(requestMatch.KeyWord.ToLower())) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.ShortDescription != null && locale.ShortDescription.ToLower().Equals(requestMatch.KeyWord.ToLower())))),
|
||||
|
||||
(ShortDescription, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.ShortDescription.StartsWith(requestMatch.KeyWord)) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.ShortDescription != null && locale.ShortDescription.StartsWith(requestMatch.KeyWord)))),
|
||||
|
||||
(ShortDescription, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.DefaultLocale.ShortDescription.Contains(requestMatch.KeyWord)) || manifest.Versions.Any(extended => extended.Locales != null && extended.Locales.Any(locale => locale.ShortDescription != null && locale.ShortDescription.Contains(requestMatch.KeyWord)))),
|
||||
|
||||
(PackageMatchFields.Market, MatchType.Exact) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.AllowedMarkets != null && installer.Markets.AllowedMarkets.Any(market => market.Equals(requestMatch.KeyWord)))) || manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.ExcludedMarkets != null && installer.Markets.ExcludedMarkets.Any(market => !market.Equals(requestMatch.KeyWord))))),
|
||||
|
||||
(PackageMatchFields.Market, MatchType.CaseInsensitive) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.AllowedMarkets != null && installer.Markets.AllowedMarkets.Any(market => market.ToLower().Equals(requestMatch.KeyWord.ToLower())))) || manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.ExcludedMarkets != null && installer.Markets.ExcludedMarkets.Any(market => !market.ToLower().Equals(requestMatch.KeyWord.ToLower()))))),
|
||||
|
||||
(PackageMatchFields.Market, MatchType.StartsWith) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.AllowedMarkets != null && installer.Markets.AllowedMarkets.Any(market => market.StartsWith(requestMatch.KeyWord)))) || manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.ExcludedMarkets != null && installer.Markets.ExcludedMarkets.Any(market => !market.StartsWith(requestMatch.KeyWord))))),
|
||||
|
||||
(PackageMatchFields.Market, MatchType.Substring) =>
|
||||
manifest => manifest.Versions != null && (manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.AllowedMarkets != null && installer.Markets.AllowedMarkets.Any(market => market.Contains(requestMatch.KeyWord)))) || manifest.Versions.Any(extended => extended.Installers != null && extended.Installers.Any(installer => installer.Markets != null && installer.Markets.ExcludedMarkets != null && installer.Markets.ExcludedMarkets.Any(market => !market.Contains(requestMatch.KeyWord))))),
|
||||
|
||||
_ => throw new InvalidArgumentException(new InternalRestError(ErrorConstants.ValidationFailureErrorCode, ErrorConstants.ValidationFailureErrorMessage)),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,10 +11,10 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Cosmos;
|
||||
using Microsoft.Azure.Cosmos.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.WinGet.RestSource.Utils.Common;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants;
|
||||
using Microsoft.WinGet.RestSource.Utils.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// This class retrieves a database and sets it up if it does not exist.
|
||||
|
@ -51,6 +51,27 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
this.readWriteContainer = this.readWriteClient.GetContainer(databaseId, containerId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CreateContainer(int? throughput = null)
|
||||
{
|
||||
var database = await this.readWriteClient.CreateDatabaseIfNotExistsAsync(this.databaseId);
|
||||
await database.Database.CreateContainerIfNotExistsAsync(this.containerId, "/id", throughput);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteContainer()
|
||||
{
|
||||
await this.readWriteContainer.DeleteContainerAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> Count<T>()
|
||||
where T : class
|
||||
{
|
||||
int count = await this.readOnlyContainer.GetItemLinqQueryable<T>().CountAsync();
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add<T>(CosmosDocument<T> cosmosDocument)
|
||||
where T : class, ICosmosIdDocument
|
||||
|
@ -112,7 +133,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
try
|
||||
{
|
||||
var requestOptions = new ItemRequestOptions { IfMatchEtag = cosmosDocument.Etag };
|
||||
await this.readWriteContainer.ReplaceItemAsync(cosmosDocument.Document, cosmosDocument.Id, requestOptions: requestOptions);
|
||||
await this.readWriteContainer.ReplaceItemAsync(cosmosDocument.Document, cosmosDocument.Id, new PartitionKey(cosmosDocument.Id), requestOptions: requestOptions);
|
||||
}
|
||||
catch (CosmosException cosmosException)
|
||||
{
|
||||
|
@ -125,7 +146,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IQueryable<T> GetIQueryable<T>(QueryRequestOptions requestOptions = null, string continuationToken = null)
|
||||
public IOrderedQueryable<T> GetIQueryable<T>(QueryRequestOptions requestOptions = null, string continuationToken = null)
|
||||
where T : class
|
||||
{
|
||||
try
|
||||
|
@ -135,7 +156,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
requestOptions = new QueryRequestOptions { ResponseContinuationTokenLimitInKb = CosmosConnectionConstants.ResponseContinuationTokenLimitInKb };
|
||||
}
|
||||
|
||||
IQueryable<T> iQueryable = this.readOnlyContainer.GetItemLinqQueryable<T>(continuationToken: continuationToken, requestOptions: requestOptions);
|
||||
IOrderedQueryable<T> iQueryable = this.readOnlyContainer.GetItemLinqQueryable<T>(true, continuationToken: continuationToken, requestOptions: requestOptions);
|
||||
return iQueryable;
|
||||
}
|
||||
catch (CosmosException cosmosException)
|
||||
|
@ -186,6 +207,7 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
try
|
||||
{
|
||||
FeedResponse<T> response = await documentQuery.ReadNextAsync();
|
||||
apiDataPage.RequestCharge = response.RequestCharge;
|
||||
apiDataPage.ContinuationToken = response.ContinuationToken;
|
||||
apiDataPage.Items = response.ToList();
|
||||
}
|
||||
|
@ -201,5 +223,39 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
// Return the model
|
||||
return apiDataPage;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ApiDataPage<T>> GetByDocumentQuery<T>(IQueryable<T> documentQuery, QueryRequestOptions feedOptions, string continuationToken)
|
||||
where T : class
|
||||
{
|
||||
ApiDataPage<T> apiDataPage = new ApiDataPage<T>();
|
||||
|
||||
try
|
||||
{
|
||||
string sql = JsonConvert.DeserializeObject<IQueryableSql>(documentQuery.ToString()).Sql;
|
||||
FeedIterator<T> query = this.readOnlyContainer.GetItemQueryIterator<T>(sql, continuationToken, feedOptions);
|
||||
FeedResponse<T> response = await query.ReadNextAsync();
|
||||
apiDataPage.RequestCharge = response.RequestCharge;
|
||||
apiDataPage.ContinuationToken = response.ContinuationToken;
|
||||
apiDataPage.Items = response.ToList();
|
||||
}
|
||||
catch (CosmosException cosmosException)
|
||||
{
|
||||
throw new CosmosDatabaseException(cosmosException);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
throw new DefaultException(exception);
|
||||
}
|
||||
|
||||
// Return the model
|
||||
return apiDataPage;
|
||||
}
|
||||
|
||||
private class IQueryableSql
|
||||
{
|
||||
[JsonProperty(PropertyName = "query")]
|
||||
public string Sql { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets partition Key.
|
||||
/// Gets partition Key.
|
||||
/// </summary>
|
||||
public string PartitionKey { get; set; }
|
||||
public string PartitionKey => this.Id;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the etag for a document.
|
||||
|
|
|
@ -2,58 +2,58 @@
|
|||
// <copyright file="CosmosPackageManifest.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas
|
||||
{
|
||||
using Microsoft.WinGet.RestSource.Cosmos;
|
||||
{
|
||||
using Microsoft.WinGet.RestSource.Cosmos;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// This is a manifest, which is an extension of the package core model, and the extended version model.
|
||||
/// </summary>
|
||||
public class CosmosPackageManifest : PackageManifest, ICosmosIdDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
public CosmosPackageManifest()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cosmosPackageManifest">manifest.</param>
|
||||
public CosmosPackageManifest(CosmosPackageManifest cosmosPackageManifest)
|
||||
: base(cosmosPackageManifest)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="packageManifest">manifest.</param>
|
||||
public CosmosPackageManifest(PackageManifest packageManifest)
|
||||
: base(packageManifest)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets ID.
|
||||
/// </summary>
|
||||
[JsonProperty("id", Order = 1)]
|
||||
public string Id => this.PackageIdentifier.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Converts to a Package Core.
|
||||
/// </summary>
|
||||
/// <returns>Package Core.</returns>
|
||||
public PackageManifest ToManifest()
|
||||
{
|
||||
PackageManifest packageManifest = new PackageManifest(this);
|
||||
return packageManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
using Newtonsoft.Json;
|
||||
|
||||
/// <summary>
|
||||
/// This is a manifest, which is an extension of the package core model, and the extended version model.
|
||||
/// </summary>
|
||||
public class CosmosPackageManifest : PackageManifest, ICosmosIdDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
public CosmosPackageManifest()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="cosmosPackageManifest">manifest.</param>
|
||||
public CosmosPackageManifest(CosmosPackageManifest cosmosPackageManifest)
|
||||
: base(cosmosPackageManifest)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CosmosPackageManifest"/> class.
|
||||
/// </summary>
|
||||
/// <param name="packageManifest">manifest.</param>
|
||||
public CosmosPackageManifest(PackageManifest packageManifest)
|
||||
: base(packageManifest)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets ID.
|
||||
/// </summary>
|
||||
[JsonProperty("id", Order = 1)]
|
||||
public string Id => this.PackageIdentifier.ToString();
|
||||
|
||||
/// <summary>
|
||||
/// Converts to a Package Core.
|
||||
/// </summary>
|
||||
/// <returns>Package Core.</returns>
|
||||
public PackageManifest ToManifest()
|
||||
{
|
||||
PackageManifest packageManifest = new PackageManifest(this);
|
||||
return packageManifest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,27 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
/// </summary>
|
||||
public interface ICosmosDatabase
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a container exists, and if it doesn't, create it. This will make a read operation, and if the container is not found it will do a create operation.
|
||||
/// </summary>
|
||||
/// <param name="throughput">(Optional) The throughput provisioned for a container in measurement of Request Units per second in the Azure Cosmos DB service.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task CreateContainer(int? throughput = null);
|
||||
|
||||
/// <summary>
|
||||
/// Delete the container from the Azure Cosmos DB service as an asynchronous operation.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task DeleteContainer();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of items in the Cosmos DB.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the items to count.</typeparam>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task<int> Count<T>()
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// This will add a new document.
|
||||
/// This will fail if a document already exists that corresponds to the same ID.
|
||||
|
@ -55,13 +76,13 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
where T : class, ICosmosIdDocument;
|
||||
|
||||
/// <summary>
|
||||
/// This will return an IQueryable for building out document queries.
|
||||
/// This will return an IOrderedQueryable for building out document queries.
|
||||
/// </summary>
|
||||
/// <param name="feedOptions">Feed Options.</param>
|
||||
/// <param name="continuationToken">(Optional) The continuation token in the Azure Cosmos DB service.</param>
|
||||
/// <typeparam name="T">Document Type.</typeparam>
|
||||
/// <returns>IQueryable.</returns>
|
||||
IQueryable<T> GetIQueryable<T>(QueryRequestOptions feedOptions = null, string continuationToken = null)
|
||||
IOrderedQueryable<T> GetIQueryable<T>(QueryRequestOptions feedOptions = null, string continuationToken = null)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,5 +103,16 @@ namespace Microsoft.WinGet.RestSource.Cosmos
|
|||
/// <returns>Document.</returns>
|
||||
Task<ApiDataPage<T>> GetByDocumentQuery<T>(FeedIterator<T> documentQuery)
|
||||
where T : class;
|
||||
|
||||
/// <summary>
|
||||
/// This will retrieve a document by document query.
|
||||
/// </summary>
|
||||
/// <param name="documentQuery">Document Query.</param>
|
||||
/// <param name="feedOptions">Feed Options.</param>
|
||||
/// <param name="continuationToken">(Optional) The continuation token in the Azure Cosmos DB service.</param>
|
||||
/// <typeparam name="T">Document Type.</typeparam>
|
||||
/// <returns>Document.</returns>
|
||||
Task<ApiDataPage<T>> GetByDocumentQuery<T>(IQueryable<T> documentQuery, QueryRequestOptions feedOptions, string continuationToken)
|
||||
where T : class;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
// -----------------------------------------------------------------------
|
||||
// <copyright file="PredicateGenerator.cs" company="Microsoft Corporation">
|
||||
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
|
||||
// </copyright>
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.WinGet.RestSource.Cosmos
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.WinGet.RestSource.Utils.Common;
|
||||
using Microsoft.WinGet.RestSource.Utils.Constants.Enumerations;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.ExtendedSchemas;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Objects;
|
||||
using Microsoft.WinGet.RestSource.Utils.Models.Schemas;
|
||||
using PredicateOperator = LinqKit.PredicateOperator;
|
||||
|
||||
/// <summary>
|
||||
/// Expression predicate generator that is safe for usage with Cosmos DB.
|
||||
/// </summary>
|
||||
public class PredicateGenerator
|
||||
{
|
||||
private Expression<Func<CosmosPackageManifest, bool>> rootPredicate;
|
||||
private Expression<Func<VersionExtended, bool>> versionPredicate;
|
||||
private Expression<Func<Installer, bool>> installerPredicate;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PredicateGenerator"/> class.
|
||||
/// </summary>
|
||||
public PredicateGenerator()
|
||||
{
|
||||
this.rootPredicate = PredicateBuilder.False<CosmosPackageManifest>();
|
||||
this.versionPredicate = PredicateBuilder.False<VersionExtended>();
|
||||
this.installerPredicate = PredicateBuilder.False<Installer>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a condition to this predicate based on specified condition and operator.
|
||||
/// </summary>
|
||||
/// <param name="condition">Condition to add to predicate.</param>
|
||||
/// <param name="predicateOperator">Operator to apply to new condition.</param>
|
||||
public void AddCondition(SearchRequestPackageMatchFilter condition, PredicateOperator predicateOperator)
|
||||
{
|
||||
var op = GetOperator(condition.RequestMatch.MatchType);
|
||||
|
||||
if (GetRootClause(condition, op, out Expression<Func<CosmosPackageManifest, bool>> rootClause))
|
||||
{
|
||||
this.rootPredicate = predicateOperator == PredicateOperator.Or ? this.rootPredicate.Or(rootClause) : this.rootPredicate.And(rootClause);
|
||||
}
|
||||
else if (GetVersionClause(condition, op, out Expression<Func<VersionExtended, bool>> versionClause))
|
||||
{
|
||||
this.versionPredicate = predicateOperator == PredicateOperator.Or ? this.versionPredicate.Or(versionClause) : this.versionPredicate.And(versionClause);
|
||||
}
|
||||
else if (GetInstallerClause(condition, op, out Expression<Func<Installer, bool>> installerClause))
|
||||
{
|
||||
this.installerPredicate = predicateOperator == PredicateOperator.Or ? this.installerPredicate.Or(installerClause) : this.installerPredicate.And(installerClause);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merge all sub-predicates into a single predicate, simplifying where possible.
|
||||
/// </summary>
|
||||
/// <param name="predicateOperator">Operator to use for merging.</param>
|
||||
/// <returns>Merged root predicate.</returns>
|
||||
public Expression<Func<CosmosPackageManifest, bool>> Generate(PredicateOperator predicateOperator)
|
||||
{
|
||||
if (this.versionPredicate.IsStarted() || this.installerPredicate.IsStarted())
|
||||
{
|
||||
if (this.installerPredicate.IsStarted())
|
||||
{
|
||||
Expression<Func<VersionExtended, bool>> installerClause = v => v.Installers.Any(this.installerPredicate.Compile());
|
||||
this.versionPredicate = predicateOperator == PredicateOperator.Or ? this.versionPredicate.Or(installerClause) : this.versionPredicate.And(installerClause);
|
||||
}
|
||||
|
||||
Expression<Func<CosmosPackageManifest, bool>> versionClause = m => m.Versions.Any(this.versionPredicate.Compile());
|
||||
this.rootPredicate = predicateOperator == PredicateOperator.Or ? this.rootPredicate.Or(versionClause) : this.rootPredicate.And(versionClause);
|
||||
}
|
||||
|
||||
return this.rootPredicate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the predicate is the default predicate.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the predicate is not default, false otherwise.</returns>
|
||||
public bool IsStarted()
|
||||
{
|
||||
return this.rootPredicate.IsStarted() || this.versionPredicate.IsStarted() || this.installerPredicate.IsStarted();
|
||||
}
|
||||
|
||||
private static Expression<Func<string, string, bool>> GetOperator(string matchType)
|
||||
{
|
||||
return matchType switch
|
||||
{
|
||||
MatchType.Exact => (field, keyword) => field.Equals(keyword),
|
||||
MatchType.CaseInsensitive => (field, keyword) => field.Equals(keyword, StringComparison.OrdinalIgnoreCase),
|
||||
MatchType.StartsWith => (field, keyword) => field.StartsWith(keyword),
|
||||
MatchType.Substring => (field, keyword) => field.Contains(keyword),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static bool GetRootClause(SearchRequestPackageMatchFilter filter, Expression<Func<string, string, bool>> op, out Expression<Func<CosmosPackageManifest, bool>> clause)
|
||||
{
|
||||
string keyword = filter.RequestMatch.KeyWord;
|
||||
|
||||
clause = filter.PackageMatchField switch
|
||||
{
|
||||
PackageMatchFields.PackageIdentifier => m => LinqKit.Extensions.Invoke(op, m.PackageIdentifier, keyword),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return clause != null;
|
||||
}
|
||||
|
||||
private static bool GetVersionClause(SearchRequestPackageMatchFilter filter, Expression<Func<string, string, bool>> op, out Expression<Func<VersionExtended, bool>> clause)
|
||||
{
|
||||
string keyword = filter.RequestMatch.KeyWord;
|
||||
|
||||
clause = filter.PackageMatchField switch
|
||||
{
|
||||
PackageMatchFields.PackageName => v => LinqKit.Extensions.Invoke(op, v.DefaultLocale.PackageName, keyword),
|
||||
PackageMatchFields.Moniker => v => LinqKit.Extensions.Invoke(op, v.DefaultLocale.Moniker, keyword),
|
||||
PackageMatchFields.ShortDescription => v => LinqKit.Extensions.Invoke(op, v.DefaultLocale.ShortDescription, keyword),
|
||||
PackageMatchFields.Tag => v => v.DefaultLocale.Tags.Any(t => LinqKit.Extensions.Invoke(op, t, keyword)),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return clause != null;
|
||||
}
|
||||
|
||||
private static bool GetInstallerClause(SearchRequestPackageMatchFilter filter, Expression<Func<string, string, bool>> op, out Expression<Func<Installer, bool>> clause)
|
||||
{
|
||||
string keyword = filter.RequestMatch.KeyWord;
|
||||
|
||||
clause = filter.PackageMatchField switch
|
||||
{
|
||||
PackageMatchFields.PackageFamilyName => i => LinqKit.Extensions.Invoke(op, i.PackageFamilyName, keyword),
|
||||
PackageMatchFields.ProductCode => i => LinqKit.Extensions.Invoke(op, i.ProductCode, keyword),
|
||||
PackageMatchFields.Command => i => i.Commands.Any(c => LinqKit.Extensions.Invoke(op, c, keyword)),
|
||||
_ => null,
|
||||
};
|
||||
|
||||
return clause != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,50 +1,49 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Microsoft.WinGet.RestSource</AssemblyName>
|
||||
<RootNamespace>Microsoft.WinGet.RestSource</RootNamespace>
|
||||
<OutputType>Library</OutputType>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource\Microsoft.WinGet.RestSource.Documentation.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<WarningsAsErrors />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LinqKit" Version="1.1.24" />
|
||||
<!-- TODO: Using a preview version of this CosmosDB helper library as there's no stable version yet that supports the v3 CosmosDB SDK.
|
||||
We'll remove the preview dependency as part of the work to update the LINQ-querying logic.
|
||||
-->
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.CosmosDB" Version="4.0.0-preview2" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<AssemblyName>Microsoft.WinGet.RestSource</AssemblyName>
|
||||
<RootNamespace>Microsoft.WinGet.RestSource</RootNamespace>
|
||||
<OutputType>Library</OutputType>
|
||||
<LangVersion>8</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DebugType>full</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<NoWarn>1701;1702;NU1701</NoWarn>
|
||||
<DocumentationFile>$(SolutionDir)WinGet.RestSource\Microsoft.WinGet.RestSource.Documentation.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<WarningsAsErrors />
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Component Governance fix. Remove when dependency resolving correctly picks up new version, most likely when updating to dotnet 5.0 -->
|
||||
<PackageReference Include="System.Text.Encodings.Web" Version="4.7.2" />
|
||||
<PackageReference Include="LinqKit" Version="1.1.26" />
|
||||
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.22.0" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="3.0.10" />
|
||||
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WinGet.RestSource.Utils\WinGet.RestSource.Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -51,6 +51,11 @@
|
|||
Gets or sets continuation Token.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.WinGet.RestSource.Utils.Common.ApiDataPage`1.RequestCharge">
|
||||
<summary>
|
||||
Gets or sets the request charge for this request from the Azure Cosmos DB service.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Microsoft.WinGet.RestSource.Utils.Common.FormatJSON">
|
||||
<summary>
|
||||
Class that contains JSON helpers for printing and exporting data.
|
||||
|
@ -103,6 +108,12 @@
|
|||
This provides an interface for IApiDataStore.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.Count">
|
||||
<summary>
|
||||
Returns the count of items in the data store.
|
||||
</summary>
|
||||
<returns>The number if items in the data store.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.AddPackage(Microsoft.WinGet.RestSource.Utils.Models.Schemas.Package)">
|
||||
<summary>
|
||||
This will add a package to the data store.
|
||||
|
@ -125,12 +136,12 @@
|
|||
<param name="package">Package.</param>
|
||||
<returns>Task.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetPackages(System.String,Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetPackages(System.String,System.String)">
|
||||
<summary>
|
||||
This will retrieve a set of packages based on a package identifier and a continuation token.
|
||||
</summary>
|
||||
<param name="packageIdentifier">Package Identifier.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<param name="continuationToken">(Optional) Continuation token.</param>
|
||||
<returns>CosmosPage of Package Manifests.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.AddVersion(System.String,Microsoft.WinGet.RestSource.Utils.Models.Schemas.Version)">
|
||||
|
@ -158,13 +169,12 @@
|
|||
<param name="version">Version.</param>
|
||||
<returns>Task.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetVersions(System.String,System.String,Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetVersions(System.String,System.String)">
|
||||
<summary>
|
||||
This will get a set of versions based on the package identifier, package version, and any continuation token.
|
||||
</summary>
|
||||
<param name="packageIdentifier">Package Identifier.</param>
|
||||
<param name="packageVersion">Package Version.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<returns>CosmosPage of Version.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.AddInstaller(System.String,System.String,Microsoft.WinGet.RestSource.Utils.Models.Schemas.Installer)">
|
||||
|
@ -195,14 +205,13 @@
|
|||
<param name="installer">Installer.</param>
|
||||
<returns>Task.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetInstallers(System.String,System.String,System.String,Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetInstallers(System.String,System.String,System.String)">
|
||||
<summary>
|
||||
This will get a set of Installers based on the package identifier, package version, installer identifier, and any continuation token.
|
||||
</summary>
|
||||
<param name="packageIdentifier">Package Identifier.</param>
|
||||
<param name="packageVersion">Package Version.</param>
|
||||
<param name="installerIdentifier">Installer Identifier.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<returns>CosmosPage of Installer.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.AddLocale(System.String,System.String,Microsoft.WinGet.RestSource.Utils.Models.Schemas.Locale)">
|
||||
|
@ -233,14 +242,13 @@
|
|||
<param name="locale">Locale.</param>
|
||||
<returns>Task.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetLocales(System.String,System.String,System.String,Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetLocales(System.String,System.String,System.String)">
|
||||
<summary>
|
||||
This will get a set of Installers based on the package identifier, package version, installer identifier, and any continuation token.
|
||||
</summary>
|
||||
<param name="packageIdentifier">Package Identifier.</param>
|
||||
<param name="packageVersion">Package Version.</param>
|
||||
<param name="packageLocale">Package Locale.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<returns>CosmosPage of Locale.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.AddPackageManifest(Microsoft.WinGet.RestSource.Utils.Models.Schemas.PackageManifest)">
|
||||
|
@ -265,21 +273,23 @@
|
|||
<param name="packageManifest">Package Manifest.</param>
|
||||
<returns>Task.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetPackageManifests(System.String,Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.GetPackageManifests(System.String,System.String,System.String,System.String,System.String)">
|
||||
<summary>
|
||||
Get a Package Manifest based on package identifier and query parameters.
|
||||
</summary>
|
||||
<param name="packageIdentifier">Package Identifier.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<param name="continuationToken">(Optional) Continuation token.</param>
|
||||
<param name="versionFilter">(Optional) Version filter.</param>
|
||||
<param name="channelFilter">(Optional) Channel filter.</param>
|
||||
<param name="marketFilter">(Optional) Market filter.</param>
|
||||
<returns>CosmosPage of Package Manifests.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.SearchPackageManifests(Microsoft.WinGet.RestSource.Utils.Models.Schemas.ManifestSearchRequest,System.Collections.Generic.Dictionary{System.String,System.String},Microsoft.AspNetCore.Http.IQueryCollection)">
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.IApiDataStore.SearchPackageManifests(Microsoft.WinGet.RestSource.Utils.Models.Schemas.ManifestSearchRequest,System.String)">
|
||||
<summary>
|
||||
This will search for manifests based on a manifest search request and a set of query parameters.
|
||||
</summary>
|
||||
<param name="manifestSearchRequest">Manifest Search Request.</param>
|
||||
<param name="headerParameters">Header Parameters.</param>
|
||||
<param name="queryParameters">Query Parameters.</param>
|
||||
<param name="continuationToken">(Optional) Continuation token.</param>
|
||||
<returns>CosmosPage of ManifestSearchResponse.</returns>
|
||||
</member>
|
||||
<member name="T:Microsoft.WinGet.RestSource.Utils.Common.Parser">
|
||||
|
@ -305,6 +315,71 @@
|
|||
<typeparam name="T">Object to return.</typeparam>
|
||||
<returns>Object.</returns>
|
||||
</member>
|
||||
<member name="T:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder">
|
||||
<summary>
|
||||
Enables the efficient, dynamic composition of query predicates.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.True``1">
|
||||
<summary>
|
||||
Creates a predicate that evaluates to true.
|
||||
</summary>
|
||||
<typeparam name="T">The expression type.</typeparam>
|
||||
<returns>A constant predicate which always returns true.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.False``1">
|
||||
<summary>
|
||||
Creates a predicate that evaluates to false.
|
||||
</summary>
|
||||
<typeparam name="T">The expression type.</typeparam>
|
||||
<returns>A constant predicate which always returns true.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.And``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
|
||||
<summary>
|
||||
Combines the first predicate with the second using the logical "and".
|
||||
</summary>
|
||||
<typeparam name="T">The expression type.</typeparam>
|
||||
<param name="first">The base expression.</param>
|
||||
<param name="second">The expression to combine onto the base.</param>
|
||||
<returns>A new AND-combined predicate.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.Or``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}},System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
|
||||
<summary>
|
||||
Combines the first predicate with the second using the logical "or".
|
||||
</summary>
|
||||
<typeparam name="T">The expression type.</typeparam>
|
||||
<param name="first">The base expression.</param>
|
||||
<param name="second">The expression to combine onto the base.</param>
|
||||
<returns>A new OR-combined predicate.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.Not``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
|
||||
<summary>
|
||||
Negates the predicate.
|
||||
</summary>
|
||||
<typeparam name="T">The expression type.</typeparam>
|
||||
<param name="expression">The base expression.</param>
|
||||
<returns>A new negated expression.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.IsStarted``1(System.Linq.Expressions.Expression{System.Func{``0,System.Boolean}})">
|
||||
<summary>
|
||||
Determines if the predicate is started.
|
||||
</summary>
|
||||
<typeparam name="T">Type of expression.</typeparam>
|
||||
<param name="expression">Expression to check.</param>
|
||||
<returns>Returns true if predicate has been started (not simply constant true or false), false otherwise.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.New``1">
|
||||
<summary>
|
||||
Create a new predicate with default expression (false).
|
||||
</summary>
|
||||
<typeparam name="T">Type of expression.</typeparam>
|
||||
<returns>A new default expression.</returns>
|
||||
</member>
|
||||
<member name="M:Microsoft.WinGet.RestSource.Utils.Common.PredicateBuilder.Compose``1(System.Linq.Expressions.Expression{``0},System.Linq.Expressions.Expression{``0},System.Func{System.Linq.Expressions.Expression,System.Linq.Expressions.Expression,System.Linq.Expressions.Expression})">
|
||||
<summary>
|
||||
Combines the first expression with the second using the specified merge function.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Microsoft.WinGet.RestSource.Utils.Common.StringEncoder">
|
||||
<summary>
|
||||
String Encoder.
|
||||
|
@ -523,21 +598,6 @@
|
|||
Substring.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.MatchType.Wildcard">
|
||||
<summary>
|
||||
Wildcard.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.MatchType.Fuzzy">
|
||||
<summary>
|
||||
Fuzzy.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.MatchType.FuzzySubstring">
|
||||
<summary>
|
||||
FuzzySubstring.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.PackageMatchFields">
|
||||
<summary>
|
||||
Package Match Field Constants.
|
||||
|
@ -578,6 +638,11 @@
|
|||
ProductCode.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.PackageMatchFields.ShortDescription">
|
||||
<summary>
|
||||
ShortDescription.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.WinGet.RestSource.Utils.Constants.Enumerations.PackageMatchFields.NormalizedPackageNameAndPublisher">
|
||||
<summary>
|
||||
NormalizedPackageNameAndPublisher.
|
||||
|
|
Загрузка…
Ссылка в новой задаче