Merge branch 'namc/mondo' into release/2.1
This commit is contained in:
commit
4db2631735
|
|
@ -3,28 +3,20 @@ trigger:
|
|||
- release/*
|
||||
|
||||
jobs:
|
||||
- template: project-ci.yml
|
||||
- template: jobs/default-build.yml
|
||||
parameters:
|
||||
jobName: PR_FastCheck
|
||||
jobDisplayName: Fast Check
|
||||
agentOs: Windows
|
||||
buildArgs: "/t:FastCheck"
|
||||
- job: RepoBuilds
|
||||
pool:
|
||||
vmImage: vs2017-win2016
|
||||
strategy:
|
||||
maxParallel: 3
|
||||
matrix:
|
||||
DataProtection:
|
||||
_FolderName: DataProtection
|
||||
WebSockets:
|
||||
_FolderName: WebSockets
|
||||
steps:
|
||||
- script: src/$(_FolderName)/build.cmd -ci
|
||||
displayName: Run src/$(_FolderName)/build.cmd
|
||||
- task: PublishTestResults@2
|
||||
displayName: Publish test results
|
||||
condition: always()
|
||||
inputs:
|
||||
testRunner: vstest
|
||||
testResultsFiles: 'src/$(_FolderName)/artifacts/logs/**/*.trx'
|
||||
- template: jobs/default-build.yml
|
||||
parameters:
|
||||
jobName: Windows_Build
|
||||
jobDisplayName: "Build: Windows"
|
||||
agentOs: Windows
|
||||
beforeBuild:
|
||||
- powershell: "& ./src/IISIntegration/tools/UpdateIISExpressCertificate.ps1"
|
||||
displayName: Setup IISExpress test certificates"
|
||||
- template: jobs/iisintegration-job.yml
|
||||
parameters:
|
||||
variables:
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ jobs:
|
|||
- job: ${{ coalesce(parameters.jobName, parameters.agentOs) }}
|
||||
displayName: ${{ coalesce(parameters.jobDisplayName, parameters.agentOs) }}
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
timeoutInMinutes: 90
|
||||
workspace:
|
||||
clean: all
|
||||
strategy:
|
||||
|
|
|
|||
|
|
@ -66,10 +66,6 @@
|
|||
path = modules/JavaScriptServices
|
||||
url = https://github.com/aspnet/JavaScriptServices.git
|
||||
branch = release/2.1
|
||||
[submodule "modules/JsonPatch"]
|
||||
path = modules/JsonPatch
|
||||
url = https://github.com/aspnet/JsonPatch.git
|
||||
branch = release/2.1
|
||||
[submodule "modules/KestrelHttpServer"]
|
||||
path = modules/KestrelHttpServer
|
||||
url = https://github.com/aspnet/KestrelHttpServer.git
|
||||
|
|
|
|||
|
|
@ -34,7 +34,15 @@
|
|||
<SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<StandardTestTfms>netcoreapp2.1;net461</StandardTestTfms>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="eng\Dependencies.props" />
|
||||
<Import Project="eng\PatchConfig.props" />
|
||||
<Import Project="eng\ProjectReferences.props" />
|
||||
<Import Project="eng\targets\Wix.Common.props" Condition="'$(MSBuildProjectExtension)' == '.wixproj'" />
|
||||
<Import Project="eng\targets\CSharp.Common.props" Condition="'$(MSBuildProjectExtension)' == '.csproj'" />
|
||||
<Import Project="eng\targets\Cpp.Common.props" Condition="'$(MSBuildProjectExtension)' == '.vcxproj'" />
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,39 @@
|
|||
<Project>
|
||||
<!-- Properties which should be set after the project has been evaluated -->
|
||||
<PropertyGroup Condition=" '$(MSBuildProjectExtension)' == '.csproj' ">
|
||||
<PackageId Condition=" '$(PackageId)' == '' ">$(AssemblyName)</PackageId>
|
||||
<IsPackable Condition="'$(IsPackable)' == '' AND ( '$(IsTestProject)' == 'true' OR '$(IsTestAssetProject)' == 'true' OR '$(IsBenchmarkProject)' == 'true' OR '$(IsSampleProject)' == 'true' ) ">false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<Import Project="eng\Baseline.props" />
|
||||
|
||||
<PropertyGroup Condition=" '$(IsPackable)' != 'false' AND '$(AspNetCorePatchVersion)' != '0' ">
|
||||
<!-- Always include framework metapackages in patch updates. -->
|
||||
<IsPackageInThisPatch Condition="'$(IsFrameworkMetapackage)' == 'true'">true</IsPackageInThisPatch>
|
||||
<IsPackageInThisPatch Condition="'$(IsPackageInThisPatch)' == ''">$(PackagesInPatch.Contains(' $(PackageId);'))</IsPackageInThisPatch>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(IsPackable)' != 'false' AND '$(IsServicingBuild)' == 'true' ">
|
||||
<!-- Used to distinguish between packages building -->
|
||||
<IsPackableInNonServicingBuild>true</IsPackableInNonServicingBuild>
|
||||
<!-- Suppress creation of .nupkg for servicing builds. -->
|
||||
<IsPackable Condition=" '$(IsPackageInThisPatch)' != 'true' ">false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(IsPackageInThisPatch)' != 'true' AND '$(BaselinePackageVersion)' != '' ">
|
||||
<!-- This keeps assembly and package versions consistent across patches. If a package is not included in a patch, its version should stay at the baseline. -->
|
||||
<Version>$(BaselinePackageVersion).0</Version>
|
||||
<AssemblyVersion>$(BaselinePackageVersion).0</AssemblyVersion>
|
||||
<PackageVersion>$(BaselinePackageVersion)</PackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Implementation projects are the projects which produce nuget packages or shipping assemblies. -->
|
||||
<IsImplementationProject Condition=" '$(IsImplementationProject)' == '' AND '$(IsTestAssetProject)' != 'true' AND '$(IsTestProject)' != 'true' AND '$(IsBenchmarkProject)' != 'true' AND '$(IsSampleProject)' != 'true' ">true</IsImplementationProject>
|
||||
|
||||
<!-- Suppress KoreBuild warnings about the mismatch of repo version and local project version. The versioning in this mega repo is sufficiently complicated that KoreBuild's validation isn't helpful. -->
|
||||
<VerifyVersion>false</VerifyVersion>
|
||||
|
||||
<EnableApiCheck Condition=" '$(EnableApiCheck)' != '' ">$(IsImplementationProject)</EnableApiCheck>
|
||||
<IsPackable Condition="'$(IsPackable)' == '' AND '$(IsImplementationProject)' == 'true' ">true</IsPackable>
|
||||
<IsPackable Condition="'$(IsPackable)' == '' ">false</IsPackable>
|
||||
|
|
@ -14,5 +44,6 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<Import Project="eng\targets\Wix.Common.targets" Condition="'$(MSBuildProjectExtension)' == '.wixproj'" />
|
||||
<Import Project="eng\targets\CSharp.Common.targets" Condition="'$(MSBuildProjectExtension)' == '.csproj'" />
|
||||
<Import Project="eng\targets\Cpp.Common.targets" Condition="'$(MSBuildProjectExtension)' == '.vcxproj'" />
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="_BuildMetapackage" DependsOnTargets="ResolveRepoInfo">
|
||||
<Target Name="_BuildMetapackage" DependsOnTargets="GetProjectArtifactInfo;ResolveRepoInfo">
|
||||
<PropertyGroup>
|
||||
<MetapackageSource>$(_MetapackageSrcRoot)$(MetapackageName)\</MetapackageSource>
|
||||
<MetapackageWorkDirectory>$(_WorkRoot)pkg\$(MetapackageName)\</MetapackageWorkDirectory>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<RepositoryBuildOrder Include="JsonPatch" Order="1" />
|
||||
<RepositoryBuildOrder Include="DotNetTools" Order="1" />
|
||||
<RepositoryBuildOrder Include="HtmlAbstractions" Order="1" />
|
||||
<RepositoryBuildOrder Include="Razor" Order="6" />
|
||||
|
|
@ -30,7 +29,6 @@
|
|||
<RepositoryBuildOrder Include="ServerTests" Order="11" />
|
||||
<RepositoryBuildOrder Include="Diagnostics" Order="12" />
|
||||
<RepositoryBuildOrder Include="Localization" Order="12" />
|
||||
<RepositoryBuildOrder Include="WebSockets" Order="13" RootPath="$(RepositoryRoot)src\WebSockets\" />
|
||||
<RepositoryBuildOrder Include="Security" Order="13" />
|
||||
<RepositoryBuildOrder Include="MetaPackages" Order="13" />
|
||||
<RepositoryBuildOrder Include="Mvc" Order="14" />
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<!-- Determined by build tools -->
|
||||
<InternalAspNetCoreSdkPackageVersion>$(KoreBuildVersion)</InternalAspNetCoreSdkPackageVersion>
|
||||
<InternalAspNetCoreSiteExtensionSdkPackageVersion>$(KoreBuildVersion)</InternalAspNetCoreSiteExtensionSdkPackageVersion>
|
||||
<InternalAspNetCoreSdkPackageVersion Condition=" '$(KoreBuildVersion)' == '' ">2.1.3-rtm-15842</InternalAspNetCoreSdkPackageVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- These are package versions that should not be overridden or updated by automation. -->
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
<PropertyGroup>
|
||||
<!-- This repo does not have solutions to build -->
|
||||
<DisableDefaultTargets>true</DisableDefaultTargets>
|
||||
<GenerateSignRequest>false</GenerateSignRequest>
|
||||
|
||||
<SignType Condition=" '$(SignType)' == '' ">public</SignType>
|
||||
<DisableDefaultItems>true</DisableDefaultItems>
|
||||
<BuildSolutions>false</BuildSolutions>
|
||||
|
||||
<SkipTests>false</SkipTests>
|
||||
<SkipTests Condition="'$(CompileOnly)' == 'true'">true</SkipTests>
|
||||
|
|
@ -42,6 +41,16 @@
|
|||
<SharedFrameworkName Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectToExclude Include="$(RepositoryRoot)src\Middleware\WebSockets\samples\**\*.csproj" />
|
||||
|
||||
<ProjectToBuild Include="
|
||||
$(RepositoryRoot)src\Features\JsonPatch\**\*.*proj;
|
||||
$(RepositoryRoot)src\Middleware\**\*.*proj;
|
||||
"
|
||||
Exclude="@(ProjectToExclude)" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Properties for publishing -->
|
||||
<PropertyGroup>
|
||||
<!-- myget = non-orchestrated builds -->
|
||||
|
|
|
|||
|
|
@ -16,18 +16,52 @@
|
|||
|
||||
<PrepareDependsOn>SetTeamCityBuildNumberToVersion;$(PrepareDependsOn);VerifyPackageArtifactConfig;VerifyExternalDependencyConfig;PrepareOutputPaths</PrepareDependsOn>
|
||||
<CleanDependsOn>$(CleanDependsOn);CleanArtifacts;CleanRepoArtifacts</CleanDependsOn>
|
||||
<RestoreDependsOn>$(RestoreDependsOn);InstallDotNet</RestoreDependsOn>
|
||||
<CompileDependsOn>$(CompileDependsOn);BuildRepositories</CompileDependsOn>
|
||||
<RestoreDependsOn>$(RestoreDependsOn);InstallDotNet;RestoreProjects</RestoreDependsOn>
|
||||
<CompileDependsOn>$(CompileDependsOn);BuildProjects;PackProjects;BuildRepositories</CompileDependsOn>
|
||||
<PackageDependsOn Condition="'$(TestOnly)' != 'true'">$(PackageDependsOn);BuildMetapackages;CheckExpectedPackagesExist</PackageDependsOn>
|
||||
<TestDependsOn>$(TestDependsOn);_TestRepositories</TestDependsOn>
|
||||
<GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);ResolveRepoInfo</GetArtifactInfoDependsOn>
|
||||
<TestDependsOn>$(TestDependsOn);TestProjects;_TestRepositories</TestDependsOn>
|
||||
<GetArtifactInfoDependsOn>$(GetArtifactInfoDependsOn);GetProjectArtifactInfo;ResolveRepoInfo</GetArtifactInfoDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PrepareOutputPaths">
|
||||
<MakeDir Directories="$(ArtifactsDir);$(BuildDir)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ResolveRepoInfo" DependsOnTargets="_PrepareRepositories;GetMetapackageArtifactInfo;GetLineupPackageInfo">
|
||||
<Target Name="GenerateProjectList" DependsOnTargets="ResolveProjects">
|
||||
<MSBuild Projects="@(ProjectToBuild)"
|
||||
Targets="GetReferencesProvided"
|
||||
BuildInParallel="true"
|
||||
SkipNonexistentTargets="true"
|
||||
SkipNonexistentProjects="true" >
|
||||
|
||||
<Output TaskParameter="TargetOutputs" ItemName="_ProjectReferenceProvider"/>
|
||||
</MSBuild>
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectListFile>$(MSBuildThisFileDirectory)..\eng\ProjectReferences.props</ProjectListFile>
|
||||
<ProjectListContent>
|
||||
<![CDATA[
|
||||
<!-- This file is automatically generated. Run `build.cmd /t:GenerateProjectList` to update. -->
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
@(_ProjectReferenceProvider->'<ProjectReferenceProvider Include="%(Identity)" ProjectPath="%24(RepositoryRoot)%(ProjectFileRelativePath)" />', '%0A ')
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
]]>
|
||||
</ProjectListContent>
|
||||
</PropertyGroup>
|
||||
|
||||
<WriteLinesToFile File="$(ProjectListFile)" Lines="$(ProjectListContent)" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="_ResolveProjectArtifactsInfoShipped" AfterTargets="GetProjectArtifactInfo">
|
||||
<ItemGroup>
|
||||
<ShippedArtifactInfo Include="@(ArtifactInfo)" Condition="'%(ArtifactInfo.IsShipped)' == 'true'" />
|
||||
<ArtifactInfo Remove="@(ShippedArtifactInfo)" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="ResolveRepoInfo" DependsOnTargets="_ResolveProjectArtifactsInfoShipped;_PrepareRepositories;GetMetapackageArtifactInfo;GetLineupPackageInfo">
|
||||
<!-- We need to pass the NETCoreApp package versions to msbuild so that it doesn't complain about us using a different one than it was restored against. -->
|
||||
<PropertyGroup>
|
||||
<DesignTimeBuildProps>MicrosoftNETCoreAppPackageVersion=$(MicrosoftNETCoreAppPackageVersion);</DesignTimeBuildProps>
|
||||
|
|
@ -216,7 +250,7 @@
|
|||
<WriteLinesToFile File="$(RepositoryRoot)artifacts\packages.csv" Lines="PackageId,Version;@(ArtifactInfo->WithMetadataValue('ArtifactType', 'NuGetPackage')->'%(PackageId),%(Version)')" Overwrite="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ComputeGraph" DependsOnTargets="ResolveRepoInfo;GeneratePropsFiles">
|
||||
<Target Name="ComputeGraph" DependsOnTargets="GetProjectArtifactInfo;ResolveRepoInfo;GeneratePropsFiles">
|
||||
<RepoTasks.CheckRepoGraph Condition=" ! $([MSBuild]::IsOSUnixLike())"
|
||||
Solutions="@(Solution)"
|
||||
Artifacts="@(ArtifactInfo);@(ShippedArtifactInfo)"
|
||||
|
|
@ -272,8 +306,14 @@
|
|||
Condition=" @(ExternalDependency->WithMetadataValue('Version', '')->Count()) != 0 " />
|
||||
</Target>
|
||||
|
||||
<Target Name="_SetDesignTimeBuild">
|
||||
<PropertyGroup>
|
||||
<BuildProperties>$(BuildProperties);DesignTimeBuild=true</BuildProperties>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="FastCheck"
|
||||
DependsOnTargets="ComputeGraph;VerifyPackageArtifactConfig;VerifyAllReposHaveNuGetPackageVerifier" />
|
||||
DependsOnTargets="_SetDesignTimeBuild;ComputeGraph;VerifyPackageArtifactConfig;VerifyAllReposHaveNuGetPackageVerifier" />
|
||||
|
||||
<Target Name="CheckExpectedPackagesExist">
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -77,6 +77,5 @@
|
|||
<ShippedRepository Include="Session" />
|
||||
<ShippedRepository Include="SignalR" />
|
||||
<ShippedRepository Include="StaticFiles" />
|
||||
<ShippedRepository Include="WebSockets" RootPath="$(RepositoryRoot)src\WebSockets\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Remove="Internal.AspNetCore.Sdk" />
|
||||
<PackageReference Include="Microsoft.DotNet.Archive" Version="$(MicrosoftDotNetArchivePackageVersion)" />
|
||||
<PackageReference Include="NuGet.Build.Tasks" Version="$(NuGetInMSBuildVersion)" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(DevDependency_MicrosoftExtensionsDependencyModelPackageVersion)" PrivateAssets="All" />
|
||||
|
|
|
|||
|
|
@ -10,11 +10,15 @@ In order to prepare this repo to build a new servicing update, the following cha
|
|||
+ <AspNetCorePatchVersion>8</AspNetCorePatchVersion>
|
||||
```
|
||||
|
||||
* Update the package archive baselines. This is used to make sure each build
|
||||
of the package archives we give to Azure only contains new files and does
|
||||
* Update the package archive baselines. This is used to make sure each build of the package archives we give to Azure only contains new files and does
|
||||
not require overwriting existing files. See [src/PackageArchive/ZipManifestGenerator/](/src/PackageArchive/ZipManifestGenerator/README.md) for instructions on how to run this tool.
|
||||
|
||||
* Update the list of repositories which will contain changes in [build/submodules.props](/build/submodules.props).
|
||||
* Update the package baselines. This is used to ensure packages keep a consistent set of dependencies between releases.
|
||||
See [eng/tools/BaselineGenerator/](/eng/tools/BaselineGenerator/README.md) for instructions on how to run this tool.
|
||||
|
||||
* **For packages with source code in this repo (not a submodule):** Update the list of packages in [eng/PatchConfig.props](/eng/PatchConfig.props) to list which packages should be patching in this release.
|
||||
|
||||
* **For packages still building from submodules:** Update the list of repositories which will contain changes in [build/submodules.props](/build/submodules.props).
|
||||
|
||||
* `<ShippedRepository>` items represent repos which were released in a previous patch, and will not contain servicing updates in the next patch.
|
||||
* `<Repository>` items represent repos which will produce new packages in this patch.
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
<!-- Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. -->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<AspNetCoreBaselineVersion>2.1.6</AspNetCoreBaselineVersion>
|
||||
</PropertyGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.Cryptography.Internal-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Cryptography.Internal' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Cryptography.Internal' AND '$(TargetFramework)' == 'netstandard2.0' " />
|
||||
<!-- Package: Microsoft.AspNetCore.Cryptography.KeyDerivation-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Cryptography.KeyDerivation' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Cryptography.KeyDerivation' AND '$(TargetFramework)' == 'netcoreapp2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Cryptography.Internal" Version="[2.1.1, )" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Cryptography.KeyDerivation' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Cryptography.Internal" Version="[2.1.1, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Cryptography.Internal" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection.Abstractions" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Win32.Registry" Version="[4.5.0, )" />
|
||||
<BaselinePackageReference Include="System.Security.Cryptography.Xml" Version="[4.5.0, )" />
|
||||
<BaselinePackageReference Include="System.Security.Principal.Windows" Version="[4.5.0, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.Abstractions-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Abstractions' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' " />
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.AzureKeyVault-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.AzureKeyVault' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.AzureKeyVault' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Azure.KeyVault" Version="[2.3.2, )" />
|
||||
<BaselinePackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="[3.14.2, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.AzureStorage-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.AzureStorage' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.AzureStorage' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="WindowsAzure.Storage" Version="[8.1.4, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.Extensions-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Extensions' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Extensions' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[2.1.1, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.Redis-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Redis' ">
|
||||
<BaselinePackageVersion>0.4.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.Redis' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="StackExchange.Redis.StrongName" Version="[1.2.4, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.DataProtection.SystemWeb-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.SystemWeb' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.DataProtection.SystemWeb' AND '$(TargetFramework)' == 'net461' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.DataProtection" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[2.1.1, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.JsonPatch-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.JsonPatch' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.JsonPatch' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.CSharp" Version="[4.5.0, )" />
|
||||
<BaselinePackageReference Include="Newtonsoft.Json" Version="[11.0.2, )" />
|
||||
</ItemGroup>
|
||||
<!-- Package: Microsoft.AspNetCore.WebSockets-->
|
||||
<PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebSockets' ">
|
||||
<BaselinePackageVersion>2.1.1</BaselinePackageVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebSockets' AND '$(TargetFramework)' == 'netstandard2.0' ">
|
||||
<BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
|
||||
<BaselinePackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="[4.5.1, )" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<!-- This file is a work in progress as we merge repos and move content here from build/dependencies.props. -->
|
||||
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="ProdCon dependencies">
|
||||
<!-- These dependencies must use version variables because they may be overriden by ProdCon builds. -->
|
||||
<LatestPackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.CSharp" Version="$(MicrosoftCSharpPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.ClosedGenericMatcher.Sources" Version="$(MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
|
||||
<LatestPackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
|
||||
<LatestPackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Label="External dependencies">
|
||||
<LatestPackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
|
||||
<LatestPackageReference Include="Moq" Version="4.10.0" />
|
||||
<!-- This version is required by MSBuild tasks or Visual Studio extensions. -->
|
||||
<LatestPackageReference Include="Newtonsoft.Json" Version="9.0.1" Condition="'$(UseMSBuildJsonNet)' == 'true'" />
|
||||
<!-- This version should be used by runtime packages -->
|
||||
<LatestPackageReference Include="Newtonsoft.Json" Version="11.0.2" Condition="'$(UseMSBuildJsonNet)' != 'true'" />
|
||||
<LatestPackageReference Include="xunit.abstractions" Version="2.0.1" />
|
||||
<LatestPackageReference Include="xunit.analyzers" Version="0.10.0" />
|
||||
<LatestPackageReference Include="xunit.assert" Version="2.3.1" />
|
||||
<LatestPackageReference Include="xunit.extensibility.core" Version="2.3.1" />
|
||||
<LatestPackageReference Include="xunit.extensibility.execution" Version="2.3.1" />
|
||||
<LatestPackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
|
||||
<LatestPackageReference Include="xunit" Version="2.4.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IsTestProject)' == 'true' ">
|
||||
<Reference Include="Microsoft.AspNetCore.Testing" />
|
||||
<Reference Include="Microsoft.NET.Test.Sdk" />
|
||||
<Reference Include="Moq" />
|
||||
<Reference Include="xunit" />
|
||||
<Reference Include="xunit.analyzers" />
|
||||
<Reference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(VersionPrefix)' == '2.1.7' ">
|
||||
<PackagesInPatch>
|
||||
Microsoft.AspNetCore;
|
||||
Microsoft.AspNetCore.Server.IISIntegration;
|
||||
</PackagesInPatch>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<!-- This file is automatically generated. Run `build.cmd /t:GenerateProjectList` to update. -->
|
||||
<Project>
|
||||
<ItemGroup>
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.JsonPatch" ProjectPath="$(RepositoryRoot)src\Features\JsonPatch\src\Microsoft.AspNetCore.JsonPatch.csproj" />
|
||||
<ProjectReferenceProvider Include="Microsoft.AspNetCore.WebSockets" ProjectPath="$(RepositoryRoot)src\Middleware\WebSockets\src\Microsoft.AspNetCore.WebSockets.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
|
||||
<!-- Instructs the compiler to use SHA256 instead of SHA1 when adding file hashes to PDBs. -->
|
||||
<ChecksumAlgorithm>SHA256</ChecksumAlgorithm>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<Project>
|
||||
|
||||
<Import Project="Packaging.targets" />
|
||||
<Import Project="ResolveReferences.targets" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<Project>
|
||||
|
||||
<Target Name="EnsureBaselineIsUpdated"
|
||||
Condition="'$(IsServicingBuild)' == 'true' AND '$(AspNetCoreBaselineVersion)' != '$(PreviousAspNetCoreReleaseVersion)'"
|
||||
BeforeTargets="BeforeBuild">
|
||||
<Error Text="The package baseline ($(AspNetCoreBaselineVersion)) is out of date with the latest release of this repo ($(PreviousAspNetCoreReleaseVersion)).
|
||||
See $(RepositoryRoot)eng\tools\BaselineGenerator\README.md for instructions on updating this baseline." />
|
||||
</Target>
|
||||
|
||||
<!-- Temporary: this target is used to gather version information to pass to submodule builds. This can be removed after we finish merging submodules. -->
|
||||
<Target Name="GetBaselineArtifactInfo"
|
||||
Condition="'$(IsPackableInNonServicingBuild)' == 'true'"
|
||||
Returns="@(ArtifactInfo)"
|
||||
BeforeTargets="GetArtifactInfo">
|
||||
|
||||
<PropertyGroup>
|
||||
<FullPackageOutputPath>$(PackageOutputPath)$(PackageId).$(PackageVersion).nupkg</FullPackageOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ArtifactInfo Include="$(FullPackageOutputPath)">
|
||||
<ArtifactType>NuGetPackage</ArtifactType>
|
||||
<PackageId>$(PackageId)</PackageId>
|
||||
<Version>$(PackageVersion)</Version>
|
||||
<RepositoryRoot>$(RepositoryRoot)</RepositoryRoot>
|
||||
<IsShipped>true</IsShipped>
|
||||
</ArtifactInfo>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<ResolveReferencesDependsOn>
|
||||
ResolveCustomReferences;
|
||||
$(ResolveReferencesDependsOn);
|
||||
</ResolveReferencesDependsOn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
Projects should only use the latest package references when:
|
||||
* preparing a new major or minor release (i.e. a non-servicing builds)
|
||||
* when a project is a test or sample project
|
||||
* when a package is releasing a new patch (we like to update external dependencies in patches when possible)
|
||||
-->
|
||||
<UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsServicingBuild)' != 'true' ">true</UseLatestPackageReferences>
|
||||
<UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsImplementationProject)' != 'true' ">true</UseLatestPackageReferences>
|
||||
<UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' AND '$(IsImplementationProject)' == 'true' AND ( '$(IsServicingBuild)' != 'true' OR '$(IsPackable)' == 'true' ) ">true</UseLatestPackageReferences>
|
||||
<UseLatestPackageReferences Condition=" '$(UseLatestPackageReferences)' == '' ">false</UseLatestPackageReferences>
|
||||
|
||||
<!--
|
||||
Projects should only use the project references instead of baseline package references when:
|
||||
* preparing a new major or minor release (i.e. a non-servicing builds)
|
||||
* when a project is a test or sample project
|
||||
We don't use project references between components in servicing builds between compontents to preserve the baseline as much as possible.
|
||||
-->
|
||||
<UseProjectReferences Condition=" '$(UseProjectReferences)' == '' AND '$(IsServicingBuild)' != 'true' ">true</UseProjectReferences>
|
||||
<UseProjectReferences Condition=" '$(UseProjectReferences)' == '' AND '$(IsImplementationProject)' != 'true' ">true</UseProjectReferences>
|
||||
<UseProjectReferences Condition=" '$(UseProjectReferences)' == '' ">false</UseProjectReferences>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ImplicitPackageReference Include="@(PackageReference->WithMetadataValue('IsImplicitlyDefined', 'true'))" />
|
||||
<_ExplicitPackageReference Include="@(PackageReference)" Exclude="@(_ImplicitPackageReference)" />
|
||||
<_ExplicitPackageReference Remove="Internal.AspNetCore.Sdk" />
|
||||
|
||||
<UnusedProjectReferenceProvider Include="@(ProjectReferenceProvider)" Exclude="@(Reference)" />
|
||||
|
||||
<!-- Order matters. Projects should be used when possible instead of packages. -->
|
||||
<_ProjectReferenceByAssemblyName Condition="'$(UseProjectReferences)' == 'true'"
|
||||
Include="@(ProjectReferenceProvider)"
|
||||
Exclude="@(UnusedProjectReferenceProvider)" />
|
||||
|
||||
<ProjectReference Include="@(_ProjectReferenceByAssemblyName->'%(ProjectPath)')" />
|
||||
|
||||
<Reference Remove="@(_ProjectReferenceByAssemblyName)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="ResolveCustomReferences" BeforeTargets="CollectPackageReferences;ResolveAssemblyReferencesDesignTime;ResolveAssemblyReferences" Condition=" '$(TargetFramework)' != '' ">
|
||||
<ItemGroup>
|
||||
<UnusedBaselinePackageReference Include="@(BaselinePackageReference)" Exclude="@(Reference);@(_ProjectReferenceByAssemblyName)" />
|
||||
|
||||
<!--
|
||||
MSBuild does not provide a way to join on matching identities in a Condition,
|
||||
but you can do a cartesian product of two item groups and filter out mismatched id's in a second pass.
|
||||
-->
|
||||
<_LatestPackageReferenceWithVersion Include="@(Reference)" Condition=" '$(UseLatestPackageReferences)' == 'true' ">
|
||||
<Id>%(LatestPackageReference.Identity)</Id>
|
||||
<Version>%(LatestPackageReference.Version)</Version>
|
||||
</_LatestPackageReferenceWithVersion>
|
||||
<_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
|
||||
|
||||
<!-- Remove reference items that have been resolved to a LatestPackageReference item. -->
|
||||
<Reference Remove="@(_LatestPackageReferenceWithVersion)" />
|
||||
<PackageReference Include="@(_LatestPackageReferenceWithVersion)" IsImplicitlyDefined="true" />
|
||||
|
||||
<!-- Resolve references from BaselinePackageReference for servicing builds. -->
|
||||
<_BaselinePackageReferenceWithVersion Include="@(Reference)" Condition=" '$(IsServicingBuild)' == 'true' OR '$(UseLatestPackageReferences)' != 'true' ">
|
||||
<Id>%(BaselinePackageReference.Identity)</Id>
|
||||
<Version>%(BaselinePackageReference.Version)</Version>
|
||||
</_BaselinePackageReferenceWithVersion>
|
||||
|
||||
<_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
|
||||
|
||||
<!-- Remove reference items that have been resolved to a BaselinePackageReference item. -->
|
||||
<PackageReference Include="@(_BaselinePackageReferenceWithVersion)" IsImplicitlyDefined="true" />
|
||||
<Reference Remove="@(_BaselinePackageReferenceWithVersion)" />
|
||||
|
||||
<!-- For PrivateAssets=All references, like .Sources packages, fallback to LatestPackageReferences. -->
|
||||
<_PrivatePackageReferenceWithVersion Include="@(Reference->WithMetadataValue('PrivateAssets', 'All'))">
|
||||
<Id>%(LatestPackageReference.Identity)</Id>
|
||||
<Version>%(LatestPackageReference.Version)</Version>
|
||||
</_PrivatePackageReferenceWithVersion>
|
||||
|
||||
<_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" Condition="'%(Id)' != '%(Identity)' " />
|
||||
|
||||
<!-- Remove reference items that have been resolved to a LatestPackageReference item. -->
|
||||
<PackageReference Include="@(_PrivatePackageReferenceWithVersion)" IsImplicitlyDefined="true" />
|
||||
<Reference Remove="@(_PrivatePackageReferenceWithVersion)" />
|
||||
|
||||
<!-- Free up memory for unnecessary items -->
|
||||
<_LatestPackageReferenceWithVersion Remove="@(_LatestPackageReferenceWithVersion)" />
|
||||
<_BaselinePackageReferenceWithVersion Remove="@(_BaselinePackageReferenceWithVersion)" />
|
||||
<_PrivatePackageReferenceWithVersion Remove="@(_PrivatePackageReferenceWithVersion)" />
|
||||
<_ImplicitPackageReference Remove="@(_ImplicitPackageReference)" />
|
||||
</ItemGroup>
|
||||
<!--
|
||||
<Error Condition="@(_ExplicitPackageReference->Count()) != 0"
|
||||
Text="PackageReference items are not allowed. Use <Reference> instead. " /> -->
|
||||
|
||||
<ItemGroup>
|
||||
<_ExplicitPackageReference Remove="@(_ExplicitPackageReference)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Warning Condition="@(UnusedBaselinePackageReference->Count()) != 0"
|
||||
Text="Package references changed since the last release. This could be a breaking change. References removed:%0A - @(UnusedBaselinePackageReference, '%0A -')" />
|
||||
|
||||
<Error Condition="'$(TargetFrameworkIdentifier)' != '.NETFramework' AND '%(Reference.Identity)' != '' AND ! Exists('%(Reference.Identity)')"
|
||||
Code="MSB3245"
|
||||
Text="Could not resolve this reference. Could not locate the package or project for "%(Reference.Identity)"" />
|
||||
</Target>
|
||||
|
||||
<Target Name="GetReferencesProvided" Returns="@(ProvidesReference)">
|
||||
<ItemGroup>
|
||||
<_TargetFramework Remove="@(_TargetFramework)" />
|
||||
<_TargetFramework Include="$(TargetFramework)" Condition="'$(TargetFramework)' != '' "/>
|
||||
<_TargetFramework Include="$(TargetFrameworks)" Condition="'$(TargetFramework)' == '' "/>
|
||||
</ItemGroup>
|
||||
|
||||
<MSBuild Projects="$(MSBuildProjectFullPath)"
|
||||
Targets="_GetReferencesProvided"
|
||||
Properties="TargetFramework=%(_TargetFramework.Identity)">
|
||||
<Output TaskParameter="TargetOutputs" ItemName="ProvidesReference" />
|
||||
</MSBuild>
|
||||
</Target>
|
||||
|
||||
<Target Name="_GetReferencesProvided" Returns="@(ProvidesReference)">
|
||||
<ItemGroup Condition=" '$(IsImplementationProject)' == 'true' OR '$(IsProjectReferenceProvider)' == 'true' ">
|
||||
<ProvidesReference Include="$(AssemblyName)">
|
||||
<ProjectFileRelativePath>$([MSBuild]::MakeRelative($(RepositoryRoot), $(MSBuildProjectFullPath)))</ProjectFileRelativePath>
|
||||
</ProvidesReference>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<StartArguments>-o "$(MSBuildThisFileDirectory)../../Baseline.props"</StartArguments>
|
||||
<StartWorkingDirectory>$(MSBuildProjectDirectory)</StartWorkingDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NuGet.Packaging" Version="4.8.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Extensions.CommandLineUtils;
|
||||
using NuGet.Packaging;
|
||||
using NuGet.Packaging.Core;
|
||||
|
||||
namespace PackageBaselineGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// This generates Baseline.props with information about the last RTM release.
|
||||
/// </summary>
|
||||
class Program : CommandLineApplication
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
new Program().Execute(args);
|
||||
}
|
||||
|
||||
private readonly CommandOption _source;
|
||||
private readonly CommandOption _output;
|
||||
|
||||
public Program()
|
||||
{
|
||||
_source = Option("-s|--source <SOURCE>", "The NuGet v2 source of the package to fetch", CommandOptionType.SingleValue);
|
||||
_output = Option("-o|--output <OUT>", "The generated file output path", CommandOptionType.SingleValue);
|
||||
|
||||
Invoke = () => Run().GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private async Task<int> Run()
|
||||
{
|
||||
var source = _source.HasValue()
|
||||
? _source.Value()
|
||||
: "https://www.nuget.org/api/v2/package";
|
||||
|
||||
var packageCache = Environment.GetEnvironmentVariable("NUGET_PACKAGES") != null
|
||||
? Environment.GetEnvironmentVariable("NUGET_PACKAGES")
|
||||
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
|
||||
|
||||
var tempDir = Path.Combine(Directory.GetCurrentDirectory(), "obj", "tmp");
|
||||
Directory.CreateDirectory(tempDir);
|
||||
|
||||
var input = XDocument.Load(Path.Combine(Directory.GetCurrentDirectory(), "baseline.xml"));
|
||||
|
||||
var output = _output.HasValue()
|
||||
? _output.Value()
|
||||
: Path.Combine(Directory.GetCurrentDirectory(), "Baseline.props");
|
||||
|
||||
var baselineVersion = input.Root.Attribute("Version").Value;
|
||||
|
||||
var doc = new XDocument(
|
||||
new XComment(" Auto generated. Do not edit manually, use eng/tools/BaselineGenerator/ to recreate. "),
|
||||
new XElement("Project",
|
||||
new XElement("PropertyGroup",
|
||||
new XElement("MSBuildAllProjects", "$(MSBuildAllProjects);$(MSBuildThisFileFullPath)"),
|
||||
new XElement("AspNetCoreBaselineVersion", baselineVersion))));
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
foreach (var pkg in input.Root.Descendants("Package"))
|
||||
{
|
||||
var id = pkg.Attribute("Id").Value;
|
||||
var version = pkg.Attribute("Version").Value;
|
||||
var packageFileName = $"{id}.{version}.nupkg";
|
||||
var nupkgPath = Path.Combine(packageCache, id.ToLowerInvariant(), version, packageFileName);
|
||||
if (!File.Exists(nupkgPath))
|
||||
{
|
||||
nupkgPath = Path.Combine(tempDir, packageFileName);
|
||||
}
|
||||
|
||||
if (!File.Exists(nupkgPath))
|
||||
{
|
||||
var url = $"{source}/{id}/{version}";
|
||||
using (var file = File.Create(nupkgPath))
|
||||
{
|
||||
Console.WriteLine($"Downloading {url}");
|
||||
var response = await client.GetStreamAsync(url);
|
||||
await response.CopyToAsync(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
using (var reader = new PackageArchiveReader(nupkgPath))
|
||||
{
|
||||
var first = true;
|
||||
foreach (var group in reader.NuspecReader.GetDependencyGroups())
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
first = false;
|
||||
doc.Root.Add(new XComment($" Package: {id}"));
|
||||
|
||||
var propertyGroup = new XElement("PropertyGroup",
|
||||
new XAttribute("Condition", $" '$(PackageId)' == '{id}' "),
|
||||
new XElement("BaselinePackageVersion", version));
|
||||
doc.Root.Add(propertyGroup);
|
||||
}
|
||||
|
||||
var itemGroup = new XElement("ItemGroup", new XAttribute("Condition", $" '$(PackageId)' == '{id}' AND '$(TargetFramework)' == '{group.TargetFramework.GetShortFolderName()}' "));
|
||||
doc.Root.Add(itemGroup);
|
||||
|
||||
foreach (var dependency in group.Packages)
|
||||
{
|
||||
itemGroup.Add(new XElement("BaselinePackageReference", new XAttribute("Include", dependency.Id), new XAttribute("Version", dependency.VersionRange.ToString())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var settings = new XmlWriterSettings
|
||||
{
|
||||
OmitXmlDeclaration = true,
|
||||
Encoding = Encoding.UTF8,
|
||||
Indent = true,
|
||||
};
|
||||
using (var writer = XmlWriter.Create(output, settings))
|
||||
{
|
||||
doc.Save(writer);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
BaselineGenerator
|
||||
=================
|
||||
|
||||
This tool is used to generate an MSBuild file which sets the "baseline" against which servicing updates are built.
|
||||
|
||||
## Usage
|
||||
|
||||
1. Add to the [baseline.xml](./baseline.xml) a list of package ID's and their latest released versions. The source of this information can typically
|
||||
be found in the build.xml file generated during ProdCon builds. See https://github.com/dotnet/versions/blob/master/build-info/dotnet/product/cli/release/2.1.6/build.xml for example.
|
||||
2. Run `dotnet run` on this project.
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Baseline Version="2.1.6">
|
||||
<Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.Abstractions" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.AzureKeyVault" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.AzureStorage" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.Extensions" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.Redis" Version="0.4.1" />
|
||||
<Package Id="Microsoft.AspNetCore.DataProtection.SystemWeb" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.JsonPatch" Version="2.1.1" />
|
||||
<Package Id="Microsoft.AspNetCore.WebSockets" Version="2.1.1" />
|
||||
</Baseline>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<Project>
|
||||
<Import Project="..\..\build\sources.props" />
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<Project>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaselineGenerator", "BaselineGenerator\BaselineGenerator.csproj", "{CF76A947-3A72-4824-87E6-BF029D84218B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{CF76A947-3A72-4824-87E6-BF029D84218B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 218064c300a7cf5a76669e133340a98a0c5517a5
|
||||
|
|
@ -5,20 +5,4 @@
|
|||
<Import Project="version.props" />
|
||||
<Import Project="dependencies.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<VerifyVersion>false</VerifyVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Internal.AspNetCore.Sdk" PrivateAssets="All" Version="$(InternalAspNetCoreSdkPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(IsTestProject)' == 'true' ">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
|
||||
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the operations that can be performed on a JSON patch document.
|
||||
/// </summary>
|
||||
public interface IObjectAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Using the "add" operation a new value is inserted into the root of the target
|
||||
/// document, into the target array at the specified valid index, or to a target object at
|
||||
/// the specified location.
|
||||
///
|
||||
/// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
|
||||
/// To append the value to the array, the index of "-" character is used (see [RFC6901]).
|
||||
///
|
||||
/// When adding to an object, if an object member does not already exist, a new member is added to the object at the
|
||||
/// specified location or if an object member does exist, that member's value is replaced.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member whose content
|
||||
/// specifies the value to be added.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-4
|
||||
/// </summary>
|
||||
/// <param name="operation">The add operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Add(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "copy" operation, a value is copied from a specified location to the
|
||||
/// target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which references the location in the
|
||||
/// target document to copy the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
|
||||
/// </summary>
|
||||
/// <param name="operation">The copy operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Copy(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "move" operation the value at a specified location is removed and
|
||||
/// added to the target location.
|
||||
///
|
||||
/// The operation object MUST contain a "from" member, which references the location in the
|
||||
/// target document to move the value from.
|
||||
///
|
||||
/// The "from" location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
///
|
||||
/// A location cannot be moved into one of its children.
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The move operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Move(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "remove" operation the value at the target location is removed.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
///
|
||||
/// If removing an element from an array, any elements above the
|
||||
/// specified index are shifted one position to the left.
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The remove operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Remove(Operation operation, object objectToApplyTo);
|
||||
|
||||
/// <summary>
|
||||
/// Using the "replace" operation he value at the target location is replaced
|
||||
/// with a new value. The operation object MUST contain a "value" member
|
||||
/// which specifies the replacement value.
|
||||
///
|
||||
/// The target location MUST exist for the operation to be successful.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-6
|
||||
/// </summary>
|
||||
/// <param name="operation">The replace operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Replace(Operation operation, object objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the operations that can be performed on a JSON patch document, including "test".
|
||||
/// </summary>
|
||||
public interface IObjectAdapterWithTest : IObjectAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Using the "test" operation a value at the target location is compared for
|
||||
/// equality to a specified value.
|
||||
///
|
||||
/// The operation object MUST contain a "value" member that specifies
|
||||
/// value to be compared to the target location's value.
|
||||
///
|
||||
/// The target location MUST be equal to the "value" value for the
|
||||
/// operation to be considered successful.
|
||||
///
|
||||
/// For example:
|
||||
/// { "op": "test", "path": "/a/b/c", "value": "foo" }
|
||||
///
|
||||
/// See RFC 6902 https://tools.ietf.org/html/rfc6902#page-7
|
||||
/// </summary>
|
||||
/// <param name="operation">The test operation.</param>
|
||||
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
|
||||
void Test(Operation operation, object objectToApplyTo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,328 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class ObjectAdapter : IObjectAdapterWithTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ObjectAdapter"/>.
|
||||
/// </summary>
|
||||
/// <param name="contractResolver">The <see cref="IContractResolver"/>.</param>
|
||||
/// <param name="logErrorAction">The <see cref="Action"/> for logging <see cref="JsonPatchError"/>.</param>
|
||||
public ObjectAdapter(
|
||||
IContractResolver contractResolver,
|
||||
Action<JsonPatchError> logErrorAction)
|
||||
{
|
||||
ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
|
||||
LogErrorAction = logErrorAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IContractResolver"/>.
|
||||
/// </summary>
|
||||
public IContractResolver ContractResolver { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Action for logging <see cref="JsonPatchError"/>.
|
||||
/// </summary>
|
||||
public Action<JsonPatchError> LogErrorAction { get; }
|
||||
|
||||
public void Add(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
Add(operation.path, operation.value, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add is used by various operations (eg: add, copy, ...), yet through different operations;
|
||||
/// This method allows code reuse yet reporting the correct operation on error
|
||||
/// </summary>
|
||||
private void Add(
|
||||
string path,
|
||||
object value,
|
||||
object objectToApplyTo,
|
||||
Operation operation)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
var parsedPath = new ParsedPath(path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToApplyTo;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!adapter.TryAdd(target, parsedPath.LastSegment, ContractResolver, value, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Move(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
// Get value at 'from' location and add that value to the 'path' location
|
||||
if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
|
||||
{
|
||||
// remove that value
|
||||
Remove(operation.from, objectToApplyTo, operation);
|
||||
|
||||
// add that value to the path location
|
||||
Add(operation.path,
|
||||
propertyValue,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
Remove(operation.path, objectToApplyTo, operation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove is used by various operations (eg: remove, move, ...), yet through different operations;
|
||||
/// This method allows code reuse yet reporting the correct operation on error. The return value
|
||||
/// contains the type of the item that has been removed (and a bool possibly signifying an error)
|
||||
/// This can be used by other methods, like replace, to ensure that we can pass in the correctly
|
||||
/// typed value to whatever method follows.
|
||||
/// </summary>
|
||||
private void Remove(string path, object objectToApplyTo, Operation operationToReport)
|
||||
{
|
||||
var parsedPath = new ParsedPath(path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToApplyTo;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, path, operationToReport, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!adapter.TryRemove(target, parsedPath.LastSegment, ContractResolver, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, path, operationToReport, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Replace(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var parsedPath = new ParsedPath(operation.path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToApplyTo;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!adapter.TryReplace(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void Copy(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
// Get value at 'from' location and add that value to the 'path' location
|
||||
if (TryGetValue(operation.from, objectToApplyTo, operation, out var propertyValue))
|
||||
{
|
||||
// Create deep copy
|
||||
var copyResult = ConversionResultProvider.CopyTo(propertyValue, propertyValue.GetType());
|
||||
if (copyResult.CanBeConverted)
|
||||
{
|
||||
Add(operation.path,
|
||||
copyResult.ConvertedInstance,
|
||||
objectToApplyTo,
|
||||
operation);
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, Resources.FormatCannotCopyProperty(operation.from));
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Test(Operation operation, object objectToApplyTo)
|
||||
{
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var parsedPath = new ParsedPath(operation.path);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToApplyTo;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!adapter.TryTest(target, parsedPath.LastSegment, ContractResolver, operation.value, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToApplyTo, operation.path, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetValue(
|
||||
string fromLocation,
|
||||
object objectToGetValueFrom,
|
||||
Operation operation,
|
||||
out object propertyValue)
|
||||
{
|
||||
if (fromLocation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(fromLocation));
|
||||
}
|
||||
|
||||
if (objectToGetValueFrom == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToGetValueFrom));
|
||||
}
|
||||
|
||||
if (operation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operation));
|
||||
}
|
||||
|
||||
propertyValue = null;
|
||||
|
||||
var parsedPath = new ParsedPath(fromLocation);
|
||||
var visitor = new ObjectVisitor(parsedPath, ContractResolver);
|
||||
|
||||
var target = objectToGetValueFrom;
|
||||
if (!visitor.TryVisit(ref target, out var adapter, out var errorMessage))
|
||||
{
|
||||
var error = CreatePathNotFoundError(objectToGetValueFrom, fromLocation, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!adapter.TryGet(target, parsedPath.LastSegment, ContractResolver, out propertyValue, out errorMessage))
|
||||
{
|
||||
var error = CreateOperationFailedError(objectToGetValueFrom, fromLocation, operation, errorMessage);
|
||||
ErrorReporter(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Action<JsonPatchError> ErrorReporter
|
||||
{
|
||||
get
|
||||
{
|
||||
return LogErrorAction ?? Internal.ErrorReporter.Default;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonPatchError CreateOperationFailedError(object target, string path, Operation operation, string errorMessage)
|
||||
{
|
||||
return new JsonPatchError(
|
||||
target,
|
||||
operation,
|
||||
errorMessage ?? Resources.FormatCannotPerformOperation(operation.op, path));
|
||||
}
|
||||
|
||||
private JsonPatchError CreatePathNotFoundError(object target, string path, Operation operation, string errorMessage)
|
||||
{
|
||||
return new JsonPatchError(
|
||||
target,
|
||||
operation,
|
||||
errorMessage ?? Resources.FormatTargetLocationNotFound(operation.op, path));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Converters
|
||||
{
|
||||
public class JsonPatchDocumentConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
if (objectType != typeof(JsonPatchDocument))
|
||||
{
|
||||
throw new ArgumentException(Resources.FormatParameterMustMatchType("objectType", "JsonPatchDocument"), "objectType");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// load jObject
|
||||
var jObject = JArray.Load(reader);
|
||||
|
||||
// Create target object for Json => list of operations
|
||||
var targetOperations = new List<Operation>();
|
||||
|
||||
// Create a new reader for this jObject, and set all properties
|
||||
// to match the original reader.
|
||||
var jObjectReader = jObject.CreateReader();
|
||||
jObjectReader.Culture = reader.Culture;
|
||||
jObjectReader.DateParseHandling = reader.DateParseHandling;
|
||||
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
|
||||
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
|
||||
|
||||
// Populate the object properties
|
||||
serializer.Populate(jObjectReader, targetOperations);
|
||||
|
||||
// container target: the JsonPatchDocument.
|
||||
var container = new JsonPatchDocument(targetOperations, new DefaultContractResolver());
|
||||
|
||||
return container;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is IJsonPatchDocument)
|
||||
{
|
||||
var jsonPatchDoc = (IJsonPatchDocument)value;
|
||||
var lst = jsonPatchDoc.GetOperations();
|
||||
|
||||
// write out the operations, no envelope
|
||||
serializer.Serialize(writer, lst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Converters
|
||||
{
|
||||
public class TypedJsonPatchDocumentConverter : JsonPatchDocumentConverter
|
||||
{
|
||||
public override object ReadJson(
|
||||
JsonReader reader,
|
||||
Type objectType,
|
||||
object existingValue,
|
||||
JsonSerializer serializer)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (reader.TokenType == JsonToken.Null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var genericType = objectType.GetTypeInfo().GenericTypeArguments[0];
|
||||
|
||||
// load jObject
|
||||
var jObject = JArray.Load(reader);
|
||||
|
||||
// Create target object for Json => list of operations, typed to genericType
|
||||
var genericOperation = typeof(Operation<>);
|
||||
var concreteOperationType = genericOperation.MakeGenericType(genericType);
|
||||
|
||||
var genericList = typeof(List<>);
|
||||
var concreteList = genericList.MakeGenericType(concreteOperationType);
|
||||
|
||||
var targetOperations = Activator.CreateInstance(concreteList);
|
||||
|
||||
//Create a new reader for this jObject, and set all properties to match the original reader.
|
||||
var jObjectReader = jObject.CreateReader();
|
||||
jObjectReader.Culture = reader.Culture;
|
||||
jObjectReader.DateParseHandling = reader.DateParseHandling;
|
||||
jObjectReader.DateTimeZoneHandling = reader.DateTimeZoneHandling;
|
||||
jObjectReader.FloatParseHandling = reader.FloatParseHandling;
|
||||
|
||||
// Populate the object properties
|
||||
serializer.Populate(jObjectReader, targetOperations);
|
||||
|
||||
// container target: the typed JsonPatchDocument.
|
||||
var container = Activator.CreateInstance(objectType, targetOperations, new DefaultContractResolver());
|
||||
|
||||
return container;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JsonSerializationException(Resources.InvalidJsonPatchDocument, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Exceptions
|
||||
{
|
||||
public class JsonPatchException : Exception
|
||||
{
|
||||
public Operation FailedOperation { get; private set; }
|
||||
public object AffectedObject { get; private set; }
|
||||
|
||||
|
||||
public JsonPatchException()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public JsonPatchException(JsonPatchError jsonPatchError, Exception innerException)
|
||||
: base(jsonPatchError.ErrorMessage, innerException)
|
||||
{
|
||||
FailedOperation = jsonPatchError.Operation;
|
||||
AffectedObject = jsonPatchError.AffectedObject;
|
||||
}
|
||||
|
||||
public JsonPatchException(JsonPatchError jsonPatchError)
|
||||
: this(jsonPatchError, null)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonPatchException(string message, Exception innerException)
|
||||
: base (message, innerException)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Return value for the helper method used by Copy/Move. Needed to ensure we can make a different
|
||||
/// decision in the calling method when the value is null because it cannot be fetched (HasError = true)
|
||||
/// versus when it actually is null (much like why RemovedPropertyTypeResult is used for returning
|
||||
/// type in the Remove operation).
|
||||
/// </summary>
|
||||
public class GetValueResult
|
||||
{
|
||||
public GetValueResult(object propertyValue, bool hasError)
|
||||
{
|
||||
PropertyValue = propertyValue;
|
||||
HasError = hasError;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value of the property we're trying to get
|
||||
/// </summary>
|
||||
public object PropertyValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// HasError: true when an error occurred, the operation didn't complete succesfully
|
||||
/// </summary>
|
||||
public bool HasError { get; private set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata for JsonProperty.
|
||||
/// </summary>
|
||||
public class JsonPatchProperty
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance.
|
||||
/// </summary>
|
||||
public JsonPatchProperty(JsonProperty property, object parent)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(parent));
|
||||
}
|
||||
|
||||
Property = property;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets JsonProperty.
|
||||
/// </summary>
|
||||
public JsonProperty Property { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets Parent.
|
||||
/// </summary>
|
||||
public object Parent { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public interface IJsonPatchDocument
|
||||
{
|
||||
IContractResolver ContractResolver { get; set; }
|
||||
|
||||
IList<Operation> GetOperations();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ConversionResult
|
||||
{
|
||||
public ConversionResult(bool canBeConverted, object convertedInstance)
|
||||
{
|
||||
CanBeConverted = canBeConverted;
|
||||
ConvertedInstance = convertedInstance;
|
||||
}
|
||||
|
||||
public bool CanBeConverted { get; }
|
||||
public object ConvertedInstance { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public static class ConversionResultProvider
|
||||
{
|
||||
public static ConversionResult ConvertTo(object value, Type typeToConvertTo)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return new ConversionResult(IsNullableType(typeToConvertTo), null);
|
||||
}
|
||||
else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
|
||||
{
|
||||
// No need to convert
|
||||
return new ConversionResult(true, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), typeToConvertTo);
|
||||
return new ConversionResult(true, deserialized);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ConversionResult(canBeConverted: false, convertedInstance: null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ConversionResult CopyTo(object value, Type typeToConvertTo)
|
||||
{
|
||||
var targetType = typeToConvertTo;
|
||||
if (value == null)
|
||||
{
|
||||
return new ConversionResult(IsNullableType(typeToConvertTo), null);
|
||||
}
|
||||
else if (typeToConvertTo.IsAssignableFrom(value.GetType()))
|
||||
{
|
||||
// Keep original type
|
||||
targetType = value.GetType();
|
||||
}
|
||||
try
|
||||
{
|
||||
var deserialized = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType);
|
||||
return new ConversionResult(true, deserialized);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new ConversionResult(canBeConverted: false, convertedInstance: null);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNullableType(Type type)
|
||||
{
|
||||
var typeInfo = type.GetTypeInfo();
|
||||
if (typeInfo.IsValueType)
|
||||
{
|
||||
// value types are only nullable if they are Nullable<T>
|
||||
return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
}
|
||||
else
|
||||
{
|
||||
// reference types are always nullable
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DictionaryAdapter<TKey, TValue> : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
// As per JsonPatch spec, if a key already exists, adding should replace the existing value
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary[convertedKey] = convertedValue;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = dictionary[convertedKey];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary.Remove(convertedKey);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for remove to be successful
|
||||
if (!dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dictionary[convertedKey] = convertedValue;
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// As per JsonPatch spec, the target location must exist for test to be successful
|
||||
if (!dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = dictionary[convertedKey];
|
||||
|
||||
// The target segment does not have an assigned value to compare the test value with
|
||||
if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
|
||||
{
|
||||
errorMessage = Resources.FormatValueForTargetSegmentCannotBeNullOrEmpty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage)
|
||||
{
|
||||
var contract = (JsonDictionaryContract)contractResolver.ResolveContract(target.GetType());
|
||||
var key = contract.DictionaryKeyResolver(segment);
|
||||
var dictionary = (IDictionary<TKey, TValue>)target;
|
||||
|
||||
if (!TryConvertKey(key, out var convertedKey, out errorMessage))
|
||||
{
|
||||
nextTarget = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dictionary.ContainsKey(convertedKey))
|
||||
{
|
||||
nextTarget = dictionary[convertedKey];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTarget = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConvertKey(string key, out TKey convertedKey, out string errorMessage)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(key, typeof(TKey));
|
||||
if (conversionResult.CanBeConverted)
|
||||
{
|
||||
errorMessage = null;
|
||||
convertedKey = (TKey)conversionResult.ConvertedInstance;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidPathSegment(key);
|
||||
convertedKey = default(TKey);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConvertValue(object value, out TValue convertedValue, out string errorMessage)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(value, typeof(TValue));
|
||||
if (conversionResult.CanBeConverted)
|
||||
{
|
||||
errorMessage = null;
|
||||
convertedValue = (TValue)conversionResult.ConvertedInstance;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
convertedValue = default(TValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using CSharpBinder = Microsoft.CSharp.RuntimeBinder;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DynamicObjectAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out value, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
object value = null;
|
||||
if (property.GetType().GetTypeInfo().IsValueType
|
||||
&& Nullable.GetUnderlyingType(property.GetType()) == null)
|
||||
{
|
||||
value = Activator.CreateInstance(property.GetType());
|
||||
}
|
||||
|
||||
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, value, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, property.GetType(), out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TrySetDynamicObjectProperty(target, contractResolver, segment, convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, property.GetType(), out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(property), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(property, value, segment);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetDynamicObjectProperty(target, contractResolver, segment, out var property, out errorMessage))
|
||||
{
|
||||
nextTarget = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextTarget = property;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetDynamicObjectProperty(
|
||||
object target,
|
||||
IContractResolver contractResolver,
|
||||
string segment,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
|
||||
|
||||
var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
|
||||
|
||||
var binder = CSharpBinder.Binder.GetMember(
|
||||
CSharpBinderFlags.None,
|
||||
propertyName,
|
||||
target.GetType(),
|
||||
new List<CSharpArgumentInfo>
|
||||
{
|
||||
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
|
||||
});
|
||||
|
||||
var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);
|
||||
|
||||
try
|
||||
{
|
||||
value = callsite.Target(callsite, target);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
catch (RuntimeBinderException)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySetDynamicObjectProperty(
|
||||
object target,
|
||||
IContractResolver contractResolver,
|
||||
string segment,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var jsonDynamicContract = (JsonDynamicContract)contractResolver.ResolveContract(target.GetType());
|
||||
|
||||
var propertyName = jsonDynamicContract.PropertyNameResolver(segment);
|
||||
|
||||
var binder = CSharpBinder.Binder.SetMember(
|
||||
CSharpBinderFlags.None,
|
||||
propertyName,
|
||||
target.GetType(),
|
||||
new List<CSharpArgumentInfo>
|
||||
{
|
||||
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
|
||||
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
|
||||
});
|
||||
|
||||
var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);
|
||||
|
||||
try
|
||||
{
|
||||
callsite.Target(callsite, target, value);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
catch (RuntimeBinderException)
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
convertedValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = conversionResult.ConvertedInstance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal static class ErrorReporter
|
||||
{
|
||||
public static readonly Action<JsonPatchError> Default = (error) =>
|
||||
{
|
||||
throw new JsonPatchException(error);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public interface IAdapter
|
||||
{
|
||||
bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object nextTarget,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
|
||||
bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ListAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Add, out var positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list.Add(convertedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Insert(positionInfo.Index, convertedValue);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Get, out var positionInfo, out errorMessage))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
value = list[list.Count - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
value = list[positionInfo.Index];
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Remove, out var positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list.RemoveAt(list.Count - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.RemoveAt(positionInfo.Index);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (positionInfo.Type == PositionType.EndOfList)
|
||||
{
|
||||
list[list.Count - 1] = convertedValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
list[positionInfo.Index] = convertedValue;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = (IList)target;
|
||||
|
||||
if (!TryGetListTypeArgument(list, out var typeArgument, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryGetPositionInfo(list, segment, OperationType.Replace, out var positionInfo, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, typeArgument, segment, out var convertedValue, out errorMessage))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = list[positionInfo.Index];
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueAtListPositionNotEqualToTestValue(currentValue, value, positionInfo.Index);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
var list = target as IList;
|
||||
if (list == null)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = -1;
|
||||
if (!int.TryParse(segment, out index))
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatInvalidIndexValue(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= list.Count)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = Resources.FormatIndexOutOfBounds(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = list[index];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryConvertValue(
|
||||
object originalValue,
|
||||
Type listTypeArgument,
|
||||
string segment,
|
||||
out object convertedValue,
|
||||
out string errorMessage)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(originalValue, listTypeArgument);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
convertedValue = null;
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(originalValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = conversionResult.ConvertedInstance;
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryGetListTypeArgument(IList list, out Type listTypeArgument, out string errorMessage)
|
||||
{
|
||||
// Arrays are not supported as they have fixed size and operations like Add, Insert do not make sense
|
||||
var listType = list.GetType();
|
||||
if (listType.IsArray)
|
||||
{
|
||||
errorMessage = Resources.FormatPatchNotSupportedForArrays(listType.FullName);
|
||||
listTypeArgument = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var genericList = ClosedGenericMatcher.ExtractGenericInterface(listType, typeof(IList<>));
|
||||
if (genericList == null)
|
||||
{
|
||||
errorMessage = Resources.FormatPatchNotSupportedForNonGenericLists(listType.FullName);
|
||||
listTypeArgument = null;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
listTypeArgument = genericList.GenericTypeArguments[0];
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetPositionInfo(
|
||||
IList list,
|
||||
string segment,
|
||||
OperationType operationType,
|
||||
out PositionInfo positionInfo,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (segment == "-")
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.EndOfList, -1);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
var position = -1;
|
||||
if (int.TryParse(segment, out position))
|
||||
{
|
||||
if (position >= 0 && position < list.Count)
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.Index, position);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
// As per JSON Patch spec, for Add operation the index value representing the number of elements is valid,
|
||||
// where as for other operations like Remove, Replace, Move and Copy the target index MUST exist.
|
||||
else if (position == list.Count && operationType == OperationType.Add)
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.EndOfList, -1);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.OutOfBounds, position);
|
||||
errorMessage = Resources.FormatIndexOutOfBounds(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
positionInfo = new PositionInfo(PositionType.Invalid, -1);
|
||||
errorMessage = Resources.FormatInvalidIndexValue(segment);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private struct PositionInfo
|
||||
{
|
||||
public PositionInfo(PositionType type, int index)
|
||||
{
|
||||
Type = type;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public PositionType Type { get; }
|
||||
public int Index { get; }
|
||||
}
|
||||
|
||||
private enum PositionType
|
||||
{
|
||||
Index, // valid index
|
||||
EndOfList, // '-'
|
||||
Invalid, // Ex: not an integer
|
||||
OutOfBounds
|
||||
}
|
||||
|
||||
private enum OperationType
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Get,
|
||||
Replace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitor
|
||||
{
|
||||
private readonly IContractResolver _contractResolver;
|
||||
private readonly ParsedPath _path;
|
||||
|
||||
public ObjectVisitor(ParsedPath path, IContractResolver contractResolver)
|
||||
{
|
||||
_path = path;
|
||||
_contractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
|
||||
}
|
||||
|
||||
public bool TryVisit(ref object target, out IAdapter adapter, out string errorMessage)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
adapter = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
adapter = SelectAdapter(target);
|
||||
|
||||
// Traverse until the penultimate segment to get the target object and adapter
|
||||
for (var i = 0; i < _path.Segments.Count - 1; i++)
|
||||
{
|
||||
if (!adapter.TryTraverse(target, _path.Segments[i], _contractResolver, out var next, out errorMessage))
|
||||
{
|
||||
adapter = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
target = next;
|
||||
adapter = SelectAdapter(target);
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private IAdapter SelectAdapter(object targetObject)
|
||||
{
|
||||
var jsonContract = _contractResolver.ResolveContract(targetObject.GetType());
|
||||
|
||||
if (targetObject is IList)
|
||||
{
|
||||
return new ListAdapter();
|
||||
}
|
||||
else if (jsonContract is JsonDictionaryContract jsonDictionaryContract)
|
||||
{
|
||||
var type = typeof(DictionaryAdapter<,>).MakeGenericType(jsonDictionaryContract.DictionaryKeyType, jsonDictionaryContract.DictionaryValueType);
|
||||
return (IAdapter)Activator.CreateInstance(type);
|
||||
}
|
||||
else if (jsonContract is JsonDynamicContract)
|
||||
{
|
||||
return new DynamicObjectAdapter();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new PocoAdapter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public struct ParsedPath
|
||||
{
|
||||
private static readonly string[] Empty = null;
|
||||
|
||||
private readonly string[] _segments;
|
||||
|
||||
public ParsedPath(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
_segments = ParsePath(path);
|
||||
}
|
||||
|
||||
public string LastSegment
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_segments == null || _segments.Length == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _segments[_segments.Length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<string> Segments => _segments ?? Empty;
|
||||
|
||||
private static string[] ParsePath(string path)
|
||||
{
|
||||
var strings = new List<string>();
|
||||
var sb = new StringBuilder(path.Length);
|
||||
|
||||
for (var i = 0; i < path.Length; i++)
|
||||
{
|
||||
if (path[i] == '/')
|
||||
{
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
strings.Add(sb.ToString());
|
||||
sb.Length = 0;
|
||||
}
|
||||
}
|
||||
else if (path[i] == '~')
|
||||
{
|
||||
++i;
|
||||
if (i >= path.Length)
|
||||
{
|
||||
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
|
||||
}
|
||||
|
||||
if (path[i] == '0')
|
||||
{
|
||||
sb.Append('~');
|
||||
}
|
||||
else if (path[i] == '1')
|
||||
{
|
||||
sb.Append('/');
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(path[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
strings.Add(sb.ToString());
|
||||
}
|
||||
|
||||
return strings.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal static class PathHelpers
|
||||
{
|
||||
internal static string ValidateAndNormalizePath(string path)
|
||||
{
|
||||
// check for most common path errors on create. This is not
|
||||
// absolutely necessary, but it allows us to already catch mistakes
|
||||
// on creation of the patch document rather than on execute.
|
||||
|
||||
if (path.Contains("//"))
|
||||
{
|
||||
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
|
||||
}
|
||||
|
||||
if (!path.StartsWith("/", StringComparison.Ordinal))
|
||||
{
|
||||
return "/" + path;
|
||||
}
|
||||
else
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class PocoAdapter : IAdapter
|
||||
{
|
||||
public bool TryAdd(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, convertedValue);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGet(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Readable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotReadProperty(segment);
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = jsonProperty.ValueProvider.GetValue(target);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryRemove(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setting the value to "null" will use the default value in case of value types, and
|
||||
// null in case of reference types
|
||||
object value = null;
|
||||
if (jsonProperty.PropertyType.GetTypeInfo().IsValueType
|
||||
&& Nullable.GetUnderlyingType(jsonProperty.PropertyType) == null)
|
||||
{
|
||||
value = Activator.CreateInstance(jsonProperty.PropertyType);
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, value);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReplace(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver
|
||||
contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Writable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotUpdateProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
jsonProperty.ValueProvider.SetValue(target, convertedValue);
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTest(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver
|
||||
contractResolver,
|
||||
object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (!TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!jsonProperty.Readable)
|
||||
{
|
||||
errorMessage = Resources.FormatCannotReadProperty(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TryConvertValue(value, jsonProperty.PropertyType, out var convertedValue))
|
||||
{
|
||||
errorMessage = Resources.FormatInvalidValueForProperty(value);
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentValue = jsonProperty.ValueProvider.GetValue(target);
|
||||
if (!JToken.DeepEquals(JsonConvert.SerializeObject(currentValue), JsonConvert.SerializeObject(convertedValue)))
|
||||
{
|
||||
errorMessage = Resources.FormatValueNotEqualToTestValue(currentValue, value, segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryTraverse(
|
||||
object target,
|
||||
string segment,
|
||||
IContractResolver contractResolver,
|
||||
out object value,
|
||||
out string errorMessage)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
value = null;
|
||||
errorMessage = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TryGetJsonProperty(target, contractResolver, segment, out var jsonProperty))
|
||||
{
|
||||
value = jsonProperty.ValueProvider.GetValue(target);
|
||||
errorMessage = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = null;
|
||||
errorMessage = Resources.FormatTargetLocationAtPathSegmentNotFound(segment);
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetJsonProperty(
|
||||
object target,
|
||||
IContractResolver contractResolver,
|
||||
string segment,
|
||||
out JsonProperty jsonProperty)
|
||||
{
|
||||
if (contractResolver.ResolveContract(target.GetType()) is JsonObjectContract jsonObjectContract)
|
||||
{
|
||||
var pocoProperty = jsonObjectContract
|
||||
.Properties
|
||||
.FirstOrDefault(p => string.Equals(p.PropertyName, segment, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (pocoProperty != null)
|
||||
{
|
||||
jsonProperty = pocoProperty;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
jsonProperty = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryConvertValue(object value, Type propertyType, out object convertedValue)
|
||||
{
|
||||
var conversionResult = ConversionResultProvider.ConvertTo(value, propertyType);
|
||||
if (!conversionResult.CanBeConverted)
|
||||
{
|
||||
convertedValue = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
convertedValue = conversionResult.ConvertedInstance;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Converters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
// Implementation details: the purpose of this type of patch document is to allow creation of such
|
||||
// documents for cases where there's no class/DTO to work on. Typical use case: backend not built in
|
||||
// .NET or architecture doesn't contain a shared DTO layer.
|
||||
[JsonConverter(typeof(JsonPatchDocumentConverter))]
|
||||
public class JsonPatchDocument : IJsonPatchDocument
|
||||
{
|
||||
public List<Operation> Operations { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IContractResolver ContractResolver { get; set; }
|
||||
|
||||
public JsonPatchDocument()
|
||||
{
|
||||
Operations = new List<Operation>();
|
||||
ContractResolver = new DefaultContractResolver();
|
||||
}
|
||||
|
||||
public JsonPatchDocument(List<Operation> operations, IContractResolver contractResolver)
|
||||
{
|
||||
if (operations == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(operations));
|
||||
}
|
||||
|
||||
if (contractResolver == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(contractResolver));
|
||||
}
|
||||
|
||||
Operations = operations;
|
||||
ContractResolver = contractResolver;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add operation. Will result in, for example,
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Add(string path, object value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("add", PathHelpers.ValidateAndNormalizePath(path), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value at target location. Will result in, for example,
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Remove(string path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("remove", PathHelpers.ValidateAndNormalizePath(path), null, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value. Will result in, for example,
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Replace(string path, object value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("replace", PathHelpers.ValidateAndNormalizePath(path), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value. Will result in, for example,
|
||||
/// { "op": "test", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Test(string path, object value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("test", PathHelpers.ValidateAndNormalizePath(path), null, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
/// </summary>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Move(string from, string path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("move", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the value at specified location to the target location. Will result in, for example:
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
/// </summary>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument Copy(string from, string path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation("copy", PathHelpers.ValidateAndNormalizePath(path), PathHelpers.ValidateAndNormalizePath(from)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
public void ApplyTo(object objectToApplyTo)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="logErrorAction">Action to log errors</param>
|
||||
public void ApplyTo(object objectToApplyTo, Action<JsonPatchError> logErrorAction)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
try
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
catch (JsonPatchException jsonPatchException)
|
||||
{
|
||||
var errorReporter = logErrorAction ?? ErrorReporter.Default;
|
||||
errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
|
||||
|
||||
// As per JSON Patch spec if an operation results in error, further operations should not be executed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
|
||||
public void ApplyTo(object objectToApplyTo, IObjectAdapter adapter)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (adapter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapter));
|
||||
}
|
||||
|
||||
// apply each operation in order
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
IList<Operation> IJsonPatchDocument.GetOperations()
|
||||
{
|
||||
var allOps = new List<Operation>();
|
||||
|
||||
if (Operations != null)
|
||||
{
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
var untypedOp = new Operation();
|
||||
|
||||
untypedOp.op = op.op;
|
||||
untypedOp.value = op.value;
|
||||
untypedOp.path = op.path;
|
||||
untypedOp.from = op.from;
|
||||
|
||||
allOps.Add(untypedOp);
|
||||
}
|
||||
}
|
||||
|
||||
return allOps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,869 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Converters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
// Implementation details: the purpose of this type of patch document is to ensure we can do type-checking
|
||||
// when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
|
||||
// including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's
|
||||
// not according to RFC 6902, and would thus break cross-platform compatibility.
|
||||
[JsonConverter(typeof(TypedJsonPatchDocumentConverter))]
|
||||
public class JsonPatchDocument<TModel> : IJsonPatchDocument where TModel : class
|
||||
{
|
||||
public List<Operation<TModel>> Operations { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public IContractResolver ContractResolver { get; set; }
|
||||
|
||||
public JsonPatchDocument()
|
||||
{
|
||||
Operations = new List<Operation<TModel>>();
|
||||
ContractResolver = new DefaultContractResolver();
|
||||
}
|
||||
|
||||
// Create from list of operations
|
||||
public JsonPatchDocument(List<Operation<TModel>> operations, IContractResolver contractResolver)
|
||||
{
|
||||
Operations = operations ?? throw new ArgumentNullException(nameof(operations));
|
||||
ContractResolver = contractResolver ?? throw new ArgumentNullException(nameof(contractResolver));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add operation. Will result in, for example,
|
||||
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Add<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"add",
|
||||
GetPath(path, null),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add value to list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Add<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> path,
|
||||
TProp value,
|
||||
int position)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"add",
|
||||
GetPath(path, position.ToString()),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add value to the end of the list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Add<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"add",
|
||||
GetPath(path, "-"),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value at target location. Will result in, for example,
|
||||
/// { "op": "remove", "path": "/a/b/c" }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, TProp>> path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>("remove", GetPath(path, null), from: null));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, IList<TProp>>> path, int position)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"remove",
|
||||
GetPath(path, position.ToString()),
|
||||
from: null));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove value from end of list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Remove<TProp>(Expression<Func<TModel, IList<TProp>>> path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"remove",
|
||||
GetPath(path, "-"),
|
||||
from: null));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value. Will result in, for example,
|
||||
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"replace",
|
||||
GetPath(path, null),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, IList<TProp>>> path,
|
||||
TProp value, int position)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"replace",
|
||||
GetPath(path, position.ToString()),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Replace<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"replace",
|
||||
GetPath(path, "-"),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value. Will result in, for example,
|
||||
/// { "op": "test", "path": "/a/b/c", "value": 42 }
|
||||
/// </summary>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, TProp>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, null),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value in a list at given position
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <param name="position">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, IList<TProp>>> path,
|
||||
TProp value, int position)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, position.ToString()),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test value at end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp">value type</typeparam>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="value">value</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Test<TProp>(Expression<Func<TModel, IList<TProp>>> path, TProp value)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"test",
|
||||
GetPath(path, "-"),
|
||||
from: null,
|
||||
value: value));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at specified location and add it to the target location. Will result in, for example:
|
||||
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
|
||||
/// </summary>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, TProp>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, null),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, TProp>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, null),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="positionTo">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, IList<TProp>>> path,
|
||||
int positionTo)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, positionTo.ToString()),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to another location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position (source)</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="positionTo">position (target)</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, IList<TProp>>> path,
|
||||
int positionTo)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, positionTo.ToString()),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, IList<TProp>>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, "-"),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Move<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, IList<TProp>>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"move",
|
||||
GetPath(path, "-"),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy the value at specified location to the target location. Willr esult in, for example:
|
||||
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
|
||||
/// </summary>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, TProp>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, null),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, TProp>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, null),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a property to a location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="positionTo">position</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, IList<TProp>>> path,
|
||||
int positionTo)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, positionTo.ToString()),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to a new location in a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position (source)</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <param name="positionTo">position (target)</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, IList<TProp>>> path,
|
||||
int positionTo)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, positionTo.ToString()),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy from a position in a list to the end of another list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="positionFrom">position</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, IList<TProp>>> from,
|
||||
int positionFrom,
|
||||
Expression<Func<TModel, IList<TProp>>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, "-"),
|
||||
GetPath(from, positionFrom.ToString())));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy to the end of a list
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="from">source location</param>
|
||||
/// <param name="path">target location</param>
|
||||
/// <returns></returns>
|
||||
public JsonPatchDocument<TModel> Copy<TProp>(
|
||||
Expression<Func<TModel, TProp>> from,
|
||||
Expression<Func<TModel, IList<TProp>>> path)
|
||||
{
|
||||
if (from == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(from));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Operations.Add(new Operation<TModel>(
|
||||
"copy",
|
||||
GetPath(path, "-"),
|
||||
GetPath(from, null)));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
public void ApplyTo(TModel objectToApplyTo)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
ApplyTo(objectToApplyTo, new ObjectAdapter(ContractResolver, logErrorAction: null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="logErrorAction">Action to log errors</param>
|
||||
public void ApplyTo(TModel objectToApplyTo, Action<JsonPatchError> logErrorAction)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
var adapter = new ObjectAdapter(ContractResolver, logErrorAction);
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
try
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
catch (JsonPatchException jsonPatchException)
|
||||
{
|
||||
var errorReporter = logErrorAction ?? ErrorReporter.Default;
|
||||
errorReporter(new JsonPatchError(objectToApplyTo, op, jsonPatchException.Message));
|
||||
|
||||
// As per JSON Patch spec if an operation results in error, further operations should not be executed.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Apply this JsonPatchDocument
|
||||
/// </summary>
|
||||
/// <param name="objectToApplyTo">Object to apply the JsonPatchDocument to</param>
|
||||
/// <param name="adapter">IObjectAdapter instance to use when applying</param>
|
||||
public void ApplyTo(TModel objectToApplyTo, IObjectAdapter adapter)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (adapter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapter));
|
||||
}
|
||||
|
||||
// apply each operation in order
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
op.Apply(objectToApplyTo, adapter);
|
||||
}
|
||||
}
|
||||
|
||||
IList<Operation> IJsonPatchDocument.GetOperations()
|
||||
{
|
||||
var allOps = new List<Operation>();
|
||||
|
||||
if (Operations != null)
|
||||
{
|
||||
foreach (var op in Operations)
|
||||
{
|
||||
var untypedOp = new Operation
|
||||
{
|
||||
op = op.op,
|
||||
value = op.value,
|
||||
path = op.path,
|
||||
from = op.from
|
||||
};
|
||||
|
||||
allOps.Add(untypedOp);
|
||||
}
|
||||
}
|
||||
|
||||
return allOps;
|
||||
}
|
||||
|
||||
// Internal for testing
|
||||
internal string GetPath<TProp>(Expression<Func<TModel, TProp>> expr, string position)
|
||||
{
|
||||
var segments = GetPathSegments(expr.Body);
|
||||
var path = String.Join("/", segments);
|
||||
if (position != null)
|
||||
{
|
||||
path += "/" + position;
|
||||
if (segments.Count == 0)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
return "/" + path;
|
||||
}
|
||||
|
||||
private List<string> GetPathSegments(Expression expr)
|
||||
{
|
||||
var listOfSegments = new List<string>();
|
||||
switch (expr.NodeType)
|
||||
{
|
||||
case ExpressionType.ArrayIndex:
|
||||
var binaryExpression = (BinaryExpression)expr;
|
||||
listOfSegments.AddRange(GetPathSegments(binaryExpression.Left));
|
||||
listOfSegments.Add(binaryExpression.Right.ToString());
|
||||
return listOfSegments;
|
||||
|
||||
case ExpressionType.Call:
|
||||
var methodCallExpression = (MethodCallExpression)expr;
|
||||
listOfSegments.AddRange(GetPathSegments(methodCallExpression.Object));
|
||||
listOfSegments.Add(EvaluateExpression(methodCallExpression.Arguments[0]));
|
||||
return listOfSegments;
|
||||
|
||||
case ExpressionType.Convert:
|
||||
listOfSegments.AddRange(GetPathSegments(((UnaryExpression)expr).Operand));
|
||||
return listOfSegments;
|
||||
|
||||
case ExpressionType.MemberAccess:
|
||||
var memberExpression = expr as MemberExpression;
|
||||
listOfSegments.AddRange(GetPathSegments(memberExpression.Expression));
|
||||
// Get property name, respecting JsonProperty attribute
|
||||
listOfSegments.Add(GetPropertyNameFromMemberExpression(memberExpression));
|
||||
return listOfSegments;
|
||||
|
||||
case ExpressionType.Parameter:
|
||||
// Fits "x => x" (the whole document which is "" as JSON pointer)
|
||||
return listOfSegments;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(Resources.FormatExpressionTypeNotSupported(expr));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPropertyNameFromMemberExpression(MemberExpression memberExpression)
|
||||
{
|
||||
var jsonObjectContract = ContractResolver.ResolveContract(memberExpression.Expression.Type) as JsonObjectContract;
|
||||
if (jsonObjectContract != null)
|
||||
{
|
||||
return jsonObjectContract.Properties
|
||||
.First(jsonProperty => jsonProperty.UnderlyingName == memberExpression.Member.Name)
|
||||
.PropertyName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool ContinueWithSubPath(ExpressionType expressionType)
|
||||
{
|
||||
return (expressionType == ExpressionType.ArrayIndex
|
||||
|| expressionType == ExpressionType.Call
|
||||
|| expressionType == ExpressionType.Convert
|
||||
|| expressionType == ExpressionType.MemberAccess);
|
||||
|
||||
}
|
||||
|
||||
// Evaluates the value of the key or index which may be an int or a string,
|
||||
// or some other expression type.
|
||||
// The expression is converted to a delegate and the result of executing the delegate is returned as a string.
|
||||
private static string EvaluateExpression(Expression expression)
|
||||
{
|
||||
var converted = Expression.Convert(expression, typeof(object));
|
||||
var fakeParameter = Expression.Parameter(typeof(object), null);
|
||||
var lambda = Expression.Lambda<Func<object, object>>(converted, fakeParameter);
|
||||
var func = lambda.Compile();
|
||||
|
||||
return Convert.ToString(func(null), CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Operations;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Captures error message and the related entity and the operation that caused it.
|
||||
/// </summary>
|
||||
public class JsonPatchError
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="JsonPatchError"/>.
|
||||
/// </summary>
|
||||
/// <param name="affectedObject">The object that is affected by the error.</param>
|
||||
/// <param name="operation">The <see cref="Operation"/> that caused the error.</param>
|
||||
/// <param name="errorMessage">The error message.</param>
|
||||
public JsonPatchError(
|
||||
object affectedObject,
|
||||
Operation operation,
|
||||
string errorMessage)
|
||||
{
|
||||
if (errorMessage == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(errorMessage));
|
||||
}
|
||||
|
||||
AffectedObject = affectedObject;
|
||||
Operation = operation;
|
||||
ErrorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object that is affected by the error.
|
||||
/// </summary>
|
||||
public object AffectedObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Operation"/> that caused the error.
|
||||
/// </summary>
|
||||
public Operation Operation { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>ASP.NET Core support for JSON PATCH.</Description>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<PackageTags>aspnetcore;json;jsonpatch</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="Newtonsoft.Json" />
|
||||
<Reference Include="Microsoft.Extensions.ClosedGenericMatcher.Sources" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Operations
|
||||
{
|
||||
public class Operation : OperationBase
|
||||
{
|
||||
[JsonProperty("value")]
|
||||
public object value { get; set; }
|
||||
|
||||
public Operation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Operation(string op, string path, string from, object value)
|
||||
: base(op, path, from)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation(string op, string path, string from)
|
||||
: base(op, path, from)
|
||||
{
|
||||
}
|
||||
|
||||
public void Apply(object objectToApplyTo, IObjectAdapter adapter)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (adapter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapter));
|
||||
}
|
||||
|
||||
switch (OperationType)
|
||||
{
|
||||
case OperationType.Add:
|
||||
adapter.Add(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Remove:
|
||||
adapter.Remove(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Replace:
|
||||
adapter.Replace(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Move:
|
||||
adapter.Move(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Copy:
|
||||
adapter.Copy(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Test:
|
||||
if (adapter is IObjectAdapterWithTest adapterWithTest)
|
||||
{
|
||||
adapterWithTest.Test(this, objectToApplyTo);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException(Resources.TestOperationNotSupported);
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShouldSerializevalue()
|
||||
{
|
||||
return (OperationType == OperationType.Add
|
||||
|| OperationType == OperationType.Replace
|
||||
|| OperationType == OperationType.Test);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Operations
|
||||
{
|
||||
public class OperationBase
|
||||
{
|
||||
private string _op;
|
||||
private OperationType _operationType;
|
||||
|
||||
[JsonIgnore]
|
||||
public OperationType OperationType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _operationType;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("path")]
|
||||
public string path { get; set; }
|
||||
|
||||
[JsonProperty("op")]
|
||||
public string op
|
||||
{
|
||||
get
|
||||
{
|
||||
return _op;
|
||||
}
|
||||
set
|
||||
{
|
||||
OperationType result;
|
||||
if (!Enum.TryParse(value, ignoreCase: true, result: out result))
|
||||
{
|
||||
result = OperationType.Invalid;
|
||||
}
|
||||
_operationType = result;
|
||||
_op = value;
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty("from")]
|
||||
public string from { get; set; }
|
||||
|
||||
public OperationBase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OperationBase(string op, string path, string from)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(op));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
this.op = op;
|
||||
this.path = path;
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
public bool ShouldSerializefrom()
|
||||
{
|
||||
return (OperationType == OperationType.Move
|
||||
|| OperationType == OperationType.Copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.JsonPatch.Adapters;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Operations
|
||||
{
|
||||
public class Operation<TModel> : Operation where TModel : class
|
||||
{
|
||||
public Operation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Operation(string op, string path, string from, object value)
|
||||
: base(op, path, from)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(op));
|
||||
}
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Operation(string op, string path, string from)
|
||||
: base(op, path, from)
|
||||
{
|
||||
if (op == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(op));
|
||||
}
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Apply(TModel objectToApplyTo, IObjectAdapter adapter)
|
||||
{
|
||||
if (objectToApplyTo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(objectToApplyTo));
|
||||
}
|
||||
|
||||
if (adapter == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(adapter));
|
||||
}
|
||||
|
||||
switch (OperationType)
|
||||
{
|
||||
case OperationType.Add:
|
||||
adapter.Add(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Remove:
|
||||
adapter.Remove(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Replace:
|
||||
adapter.Replace(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Move:
|
||||
adapter.Move(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Copy:
|
||||
adapter.Copy(this, objectToApplyTo);
|
||||
break;
|
||||
case OperationType.Test:
|
||||
if (adapter is IObjectAdapterWithTest adapterWithTest)
|
||||
{
|
||||
adapterWithTest.Test(this, objectToApplyTo);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new JsonPatchException(new JsonPatchError(objectToApplyTo, this, Resources.TestOperationNotSupported));
|
||||
}
|
||||
case OperationType.Invalid:
|
||||
throw new JsonPatchException(
|
||||
Resources.FormatInvalidJsonPatchOperation(op), innerException: null);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Operations
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Add,
|
||||
Remove,
|
||||
Replace,
|
||||
Move,
|
||||
Copy,
|
||||
Test,
|
||||
Invalid
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.JsonPatch.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
|
||||
|
|
@ -0,0 +1,338 @@
|
|||
// <auto-generated />
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
|
||||
internal static class Resources
|
||||
{
|
||||
private static readonly ResourceManager _resourceManager
|
||||
= new ResourceManager("Microsoft.AspNetCore.JsonPatch.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be copied.
|
||||
/// </summary>
|
||||
internal static string CannotCopyProperty
|
||||
{
|
||||
get => GetString("CannotCopyProperty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be copied.
|
||||
/// </summary>
|
||||
internal static string FormatCannotCopyProperty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotCopyProperty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The type of the property at path '{0}' could not be determined.
|
||||
/// </summary>
|
||||
internal static string CannotDeterminePropertyType
|
||||
{
|
||||
get => GetString("CannotDeterminePropertyType");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type of the property at path '{0}' could not be determined.
|
||||
/// </summary>
|
||||
internal static string FormatCannotDeterminePropertyType(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotDeterminePropertyType"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' operation at path '{1}' could not be performed.
|
||||
/// </summary>
|
||||
internal static string CannotPerformOperation
|
||||
{
|
||||
get => GetString("CannotPerformOperation");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The '{0}' operation at path '{1}' could not be performed.
|
||||
/// </summary>
|
||||
internal static string FormatCannotPerformOperation(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotPerformOperation"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be read.
|
||||
/// </summary>
|
||||
internal static string CannotReadProperty
|
||||
{
|
||||
get => GetString("CannotReadProperty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at '{0}' could not be read.
|
||||
/// </summary>
|
||||
internal static string FormatCannotReadProperty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotReadProperty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be updated.
|
||||
/// </summary>
|
||||
internal static string CannotUpdateProperty
|
||||
{
|
||||
get => GetString("CannotUpdateProperty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The property at path '{0}' could not be updated.
|
||||
/// </summary>
|
||||
internal static string FormatCannotUpdateProperty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("CannotUpdateProperty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
|
||||
/// </summary>
|
||||
internal static string ExpressionTypeNotSupported
|
||||
{
|
||||
get => GetString("ExpressionTypeNotSupported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.
|
||||
/// </summary>
|
||||
internal static string FormatExpressionTypeNotSupported(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ExpressionTypeNotSupported"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The index value provided by path segment '{0}' is out of bounds of the array size.
|
||||
/// </summary>
|
||||
internal static string IndexOutOfBounds
|
||||
{
|
||||
get => GetString("IndexOutOfBounds");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The index value provided by path segment '{0}' is out of bounds of the array size.
|
||||
/// </summary>
|
||||
internal static string FormatIndexOutOfBounds(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("IndexOutOfBounds"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The path segment '{0}' is invalid for an array index.
|
||||
/// </summary>
|
||||
internal static string InvalidIndexValue
|
||||
{
|
||||
get => GetString("InvalidIndexValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The path segment '{0}' is invalid for an array index.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidIndexValue(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidIndexValue"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The JSON patch document was malformed and could not be parsed.
|
||||
/// </summary>
|
||||
internal static string InvalidJsonPatchDocument
|
||||
{
|
||||
get => GetString("InvalidJsonPatchDocument");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The JSON patch document was malformed and could not be parsed.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidJsonPatchDocument()
|
||||
=> GetString("InvalidJsonPatchDocument");
|
||||
|
||||
/// <summary>
|
||||
/// Invalid JsonPatch operation '{0}'.
|
||||
/// </summary>
|
||||
internal static string InvalidJsonPatchOperation
|
||||
{
|
||||
get => GetString("InvalidJsonPatchOperation");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invalid JsonPatch operation '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidJsonPatchOperation(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidJsonPatchOperation"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The provided path segment '{0}' cannot be converted to the target type.
|
||||
/// </summary>
|
||||
internal static string InvalidPathSegment
|
||||
{
|
||||
get => GetString("InvalidPathSegment");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided path segment '{0}' cannot be converted to the target type.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidPathSegment(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidPathSegment"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The provided string '{0}' is an invalid path.
|
||||
/// </summary>
|
||||
internal static string InvalidValueForPath
|
||||
{
|
||||
get => GetString("InvalidValueForPath");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The provided string '{0}' is an invalid path.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidValueForPath(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForPath"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid for target location.
|
||||
/// </summary>
|
||||
internal static string InvalidValueForProperty
|
||||
{
|
||||
get => GetString("InvalidValueForProperty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value '{0}' is invalid for target location.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidValueForProperty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidValueForProperty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' must be of type '{1}'.
|
||||
/// </summary>
|
||||
internal static string ParameterMustMatchType
|
||||
{
|
||||
get => GetString("ParameterMustMatchType");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// '{0}' must be of type '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatParameterMustMatchType(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ParameterMustMatchType"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
|
||||
/// </summary>
|
||||
internal static string PatchNotSupportedForArrays
|
||||
{
|
||||
get => GetString("PatchNotSupportedForArrays");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.
|
||||
/// </summary>
|
||||
internal static string FormatPatchNotSupportedForArrays(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForArrays"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
|
||||
/// </summary>
|
||||
internal static string PatchNotSupportedForNonGenericLists
|
||||
{
|
||||
get => GetString("PatchNotSupportedForNonGenericLists");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.
|
||||
/// </summary>
|
||||
internal static string FormatPatchNotSupportedForNonGenericLists(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("PatchNotSupportedForNonGenericLists"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The target location specified by path segment '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string TargetLocationAtPathSegmentNotFound
|
||||
{
|
||||
get => GetString("TargetLocationAtPathSegmentNotFound");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The target location specified by path segment '{0}' was not found.
|
||||
/// </summary>
|
||||
internal static string FormatTargetLocationAtPathSegmentNotFound(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationAtPathSegmentNotFound"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the target location specified by path '{1}' was not found.
|
||||
/// </summary>
|
||||
internal static string TargetLocationNotFound
|
||||
{
|
||||
get => GetString("TargetLocationNotFound");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For operation '{0}', the target location specified by path '{1}' was not found.
|
||||
/// </summary>
|
||||
internal static string FormatTargetLocationNotFound(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("TargetLocationNotFound"), p0, p1);
|
||||
|
||||
/// <summary>
|
||||
/// The test operation is not supported.
|
||||
/// </summary>
|
||||
internal static string TestOperationNotSupported
|
||||
{
|
||||
get => GetString("TestOperationNotSupported");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The test operation is not supported.
|
||||
/// </summary>
|
||||
internal static string FormatTestOperationNotSupported()
|
||||
=> GetString("TestOperationNotSupported");
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string ValueAtListPositionNotEqualToTestValue
|
||||
{
|
||||
get => GetString("ValueAtListPositionNotEqualToTestValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at position '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatValueAtListPositionNotEqualToTestValue(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueAtListPositionNotEqualToTestValue"), p0, p1, p2);
|
||||
|
||||
/// <summary>
|
||||
/// The value at '{0}' cannot be null or empty to perform the test operation.
|
||||
/// </summary>
|
||||
internal static string ValueForTargetSegmentCannotBeNullOrEmpty
|
||||
{
|
||||
get => GetString("ValueForTargetSegmentCannotBeNullOrEmpty");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The value at '{0}' cannot be null or empty to perform the test operation.
|
||||
/// </summary>
|
||||
internal static string FormatValueForTargetSegmentCannotBeNullOrEmpty(object p0)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueForTargetSegmentCannotBeNullOrEmpty"), p0);
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string ValueNotEqualToTestValue
|
||||
{
|
||||
get => GetString("ValueNotEqualToTestValue");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current value '{0}' at path '{2}' is not equal to the test value '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatValueNotEqualToTestValue(object p0, object p1, object p2)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("ValueNotEqualToTestValue"), p0, p1, p2);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
||||
System.Diagnostics.Debug.Assert(value != null);
|
||||
|
||||
if (formatterNames != null)
|
||||
{
|
||||
for (var i = 0; i < formatterNames.Length; i++)
|
||||
{
|
||||
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CannotCopyProperty" xml:space="preserve">
|
||||
<value>The property at '{0}' could not be copied.</value>
|
||||
</data>
|
||||
<data name="CannotDeterminePropertyType" xml:space="preserve">
|
||||
<value>The type of the property at path '{0}' could not be determined.</value>
|
||||
</data>
|
||||
<data name="CannotPerformOperation" xml:space="preserve">
|
||||
<value>The '{0}' operation at path '{1}' could not be performed.</value>
|
||||
</data>
|
||||
<data name="CannotReadProperty" xml:space="preserve">
|
||||
<value>The property at '{0}' could not be read.</value>
|
||||
</data>
|
||||
<data name="CannotUpdateProperty" xml:space="preserve">
|
||||
<value>The property at path '{0}' could not be updated.</value>
|
||||
</data>
|
||||
<data name="ExpressionTypeNotSupported" xml:space="preserve">
|
||||
<value>The expression '{0}' is not supported. Supported expressions include member access and indexer expressions.</value>
|
||||
</data>
|
||||
<data name="IndexOutOfBounds" xml:space="preserve">
|
||||
<value>The index value provided by path segment '{0}' is out of bounds of the array size.</value>
|
||||
</data>
|
||||
<data name="InvalidIndexValue" xml:space="preserve">
|
||||
<value>The path segment '{0}' is invalid for an array index.</value>
|
||||
</data>
|
||||
<data name="InvalidJsonPatchDocument" xml:space="preserve">
|
||||
<value>The JSON patch document was malformed and could not be parsed.</value>
|
||||
</data>
|
||||
<data name="InvalidJsonPatchOperation" xml:space="preserve">
|
||||
<value>Invalid JsonPatch operation '{0}'.</value>
|
||||
</data>
|
||||
<data name="InvalidPathSegment" xml:space="preserve">
|
||||
<value>The provided path segment '{0}' cannot be converted to the target type.</value>
|
||||
</data>
|
||||
<data name="InvalidValueForPath" xml:space="preserve">
|
||||
<value>The provided string '{0}' is an invalid path.</value>
|
||||
</data>
|
||||
<data name="InvalidValueForProperty" xml:space="preserve">
|
||||
<value>The value '{0}' is invalid for target location.</value>
|
||||
</data>
|
||||
<data name="ParameterMustMatchType" xml:space="preserve">
|
||||
<value>'{0}' must be of type '{1}'.</value>
|
||||
</data>
|
||||
<data name="PatchNotSupportedForArrays" xml:space="preserve">
|
||||
<value>The type '{0}' which is an array is not supported for json patch operations as it has a fixed size.</value>
|
||||
</data>
|
||||
<data name="PatchNotSupportedForNonGenericLists" xml:space="preserve">
|
||||
<value>The type '{0}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.</value>
|
||||
</data>
|
||||
<data name="TargetLocationAtPathSegmentNotFound" xml:space="preserve">
|
||||
<value>The target location specified by path segment '{0}' was not found.</value>
|
||||
</data>
|
||||
<data name="TargetLocationNotFound" xml:space="preserve">
|
||||
<value>For operation '{0}', the target location specified by path '{1}' was not found.</value>
|
||||
</data>
|
||||
<data name="TestOperationNotSupported" xml:space="preserve">
|
||||
<value>The test operation is not supported.</value>
|
||||
</data>
|
||||
<data name="ValueAtListPositionNotEqualToTestValue" xml:space="preserve">
|
||||
<value>The current value '{0}' at position '{2}' is not equal to the test value '{1}'.</value>
|
||||
</data>
|
||||
<data name="ValueForTargetSegmentCannotBeNullOrEmpty" xml:space="preserve">
|
||||
<value>The value at '{0}' cannot be null or empty to perform the test operation.</value>
|
||||
</data>
|
||||
<data name="ValueNotEqualToTestValue" xml:space="preserve">
|
||||
<value>The current value '{0}' at path '{2}' is not equal to the test value '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class CustomNamingStrategyTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddProperty_ToDynamicTestObject_WithCustomNamingStrategy()
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new TestNamingStrategy()
|
||||
};
|
||||
|
||||
dynamic targetObject = new DynamicTestObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("NewInt", 1);
|
||||
patchDocument.ContractResolver = contractResolver;
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, targetObject.customNewInt);
|
||||
Assert.Equal(1, targetObject.Test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyPropertyValue_ToDynamicTestObject_WithCustomNamingStrategy()
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new TestNamingStrategy()
|
||||
};
|
||||
|
||||
dynamic targetObject = new DynamicTestObject();
|
||||
targetObject.customStringProperty = "A";
|
||||
targetObject.customAnotherStringProperty = "B";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("StringProperty", "AnotherStringProperty");
|
||||
patchDocument.ContractResolver = contractResolver;
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.customAnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new TestNamingStrategy()
|
||||
};
|
||||
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.customStringProperty = "A";
|
||||
targetObject.customAnotherStringProperty = "B";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("StringProperty", "AnotherStringProperty");
|
||||
patchDocument.ContractResolver = contractResolver;
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
var cont = targetObject as IDictionary<string, object>;
|
||||
cont.TryGetValue("customStringProperty", out var valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.customAnotherStringProperty);
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveProperty_FromDictionaryObject_WithCustomNamingStrategy()
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new TestNamingStrategy()
|
||||
};
|
||||
|
||||
var targetObject = new Dictionary<string, int>()
|
||||
{
|
||||
{ "customTest", 1},
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Test");
|
||||
patchDocument.ContractResolver = contractResolver;
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
var cont = targetObject as IDictionary<string, int>;
|
||||
cont.TryGetValue("customTest", out var valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacePropertyValue_ForExpandoObject_WithCustomNamingStrategy()
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver
|
||||
{
|
||||
NamingStrategy = new TestNamingStrategy()
|
||||
};
|
||||
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.customTest = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("Test", 2);
|
||||
patchDocument.ContractResolver = contractResolver;
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, targetObject.customTest);
|
||||
}
|
||||
|
||||
private class TestNamingStrategy : NamingStrategy
|
||||
{
|
||||
public new bool ProcessDictionaryKeys => true;
|
||||
|
||||
public override string GetDictionaryKey(string key)
|
||||
{
|
||||
return "custom" + key;
|
||||
}
|
||||
|
||||
protected override string ResolvePropertyName(string name)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class AnonymousObjectIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddNewProperty_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new { };
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("NewProperty", 4);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'NewProperty' was not found.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddNewProperty_ToNestedAnonymousObject_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new
|
||||
{
|
||||
Test = 1,
|
||||
nested = new { }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("Nested/NewInt", 1);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'NewInt' was not found.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddDoesNotReplace()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new
|
||||
{
|
||||
StringProperty = "A"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("StringProperty", "B");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The property at path 'StringProperty' could not be updated.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveProperty_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new
|
||||
{
|
||||
Test = 1
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Test");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The property at path 'Test' could not be updated.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceProperty_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The property at path 'StringProperty' could not be updated.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveProperty_ShouldFail()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The property at path 'StringProperty' could not be updated.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStringProperty_IsSucessful()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("StringProperty", "A");
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStringProperty_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("StringProperty", "B");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The current value 'A' at path 'StringProperty' is not equal to the test value 'B'.",
|
||||
exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class DictionaryTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestIntegerValue_IsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("/DictionaryOfStringToInteger/two", 2);
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddIntegerValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("/DictionaryOfStringToInteger/three", 3);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(3, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger["two"]);
|
||||
Assert.Equal(3, model.DictionaryOfStringToInteger["three"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveIntegerValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("/DictionaryOfStringToInteger/two");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveIntegerValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("/DictionaryOfStringToInteger/one", "/DictionaryOfStringToInteger/two");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["two"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceIntegerValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("/DictionaryOfStringToInteger/two", 20);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
Assert.Equal(20, model.DictionaryOfStringToInteger["two"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyIntegerValue_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger["one"] = 1;
|
||||
model.DictionaryOfStringToInteger["two"] = 2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("/DictionaryOfStringToInteger/one", "/DictionaryOfStringToInteger/two");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["one"]);
|
||||
Assert.Equal(1, model.DictionaryOfStringToInteger["two"]);
|
||||
}
|
||||
|
||||
private class Customer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public Address Address { get; set; }
|
||||
}
|
||||
|
||||
private class Address
|
||||
{
|
||||
public string City { get; set; }
|
||||
}
|
||||
|
||||
private class IntDictionary
|
||||
{
|
||||
public IDictionary<string, int> DictionaryOfStringToInteger { get; } = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
private class CustomerDictionary
|
||||
{
|
||||
public IDictionary<int, Customer> DictionaryOfStringToCustomer { get; } = new Dictionary<int, Customer>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "James" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(model);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestPocoObject_FailsWhenTestValueIsNotEqualToObjectValue()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "James" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test($"/DictionaryOfStringToCustomer/{key1}/Name", "Mike");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(model);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The current value 'James' at path 'Name' is not equal to the test value 'Mike'.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddReplacesPocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = 200;
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemovePocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = 200;
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove($"/DictionaryOfStringToCustomer/{key1}/Name");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.Null(actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovePocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "James" };
|
||||
var key2 = 200;
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move($"/DictionaryOfStringToCustomer/{key1}/Name", $"/DictionaryOfStringToCustomer/{key2}/Name");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
var actualValue2 = model.DictionaryOfStringToCustomer[key2];
|
||||
Assert.NotNull(actualValue2);
|
||||
Assert.Equal("James", actualValue2.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyPocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "James" };
|
||||
var key2 = 200;
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy($"/DictionaryOfStringToCustomer/{key1}/Name", $"/DictionaryOfStringToCustomer/{key2}/Name");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue2 = model.DictionaryOfStringToCustomer[key2];
|
||||
Assert.NotNull(actualValue2);
|
||||
Assert.Equal("James", actualValue2.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacePocoObject_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = 100;
|
||||
var value1 = new Customer() { Name = "Jamesss" };
|
||||
var key2 = 200;
|
||||
var value2 = new Customer() { Name = "Mike" };
|
||||
var model = new CustomerDictionary();
|
||||
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToCustomer/{key1}/Name", "James");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||
Assert.NotNull(actualValue1);
|
||||
Assert.Equal("James", actualValue1.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacePocoObject_WithEscaping_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "Foo/Name";
|
||||
var value1 = 100;
|
||||
var key2 = "Foo";
|
||||
var value2 = 200;
|
||||
var model = new IntDictionary();
|
||||
model.DictionaryOfStringToInteger[key1] = value1;
|
||||
model.DictionaryOfStringToInteger[key2] = value2;
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace($"/DictionaryOfStringToInteger/Foo~1Name", 300);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(model);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, model.DictionaryOfStringToInteger.Count);
|
||||
var actualValue1 = model.DictionaryOfStringToInteger[key1];
|
||||
var actualValue2 = model.DictionaryOfStringToInteger[key2];
|
||||
Assert.Equal(300, actualValue1);
|
||||
Assert.Equal(200, actualValue2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class DynamicObjectIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddResults_ShouldReplaceExistingPropertyValue_InNestedDynamicObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Nested = new NestedObject();
|
||||
dynamicTestObject.Nested.DynamicProperty = new DynamicTestObject();
|
||||
dynamicTestObject.Nested.DynamicProperty.InBetweenFirst = new DynamicTestObject();
|
||||
dynamicTestObject.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond = new DynamicTestObject();
|
||||
dynamicTestObject.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("/Nested/DynamicProperty/InBetweenFirst/InBetweenSecond/StringProperty", "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", dynamicTestObject.Nested.DynamicProperty.InBetweenFirst.InBetweenSecond.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeAbleToAdd_ToNonExistingProperty_ThatIsNotTheRoot()
|
||||
{
|
||||
//Adding to a Nonexistent Target
|
||||
//
|
||||
// An example target JSON document:
|
||||
// { "foo": "bar" }
|
||||
// A JSON Patch document:
|
||||
// [
|
||||
// { "op": "add", "path": "/baz/bat", "value": "qux" }
|
||||
// ]
|
||||
// This JSON Patch document, applied to the target JSON document above,
|
||||
// would result in an error (therefore, it would not be applied),
|
||||
// because the "add" operation's target location that references neither
|
||||
// the root of the document, nor a member of an existing object, nor a
|
||||
// member of an existing array.
|
||||
|
||||
// Arrange
|
||||
var nestedObject = new NestedObject()
|
||||
{
|
||||
DynamicProperty = new DynamicTestObject()
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("DynamicProperty/OtherProperty/IntProperty", 1);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(nestedObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'OtherProperty' was not found.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyProperties_InNestedDynamicObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.NestedDynamicObject = new DynamicTestObject();
|
||||
dynamicTestObject.NestedDynamicObject.StringProperty = "A";
|
||||
dynamicTestObject.NestedDynamicObject.AnotherStringProperty = "B";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("NestedDynamicObject/StringProperty", "NestedDynamicObject/AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", dynamicTestObject.NestedDynamicObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void MoveToNonExistingProperty_InDynamicObject_ShouldAddNewProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
dynamicTestObject.TryGetValue("StringProperty", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", dynamicTestObject.AnotherStringProperty);
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MovePropertyValue_FromDynamicObject_ToTypedObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.StringProperty = "A";
|
||||
dynamicTestObject.SimpleObject = new SimpleObject() { AnotherStringProperty = "B" };
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("StringProperty", "SimpleObject/AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
dynamicTestObject.TryGetValue("StringProperty", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", dynamicTestObject.SimpleObject.AnotherStringProperty);
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveNestedProperty_FromDynamicObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Test = new DynamicTestObject();
|
||||
dynamicTestObject.Test.AnotherTest = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Test");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
dynamicTestObject.TryGetValue("Test", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveFromNestedObject_InDynamicObject_MixedCase_ThrowsPathNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.SimpleObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Simpleobject/stringProperty");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'Simpleobject' was not found.", exception.Message);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ReplaceNestedTypedObject_InDynamicObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerValue = 5,
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var newObject = new SimpleObject()
|
||||
{
|
||||
DoubleValue = 1
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("SimpleObject", newObject);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, dynamicTestObject.SimpleObject.DoubleValue);
|
||||
Assert.Equal(0, dynamicTestObject.SimpleObject.IntegerValue);
|
||||
Assert.Null(dynamicTestObject.SimpleObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStringPropertyValue_IsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Property = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("Property", "A");
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestIntegerPropertyValue_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicTestObject = new DynamicTestObject();
|
||||
dynamicTestObject.Nested = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("Nested/IntegerList/0", 2);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(dynamicTestObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The current value '1' at position '0' is not equal to the test value '2'.", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,327 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class ExpandoObjectIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void AddNewIntProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("NewInt", 1);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, targetObject.NewInt);
|
||||
Assert.Equal(1, targetObject.Test);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddNewProperty_ToTypedObject_InExpandoObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic dynamicProperty = new ExpandoObject();
|
||||
dynamicProperty.StringProperty = "A";
|
||||
|
||||
var targetObject = new NestedObject()
|
||||
{
|
||||
DynamicProperty = dynamicProperty
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("DynamicProperty/StringProperty", "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.DynamicProperty.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddReplaces_ExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("StringProperty", "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddReplaces_ExistingProperty_InNestedExpandoObject()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.InBetweenFirst = new ExpandoObject();
|
||||
targetObject.InBetweenFirst.InBetweenSecond = new ExpandoObject();
|
||||
targetObject.InBetweenFirst.InBetweenSecond.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("/InBetweenFirst/InBetweenSecond/StringProperty", "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.InBetweenFirst.InBetweenSecond.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotReplaceProperty_WithDifferentCase()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("stringproperty", "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.StringProperty);
|
||||
Assert.Equal("B", targetObject.stringproperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestIntegerProperty_IsSucessful()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("Test", 1);
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStringProperty_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = "Value";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("Test", "TestValue");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The current value 'Value' at path 'Test' is not equal to the test value 'TestValue'.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyStringProperty_ToAnotherStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
|
||||
targetObject.StringProperty = "A";
|
||||
targetObject.AnotherStringProperty = "B";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveIntegerValue_ToAnotherIntegerProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.IntegerValue = 100;
|
||||
targetObject.AnotherIntegerValue = 200;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("IntegerValue", "AnotherIntegerValue");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
Assert.Equal(100, targetObject.AnotherIntegerValue);
|
||||
|
||||
var cont = targetObject as IDictionary<string, object>;
|
||||
cont.TryGetValue("IntegerValue", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_ToNonExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.StringProperty = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
Assert.Equal("A", targetObject.AnotherStringProperty);
|
||||
|
||||
var cont = targetObject as IDictionary<string, object>;
|
||||
cont.TryGetValue("StringProperty", out var valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveProperty_ShouldFail_IfItDoesntExist()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("NonExisting");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'NonExisting' was not found.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Test");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
var cont = targetObject as IDictionary<string, object>;
|
||||
cont.TryGetValue("Test", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveProperty_MixedCase_ThrowsPathNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = 1;
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("test");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'test' was not found.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveNestedProperty()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = new ExpandoObject();
|
||||
targetObject.Test.AnotherTest = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("Test");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
var cont = targetObject as IDictionary<string, object>;
|
||||
cont.TryGetValue("Test", out object valueFromDictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Null(valueFromDictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveNestedProperty_MixedCase_ThrowsPathNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.Test = new ExpandoObject();
|
||||
targetObject.Test.AnotherTest = "A";
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("test");
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The target location specified by path segment 'test' was not found.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceGuid()
|
||||
{
|
||||
// Arrange
|
||||
dynamic targetObject = new ExpandoObject();
|
||||
targetObject.GuidValue = Guid.NewGuid();
|
||||
|
||||
var newGuid = Guid.NewGuid();
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("GuidValue", newGuid);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(newGuid, targetObject.GuidValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class ListIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestInList_IsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Test(o => o.SimpleObject.IntegerList, 3, 2);
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestInList_InvalidPosition()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Test(o => o.SimpleObject.IntegerList, 4, -1);
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDocument.ApplyTo(targetObject); });
|
||||
Assert.Equal("The index value provided by path segment '-1' is out of bounds of the array size.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToIntegerIList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerIList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Add(o => (List<int>)o.SimpleObject.IntegerIList, 4, 0);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 1, 2, 3 }, targetObject.SimpleObject.IntegerIList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToComplextTypeList_SpecifyIndex()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObjectList = new List<SimpleObject>()
|
||||
{
|
||||
new SimpleObject
|
||||
{
|
||||
StringProperty = "String1"
|
||||
},
|
||||
new SimpleObject
|
||||
{
|
||||
StringProperty = "String2"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Add(o => o.SimpleObjectList[0].StringProperty, "ChangedString1");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("ChangedString1", targetObject.SimpleObjectList[0].StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddToListAppend()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Add(o => o.SimpleObject.IntegerList, 4);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 4 }, targetObject.SimpleObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveFromList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("IntegerList/2");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2 }, targetObject.IntegerList);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("3")]
|
||||
[InlineData("-1")]
|
||||
public void RemoveFromList_InvalidPosition(string position)
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("IntegerList/" + position);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal($"The index value provided by path segment '{position}' is out of bounds of the array size.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_FromEndOfList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Remove<int>(o => o.SimpleObject.IntegerList);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2 }, targetObject.SimpleObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceFullList_WithCollection()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("IntegerList", new Collection<int>() { 4, 5, 6 });
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 4, 5, 6 }, targetObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_AtEndOfList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Replace(o => o.SimpleObject.IntegerList, 5);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 5 }, targetObject.SimpleObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_InList_InvalidPosition()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Replace(o => o.SimpleObject.IntegerList, 5, -1);
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() => { patchDocument.ApplyTo(targetObject); });
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The index value provided by path segment '-1' is out of bounds of the array size.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyFromListToEndOfList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("IntegerList/0", "IntegerList/-");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 1 }, targetObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyFromListToNonList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("IntegerList/0", "IntegerValue");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, targetObject.IntegerValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveToEndOfList()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerValue = 5,
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("IntegerValue", "IntegerList/-");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, targetObject.IntegerValue);
|
||||
Assert.Equal(new List<int>() { 1, 2, 3, 5 }, targetObject.IntegerList);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReferenceInList()
|
||||
{
|
||||
// Arrange
|
||||
var simpleObject1 = new SimpleObject() { IntegerValue = 1 };
|
||||
var simpleObject2 = new SimpleObject() { IntegerValue = 2 };
|
||||
var simpleObject3 = new SimpleObject() { IntegerValue = 3 };
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObjectList = new List<SimpleObject>() {
|
||||
simpleObject1,
|
||||
simpleObject2,
|
||||
simpleObject3
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Move(o => o.SimpleObjectList, 0, o => o.SimpleObjectList, 1);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<SimpleObject>() { simpleObject2, simpleObject1, simpleObject3 }, targetObject.SimpleObjectList);
|
||||
Assert.Equal(2, targetObject.SimpleObjectList[0].IntegerValue);
|
||||
Assert.Equal(1, targetObject.SimpleObjectList[1].IntegerValue);
|
||||
Assert.Same(simpleObject2, targetObject.SimpleObjectList[0]);
|
||||
Assert.Same(simpleObject1, targetObject.SimpleObjectList[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveFromList_ToNonList_BetweenHierarchy()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerList = new List<int>() { 1, 2, 3 }
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Move(o => o.SimpleObject.IntegerList, 0, o => o.IntegerValue);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new List<int>() { 2, 3 }, targetObject.SimpleObject.IntegerList);
|
||||
Assert.Equal(1, targetObject.IntegerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class NestedObjectIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void Replace_DTOWithNullCheck()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObjectWithNullCheck()
|
||||
{
|
||||
SimpleObjectWithNullCheck = new SimpleObjectWithNullCheck()
|
||||
{
|
||||
StringProperty = "A"
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObjectWithNullCheck>();
|
||||
patchDocument.Replace(o => o.SimpleObjectWithNullCheck.StringProperty, "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.SimpleObjectWithNullCheck.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceNestedObject_WithSerialization()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
IntegerValue = 1
|
||||
};
|
||||
|
||||
var newNested = new NestedObject() { StringProperty = "B" };
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Replace(o => o.NestedObject, newNested);
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDocument);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleObjectWithNestedObject>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.NestedObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestStringProperty_InNestedObject()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
NestedObject = new NestedObject() { StringProperty = "A"}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<NestedObject>();
|
||||
patchDocument.Test(o => o.StringProperty, "A");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject.NestedObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.NestedObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestNestedObject()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
NestedObject = new NestedObject() { StringProperty = "B"}
|
||||
};
|
||||
|
||||
var testNested = new NestedObject() { StringProperty = "B" };
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Test(o => o.NestedObject, testNested);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.NestedObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddReplaces_ExistingStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A"
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Add(o => o.SimpleObject.StringProperty, "B");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("B", targetObject.SimpleObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddNewProperty_ToExpandoOject_InTypedObject()
|
||||
{
|
||||
var targetObject = new NestedObject()
|
||||
{
|
||||
DynamicProperty = new ExpandoObject()
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("DynamicProperty/NewInt", 1);
|
||||
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
Assert.Equal(1, targetObject.DynamicProperty.NewInt);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A"
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Remove(o => o.SimpleObject.StringProperty);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Null(targetObject.SimpleObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyStringProperty_ToAnotherStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Copy(o => o.SimpleObject.StringProperty, o => o.SimpleObject.AnotherStringProperty);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.SimpleObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_DeepClonesObject()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
},
|
||||
InheritedObject = new InheritedObject()
|
||||
{
|
||||
StringProperty = "C",
|
||||
AnotherStringProperty = "D"
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Copy(o => o.InheritedObject, o => o.SimpleObject);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("C", targetObject.SimpleObject.StringProperty);
|
||||
Assert.Equal("D", targetObject.SimpleObject.AnotherStringProperty);
|
||||
Assert.Equal("C", targetObject.InheritedObject.StringProperty);
|
||||
Assert.Equal("D", targetObject.InheritedObject.AnotherStringProperty);
|
||||
Assert.NotSame(targetObject.SimpleObject.StringProperty, targetObject.InheritedObject.StringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_KeepsObjectType()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject(),
|
||||
InheritedObject = new InheritedObject()
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Copy(o => o.InheritedObject, o => o.SimpleObject);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(typeof(InheritedObject), targetObject.SimpleObject.GetType());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Copy_BreaksObjectReference()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject(),
|
||||
InheritedObject = new InheritedObject()
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Copy(o => o.InheritedObject, o => o.SimpleObject);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(targetObject.SimpleObject, targetObject.InheritedObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveIntegerValue_ToAnotherIntegerProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = new SimpleObject()
|
||||
{
|
||||
IntegerValue = 2,
|
||||
AnotherIntegerValue = 3
|
||||
}
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Move(o => o.SimpleObject.IntegerValue, o => o.SimpleObject.AnotherIntegerValue);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, targetObject.SimpleObject.AnotherIntegerValue);
|
||||
Assert.Equal(0, targetObject.SimpleObject.IntegerValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_KeepsObjectReference()
|
||||
{
|
||||
// Arrange
|
||||
var sDto = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
var iDto = new InheritedObject()
|
||||
{
|
||||
StringProperty = "C",
|
||||
AnotherStringProperty = "D"
|
||||
};
|
||||
var targetObject = new SimpleObjectWithNestedObject()
|
||||
{
|
||||
SimpleObject = sDto,
|
||||
InheritedObject = iDto
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
patchDocument.Move(o => o.InheritedObject, o => o.SimpleObject);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("C", targetObject.SimpleObject.StringProperty);
|
||||
Assert.Equal("D", targetObject.SimpleObject.AnotherStringProperty);
|
||||
Assert.Same(iDto, targetObject.SimpleObject);
|
||||
Assert.Null(targetObject.InheritedObject);
|
||||
}
|
||||
|
||||
private class SimpleObjectWithNullCheck
|
||||
{
|
||||
private string stringProperty;
|
||||
|
||||
public string StringProperty
|
||||
{
|
||||
get
|
||||
{
|
||||
return stringProperty;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
stringProperty = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SimpleObjectWithNestedObjectWithNullCheck
|
||||
{
|
||||
public SimpleObjectWithNullCheck SimpleObjectWithNullCheck { get; set; }
|
||||
|
||||
public SimpleObjectWithNestedObjectWithNullCheck()
|
||||
{
|
||||
SimpleObjectWithNullCheck = new SimpleObjectWithNullCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.IntegrationTests
|
||||
{
|
||||
public class SimpleObjectIntegrationTest
|
||||
{
|
||||
[Fact]
|
||||
public void TestDoubleValueProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
DoubleValue = 9.8
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Test("DoubleValue", 9.8);
|
||||
|
||||
// Act & Assert
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CopyStringProperty_ToAnotherStringProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("StringProperty", "AnotherStringProperty");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MoveIntegerProperty_ToAnotherIntegerProperty()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
IntegerValue = 2,
|
||||
AnotherIntegerValue = 3
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Move("IntegerValue", "AnotherIntegerValue");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, targetObject.AnotherIntegerValue);
|
||||
Assert.Equal(0, targetObject.IntegerValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveDecimalPropertyValue()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
DecimalValue = 9.8M
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Remove("DecimalValue");
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(0, targetObject.DecimalValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplaceGuid()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
GuidValue = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var newGuid = Guid.NewGuid();
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Replace("GuidValue", newGuid);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(newGuid, targetObject.GuidValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddReplacesGuid()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
GuidValue = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var newGuid = Guid.NewGuid();
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Add("GuidValue", newGuid);
|
||||
|
||||
// Act
|
||||
patchDocument.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(newGuid, targetObject.GuidValue);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,311 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DictionaryAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void Add_KeyWhichAlreadyExists_ReplacesExistingValue()
|
||||
{
|
||||
// Arrange
|
||||
var key = "Status";
|
||||
var dictionary = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
dictionary[key] = 404;
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, int>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, key, resolver, 200, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal(200, dictionary[key]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_IntKeyWhichAlreadyExists_ReplacesExistingValue()
|
||||
{
|
||||
// Arrange
|
||||
var intKey = 1;
|
||||
var dictionary = new Dictionary<int, object>();
|
||||
dictionary[intKey] = "Mike";
|
||||
var dictionaryAdapter = new DictionaryAdapter<int, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, intKey.ToString(), resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[intKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetInvalidKey_ThrowsInvalidPathSegmentException()
|
||||
{
|
||||
// Arrange
|
||||
var dictionaryAdapter = new DictionaryAdapter<int, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
var key = 1;
|
||||
var dictionary = new Dictionary<int, object>();
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, key.ToString(), resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[key]);
|
||||
|
||||
// Act
|
||||
var guidKey = new Guid();
|
||||
var getStatus = dictionaryAdapter.TryGet(dictionary, guidKey.ToString(), resolver, out var outValue, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(getStatus);
|
||||
Assert.Equal($"The provided path segment '{guidKey.ToString()}' cannot be converted to the target type.", message);
|
||||
Assert.Null(outValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UsingCaseSensitiveKey_FailureScenario()
|
||||
{
|
||||
// Arrange
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, nameKey, resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
|
||||
// Act
|
||||
var getStatus = dictionaryAdapter.TryGet(dictionary, nameKey.ToUpper(), resolver, out var outValue, out message);
|
||||
|
||||
// Assert
|
||||
Assert.False(getStatus);
|
||||
Assert.Equal("The target location specified by path segment 'NAME' was not found.", message);
|
||||
Assert.Null(outValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_UsingCaseSensitiveKey_SuccessScenario()
|
||||
{
|
||||
// Arrange
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
|
||||
// Act
|
||||
var addStatus = dictionaryAdapter.TryAdd(dictionary, nameKey, resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
|
||||
// Act
|
||||
addStatus = dictionaryAdapter.TryGet(dictionary, nameKey, resolver, out var outValue, out message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal("James", outValue?.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacingExistingItem()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
dictionary.Add(nameKey, "Mike");
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, nameKey, resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[nameKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacingExistingItem_WithGuidKey()
|
||||
{
|
||||
// Arrange
|
||||
var guidKey = new Guid();
|
||||
var dictionary = new Dictionary<Guid, object>();
|
||||
dictionary.Add(guidKey, "Mike");
|
||||
var dictionaryAdapter = new DictionaryAdapter<Guid, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, guidKey.ToString(), resolver, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Single(dictionary);
|
||||
Assert.Equal("James", dictionary[guidKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacingWithInvalidValue_ThrowsInvalidValueForPropertyException()
|
||||
{
|
||||
// Arrange
|
||||
var guidKey = new Guid();
|
||||
var dictionary = new Dictionary<Guid, int>();
|
||||
dictionary.Add(guidKey, 5);
|
||||
var dictionaryAdapter = new DictionaryAdapter<Guid, int>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, guidKey.ToString(), resolver, "test", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal("The value 'test' is invalid for target location.", message);
|
||||
Assert.Equal(5, dictionary[guidKey]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_NonExistingKey_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var replaceStatus = dictionaryAdapter.TryReplace(dictionary, nameKey, resolver, "Mike", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal("The target location specified by path segment 'Name' was not found.", message);
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_NonExistingKey_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var removeStatus = dictionaryAdapter.TryRemove(dictionary, nameKey, resolver, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal("The target location specified by path segment 'Name' was not found.", message);
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_RemovesFromDictionary()
|
||||
{
|
||||
// Arrange
|
||||
var nameKey = "Name";
|
||||
var dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
|
||||
dictionary[nameKey] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var removeStatus = dictionaryAdapter.TryRemove(dictionary, nameKey, resolver, out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(removeStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_RemovesFromDictionary_WithUriKey()
|
||||
{
|
||||
// Arrange
|
||||
var uriKey = new Uri("http://www.test.com/name");
|
||||
var dictionary = new Dictionary<Uri, object>();
|
||||
dictionary[uriKey] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter<Uri, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var removeStatus = dictionaryAdapter.TryRemove(dictionary, uriKey.ToString(), resolver, out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(removeStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Empty(dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_DoesNotThrowException_IfTestIsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var key = "Name";
|
||||
var dictionary = new Dictionary<string, List<object>>();
|
||||
var value = new List<object>()
|
||||
{
|
||||
"James",
|
||||
2,
|
||||
new Customer("James", 25)
|
||||
};
|
||||
dictionary[key] = value;
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, List<object>>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var testStatus = dictionaryAdapter.TryTest(dictionary, key, resolver, value, out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var key = "Name";
|
||||
var dictionary = new Dictionary<string, object>();
|
||||
dictionary[key] = "James";
|
||||
var dictionaryAdapter = new DictionaryAdapter<string, object>();
|
||||
var resolver = new DefaultContractResolver();
|
||||
var expectedErrorMessage = "The current value 'James' at path 'Name' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = dictionaryAdapter.TryTest(dictionary, key, resolver, "John", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class DynamicObjectAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryAdd_AddsNewProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryAdd(target, segment, resolver, "new", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.True(status);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal("new", target.NewProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ReplacesExistingPropertyValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.List = new List<int>() { 1, 2, 3 };
|
||||
var value = new List<string>() { "stringValue1", "stringValue2" };
|
||||
var segment = "List";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryAdd(target, segment, resolver, value, out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.True(status);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal(value, target.List);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_GetsPropertyValue_ForExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act 1
|
||||
var addStatus = adapter.TryAdd(target, segment, resolver, "new", out string errorMessage);
|
||||
|
||||
// Assert 1
|
||||
Assert.True(addStatus);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal("new", target.NewProperty);
|
||||
|
||||
// Act 2
|
||||
var getStatus = adapter.TryGet(target, segment, resolver, out object getValue, out string getErrorMessage);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(getStatus);
|
||||
Assert.Null(getErrorMessage);
|
||||
Assert.Equal(getValue, target.NewProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ThrowsPathNotFoundException_ForNonExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var getStatus = adapter.TryGet(target, segment, resolver, out object getValue, out string getErrorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(getStatus);
|
||||
Assert.Null(getValue);
|
||||
Assert.Equal($"The target location specified by path segment '{segment}' was not found.", getErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTraverse_FindsNextTarget()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NestedObject = new DynamicTestObject();
|
||||
target.NestedObject.NewProperty = "A";
|
||||
var segment = "NestedObject";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryTraverse(target, segment, resolver, out object nextTarget, out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.True(status);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal(target.NestedObject, nextTarget);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTraverse_ThrowsPathNotFoundException_ForNonExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NestedObject = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryTraverse(target.NestedObject, segment, resolver, out object nextTarget, out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(status);
|
||||
Assert.Equal($"The target location specified by path segment '{segment}' was not found.", errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ReplacesPropertyValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NewProperty = new object();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryReplace(target, segment, resolver, "new", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.True(status);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal("new", target.NewProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsPathNotFoundException_ForNonExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryReplace(target, segment, resolver, "test", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(status);
|
||||
Assert.Equal($"The target location specified by path segment '{segment}' was not found.", errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsPropertyInvalidException_IfNewValueIsNotTheSameTypeAsInitialValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NewProperty = 1;
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var status = adapter.TryReplace(target, segment, resolver, "test", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(status);
|
||||
Assert.Equal($"The value 'test' is invalid for target location.", errorMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData("new", null)]
|
||||
public void TryRemove_SetsPropertyToDefaultOrNull(object value, object expectedValue)
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act 1
|
||||
var addStatus = adapter.TryAdd(target, segment, resolver, value, out string errorMessage);
|
||||
|
||||
// Assert 1
|
||||
Assert.True(addStatus);
|
||||
Assert.Null(errorMessage);
|
||||
Assert.Equal(value, target.NewProperty);
|
||||
|
||||
// Act 2
|
||||
var removeStatus = adapter.TryRemove(target, segment, resolver, out string removeErrorMessage);
|
||||
|
||||
// Assert 2
|
||||
Assert.True(removeStatus);
|
||||
Assert.Null(removeErrorMessage);
|
||||
Assert.Equal(expectedValue, target.NewProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ThrowsPathNotFoundException_ForNonExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var removeStatus = adapter.TryRemove(target, segment, resolver, out string removeErrorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal($"The target location specified by path segment '{segment}' was not found.", removeErrorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_DoesNotThrowException_IfTestSuccessful()
|
||||
{
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
var value = new List<object>()
|
||||
{
|
||||
"Joana",
|
||||
2,
|
||||
new Customer("Joana", 25)
|
||||
};
|
||||
target.NewProperty = value;
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(target, segment, resolver, value, out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(value, target.NewProperty);
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new DynamicObjectAdapter();
|
||||
dynamic target = new DynamicTestObject();
|
||||
target.NewProperty = "Joana";
|
||||
var segment = "NewProperty";
|
||||
var resolver = new DefaultContractResolver();
|
||||
var expectedErrorMessage = $"The current value 'Joana' at path '{segment}' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(target, segment, resolver, "John", out string errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,497 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ListAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void Patch_OnArrayObject_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new[] { 20, 30 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "0", resolver.Object, "40", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal($"The type '{targetObject.GetType().FullName}' which is an array is not supported for json patch operations as it has a fixed size.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Patch_OnNonGenericListObject_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new ArrayList();
|
||||
targetObject.Add(20);
|
||||
targetObject.Add(30);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "40", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal($"The type '{targetObject.GetType().FullName}' which is a non generic list is not supported for json patch operations. Only generic list types are supported.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_WithIndexSameAsNumberOfElements_Works()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
var listAdapter = new ListAdapter();
|
||||
var position = targetObject.Count.ToString();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, "Rob", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.Null(message);
|
||||
Assert.True(addStatus);
|
||||
Assert.Equal(3, targetObject.Count);
|
||||
Assert.Equal(new List<string>() { "James", "Mike", "Rob" }, targetObject);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-1")]
|
||||
[InlineData("-2")]
|
||||
[InlineData("3")]
|
||||
public void Add_WithOutOfBoundsIndex_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, "40", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal($"The index value provided by path segment '{position}' is out of bounds of the array size.", message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("_")]
|
||||
[InlineData("blah")]
|
||||
public void Patch_WithInvalidPositionFormat_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, "40", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal($"The path segment '{position}' is invalid for an array index.", message);
|
||||
}
|
||||
|
||||
public static TheoryData<List<int>, List<int>> AppendAtEndOfListData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<List<int>, List<int>>()
|
||||
{
|
||||
{
|
||||
new List<int>() { },
|
||||
new List<int>() { 20 }
|
||||
},
|
||||
{
|
||||
new List<int>() { 5, 10 },
|
||||
new List<int>() { 5, 10, 20 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AppendAtEndOfListData))]
|
||||
public void Add_Appends_AtTheEnd(List<int> targetObject, List<int> expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "20", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_NullObject_ToReferenceTypeListWorks()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
var targetObject = new List<string>() { "James", "Mike" };
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, value: null, errorMessage: out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(3, targetObject.Count);
|
||||
Assert.Equal(new List<string>() { "James", "Mike", null }, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_CompatibleTypeWorks()
|
||||
{
|
||||
// Arrange
|
||||
var sDto = new SimpleObject();
|
||||
var iDto = new InheritedObject();
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<SimpleObject>() { sDto };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, iDto, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(2, targetObject.Count);
|
||||
Assert.Equal(new List<SimpleObject>() { sDto, iDto }, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_NonCompatibleType_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, "-", resolver.Object, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal("The value 'James' is invalid for target location.", message);
|
||||
}
|
||||
|
||||
public static TheoryData<IList, object, string, IList> AddingDifferentComplexTypeWorksData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<IList, object, string, IList>()
|
||||
{
|
||||
{
|
||||
new List<string>() { },
|
||||
"a",
|
||||
"-",
|
||||
new List<string>() { "a" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"-",
|
||||
new List<string>() { "a", "b", "c" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"0",
|
||||
new List<string>() { "c", "a", "b" }
|
||||
},
|
||||
{
|
||||
new List<string>() { "a", "b" },
|
||||
"c",
|
||||
"1",
|
||||
new List<string>() { "a", "c", "b" }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AddingDifferentComplexTypeWorksData))]
|
||||
public void Add_DifferentComplexTypeWorks(IList targetObject, object value, string position, IList expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, value, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
public static TheoryData<IList, object, string, IList> AddingKeepsObjectReferenceData
|
||||
{
|
||||
get
|
||||
{
|
||||
var sDto1 = new SimpleObject();
|
||||
var sDto2 = new SimpleObject();
|
||||
var sDto3 = new SimpleObject();
|
||||
return new TheoryData<IList, object, string, IList>()
|
||||
{
|
||||
{
|
||||
new List<SimpleObject>() { },
|
||||
sDto1,
|
||||
"-",
|
||||
new List<SimpleObject>() { sDto1 }
|
||||
},
|
||||
{
|
||||
new List<SimpleObject>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"-",
|
||||
new List<SimpleObject>() { sDto1, sDto2, sDto3 }
|
||||
},
|
||||
{
|
||||
new List<SimpleObject>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"0",
|
||||
new List<SimpleObject>() { sDto3, sDto1, sDto2 }
|
||||
},
|
||||
{
|
||||
new List<SimpleObject>() { sDto1, sDto2 },
|
||||
sDto3,
|
||||
"1",
|
||||
new List<SimpleObject>() { sDto1, sDto3, sDto2 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AddingKeepsObjectReferenceData))]
|
||||
public void Add_KeepsObjectReference(IList targetObject, object value, string position, IList expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var addStatus = listAdapter.TryAdd(targetObject, position, resolver.Object, value, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected.Count, targetObject.Count);
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new int[] { }, "0")]
|
||||
[InlineData(new[] { 10, 20 }, "-1")]
|
||||
[InlineData(new[] { 10, 20 }, "2")]
|
||||
public void Get_IndexOutOfBounds(int[] input, string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>(input);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var getStatus = listAdapter.TryGet(targetObject, position, resolver.Object, out var value, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(getStatus);
|
||||
Assert.Equal($"The index value provided by path segment '{position}' is out of bounds of the array size.", message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] { 10, 20 }, "0", 10)]
|
||||
[InlineData(new[] { 10, 20 }, "1", 20)]
|
||||
[InlineData(new[] { 10 }, "0", 10)]
|
||||
public void Get(int[] input, string position, object expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>(input);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var getStatus = listAdapter.TryGet(targetObject, position, resolver.Object, out var value, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(getStatus);
|
||||
Assert.Equal(expected, value);
|
||||
Assert.Equal(new List<int>(input), targetObject);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new int[] { }, "0")]
|
||||
[InlineData(new[] { 10, 20 }, "-1")]
|
||||
[InlineData(new[] { 10, 20 }, "2")]
|
||||
public void Remove_IndexOutOfBounds(int[] input, string position)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>(input);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var removeStatus = listAdapter.TryRemove(targetObject, position, resolver.Object, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal($"The index value provided by path segment '{position}' is out of bounds of the array size.", message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] { 10, 20 }, "0", new[] { 20 })]
|
||||
[InlineData(new[] { 10, 20 }, "1", new[] { 10 })]
|
||||
[InlineData(new[] { 10 }, "0", new int[] { })]
|
||||
public void Remove(int[] input, string position, int[] expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>(input);
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var removeStatus = listAdapter.TryRemove(targetObject, position, resolver.Object, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(removeStatus);
|
||||
Assert.Equal(new List<int>(expected), targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_NonCompatibleType_Fails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, "-", resolver.Object, "James", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal("The value 'James' is invalid for target location.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replace_ReplacesValue_AtTheEnd()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, "-", resolver.Object, "30", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(new List<int>() { 10, 30 }, targetObject);
|
||||
}
|
||||
|
||||
public static TheoryData<string, List<int>> ReplacesValuesAtPositionData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<string, List<int>>()
|
||||
{
|
||||
{
|
||||
"0",
|
||||
new List<int>() { 30, 20 }
|
||||
},
|
||||
{
|
||||
"1",
|
||||
new List<int>() { 10, 30 }
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReplacesValuesAtPositionData))]
|
||||
public void Replace_ReplacesValue_AtGivenPosition(string position, List<int> expected)
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var replaceStatus = listAdapter.TryReplace(targetObject, position, resolver.Object, "30", out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Equal(expected, targetObject);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_DoesNotThrowException_IfTestIsSuccessful()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "0", resolver.Object, "10", out var message);
|
||||
|
||||
//Assert
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
var expectedErrorMessage = "The current value '20' at position '1' is not equal to the test value '10'.";
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "1", resolver.Object, "10", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_ThrowsJsonPatchException_IfListPositionOutOfBounds()
|
||||
{
|
||||
// Arrange
|
||||
var resolver = new Mock<IContractResolver>(MockBehavior.Strict);
|
||||
var targetObject = new List<int>() { 10, 20 };
|
||||
var listAdapter = new ListAdapter();
|
||||
var expectedErrorMessage = "The index value provided by path segment '2' is out of bounds of the array size.";
|
||||
|
||||
// Act
|
||||
var testStatus = listAdapter.TryTest(targetObject, "2", resolver.Object, "10", out var errorMessage);
|
||||
|
||||
//Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ObjectVisitorTest
|
||||
{
|
||||
private class Class1
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public IList<string> States { get; set; } = new List<string>();
|
||||
public IDictionary<string, string> Countries = new Dictionary<string, string>();
|
||||
public dynamic Items { get; set; } = new ExpandoObject();
|
||||
}
|
||||
|
||||
private class Class1Nested
|
||||
{
|
||||
public List<Class1> Customers { get; set; } = new List<Class1>();
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsListAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/States/-", model.States };
|
||||
yield return new object[] { model.States, "/-", model.States };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/States/-", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel, "/Customers/0/States/0", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel.Customers, "/0/States/-", nestedModel.Customers[0].States };
|
||||
yield return new object[] { nestedModel.Customers[0], "/States/-", nestedModel.Customers[0].States };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsListAdapterData))]
|
||||
public void Visit_ValidPathToArray_ReturnsListAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<ListAdapter>(adapter);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsDictionaryAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/Countries/USA", model.Countries };
|
||||
yield return new object[] { model.Countries, "/USA", model.Countries };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Countries/USA", nestedModel.Customers[0].Countries };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsDictionaryAdapterData))]
|
||||
public void Visit_ValidPathToDictionary_ReturnsDictionaryAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.Equal(typeof(DictionaryAdapter<string, string>), adapter.GetType());
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsExpandoAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Items/Name", nestedModel.Customers[0].Items };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Items/Name", nestedModel.Customers[0].Items };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Items/Name", nestedModel.Customers[0].Items };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsExpandoAdapterData))]
|
||||
public void Visit_ValidPathToExpandoObject_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), contractResolver);
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.Same(typeof(DictionaryAdapter<string, object>), adapter.GetType());
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ReturnsPocoAdapterData
|
||||
{
|
||||
get
|
||||
{
|
||||
var model = new Class1();
|
||||
yield return new object[] { model, "/Name", model };
|
||||
|
||||
var nestedModel = new Class1Nested();
|
||||
nestedModel.Customers.Add(new Class1());
|
||||
yield return new object[] { nestedModel, "/Customers/0/Name", nestedModel.Customers[0] };
|
||||
yield return new object[] { nestedModel.Customers, "/0/Name", nestedModel.Customers[0] };
|
||||
yield return new object[] { nestedModel.Customers[0], "/Name", nestedModel.Customers[0] };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ReturnsPocoAdapterData))]
|
||||
public void Visit_ValidPath_ReturnsExpandoAdapter(object targetObject, string path, object expectedTargetObject)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath(path), new DefaultContractResolver());
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.Same(expectedTargetObject, targetObject);
|
||||
Assert.IsType<PocoAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0")]
|
||||
[InlineData("-1")]
|
||||
public void Visit_InvalidIndexToArray_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new Class1Nested();
|
||||
object targetObject = automobileDepartment;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal($"The index value provided by path segment '{position}' is out of bounds of the array size.", message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-")]
|
||||
[InlineData("foo")]
|
||||
public void Visit_InvalidIndexFormatToArray_Fails(string position)
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/Customers/{position}/States/-"), new DefaultContractResolver());
|
||||
var automobileDepartment = new Class1Nested();
|
||||
object targetObject = automobileDepartment;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Equal($"The path segment '{position}' is invalid for an array index.", message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_DoesNotValidate_FinalPathSegment()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath($"/NonExisting"), new DefaultContractResolver());
|
||||
var model = new Class1();
|
||||
object targetObject = model;
|
||||
|
||||
// Act
|
||||
var visitStatus = visitor.TryVisit(ref targetObject, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.True(visitStatus);
|
||||
Assert.True(string.IsNullOrEmpty(message), "Expected no error message");
|
||||
Assert.IsType<PocoAdapter>(adapter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Visit_NullTarget_ReturnsNullAdapter()
|
||||
{
|
||||
// Arrange
|
||||
var visitor = new ObjectVisitor(new ParsedPath("test"), new DefaultContractResolver());
|
||||
|
||||
// Act
|
||||
object target = null;
|
||||
var visitStatus = visitor.TryVisit(ref target, out var adapter, out var message);
|
||||
|
||||
// Assert
|
||||
Assert.False(visitStatus);
|
||||
Assert.Null(adapter);
|
||||
Assert.Null(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class ParsedPathTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("foo/bar~0baz", new string[] { "foo", "bar~baz" })]
|
||||
[InlineData("foo/bar~00baz", new string[] { "foo", "bar~0baz" })]
|
||||
[InlineData("foo/bar~01baz", new string[] { "foo", "bar~1baz" })]
|
||||
[InlineData("foo/bar~10baz", new string[] { "foo", "bar/0baz" })]
|
||||
[InlineData("foo/bar~1baz", new string[] { "foo", "bar/baz" })]
|
||||
[InlineData("foo/bar~0/~0/~1~1/~0~0/baz", new string[] { "foo", "bar~", "~", "//", "~~", "baz" })]
|
||||
[InlineData("~0~1foo", new string[] { "~/foo" })]
|
||||
public void ParsingValidPathShouldSucceed(string path, string[] expected)
|
||||
{
|
||||
// Arrange & Act
|
||||
var parsedPath = new ParsedPath(path);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expected, parsedPath.Segments);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("foo/bar~")]
|
||||
[InlineData("~")]
|
||||
[InlineData("~2")]
|
||||
[InlineData("foo~3bar")]
|
||||
public void PathWithInvalidEscapeSequenceShouldFail(string path)
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
var parsedPath = new ParsedPath(path);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
public class PocoAdapterTest
|
||||
{
|
||||
[Fact]
|
||||
public void TryAdd_ReplacesExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var addStatus = adapter.TryAdd(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", model.Name);
|
||||
Assert.True(addStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryAdd_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var addStatus = adapter.TryAdd(model, "LastName", contractResolver, "Smith", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(addStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ExistingProperty()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var getStatus = adapter.TryGet(model, "Name", contractResolver, out var value, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", value);
|
||||
Assert.True(getStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGet_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var getStatus = adapter.TryGet(model, "LastName", contractResolver, out var value, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Null(value);
|
||||
Assert.False(getStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_SetsPropertyToNull()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var removeStatus = adapter.TryRemove(model, "Name", contractResolver, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Null(model.Name);
|
||||
Assert.True(removeStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryRemove_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var removeStatus = adapter.TryRemove(model, "LastName", contractResolver, out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(removeStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_OverwritesExistingValue()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", model.Name);
|
||||
Assert.True(replaceStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsJsonPatchException_IfNewValueIsInvalidType()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Age = 25
|
||||
};
|
||||
|
||||
var expectedErrorMessage = "The value 'TwentySix' is invalid for target location.";
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "Age", contractResolver, "TwentySix", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(25, model.Age);
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryReplace_ThrowsJsonPatchException_IfPropertyDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The target location specified by path segment 'LastName' was not found.";
|
||||
|
||||
// Act
|
||||
var replaceStatus = adapter.TryReplace(model, "LastName", contractResolver, "Smith", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", model.Name);
|
||||
Assert.False(replaceStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_DoesNotThrowException_IfTestSuccessful()
|
||||
{
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(model, "Name", contractResolver, "Joana", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Joana", model.Name);
|
||||
Assert.True(testStatus);
|
||||
Assert.True(string.IsNullOrEmpty(errorMessage), "Expected no error message");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryTest_ThrowsJsonPatchException_IfTestFails()
|
||||
{
|
||||
// Arrange
|
||||
var adapter = new PocoAdapter();
|
||||
var contractResolver = new DefaultContractResolver();
|
||||
var model = new Customer
|
||||
{
|
||||
Name = "Joana"
|
||||
};
|
||||
var expectedErrorMessage = "The current value 'Joana' at path 'Name' is not equal to the test value 'John'.";
|
||||
|
||||
// Act
|
||||
var testStatus = adapter.TryTest(model, "Name", contractResolver, "John", out var errorMessage);
|
||||
|
||||
// Assert
|
||||
Assert.False(testStatus);
|
||||
Assert.Equal(expectedErrorMessage, errorMessage);
|
||||
}
|
||||
|
||||
private class Customer
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Age { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class JsonPatchDocumentGetPathTest
|
||||
{
|
||||
[Fact]
|
||||
public void ExpressionType_MemberAccess()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<SimpleObjectWithNestedObject>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => p.SimpleObject.IntegerList, "-");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/SimpleObject/IntegerList/-", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_ArrayIndex()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<int[]>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => p[3], null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/3", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_Call()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<Dictionary<string, int>>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => p["key"], "3");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/key/3", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_Parameter_NullPosition()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<SimpleObject>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => p, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_Parameter_WithPosition()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<SimpleObject>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => p, "-");
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/-", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_Convert()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<NestedObjectWithDerivedClass>();
|
||||
|
||||
// Act
|
||||
var path = patchDocument.GetPath(p => (BaseClass)p.DerivedObject, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("/DerivedObject", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpressionType_NotSupported()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<SimpleObject>();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
{
|
||||
patchDocument.GetPath(p => p.IntegerValue >= 4, null);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The expression '(p.IntegerValue >= 4)' is not supported. Supported expressions include member access and indexer expressions.", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
internal class DerivedClass : BaseClass
|
||||
{
|
||||
public DerivedClass()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal class NestedObjectWithDerivedClass
|
||||
{
|
||||
public DerivedClass DerivedObject { get; set; }
|
||||
}
|
||||
|
||||
internal class BaseClass
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class JsonPatchDocumentJsonPropertyAttributeTest
|
||||
{
|
||||
[Fact]
|
||||
public void Add_RespectsJsonPropertyAttribute()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<JsonPropertyObject>();
|
||||
|
||||
// Act
|
||||
patchDocument.Add(p => p.Name, "John");
|
||||
|
||||
// Assert
|
||||
var pathToCheck = patchDocument.Operations.First().path;
|
||||
Assert.Equal("/AnotherName", pathToCheck);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_RespectsJsonPropertyAttribute_WithDotWhitespaceAndBackslashInName()
|
||||
{
|
||||
// Arrange
|
||||
var obj = new JsonPropertyObjectWithStrangeNames();
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
|
||||
// Act
|
||||
patchDocument.Add("/First Name.", "John");
|
||||
patchDocument.Add("Last\\Name", "Doe");
|
||||
patchDocument.ApplyTo(obj);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("John", obj.FirstName);
|
||||
Assert.Equal("Doe", obj.LastName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_FallsbackToPropertyName_WhenJsonPropertyAttributeName_IsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument<JsonPropertyWithNoPropertyName>();
|
||||
|
||||
// Act
|
||||
patchDocument.Move(m => m.StringProperty, m => m.StringProperty2);
|
||||
|
||||
// Assert
|
||||
var fromPath = patchDocument.Operations.First().from;
|
||||
Assert.Equal("/StringProperty", fromPath);
|
||||
var toPath = patchDocument.Operations.First().path;
|
||||
Assert.Equal("/StringProperty2", toPath);
|
||||
}
|
||||
|
||||
private class JsonPropertyObject
|
||||
{
|
||||
[JsonProperty("AnotherName")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
private class JsonPropertyObjectWithStrangeNames
|
||||
{
|
||||
[JsonProperty("First Name.")]
|
||||
public string FirstName { get; set; }
|
||||
|
||||
[JsonProperty("Last\\Name")]
|
||||
public string LastName { get; set; }
|
||||
}
|
||||
|
||||
private class JsonPropertyWithNoPropertyName
|
||||
{
|
||||
[JsonProperty]
|
||||
public string StringProperty { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
public string[] ArrayProperty { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
public string StringProperty2 { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
public string SSN { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class JsonPatchDocumentTest
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidPathAtBeginningShouldThrowException()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.Add("//NewInt", 1);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"The provided string '//NewInt' is an invalid path.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidPathAtEndShouldThrowException()
|
||||
{
|
||||
// Arrange
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonPatchException>(() =>
|
||||
{
|
||||
patchDocument.Add("NewInt//", 1);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
"The provided string 'NewInt//' is an invalid path.",
|
||||
exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonGenericPatchDocToGenericMustSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument();
|
||||
patchDocument.Copy("StringProperty", "AnotherStringProperty");
|
||||
|
||||
var serialized = JsonConvert.SerializeObject(patchDocument);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleObject>>(serialized);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericPatchDocToNonGenericMustSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var targetObject = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
AnotherStringProperty = "B"
|
||||
};
|
||||
|
||||
var patchDocTyped = new JsonPatchDocument<SimpleObject>();
|
||||
patchDocTyped.Copy(o => o.StringProperty, o => o.AnotherStringProperty);
|
||||
|
||||
var patchDocUntyped = new JsonPatchDocument();
|
||||
patchDocUntyped.Copy("StringProperty", "AnotherStringProperty");
|
||||
|
||||
var serializedTyped = JsonConvert.SerializeObject(patchDocTyped);
|
||||
var serializedUntyped = JsonConvert.SerializeObject(patchDocUntyped);
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument>(serializedTyped);
|
||||
|
||||
// Act
|
||||
deserialized.ApplyTo(targetObject);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("A", targetObject.AnotherStringProperty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialization_Successful_ForValidJsonPatchDocument()
|
||||
{
|
||||
// Arrange
|
||||
var doc = new SimpleObject()
|
||||
{
|
||||
StringProperty = "A",
|
||||
DecimalValue = 10,
|
||||
DoubleValue = 10,
|
||||
FloatValue = 10,
|
||||
IntegerValue = 10
|
||||
};
|
||||
|
||||
var patchDocument = new JsonPatchDocument<SimpleObject>();
|
||||
patchDocument.Replace(o => o.StringProperty, "B");
|
||||
patchDocument.Replace(o => o.DecimalValue, 12);
|
||||
patchDocument.Replace(o => o.DoubleValue, 12);
|
||||
patchDocument.Replace(o => o.FloatValue, 12);
|
||||
patchDocument.Replace(o => o.IntegerValue, 12);
|
||||
|
||||
// default: no envelope
|
||||
var serialized = JsonConvert.SerializeObject(patchDocument);
|
||||
|
||||
// Act
|
||||
var deserialized = JsonConvert.DeserializeObject<JsonPatchDocument<SimpleObject>>(serialized);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<JsonPatchDocument<SimpleObject>>(deserialized);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialization_Fails_ForInvalidJsonPatchDocument()
|
||||
{
|
||||
// Arrange
|
||||
var serialized = "{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}";
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonSerializationException>(() =>
|
||||
{
|
||||
var deserialized
|
||||
= JsonConvert.DeserializeObject<JsonPatchDocument>(serialized);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Deserialization_Fails_ForInvalidTypedJsonPatchDocument()
|
||||
{
|
||||
// Arrange
|
||||
var serialized = "{\"Operations\": [{ \"op\": \"replace\", \"path\": \"/title\", \"value\": \"New Title\"}]}";
|
||||
|
||||
// Act
|
||||
var exception = Assert.Throws<JsonSerializationException>(() =>
|
||||
{
|
||||
var deserialized
|
||||
= JsonConvert.DeserializeObject<JsonPatchDocument<SimpleObject>>(serialized);
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The JSON patch document was malformed and could not be parsed.", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.AspNetCore.JsonPatch" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Operations
|
||||
{
|
||||
public class OperationBaseTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("ADd", OperationType.Add)]
|
||||
[InlineData("Copy", OperationType.Copy)]
|
||||
[InlineData("mOVE", OperationType.Move)]
|
||||
[InlineData("REMOVE", OperationType.Remove)]
|
||||
[InlineData("replace", OperationType.Replace)]
|
||||
[InlineData("TeSt", OperationType.Test)]
|
||||
public void SetValidOperationType(string op, OperationType operationType)
|
||||
{
|
||||
// Arrange
|
||||
var operationBase = new OperationBase();
|
||||
operationBase.op = op;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(operationType, operationBase.OperationType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid", OperationType.Invalid)]
|
||||
[InlineData("coppy", OperationType.Invalid)]
|
||||
[InlineData("notvalid", OperationType.Invalid)]
|
||||
public void InvalidOperationType_SetsOperationTypeInvalid(string op, OperationType operationType)
|
||||
{
|
||||
// Arrange
|
||||
var operationBase = new OperationBase();
|
||||
operationBase.op = op;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(operationType, operationBase.OperationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class TestErrorLogger<T> where T : class
|
||||
{
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public void LogErrorMessage(JsonPatchError patchError)
|
||||
{
|
||||
ErrorMessage = patchError.ErrorMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
internal class Customer
|
||||
{
|
||||
private string _name;
|
||||
private int _age;
|
||||
|
||||
public Customer(string name, int age)
|
||||
{
|
||||
_name = name;
|
||||
_age = age;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class DynamicTestObject : DynamicObject
|
||||
{
|
||||
private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
|
||||
|
||||
public object this[string key] { get => ((IDictionary<string, object>)_dictionary)[key]; set => ((IDictionary<string, object>)_dictionary)[key] = value; }
|
||||
|
||||
public ICollection<string> Keys => ((IDictionary<string, object>)_dictionary).Keys;
|
||||
|
||||
public ICollection<object> Values => ((IDictionary<string, object>)_dictionary).Values;
|
||||
|
||||
public int Count => ((IDictionary<string, object>)_dictionary).Count;
|
||||
|
||||
public bool IsReadOnly => ((IDictionary<string, object>)_dictionary).IsReadOnly;
|
||||
|
||||
public void Add(string key, object value)
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).ContainsKey(key);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
((IDictionary<string, object>)_dictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Remove(key);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).Remove(item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
return ((IDictionary<string, object>)_dictionary).TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public override bool TryGetMember(GetMemberBinder binder, out object result)
|
||||
{
|
||||
var name = binder.Name;
|
||||
|
||||
return TryGetValue(name, out result);
|
||||
}
|
||||
|
||||
public override bool TrySetMember(SetMemberBinder binder, object value)
|
||||
{
|
||||
_dictionary[binder.Name] = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class InheritedObject : SimpleObject
|
||||
{
|
||||
public string AdditionalStringProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class NestedObject
|
||||
{
|
||||
public string StringProperty { get; set; }
|
||||
public dynamic DynamicProperty { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class SimpleObject
|
||||
{
|
||||
public List<SimpleObject> SimpleObjectList { get; set; }
|
||||
public List<int> IntegerList { get; set; }
|
||||
public IList<int> IntegerIList { get; set; }
|
||||
public int IntegerValue { get; set; }
|
||||
public int AnotherIntegerValue { get; set; }
|
||||
public string StringProperty { get; set; }
|
||||
public string AnotherStringProperty { get; set; }
|
||||
public decimal DecimalValue { get; set; }
|
||||
public double DoubleValue { get; set; }
|
||||
public float FloatValue { get; set; }
|
||||
public Guid GuidValue { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch
|
||||
{
|
||||
public class SimpleObjectWithNestedObject
|
||||
{
|
||||
public int IntegerValue { get; set; }
|
||||
|
||||
public NestedObject NestedObject { get; set; }
|
||||
|
||||
public SimpleObject SimpleObject { get; set; }
|
||||
|
||||
public InheritedObject InheritedObject { get; set; }
|
||||
|
||||
public List<SimpleObject> SimpleObjectList { get; set; }
|
||||
|
||||
public IList<SimpleObject> SimpleObjectIList { get; set; }
|
||||
|
||||
public SimpleObjectWithNestedObject()
|
||||
{
|
||||
NestedObject = new NestedObject();
|
||||
SimpleObject = new SimpleObject();
|
||||
InheritedObject = new InheritedObject();
|
||||
SimpleObjectList = new List<SimpleObject>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26124.0
|
||||
MinimumVisualStudioVersion = 15.0.26124.0
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSockets", "WebSockets", "{E0D9867D-C23D-43EB-8D9C-DE0398A25432}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{A86EE055-ACD3-4BAC-A51D-1B3C71067AE0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EchoApp", "WebSockets\samples\EchoApp\EchoApp.csproj", "{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServer", "WebSockets\samples\TestServer\TestServer.csproj", "{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets", "WebSockets\src\Microsoft.AspNetCore.WebSockets.csproj", "{BECAA6A1-1AA4-415E-ADF3-07C103333826}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutobahnTestApp", "WebSockets\test\AutobahnTestApp\AutobahnTestApp.csproj", "{76B25812-AAFB-45BA-A71A-24F0C654ADFB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.ConformanceTests", "WebSockets\test\ConformanceTests\Microsoft.AspNetCore.WebSockets.ConformanceTests.csproj", "{88BDEE69-4DE3-40B5-A478-677EA355FB52}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSockets.Tests", "WebSockets\test\UnitTests\Microsoft.AspNetCore.WebSockets.Tests.csproj", "{93970702-1BDB-4A8C-B7F6-020294464BB6}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x64.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x64.Build.0 = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x86.ActiveCfg = Debug|Any CPU
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B}.Release|x86.Build.0 = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|x64.Build.0 = Release|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826}.Release|x86.Build.0 = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|x64.Build.0 = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB}.Release|x86.Build.0 = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|x64.Build.0 = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52}.Release|x86.Build.0 = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|x64.Build.0 = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{A86EE055-ACD3-4BAC-A51D-1B3C71067AE0} = {E0D9867D-C23D-43EB-8D9C-DE0398A25432}
|
||||
{0792C20B-1D18-4D7C-9C0F-A6F45A0F378E} = {A86EE055-ACD3-4BAC-A51D-1B3C71067AE0}
|
||||
{4E5F5FCC-172C-44D9-BEA0-39098A79CD0B} = {A86EE055-ACD3-4BAC-A51D-1B3C71067AE0}
|
||||
{BECAA6A1-1AA4-415E-ADF3-07C103333826} = {E0D9867D-C23D-43EB-8D9C-DE0398A25432}
|
||||
{76B25812-AAFB-45BA-A71A-24F0C654ADFB} = {E0D9867D-C23D-43EB-8D9C-DE0398A25432}
|
||||
{88BDEE69-4DE3-40B5-A478-677EA355FB52} = {E0D9867D-C23D-43EB-8D9C-DE0398A25432}
|
||||
{93970702-1BDB-4A8C-B7F6-020294464BB6} = {E0D9867D-C23D-43EB-8D9C-DE0398A25432}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
WebSockets
|
||||
==========
|
||||
|
||||
Contains a managed implementation of the WebSocket protocol, along with server integration components.
|
||||
|
||||
## System Requirements
|
||||
|
||||
This repo has a few special system requirements/prerequisites.
|
||||
|
||||
1. Windows IIS Express tests require IIS Express 10 and Windows 8 for WebSockets support
|
||||
2. HttpListener/ASP.NET 4.6 samples require at least Windows 8
|
||||
3. Autobahn Test Suite requires special installation see the README.md in [test/AutobahnTestApp](./test/AutobahnTestApp/README.md)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue