diff --git a/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs b/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs index 8a5d4ed8..b0a9aa1f 100644 --- a/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs +++ b/UET/Lib/Redpoint.ThirdParty.Docker.Registry.DotNet/Helpers/QueryStringExtensions.cs @@ -17,7 +17,7 @@ internal static class QueryStringExtensions /// /// internal static void AddFromObjectWithQueryParameters(this QueryString queryString, [NotNull] T instance) - where T: class + where T : class { if (instance == null) throw new ArgumentNullException(nameof(instance)); diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs new file mode 100644 index 00000000..517df098 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CloudFrameworkModelAnalyzerCodeFixProvider.cs @@ -0,0 +1,75 @@ +namespace Redpoint.CloudFramework.Analyzer +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeActions; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Rename; + using Microsoft.CodeAnalysis.Text; + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Composition; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + +#if FALSE + + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CloudFrameworkModelAnalyzerCodeFixProvider)), Shared] + public class CloudFrameworkModelAnalyzerCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds + { + get { return ImmutableArray.Create(CloudFrameworkModelAnalyzer.DiagnosticId); } + } + + public sealed override FixAllProvider GetFixAllProvider() + { + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + // TODO: Replace the following code with your own analysis, generating a CodeAction for each fix to suggest + var diagnostic = context.Diagnostics.First(); + var diagnosticSpan = diagnostic.Location.SourceSpan; + + // Find the type declaration identified by the diagnostic. + var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType().First(); + + // Register a code action that will invoke the fix. + context.RegisterCodeFix( + CodeAction.Create( + title: CodeFixResources.CodeFixTitle, + createChangedSolution: c => MakeUppercaseAsync(context.Document, declaration, c), + equivalenceKey: nameof(CodeFixResources.CodeFixTitle)), + diagnostic); + } + + private static async Task MakeUppercaseAsync(Document document, TypeDeclarationSyntax typeDecl, CancellationToken cancellationToken) + { + // Compute new uppercase name. + var identifierToken = typeDecl.Identifier; + var newName = identifierToken.Text.ToUpperInvariant(); + + // Get the symbol representing the type to be renamed. + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, cancellationToken); + + // Produce a new solution that has all references to that type renamed, including the declaration. + var originalSolution = document.Project.Solution; + var optionSet = originalSolution.Workspace.Options; + var newSolution = await Renamer.RenameSymbolAsync(document.Project.Solution, typeSymbol, newName, optionSet, cancellationToken).ConfigureAwait(false); + + // Return the new solution with the now-uppercase type name. + return newSolution; + } + } + +#endif +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs new file mode 100644 index 00000000..a4618b05 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Redpoint.CloudFramework.Analyzer { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class CodeFixResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal CodeFixResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Redpoint.CloudFramework.Analyzer.CodeFixResources", typeof(CodeFixResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Make uppercase. + /// + internal static string CodeFixTitle { + get { + return ResourceManager.GetString("CodeFixTitle", resourceCulture); + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx new file mode 100644 index 00000000..97abe689 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/CodeFixResources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Make uppercase + The title of the code fix. + + \ No newline at end of file diff --git a/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj new file mode 100644 index 00000000..367b5538 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.CodeFixes/Redpoint.CloudFramework.Analyzer.CodeFixes.csproj @@ -0,0 +1,22 @@ + + + + netstandard2.0 + false + Redpoint.CloudFramework.Analyzer + + + + + + + + + + + + + + + + diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj b/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj new file mode 100644 index 00000000..cdb91650 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Package/Redpoint.CloudFramework.Analyzer.Package.csproj @@ -0,0 +1,35 @@ + + + + netstandard2.0 + false + true + true + + + + + Redpoint.CloudFramework.Analyzer + false + true + true + $(TargetsForTfmSpecificContentInPackage);_AddAnalyzersToOutput + + + + + + + + + + + + + + + + + + + diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1 b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1 new file mode 100644 index 00000000..53136a23 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/install.ps1 @@ -0,0 +1,275 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not install analyzers via install.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + if (Test-Path $analyzersPath) + { + # Install the language agnostic analyzers. + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Install language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Add($analyzerFilePath.FullName) + } + } + } +} +# SIG # Begin signature block +# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCA/i+qRUHsWzI0s +# FVk99zLgt/HOEQ33uvkFsWtHTHZgf6CCDXYwggX0MIID3KADAgECAhMzAAADTrU8 +# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU +# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 +# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm +# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa +# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq +# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk +# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 +# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 +# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d +# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM +# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh +# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX +# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir +# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 +# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A +# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H +# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIEY4Ow3COroWH11sAEOoStJj +# 1u4sq9rcx0dAx0gyZLHCMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEAFlSLlk17KQp2AwLbr5e4T5zyE44MGZOdNejIip+Mg8co8qho16qdNSvg +# AhvoJYxPJJr70DSCU9BIUoWY7hXoi9S4P08YlAid7BUT5OciIgHlrb8I900LaE+S +# 83rSvfVU1CZCjiSwcgng5DD2VPRo0Lu4G9p2Ky14dyOPwtPvrpsib5s9kewZLdiy +# /KxEDLKX8P+cHat1xH7RaZLDNxweRS6GSomjE2vjOlQHNSW879XR8bSoAt/m4uR1 +# WyrAxTGZb4miEYX+I5HsrWvbZLw9NSCJ/crbbap3LIobfQtK5binjY7v4MQp/5Oq +# y4S/4FAfwhWDXfaQfq6YTeOjHRVQbKGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC +# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq +# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCCV3irbWIoYz2Llfx9YQhUNtcP6GOrL7+YTUXQ4y5qzWAIGZNTJrsAW +# GBMyMDIzMDgzMTAwMTI1OC45ODNaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l +# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0w +# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg +# ghHqMIIHIDCCBQigAwIBAgITMwAAAc9SNr5xS81IygABAAABzzANBgkqhkiG9w0B +# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD +# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy +# MTFaFw0yNDAyMDExOTEyMTFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z +# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046OTIwMC0wNUUwLUQ5NDcxJTAjBgNV +# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQC4Pct+15TYyrUje553lzBQodgmd5Bz7WuH8SdHpAoW +# z+01TrHExBSuaMKnxvVMsyYtas5h6aopUGAS5WKVLZAvUtH62TKmAE0JK+i1hafi +# CSXLZPcRexxeRkOqeZefLBzXp0nudMOXUUab333Ss8LkoK4l3LYxm1Ebsr3b2OTo +# 2ebsAoNJ4kSxmVuPM7C+RDhGtVKR/EmHsQ9GcwGmluu54bqiVFd0oAFBbw4txTU1 +# mruIGWP/i+sgiNqvdV/wah/QcrKiGlpWiOr9a5aGrJaPSQD2xgEDdPbrSflYxsRM +# dZCJI8vzvOv6BluPcPPGGVLEaU7OszdYjK5f4Z5Su/lPK1eST5PC4RFsVcOiS4L0 +# sI4IFZywIdDJHoKgdqWRp6Q5vEDk8kvZz6HWFnYLOlHuqMEYvQLr6OgooYU9z0A5 +# cMLHEIHYV1xiaBzx2ERiRY9MUPWohh+TpZWEUZlUm/q9anXVRN0ujejm6OsUVFDs +# sIMszRNCqEotJGwtHHm5xrCKuJkFr8GfwNelFl+XDoHXrQYL9zY7Np+frsTXQpKR +# NnmI1ashcn5EC+wxUt/EZIskWzewEft0/+/0g3+8YtMkUdaQE5+8e7C8UMiXOHkM +# K25jNNQqLCedlJwFIf9ir9SpMc72NR+1j6Uebiz/ZPV74do3jdVvq7DiPFlTb92U +# KwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFDaeKPtp0eTSVdG+gZc5BDkabTg4MB8G +# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG +# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy +# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w +# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy +# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG +# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD +# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBQgm4pnA0xkd/9uKXJMzdMYyxUfUm/ZusU +# Ba32MEZXQuMGp20pSuX2VW9/tpTMo5bkaJdBVoUyd2DbDsNb1kjr/36ntT0jvL3A +# oWStAFhZBypmpPbx+BPK49ZlejlM4d5epX668tRRGfFip9Til9yKRfXBrXnM/q64 +# IinN7zXEQ3FFQhdJMzt8ibXClO7eFA+1HiwZPWysYWPb/ZOFobPEMvXie+GmEbTK +# bhE5tze6RrA9aejjP+v1ouFoD5bMj5Qg+wfZXqe+hfYKpMd8QOnQyez+Nlj1ityn +# OZWfwHVR7dVwV0yLSlPT+yHIO8g+3fWiAwpoO17bDcntSZ7YOBljXrIgad4W4gX+ +# 4tp1eBsc6XWIITPBNzxQDZZRxD4rXzOB6XRlEVJdYZQ8gbXOirg/dNvS2GxcR50Q +# dOXDAumdEHaGNHb6y2InJadCPp2iT5QLC4MnzR+YZno1b8mWpCdOdRs9g21QbbrI +# 06iLk9KD61nx7K5ReSucuS5Z9nbkIBaLUxDesFhr1wmd1ynf0HQ51Swryh7YI7TX +# T0jr81mbvvI9xtoqjFvIhNBsICdCfTR91ylJTH8WtUlpDhEgSqWt3gzNLPTSvXAx +# XTpIM583sZdd+/2YGADMeWmt8PuMce6GsIcLCOF2NiYZ10SXHZS5HRrLrChuzedD +# RisWpIu5uTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI +# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy +# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg +# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF +# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 +# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp +# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu +# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E +# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 +# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q +# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ +# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA +# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw +# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG +# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV +# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK +# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG +# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x +# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC +# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 +# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM +# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS +# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d +# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn +# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs +# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL +# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL +# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN +# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn +# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjkyMDAtMDVFMC1EOTQ3MSUwIwYDVQQD +# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDq +# 8xzVXwLguauAQj1rrJ4/TyEMm6CBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpNsjAiGA8yMDIzMDgzMDIzMjIy +# NloYDzIwMjMwODMxMjMyMjI2WjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDomk2y +# AgEAMAcCAQACAi7oMAcCAQACAhLnMAoCBQDom58yAgEAMDYGCisGAQQBhFkKBAIx +# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI +# hvcNAQELBQADggEBADE16CHIs3WeXYKQG9djja+StAB7gsNJxV9p+CETL3847UL3 +# +DIeoj6p5g0FkS8PJK2xc+UbcNx6XJO+WUXEbU9GL4mrOCQjYOM+i3r8FEU+l3Gh +# 6ZG/9ygsIYRXEfDKK4lsbrrUkFQs9nDISHT3f8JZEJJXSsGmwcHWlNC0LC8bv0Jp +# e2Bw2+SNc6SlGD8Vv45r4WFPHhfSioCz4HSsF1He3/2Wku7OH85FKvugBlsca7+F +# bpGsDSL4LO9bv60DxuD+8xBZuyTB8s64ifCGlOXCNpK5VaHND48PhoJbuD0COwlM +# Rn5NlT6T4hhtkPOqNscMlzYHmTOKc5NhWK8PyrIxggQNMIIECQIBATCBkzB8MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy +# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAc9SNr5xS81IygABAAABzzAN +# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G +# CSqGSIb3DQEJBDEiBCAjGiC3/PscCMBvPZjpZqbYcL2WRZ+Ecf74oiIPQKkjSzCB +# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EILPpsLqeNS4NuYXE2VJlMuvQeWVA +# 80ZDFhpOPjSzhPa/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh +# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD +# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw +# MTACEzMAAAHPUja+cUvNSMoAAQAAAc8wIgQgWdbLA3Co0zf3nE5086HU0tdn0vK7 +# yXyU9aAFpszvWFIwDQYJKoZIhvcNAQELBQAEggIANrHN06oaP0N1lUSxxoJteJgI +# fQjN82xV5VSE0cwNCBy05fg1VydPERF59+MIwIlJHhikSo3YOj5tt3AohC78U46P +# 2IdrLHKlA3rjiLUGwQvGUKJUSsEH4uueA1mmh9jwUBJhY3NjTcMQaQqp/oxZTaya +# l7NqbubBIZvsDD126SUr8jtVWtZZzw8pnWCFb4Rijii4fY1UiQzfQLFwqQuid6tE +# I0AaY3IoTlp7U9K2wfAPWcP1G7n3qv+990GEiQlGJlCfIJSSJQodzL2QZF5HCn/K +# SfgkRPn3y/Aax8683mWCT8zricYzO3MZ9j0T7tAcqOiWb7PFCsk5Va44lq4Gv0u+ +# +60FYCAA/Qn6eMuNkqIpBeIK0+NYUcMSwPdY/riKXdgkVLwChEJC5WWznD/Iqsil +# Jj9XYailhXzYx7Pa2MLays62LPwCnUxRQBTD9/rL3XQMj69iA4lisb51dKAtrqAU +# 53aRXFn+twYYFTAUQ/oNK14nZEQE5H53xAfhphMokJOu+CnQQKeCMeYlex6Q4zfw +# TQxP/xXZ+QW2cSZTwh1d5iE0XMhKCZxhxIOF/rRA+75L5GUz60reRZPeH/7USYZL +# VPc0+kxIdSbNFJAhAp0u59wSMQdBofor3+HfDfmxmoSjfCSH4TvOkNIulX1PPJPX +# UB7H4n7XHWJPkUU0cUw= +# SIG # End signature block diff --git a/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1 b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1 new file mode 100644 index 00000000..1f6b6d4f --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Package/tools/uninstall.ps1 @@ -0,0 +1,282 @@ +param($installPath, $toolsPath, $package, $project) + +if($project.Object.SupportsPackageDependencyResolution) +{ + if($project.Object.SupportsPackageDependencyResolution()) + { + # Do not uninstall analyzers via uninstall.ps1, instead let the project system handle it. + return + } +} + +$analyzersPaths = Join-Path (Join-Path (Split-Path -Path $toolsPath -Parent) "analyzers") * -Resolve + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall the language agnostic analyzers. + if (Test-Path $analyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$analyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + } + } +} + +# $project.Type gives the language name like (C# or VB.NET) +$languageFolder = "" +if($project.Type -eq "C#") +{ + $languageFolder = "cs" +} +if($project.Type -eq "VB.NET") +{ + $languageFolder = "vb" +} +if($languageFolder -eq "") +{ + return +} + +foreach($analyzersPath in $analyzersPaths) +{ + # Uninstall language specific analyzers. + $languageAnalyzersPath = join-path $analyzersPath $languageFolder + if (Test-Path $languageAnalyzersPath) + { + foreach ($analyzerFilePath in Get-ChildItem -Path "$languageAnalyzersPath\*.dll" -Exclude *.resources.dll) + { + if($project.Object.AnalyzerReferences) + { + try + { + $project.Object.AnalyzerReferences.Remove($analyzerFilePath.FullName) + } + catch + { + + } + } + } + } +} +# SIG # Begin signature block +# MIIoLQYJKoZIhvcNAQcCoIIoHjCCKBoCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDC68wb97fg0QGL +# yXrxJhYfmibzcOh8caqC0uZprfczDaCCDXYwggX0MIID3KADAgECAhMzAAADTrU8 +# esGEb+srAAAAAANOMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjMwMzE2MTg0MzI5WhcNMjQwMzE0MTg0MzI5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDdCKiNI6IBFWuvJUmf6WdOJqZmIwYs5G7AJD5UbcL6tsC+EBPDbr36pFGo1bsU +# p53nRyFYnncoMg8FK0d8jLlw0lgexDDr7gicf2zOBFWqfv/nSLwzJFNP5W03DF/1 +# 1oZ12rSFqGlm+O46cRjTDFBpMRCZZGddZlRBjivby0eI1VgTD1TvAdfBYQe82fhm +# WQkYR/lWmAK+vW/1+bO7jHaxXTNCxLIBW07F8PBjUcwFxxyfbe2mHB4h1L4U0Ofa +# +HX/aREQ7SqYZz59sXM2ySOfvYyIjnqSO80NGBaz5DvzIG88J0+BNhOu2jl6Dfcq +# jYQs1H/PMSQIK6E7lXDXSpXzAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUnMc7Zn/ukKBsBiWkwdNfsN5pdwAw +# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW +# MBQGA1UEBRMNMjMwMDEyKzUwMDUxNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci +# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG +# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu +# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 +# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAD21v9pHoLdBSNlFAjmk +# mx4XxOZAPsVxxXbDyQv1+kGDe9XpgBnT1lXnx7JDpFMKBwAyIwdInmvhK9pGBa31 +# TyeL3p7R2s0L8SABPPRJHAEk4NHpBXxHjm4TKjezAbSqqbgsy10Y7KApy+9UrKa2 +# kGmsuASsk95PVm5vem7OmTs42vm0BJUU+JPQLg8Y/sdj3TtSfLYYZAaJwTAIgi7d +# hzn5hatLo7Dhz+4T+MrFd+6LUa2U3zr97QwzDthx+RP9/RZnur4inzSQsG5DCVIM +# pA1l2NWEA3KAca0tI2l6hQNYsaKL1kefdfHCrPxEry8onJjyGGv9YKoLv6AOO7Oh +# JEmbQlz/xksYG2N/JSOJ+QqYpGTEuYFYVWain7He6jgb41JbpOGKDdE/b+V2q/gX +# UgFe2gdwTpCDsvh8SMRoq1/BNXcr7iTAU38Vgr83iVtPYmFhZOVM0ULp/kKTVoir +# IpP2KCxT4OekOctt8grYnhJ16QMjmMv5o53hjNFXOxigkQWYzUO+6w50g0FAeFa8 +# 5ugCCB6lXEk21FFB1FdIHpjSQf+LP/W2OV/HfhC3uTPgKbRtXo83TZYEudooyZ/A +# Vu08sibZ3MkGOJORLERNwKm2G7oqdOv4Qj8Z0JrGgMzj46NFKAxkLSpE5oHQYP1H +# tPx1lPfD7iNSbJsP6LiUHXH1MIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg +# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 +# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr +# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg +# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy +# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 +# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh +# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k +# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB +# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn +# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 +# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w +# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o +# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa +# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG +# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV +# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG +# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl +# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb +# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l +# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 +# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 +# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 +# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam +# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa +# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah +# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA +# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt +# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr +# /Xmfwb1tbWrJUnMTDXpQzTGCGg0wghoJAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw +# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN +# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp +# Z25pbmcgUENBIDIwMTECEzMAAANOtTx6wYRv6ysAAAAAA04wDQYJYIZIAWUDBAIB +# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO +# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIBdcqRcs5QL71hlQnl3M636V +# 5iTZvb6co3MHeMuIr36qMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A +# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB +# BQAEggEARbhO/zqm6SWBf6DSJ3P1r82VjSyMGXaLtMfTOq/bqHOXPOqC25R1v5uO +# zu4ri+UOS8dU6EfW6C9Xf/Z1Ue/oxrvxn5j8mPvsmcs5OyDO0hW0Wv6pYEy5Z5up +# mfcqvfUfl3+ir29lgPuz0f1mLpz0XxqhjqElEi5RfZD1k1YVg65f0qHroP2txql5 +# TfC77DXJ8verzVm1wqXBHTAERQD94TJobahYTCmyaudMjLUVFakv2lTMv0YTnrQR +# So006ZQg3i1jcVCJt/bRDGKh3xUo1IHgoh3NjMEkxT3iWt8rnX8Us6T6Zg8B2OxC +# 0EnuIu/eYYUlYTLQxO9eks7w1kVcUqGCF5cwgheTBgorBgEEAYI3AwMBMYIXgzCC +# F38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq +# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl +# AwQCAQUABCBQ1HvJFFsT0ICl6oEZlSNN8DlUeQobBHt/oTUGN6iKBgIGZNTKS0Bc +# GBMyMDIzMDgzMTAwMTI1OC4zODVaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l +# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0w +# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg +# ghHtMIIHIDCCBQigAwIBAgITMwAAAdWpAs/Fp8npWgABAAAB1TANBgkqhkiG9w0B +# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD +# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzA1MjUxOTEy +# MzBaFw0yNDAyMDExOTEyMzBaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z +# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046N0YwMC0wNUUwLUQ5NDcxJTAjBgNV +# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQDFfak57Oph9vuxtloABiLc6enT+yKH619b+OhGdkyh +# gNzkX80KUGI/jEqOVMV4Sqt/UPFFidx2t7v2SETj2tAzuVKtDfq2HBpu80vZ0vyQ +# DydVt4MDL4tJSKqgYofCxDIBrWzJJjgBolKdOJx1ut2TyOc+UOm7e92tVPHpjdg+ +# Omf31TLUf/oouyAOJ/Inn2ih3ASP0QYm+AFQjhYDNDu8uzMdwHF5QdwsscNa9PVS +# GedLdDLo9jL6DoPF4NYo06lvvEQuSJ9ImwZfBGLy/8hpE7RD4ewvJKmM1+t6eQuE +# sTXjrGM2WjkW18SgUZ8n+VpL2uk6AhDkCa355I531p0Jkqpoon7dHuLUdZSQO40q +# mVIQ6qQCanvImTqmNgE/rPJ0rgr0hMPI/uR1T/iaL0mEq4bqak+3sa8I+FAYOI/P +# C7V+zEek+sdyWtaX+ndbGlv/RJb5mQaGn8NunbkfvHD1Qt5D0rmtMOekYMq7QjYq +# E3FEP/wAY4TDuJxstjsa2HXi2yUDEg4MJL6/JvsQXToOZ+IxR6KT5t5fB5FpZYBp +# VLMma3pm5z6VXvkXrYs33NXJqVWLwiswa7NUFV87Es2sou9Idw3yAZmHIYWgOQ+D +# IY1nY3aG5DODiwN1rJyEb+mbWDagrdVxcncr6UKKO49eoNTXEW+scUf6GwXG0KEy +# mQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFK/QXKNO35bBMOz3R5giX7Ala2OaMB8G +# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG +# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy +# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w +# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy +# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG +# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD +# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBmRddqvQuyjRpx0HGxvOqffFrbgFAg0j82 +# v0v7R+/8a70S2V4t7yKYKSsQGI6pvt1A8JGmuZyjmIXmw23AkI5bZkxvSgws8rrB +# tJw9vakEckcWFQb7JG6b618x0s9Q3DL0dRq46QZRnm7U6234lecvjstAow30dP0T +# nIacPWKpPc3QgB+WDnglN2fdT1ruQ6WIVBenmpjpG9ypRANKUx5NRcpdJAQW2FqE +# HTS3Ntb+0tCqIkNHJ5aFsF6ehRovWZp0MYIz9bpJHix0VrjdLVMOpe7wv62t90E3 +# UrE2KmVwpQ5wsMD6YUscoCsSRQZrA5AbwTOCZJpeG2z3vDo/huvPK8TeTJ2Ltu/I +# tXgxIlIOQp/tbHAiN8Xptw/JmIZg9edQ/FiDaIIwG5YHsfm2u7TwOFyd6OqLw18Z +# 5j/IvDPzlkwWJxk6RHJF5dS4s3fnyLw3DHBe5Dav6KYB4n8x/cEmD/R44/8gS5Pf +# uG1srjLdyyGtyh0KiRDSmjw+fa7i1VPoemidDWNZ7ksNadMad4ZoDvgkqOV4A6a+ +# N8HIc/P6g0irrezLWUgbKXSN8iH9RP+WJFx5fBHE4AFxrbAUQ2Zn5jDmHAI3wYcQ +# DnnEYP51A75WFwPsvBrfrb1+6a1fuTEH1AYdOOMy8fX8xKo0E0Ys+7bxIvFPsUpS +# zfFjBolmhzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI +# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy +# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg +# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF +# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6 +# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp +# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu +# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E +# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0 +# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q +# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ +# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA +# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw +# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG +# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV +# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK +# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG +# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x +# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC +# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449 +# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM +# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS +# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d +# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn +# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs +# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL +# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL +# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNQ +# MIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn +# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjdGMDAtMDVFMC1EOTQ3MSUwIwYDVQQD +# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQBO +# Ei+S/ZVFe6w1Id31m6Kge26lNKCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6JpN3jAiGA8yMDIzMDgzMDIzMjMx +# MFoYDzIwMjMwODMxMjMyMzEwWjB3MD0GCisGAQQBhFkKBAExLzAtMAoCBQDomk3e +# AgEAMAoCAQACAiLLAgH/MAcCAQACAhLTMAoCBQDom59eAgEAMDYGCisGAQQBhFkK +# BAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJ +# KoZIhvcNAQELBQADggEBAC/zAZ9IEqD3nHBUWwxFTfnDSRErnCJ6XGxH5jOVtR0t +# 9pi7yCpApLpA2D12g1lcv4ugnwGpsbwmFjrcF4WlHemRa77qv409xNhNKrnh3H+U +# X2hvy9Utp9LiJiqS7lOW5VN1Uv+LbnA+FWt//4J+YLv44D/dliUGjYX623X7KiEX +# dbdXPR/Sn+W2YVQ19O8liKaFDnDnIAz+WLCfL6EaoGu4Te/Mr65Khy3YWTwQfXxr +# gR/JMDzLzWossnGszYCN8S8d9X6mfzWuYv4JHLEiThW++WbMLeT2hhKPomcbvqU4 +# wPb/ylDrTrWuAr/fVndECXVjCIzYJiFwOWn/ZfN9FpQxggQNMIIECQIBATCBkzB8 +# MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk +# bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1N +# aWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAdWpAs/Fp8npWgABAAAB +# 1TANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEE +# MC8GCSqGSIb3DQEJBDEiBCAHS+EjBkJ/YPugQQv1D7eXVQOI4DWPIYpUxpssheDN +# lTCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EINm/I4YM166JMM7EKIcYvlcb +# r2CHjKC0LUOmpZIbBsH/MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +# Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m +# dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB +# IDIwMTACEzMAAAHVqQLPxafJ6VoAAQAAAdUwIgQgBVj4mNvMkXn0xEeEe3i6urr/ +# L5Eh2p4uPk3zOcR/jHIwDQYJKoZIhvcNAQELBQAEggIAiKbnM5VQAwi5gWDevpEJ +# oHiZRrb0HApEUkca54Oye2+5tyRfWo3ruZvLZueHWsbEzYXclOscHVPjyR1t6s+p +# W13utx4mDab1BaJCBq1q9IH1HDfCf/kSMb0H02UDNBumrt1s5/oaiiY75S3kigpt +# XHCKV/AKbi+4vORYdBwJhWvy4kug+zZHWAp7+K4rVeAaMbWOitriFPQaSdZlOkNT +# VKMbYUtX6VEnhyfr5O390TxPJCdkDznT6rAf5FlQEkim7wb2gb/Osi73KF0dtnRd +# 0ePNT4GxulYTrAhHsZmyuinh/FIyqJDtW3C+2eVt/lx6GJfAtDA/gVCe9mAL4bYv +# 7vjocHeLvWJ8bc0PYMCRZXMsU2zkJqVJ7pZWq+z5hGQAHemrQj9hUZBXCEFVZ8dd +# jEZbIsg2nwZOFakhvdAvvGlTXPbRMOCHblToXAKA8ksRbLob8CDL6Cstoy5VL3al +# fOAWLj3FYITlvGvAYCtJzHrHdqyphL805Co1syR6YDopR8tDrxgWzJKAby5fIolP +# 7SfunXsCa0n3xx80aaxR0apljIXZWBSMGZdJmVzASQFxlexNsU1PmaLmmpn0fih/ +# 567e+kyzFG1wiw5btwttSW9hKgCX+yze1B4IK4yquTQSfPikrCpbBaFZ0OlWmOnL +# r0eKAKY3WDyxYgNyeg6KeCc= +# SIG # End signature block diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs new file mode 100644 index 00000000..a44d6d9c --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/ModelAnalyzerTests.cs @@ -0,0 +1,86 @@ +namespace Redpoint.CloudFramework.Analyzer.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using Xunit; + + using VerifyDiag = Verifiers.CSharpAnalyzerVerifier< + CloudFrameworkModelAnalyzer>; + + //using VerifyCodeFix = Verifiers.CSharpCodeFixVerifier< + // CloudFrameworkModelAnalyzer, + // CloudFrameworkModelAnalyzerCodeFixProvider>; + + public class ModelAnalyzerTests + { + [Fact] + public async Task TestEmpty() + { + var test = string.Empty; + + await VerifyDiag.VerifyAnalyzerAsync(test); + // await VerifyCodeFix.VerifyAnalyzerAsync(test); + } + + [Fact] + public async Task TestInherit() + { + var test = + """ + namespace Redpoint.CloudFramework.Models + { + public class Model where T : Model + { + } + } + + sealed class Model1 : Redpoint.CloudFramework.Models.Model + { + } + + sealed class {|#0:Model2|} : Redpoint.CloudFramework.Models.Model + { + } + """; + + var expected = VerifyDiag + .Diagnostic(CloudFrameworkModelAnalyzer.InheritDiagnosticId) + .WithLocation(0) + .WithArguments("Model2"); + + await VerifyDiag.VerifyAnalyzerAsync(test, expected); + } + + [Fact] + public async Task TestSealed() + { + var test = + """ + namespace Redpoint.CloudFramework.Models + { + public class Model where T : Model + { + } + } + + sealed class Model1 : Redpoint.CloudFramework.Models.Model + { + } + + class {|#0:Model2|} : Redpoint.CloudFramework.Models.Model + { + } + """; + + var expected = VerifyDiag + .Diagnostic(CloudFrameworkModelAnalyzer.SealedDiagnosticId) + .WithLocation(0) + .WithArguments("Model2"); + + await VerifyDiag.VerifyAnalyzerAsync(test, expected); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj b/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj new file mode 100644 index 00000000..c8ad7bd4 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Redpoint.CloudFramework.Analyzer.Tests.csproj @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs new file mode 100644 index 00000000..9df519ee --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1+Test.cs @@ -0,0 +1,26 @@ +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : CSharpAnalyzerTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId)!.CompilationOptions; + compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs new file mode 100644 index 00000000..ea08c383 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpAnalyzerVerifier`1.cs @@ -0,0 +1,38 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs new file mode 100644 index 00000000..2759cc5d --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2+Test.cs @@ -0,0 +1,28 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : CSharpCodeFixTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId)!.CompilationOptions; + compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs new file mode 100644 index 00000000..31533c4a --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeFixVerifier`2.cs @@ -0,0 +1,61 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => CSharpCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => CSharpCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => CSharpCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs new file mode 100644 index 00000000..075cdf46 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1+Test.cs @@ -0,0 +1,26 @@ +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : CSharpCodeRefactoringTest + { + public Test() + { + SolutionTransforms.Add((solution, projectId) => + { + var compilationOptions = solution.GetProject(projectId)!.CompilationOptions; + compilationOptions = compilationOptions!.WithSpecificDiagnosticOptions( + compilationOptions.SpecificDiagnosticOptions.SetItems(CSharpVerifierHelper.NullableWarnings)); + solution = solution.WithProjectCompilationOptions(projectId, compilationOptions); + + return solution; + }); + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs new file mode 100644 index 00000000..e80f9445 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpCodeRefactoringVerifier`1.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class CSharpCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs new file mode 100644 index 00000000..068a6688 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/CSharpVerifierHelper.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + internal static class CSharpVerifierHelper + { + /// + /// By default, the compiler reports diagnostics for nullable reference types at + /// , and the analyzer test framework defaults to only validating + /// diagnostics at . This map contains all compiler diagnostic IDs + /// related to nullability mapped to , which is then used to enable all + /// of these warnings for default validation during analyzer and code fix tests. + /// + internal static ImmutableDictionary NullableWarnings { get; } = GetNullableWarningsFromCompiler(); + + private static ImmutableDictionary GetNullableWarningsFromCompiler() + { + string[] args = { "/warnaserror:nullable" }; + var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); + var nullableWarnings = commandLineArguments.CompilationOptions.SpecificDiagnosticOptions; + + // Workaround for https://github.com/dotnet/roslyn/issues/41610 + nullableWarnings = nullableWarnings + .SetItem("CS8632", ReportDiagnostic.Error) + .SetItem("CS8669", ReportDiagnostic.Error); + + return nullableWarnings; + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs new file mode 100644 index 00000000..24e0dbf2 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1+Test.cs @@ -0,0 +1,17 @@ +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + public class Test : VisualBasicAnalyzerTest + { + public Test() + { + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs new file mode 100644 index 00000000..2dafbc7e --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicAnalyzerVerifier`1.cs @@ -0,0 +1,38 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicAnalyzerVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicAnalyzerVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicAnalyzerVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicAnalyzerVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs new file mode 100644 index 00000000..4fe159ac --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2+Test.cs @@ -0,0 +1,16 @@ +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + public class Test : VisualBasicCodeFixTest + { + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs new file mode 100644 index 00000000..d5f0d397 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeFixVerifier`2.cs @@ -0,0 +1,61 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicCodeFixVerifier + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : CodeFixProvider, new() + { + /// + public static DiagnosticResult Diagnostic() + => VisualBasicCodeFixVerifier.Diagnostic(); + + /// + public static DiagnosticResult Diagnostic(string diagnosticId) + => VisualBasicCodeFixVerifier.Diagnostic(diagnosticId); + + /// + public static DiagnosticResult Diagnostic(DiagnosticDescriptor descriptor) + => VisualBasicCodeFixVerifier.Diagnostic(descriptor); + + /// + public static async Task VerifyAnalyzerAsync(string source, params DiagnosticResult[] expected) + { + var test = new Test + { + TestCode = source, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + + /// + public static async Task VerifyCodeFixAsync(string source, string fixedSource) + => await VerifyCodeFixAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) + => await VerifyCodeFixAsync(source, new[] { expected }, fixedSource); + + /// + public static async Task VerifyCodeFixAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs new file mode 100644 index 00000000..cbeeab56 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1+Test.cs @@ -0,0 +1,14 @@ +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing.Verifiers; +using Microsoft.CodeAnalysis.VisualBasic.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + public class Test : VisualBasicCodeRefactoringTest + { + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs new file mode 100644 index 00000000..45a02f1b --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer.Tests/Verifiers/VisualBasicCodeRefactoringVerifier`1.cs @@ -0,0 +1,36 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Testing; + +namespace Redpoint.CloudFramework.Analyzer.Tests.Verifiers +{ + public static partial class VisualBasicCodeRefactoringVerifier + where TCodeRefactoring : CodeRefactoringProvider, new() + { + /// + public static async Task VerifyRefactoringAsync(string source, string fixedSource) + { + await VerifyRefactoringAsync(source, DiagnosticResult.EmptyDiagnosticResults, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult expected, string fixedSource) + { + await VerifyRefactoringAsync(source, new[] { expected }, fixedSource); + } + + /// + public static async Task VerifyRefactoringAsync(string source, DiagnosticResult[] expected, string fixedSource) + { + var test = new Test + { + TestCode = source, + FixedCode = fixedSource, + }; + + test.ExpectedDiagnostics.AddRange(expected); + await test.RunAsync(CancellationToken.None); + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs b/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs new file mode 100644 index 00000000..4cee2b0d --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer/CloudFrameworkModelAnalyzer.cs @@ -0,0 +1,90 @@ +namespace Redpoint.CloudFramework.Analyzer +{ + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Threading; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class CloudFrameworkModelAnalyzer : DiagnosticAnalyzer + { + public const string InheritDiagnosticId = "CloudFrameworkModelAnalyzerInherit"; + public const string SealedDiagnosticId = "CloudFrameworkModelAnalyzerSealed"; + + private static readonly DiagnosticDescriptor _inheritRule = new DiagnosticDescriptor( + InheritDiagnosticId, + "Inheritance of Model should have T match inheriting type", + "Type {0} must inherit from Model<{0}>", + "Cloud Framework", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Cloud Framework models must inherit from Model, where T matches the type that is inheriting."); + + private static readonly DiagnosticDescriptor _sealedRule = new DiagnosticDescriptor( + SealedDiagnosticId, + "Types implementing Model should be sealed", + "Type {0} must be sealed", + "Cloud Framework", + DiagnosticSeverity.Error, + isEnabledByDefault: true, + description: "Cloud Framework models must be sealed."); + + public override ImmutableArray SupportedDiagnostics + { + get + { + return ImmutableArray.Create(_inheritRule, _sealedRule); + } + } + + public override void Initialize(AnalysisContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.EnableConcurrentExecution(); + + context.RegisterSyntaxNodeAction(AnalyzeClassDeclaration, SyntaxKind.ClassDeclaration); + } + + private static void AnalyzeClassDeclaration(SyntaxNodeAnalysisContext context) + { + if (!(context.Node is ClassDeclarationSyntax classDeclaration)) + { + return; + } + + var symbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration); + + var baseType = symbol.BaseType; + if (baseType.IsGenericType && + baseType.TypeArguments.Length >= 1) + { + var unboundGeneric = baseType.ConstructUnboundGenericType(); + if (unboundGeneric.ContainingNamespace.ToString() == "Redpoint.CloudFramework.Models" && + unboundGeneric.Name == "Model") + { + if (!SymbolEqualityComparer.Default.Equals(symbol, baseType.TypeArguments[0])) + { + var diagnostic = Diagnostic.Create(_inheritRule, symbol.Locations[0], symbol.Name); + context.ReportDiagnostic(diagnostic); + } + + if (!symbol.IsSealed) + { + var diagnostic = Diagnostic.Create(_sealedRule, symbol.Locations[0], symbol.Name); + context.ReportDiagnostic(diagnostic); + } + } + } + } + } +} diff --git a/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj b/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj new file mode 100644 index 00000000..26134e29 --- /dev/null +++ b/UET/Redpoint.CloudFramework.Analyzer/Redpoint.CloudFramework.Analyzer.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.0 + false + + + *$(MSBuildProjectFile)* + + $(NoWarn);CA1824 + + + + + + + + diff --git a/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs b/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs index 33eec358..8304193b 100644 --- a/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs +++ b/UET/Redpoint.CloudFramework.Tests.Shared/CloudFrameworkTestEnvironment.cs @@ -344,22 +344,22 @@ public Task MutateEntityBeforeWrite(string @namespace, Entity entity) return Task.CompletedTask; } - public Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new() + public Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new() { return Task.CompletedTask; } - public Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : Model, new() + public Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new() { return Task.CompletedTask; } - public Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new() + public Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new() { return Task.CompletedTask; } - public Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : Model, new() + public Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new() { return Task.CompletedTask; } diff --git a/UET/Redpoint.CloudFramework.Tests/BadModel.cs b/UET/Redpoint.CloudFramework.Tests/BadModel.cs index c155f662..a7a5aa02 100644 --- a/UET/Redpoint.CloudFramework.Tests/BadModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/BadModel.cs @@ -2,8 +2,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_badModel")] - public class BadModel : AttributedModel + [Kind("cf_badModel")] + public sealed class BadModel : Model { [Type(FieldType.String), Indexed] public object? badField { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs index ff5455b3..a529546b 100644 --- a/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/DefaultedBypassModel.cs @@ -2,8 +2,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_defaultedModel")] - public class DefaultedBypassModel : AttributedModel + [Kind("cf_defaultedModel")] + public sealed class DefaultedBypassModel : Model { [Type(FieldType.String), Indexed] public string? myString { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs index 7fe6ef0b..36dc1d3c 100644 --- a/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/DefaultedInvalidModel.cs @@ -2,8 +2,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_defaultedModel")] - public class DefaultedInvalidModel : AttributedModel + [Kind("cf_defaultedModel")] + public sealed class DefaultedInvalidModel : Model { [Type(FieldType.String), Indexed] public string? myString { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs b/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs index 587d7cbe..28a4da37 100644 --- a/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/DefaultedModel.cs @@ -2,8 +2,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_defaultedModel")] - public class DefaultedModel : AttributedModel + [Kind("cf_defaultedModel")] + public sealed class DefaultedModel : Model { // This model exists to ensure AttributedModel initializes // properties to their default values. diff --git a/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs b/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs index 915cd671..de129363 100644 --- a/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/EmbeddedEntityModel.cs @@ -5,8 +5,8 @@ using Redpoint.CloudFramework.Models; using System; - [Kind("cf_embeddedEntityModel")] - public class EmbeddedEntityModel : AttributedModel + [Kind("cf_embeddedEntityModel")] + public sealed class EmbeddedEntityModel : Model { [Type(FieldType.String), Indexed] public string? forTest { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs b/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs index d0a6a88e..1532c28b 100644 --- a/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/GeoDenseModel.cs @@ -4,8 +4,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_geoDenseModel")] - public class GeoDenseModel : AttributedModel + [Kind("cf_geoDenseModel")] + public sealed class GeoDenseModel : Model { [Type(FieldType.String), Indexed] public string? forTest { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs b/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs index ed954bb4..fd5d63e6 100644 --- a/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/GeoSparseModel.cs @@ -4,8 +4,8 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_geoSparseModel")] - public class GeoSparseModel : AttributedModel + [Kind("cf_geoSparseModel")] + public sealed class GeoSparseModel : Model { [Type(FieldType.String), Indexed] public string? forTest { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs b/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs index 56708daf..d5cee85d 100644 --- a/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs +++ b/UET/Redpoint.CloudFramework.Tests/RedisCacheRepositoryLayerTests.cs @@ -46,67 +46,67 @@ private static async Task HandleEventualConsistency(Func task) await task().ConfigureAwait(true); } - [Kind("cf_TestLoadedEntityMatchesCreatedEntity")] + [Kind("cf_TestLoadedEntityMatchesCreatedEntity")] private class TestLoadedEntityMatchesCreatedEntity_Model : RedisTestModel { } - [Kind("cf_TestLoadedEntityIsInCache")] + [Kind("cf_TestLoadedEntityIsInCache")] private class TestLoadedEntityIsInCache_Model : RedisTestModel { } - [Kind("cf_TestMultipleEntityLoadWorks")] + [Kind("cf_TestMultipleEntityLoadWorks")] private class TestMultipleEntityLoadWorks_Model : RedisTestModel { } - [Kind("cf_TestMultipleEntityLoadWorksWithoutCacheClear")] + [Kind("cf_TestMultipleEntityLoadWorksWithoutCacheClear")] private class TestMultipleEntityLoadWorksWithoutCacheClear_Model : RedisTestModel { } - [Kind("cf_TestUpdatedEntityIsNotInCache")] + [Kind("cf_TestUpdatedEntityIsNotInCache")] private class TestUpdatedEntityIsNotInCache_Model : RedisTestModel { } - [Kind("cf_TestUpsertedEntityIsNotInCache")] + [Kind("cf_TestUpsertedEntityIsNotInCache")] private class TestUpsertedEntityIsNotInCache_Model : RedisTestModel { } - [Kind("cf_TestDeletedEntityIsNotInCache")] + [Kind("cf_TestDeletedEntityIsNotInCache")] private class TestDeletedEntityIsNotInCache_Model : RedisTestModel { } - [Kind("cf_TestCreateThenQuery")] + [Kind("cf_TestCreateThenQuery")] private class TestCreateThenQuery_Model : RedisTestModel { } - [Kind("cf_TestCreateThenQueryThenUpdateThenQuery")] + [Kind("cf_TestCreateThenQueryThenUpdateThenQuery")] private class TestCreateThenQueryThenUpdateThenQuery_Model : RedisTestModel { } - [Kind("cf_TestReaderCountIsSetWhileReading")] + [Kind("cf_TestReaderCountIsSetWhileReading")] private class TestReaderCountIsSetWhileReading_Model : RedisTestModel { } - [Kind("cf_TestTransactionalUpdateInvalidatesQuery")] + [Kind("cf_TestTransactionalUpdateInvalidatesQuery")] private class TestTransactionalUpdateInvalidatesQuery_Model : RedisTestModel { } - [Kind("cf_TestCreateInvalidatesQuery")] + [Kind("cf_TestCreateInvalidatesQuery")] private class TestCreateInvalidatesQuery_Model : RedisTestModel { } - [Kind("cf_TestUpdateWithNoOriginalDataDoesNotCrash")] + [Kind("cf_TestUpdateWithNoOriginalDataDoesNotCrash")] private class TestUpdateWithNoOriginalDataDoesNotCrash_Model : RedisTestModel { } - [Kind("cf_TestUpdateInvalidatesRelevantQuery")] + [Kind("cf_TestUpdateInvalidatesRelevantQuery")] private class TestUpdateInvalidatesRelevantQuery_Model : RedisTestModel { } - [Kind("cf_TestUpdateDoesNotInvalidateIrrelevantQuery")] + [Kind("cf_TestUpdateDoesNotInvalidateIrrelevantQuery")] private class TestUpdateDoesNotInvalidateIrrelevantQuery_Model : RedisTestModel { } - [Kind("cf_TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit")] + [Kind("cf_TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit")] private class TestTransactionalUpdateDoesNotInvalidateCacheUntilCommit_Model : RedisTestModel { } - [Kind("cf_TestTransactionalUpdateFromNull")] + [Kind("cf_TestTransactionalUpdateFromNull")] private class TestTransactionalUpdateFromNull_Model : RedisTestModel { } - [Kind("cf_TestNonTransactionalUpdateFromNull")] + [Kind("cf_TestNonTransactionalUpdateFromNull")] private class TestNonTransactionalUpdateFromNull_Model : RedisTestModel { } - [Kind("cf_TestQueryOrdering")] + [Kind("cf_TestQueryOrdering")] private class TestQueryOrdering_Model : RedisTestModel { } - [Kind("cf_TestQueryEverything")] + [Kind("cf_TestQueryEverything")] private class TestQueryEverything_Model : RedisTestModel { } - [Kind("cf_TestDeletedEntityIsNotInCachedQueryEverything")] + [Kind("cf_TestDeletedEntityIsNotInCachedQueryEverything")] private class TestDeletedEntityIsNotInCachedQueryEverything_Model : RedisTestModel { } [Fact] diff --git a/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs b/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs index ab169b1f..cbedc896 100644 --- a/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/RedisTestModel.cs @@ -4,8 +4,9 @@ namespace Redpoint.CloudFramework.Tests { - [Kind("cf_redisTest")] - public class RedisTestModel : AttributedModel + [Kind("cf_redisTest")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Cloud Framework", "CloudFrameworkModelAnalyzerSealed:Types implementing Model should be sealed", Justification = "Internal testing.")] + public class RedisTestModel : Model { [Type(FieldType.String), Indexed] public string? forTest { get; set; } diff --git a/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj b/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj index c934857b..2913bad1 100644 --- a/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj +++ b/UET/Redpoint.CloudFramework.Tests/Redpoint.CloudFramework.Tests.csproj @@ -4,6 +4,10 @@ + + false + Analyzer + diff --git a/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs b/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs index 5611cc0b..7f52d1b2 100644 --- a/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs +++ b/UET/Redpoint.CloudFramework.Tests/StringEnumRepositoryLayerTests.cs @@ -22,8 +22,8 @@ internal class TestStringEnum : StringEnum public static readonly StringEnumValue C = Create("c"); } - [Kind("testString")] - internal class TestStringModel : AttributedModel + [Kind("testString")] + internal sealed class TestStringModel : Model { [Type(FieldType.String), Indexed, Default("a")] public StringEnumValue enumValue { get; set; } = TestStringEnum.A; diff --git a/UET/Redpoint.CloudFramework.Tests/TestModel.cs b/UET/Redpoint.CloudFramework.Tests/TestModel.cs index 9265839c..216b0dcb 100644 --- a/UET/Redpoint.CloudFramework.Tests/TestModel.cs +++ b/UET/Redpoint.CloudFramework.Tests/TestModel.cs @@ -6,13 +6,8 @@ namespace Redpoint.CloudFramework.Tests { // This ensures the tests pass when using properties on base classes. - [Kind("cf_testModel")] - public class TestModel : TestBaseModel - { - } - - [Kind("cf_testBaseModel")] - public class TestBaseModel : AttributedModel + [Kind("cf_testModel")] + public sealed class TestModel : Model { [Type(FieldType.String), Indexed] public string? forTest { get; set; } @@ -35,7 +30,9 @@ public class TestBaseModel : AttributedModel public TestModel? untracked { get; set; } [Type(FieldType.String), Indexed] +#pragma warning disable CS0628 // New protected member declared in sealed type protected string? protectedString1 { get; set; } +#pragma warning restore CS0628 // New protected member declared in sealed type [Type(FieldType.String), Indexed] private string? privateString1 { get; set; } diff --git a/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs index 66dc7456..7298c86f 100644 --- a/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs +++ b/UET/Redpoint.CloudFramework/Counter/DefaultShardedCounterModel.cs @@ -3,8 +3,8 @@ using Redpoint.CloudFramework.Models; using System; - [Kind("_rcfShardedCounter")] - internal class DefaultShardedCounterModel : AttributedModel + [Kind("_rcfShardedCounter")] + internal sealed class DefaultShardedCounterModel : Model { /// /// The counter name shared amongst all shards of this counter. diff --git a/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs b/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs deleted file mode 100644 index 21e7548a..00000000 --- a/UET/Redpoint.CloudFramework/Counter/IShardedCounterService.cs +++ /dev/null @@ -1,64 +0,0 @@ -namespace Redpoint.CloudFramework.Counter -{ - using Redpoint.CloudFramework.Models; - using Redpoint.CloudFramework.Repository.Transaction; - using System; - using System.Diagnostics.CodeAnalysis; - using System.Threading.Tasks; - - [Obsolete("Use IShardedCounter instead.")] - public interface IShardedCounterService - { - /// - /// Returns the value of a sharded counter. - /// - /// The name of the sharded counter. - /// The value of the sharded counter. - Task Get(string name); - - /// - /// Returns the value of a sharded counter stored in a custom model. - /// - /// The model that is used to store sharded counter data. - /// The name of the sharded counter. - /// The value of the sharded counter. - Task GetCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name) where T : Model, IShardedCounterModel, new(); - - /// - /// Adjust the value of a sharded counter. - /// - /// The name of the sharded counter. - /// The amount to modify the sharded counter by. - /// The task to await on. - Task Adjust(string name, long modifier); - - /// - /// Adjust the value of a sharded counter stored in a custom model. - /// - /// The model that is used to store sharded counter data. - /// The name of the sharded counter. - /// The amount to modify the sharded counter by. - /// The task to await on. - Task AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier) where T : Model, IShardedCounterModel, new(); - - /// - /// Adjust the value of a sharded counter inside an existing transaction. You *must* await this - /// function and call the callback it returns after you commit the provided transaction. - /// - /// The name of the sharded counter. - /// The amount to modify the sharded counter by. - /// The existing transaction to update the counter in. - /// The task to await on. - Task> Adjust(string name, long modifier, IModelTransaction existingTransaction); - - /// - /// Adjust the value of a sharded counter stored in a custom model, inside an existing transaction. - /// You *must* await this function and call the callback it returns after you commit the provided transaction. - /// - /// The name of the sharded counter. - /// The amount to modify the sharded counter by. - /// The existing transaction to update the counter in. - /// The task to await on. - Task> AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier, IModelTransaction existingTransaction) where T : Model, IShardedCounterModel, new(); - } -} diff --git a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs deleted file mode 100644 index 7163efa4..00000000 --- a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Redpoint.CloudFramework.Models; - -namespace Redpoint.CloudFramework.Counter -{ - using System.Collections.Generic; - using System.Globalization; - - [Obsolete] - internal class LegacyShardedCounterModel : Model, IShardedCounterModel - { - public long? count { get; set; } - - public override string GetKind() - { - return "RCF-SharedCounter"; - } - - public override long GetSchemaVersion() - { - return 1; - } - - public override Dictionary GetTypes() - { - return new Dictionary { - { "count", FieldType.Integer } - }; - } - - public override HashSet GetIndexes() - { - return new HashSet - { - "count", - }; - } - - public string? GetTypeFieldName() - { - return null; - } - - public string GetCountFieldName() - { - return "count"; - } - - public string FormatShardName(string name, int index) - { - return string.Format(CultureInfo.InvariantCulture, "shard-{0}-{1}", name, index); - } - } -} \ No newline at end of file diff --git a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs b/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs deleted file mode 100644 index c6523e76..00000000 --- a/UET/Redpoint.CloudFramework/Counter/LegacyShardedCounterService.cs +++ /dev/null @@ -1,224 +0,0 @@ -namespace Redpoint.CloudFramework.Counter -{ - using Google.Cloud.Datastore.V1; - using Redpoint.CloudFramework.Models; - using Redpoint.CloudFramework.Repository; - using Redpoint.CloudFramework.Repository.Transaction; - using StackExchange.Redis; - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.Linq; - using System.Reflection; - using System.Threading.Tasks; - -#pragma warning disable CS0612 // Type or member is obsolete -#pragma warning disable CS0618 // Type or member is obsolete - internal class LegacyShardedCounterService : IShardedCounterService - { - private readonly IRepository _repository; - private readonly IDatabase _redisDatabase; - private readonly Random _random; - - // This can only ever be increased; not decreased. - public const int NumShards = 60; - - private const bool _enableRedis = true; - - public LegacyShardedCounterService( - IRepository repository, - IConnectionMultiplexer connectionMultiplexer) - { - ArgumentNullException.ThrowIfNull(connectionMultiplexer); - - _repository = repository; - _redisDatabase = connectionMultiplexer.GetDatabase(); - _random = new Random(); - } - - private async IAsyncEnumerable GetAllKeys(string name) where T : Model, IShardedCounterModel, new() - { - var t = new T(); - - var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false); - for (var i = 0; i < NumShards; i++) - { - yield return keyFactory.CreateKey(t.FormatShardName(name, i)); - } - } - - public async Task AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier) where T : Model, IShardedCounterModel, new() - { - var t = new T(); - - var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (counterProperty == null) - { - throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class."); - } - - var typeName = t.GetTypeFieldName(); - var typeProperty = typeName == null ? null : typeof(T).GetProperty(typeName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - -#pragma warning disable CA5394 // Do not use insecure randomness - var idx = _random.Next(0, NumShards); -#pragma warning restore CA5394 // Do not use insecure randomness - var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false); - var key = keyFactory.CreateKey(t.FormatShardName(name, idx)); - var transaction = await _repository.BeginTransactionAsync().ConfigureAwait(false); - var rollback = true; - try - { - var create = false; - var counter = await _repository.LoadAsync(key, transaction).ConfigureAwait(false); - if (counter == null) - { - counter = new T - { - Key = key, - }; - counterProperty.SetValue(counter, modifier); - if (typeProperty != null) - { - typeProperty.SetValue(counter, "shard"); - } - create = true; - } - else - { - counterProperty.SetValue(counter, ((long?)counterProperty.GetValue(counter) ?? 0) + modifier); - } - if (create) - { - await _repository.CreateAsync(counter, transaction).ConfigureAwait(false); - } - else - { - await _repository.UpdateAsync(counter, transaction).ConfigureAwait(false); - } - await _repository.CommitAsync(transaction).ConfigureAwait(false); - if (_enableRedis) - { - await _redisDatabase.StringIncrementAsync("shard-" + name, modifier, CommandFlags.FireAndForget).ConfigureAwait(false); - } - rollback = false; - } - finally - { - if (rollback) - { - await transaction.Transaction.RollbackAsync().ConfigureAwait(false); - } - } - } - - - public async Task> AdjustCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name, long modifier, IModelTransaction existingTransaction) where T : Model, IShardedCounterModel, new() - { - var t = new T(); - - var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (counterProperty == null) - { - throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class."); - } - - var typeName = t.GetTypeFieldName(); - var typeProperty = typeName == null ? null : typeof(T).GetProperty(typeName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - -#pragma warning disable CA5394 // Do not use insecure randomness - var idx = _random.Next(0, NumShards); -#pragma warning restore CA5394 // Do not use insecure randomness - var keyFactory = await _repository.GetKeyFactoryAsync().ConfigureAwait(false); - var key = keyFactory.CreateKey(t.FormatShardName(name, idx)); - - var create = false; - var counter = await _repository.LoadAsync(key, existingTransaction).ConfigureAwait(false); - if (counter == null) - { - counter = new T - { - Key = key, - }; - counterProperty.SetValue(counter, modifier); - if (typeProperty != null) - { - typeProperty.SetValue(counter, "shard"); - } - create = true; - } - else - { - counterProperty.SetValue(counter, ((long?)counterProperty.GetValue(counter) ?? 0) + modifier); - } - if (create) - { - await _repository.CreateAsync(counter, existingTransaction).ConfigureAwait(false); - } - else - { - await _repository.UpdateAsync(counter, existingTransaction).ConfigureAwait(false); - } - return async () => - { - if (_enableRedis) - { - await _redisDatabase.StringIncrementAsync("shard-" + name, modifier, CommandFlags.FireAndForget).ConfigureAwait(false); - } - }; - } - - public async Task GetCustom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(string name) where T : Model, IShardedCounterModel, new() - { - var t = new T(); - - var counterProperty = typeof(T).GetProperty(t.GetCountFieldName(), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (counterProperty == null) - { - throw new InvalidOperationException($"The count field name specified by GetCountFieldName '{t.GetCountFieldName()}' does not exist on the class."); - } - - long total; - if (_enableRedis) - { - var shardCache = await _redisDatabase.StringGetAsync("shard-" + name).ConfigureAwait(false); - if (!(!shardCache.HasValue || !shardCache.IsInteger || !shardCache.TryParse(out total))) - { - return total; - } - } - - total = - await _repository.LoadAsync(GetAllKeys(name)) - .Where(x => x.Value != null) - .Select(x => (long?)counterProperty.GetValue(x.Value) ?? 0) - .SumAsync().ConfigureAwait(false); - if (_enableRedis) - { - await _redisDatabase.StringSetAsync( - "shard-" + name, - total, - TimeSpan.FromSeconds(60), - When.NotExists).ConfigureAwait(false); - } - return total; - } - - public Task Get(string name) - { - return GetCustom(name); - } - - public Task Adjust(string name, long modifier) - { - return AdjustCustom(name, modifier); - } - - public Task> Adjust(string name, long modifier, IModelTransaction existingTransaction) - { - return AdjustCustom(name, modifier, existingTransaction); - } - } -#pragma warning restore CS0612 // Type or member is obsolete -#pragma warning restore CS0618 // Type or member is obsolete -} diff --git a/UET/Redpoint.CloudFramework/Event/EventApi.cs b/UET/Redpoint.CloudFramework/Event/EventApi.cs index b1f46266..cf4e01fc 100644 --- a/UET/Redpoint.CloudFramework/Event/EventApi.cs +++ b/UET/Redpoint.CloudFramework/Event/EventApi.cs @@ -116,7 +116,7 @@ private SerializedEvent SerializeEvent(Event data) return o; } - if (entity is Model m) + if (entity is IModel m) { // TODO return null; diff --git a/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs b/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs index 4a30f9a4..6a5615cc 100644 --- a/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs +++ b/UET/Redpoint.CloudFramework/Models/DefaultLockModel.cs @@ -3,38 +3,14 @@ using NodaTime; using System.Collections.Generic; - public class DefaultLockModel : Model + [Kind("Lock")] + public class DefaultLockModel : Model { // The lock key is part of the name in the key. + [Type(FieldType.Timestamp), Indexed] public Instant? dateExpiresUtc { get; set; } - public string? acquisitionGuid { get; set; } - - public override string GetKind() - { - return "Lock"; - } - - public override long GetSchemaVersion() - { - return 1; - } - public override Dictionary GetTypes() - { - return new Dictionary - { - { "dateExpiresUtc", FieldType.Timestamp }, - { "acquisitionGuid", FieldType.String }, - }; - } - - public override HashSet GetIndexes() - { - return new HashSet - { - "dateExpiresUtc", - "acquisitionGuid" - }; - } + [Type(FieldType.String), Indexed] + public string? acquisitionGuid { get; set; } } } diff --git a/UET/Redpoint.CloudFramework/Models/IModel.cs b/UET/Redpoint.CloudFramework/Models/IModel.cs new file mode 100644 index 00000000..275e4393 --- /dev/null +++ b/UET/Redpoint.CloudFramework/Models/IModel.cs @@ -0,0 +1,28 @@ +namespace Redpoint.CloudFramework.Models +{ + using Google.Cloud.Datastore.V1; + using NodaTime; + using System; + using System.Collections.Generic; + using System.Reflection; + + public interface IModel + { + Key Key { get; set; } + Instant? dateCreatedUtc { get; internal set; } + Instant? dateModifiedUtc { get; internal set; } + long? schemaVersion { get; set; } + + internal Dictionary? _originalData { get; set; } + + internal string GetKind(); + internal Dictionary GetTypes(); + internal HashSet GetIndexes(); + internal Dictionary? GetDefaultValues(); + internal PropertyInfo[] GetPropertyInfos(); + internal PropertyInfo? GetPropertyInfo(string name); + + long GetSchemaVersion(); + string GetDatastoreNamespaceForLocalKeys(); + } +} diff --git a/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs b/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs deleted file mode 100644 index 46e03f30..00000000 --- a/UET/Redpoint.CloudFramework/Models/IShardedCounterModel.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Redpoint.CloudFramework.Models -{ - [Obsolete("This interface is no longer used by IShardedCounter and IGlobalShardedCounter.")] - public interface IShardedCounterModel - { - /// - /// If specified, this Datastore field on the entity will have it's value set to "shard". - /// - /// The Datastore field name, or null for no field. - string? GetTypeFieldName(); - - /// - /// The name of the field to actually store the count in. The bypasses - /// the regular ORM datastore layer for performance, so it needs to explicitly know the Datastore field name here. - /// - /// The Datastore field name. - string GetCountFieldName(); - - /// - /// Converts the sharded counter name and shard index into the name for the Datastore key. - /// - /// The sharded counter name. - /// The index of the shard in the counter. - /// The formatted name. - string FormatShardName(string name, int index); - } -} diff --git a/UET/Redpoint.CloudFramework/Models/KindAttribute.cs b/UET/Redpoint.CloudFramework/Models/KindAttribute.cs index 3f4443e0..c5cd7d12 100644 --- a/UET/Redpoint.CloudFramework/Models/KindAttribute.cs +++ b/UET/Redpoint.CloudFramework/Models/KindAttribute.cs @@ -7,8 +7,7 @@ /// Sets the kind of the entity when this model is stored in Datastore. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class KindAttribute< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : Attribute, IKindAttribute + public sealed class KindAttribute : Attribute { public KindAttribute(string kind) { @@ -16,16 +15,5 @@ public KindAttribute(string kind) } public string Kind { get; } - - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] - public Type Type => typeof(T); - } - - internal interface IKindAttribute - { - string Kind { get; } - - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] - Type Type { get; } } } diff --git a/UET/Redpoint.CloudFramework/Models/Model.cs b/UET/Redpoint.CloudFramework/Models/Model.cs index 3dc24336..1ce292b1 100644 --- a/UET/Redpoint.CloudFramework/Models/Model.cs +++ b/UET/Redpoint.CloudFramework/Models/Model.cs @@ -2,46 +2,100 @@ { using Google.Cloud.Datastore.V1; using NodaTime; + using Redpoint.CloudFramework.Repository.Converters.Value; + using Redpoint.CloudFramework.Repository.Converters.Value.Context; + using Redpoint.CloudFramework.Repository.Geographic; using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Reflection; - public abstract class Model + /// + /// A version of Model that you can inherit from, where the Datastore schema is defined + /// by attributes on the class and properties instead of implementing the abstract Model + /// methods. + /// + /// Implements caching so that when the application has to determine the schema from the + /// model class, it's slightly faster than the naive implementation of returning newly + /// constructed objects from the Model methods. + /// + public class Model<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T> : IModel, IGeoModel where T : Model { - // Declaring this field as nullable would make 99% of reading code - // overly verbose handling scenarios that can never happen (it will never - // be null for entities loaded from the database). The only time that - // the key can be null is if you are creating an entity and haven't - // called CreateAsync yet. -#pragma warning disable CS8618 + private readonly ModelInfo _modelInfo; + + /// + /// The key for the entity in the database. + /// + /// + /// Declaring this field as nullable would make 99% of reading code overly verbose handling scenarios that can never happen (it will never be null for entities loaded from the database). The only time that the key can be null is if you are creating an entity and haven't called CreateAsync yet. + /// public Key Key { get; set; } -#pragma warning restore CS8618 - public Instant? dateCreatedUtc { get; internal set; } - public Instant? dateModifiedUtc { get; internal set; } + /// + /// The date that the entity was created. Setting this field has no effect when updating entities. + /// + public Instant? dateCreatedUtc { get; set; } + + /// + /// The date that the entity was last modified. Setting this field has no effect when updating entities. + /// + public Instant? dateModifiedUtc { get; set; } + + /// + /// The schema version; this is used to detect when an entity requires a migration. + /// public long? schemaVersion { get; set; } /// - /// The original entity when it was loaded; this is used to clear caches - /// when appropriate. + /// Tracks the original data when the entity was loaded so that caches can be correctly invalidated upon write. /// - internal Dictionary? _originalData { get; set; } + Dictionary? IModel._originalData { get; set; } + +#pragma warning disable CS8618 + public Model() +#pragma warning restore CS8618 + { + _modelInfo = ModelInfoRegistry.InitModel(this); + } - public abstract string GetKind(); + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + string IModel.GetKind() => _modelInfo._kind; - public abstract Dictionary GetTypes(); + /// + /// This accessor is only for unit tests so they don't need to cast to IModel. + /// + internal string GetKind() => _modelInfo._kind; - public abstract HashSet GetIndexes(); + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + Dictionary IModel.GetTypes() => _modelInfo._types; - public abstract long GetSchemaVersion(); + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + HashSet IModel.GetIndexes() => _modelInfo._indexes; - public virtual string GetDatastoreNamespaceForLocalKeys() + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + Dictionary? IModel.GetDefaultValues() => _modelInfo._defaultValues; + + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + PropertyInfo[] IModel.GetPropertyInfos() => _modelInfo._propertyInfos; + + [SuppressMessage("Design", "CA1033:Interface methods should be callable by child types", Justification = "This method will not be called by child classes.")] + PropertyInfo? IModel.GetPropertyInfo(string name) { - throw new NotSupportedException("This model has a property of type 'local-key', but does not implement GetDatastoreNamespaceForLocalKeys"); + if (_modelInfo._propertyInfoByName.TryGetValue(name, out var propertyInfo)) + { + return propertyInfo; + } + return null; } - public virtual Dictionary? GetDefaultValues() + public virtual long GetSchemaVersion() => _modelInfo._schemaVersion; + + public virtual string GetDatastoreNamespaceForLocalKeys() { - return null; + throw new NotSupportedException("This model has a property of type 'local-key', but does not implement GetDatastoreNamespaceForLocalKeys"); } + + public virtual Dictionary GetHashKeyLengthsForGeopointFields() => _modelInfo._geoHashKeyLengths; } } diff --git a/UET/Redpoint.CloudFramework/Models/ModelInfo.cs b/UET/Redpoint.CloudFramework/Models/ModelInfo.cs new file mode 100644 index 00000000..6fa96091 --- /dev/null +++ b/UET/Redpoint.CloudFramework/Models/ModelInfo.cs @@ -0,0 +1,17 @@ +namespace Redpoint.CloudFramework.Models +{ + using System.Collections.Generic; + using System.Reflection; + + internal sealed class ModelInfo + { + public required long _schemaVersion; + public required string _kind; + public required HashSet _indexes; + public required Dictionary _types; + public required Dictionary _defaultValues; + public required Dictionary _geoHashKeyLengths; + public required PropertyInfo[] _propertyInfos; + public required Dictionary _propertyInfoByName; + } +} diff --git a/UET/Redpoint.CloudFramework/Models/AttributedModel.cs b/UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs similarity index 52% rename from UET/Redpoint.CloudFramework/Models/AttributedModel.cs rename to UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs index 784700b2..437b9c73 100644 --- a/UET/Redpoint.CloudFramework/Models/AttributedModel.cs +++ b/UET/Redpoint.CloudFramework/Models/ModelInfoRegistry.cs @@ -1,76 +1,89 @@ namespace Redpoint.CloudFramework.Models { - using Redpoint.CloudFramework.Repository.Converters.Value; using Redpoint.CloudFramework.Repository.Converters.Value.Context; - using Redpoint.CloudFramework.Repository.Geographic; + using Redpoint.CloudFramework.Repository.Converters.Value; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; + using System.Text; + using System.Threading.Tasks; - /// - /// A version of Model that you can inherit from, where the Datastore schema is defined - /// by attributes on the class and properties instead of implementing the abstract Model - /// methods. - /// - /// Implements caching so that when the application has to determine the schema from the - /// model class, it's slightly faster than the naive implementation of returning newly - /// constructed objects from the Model methods. - /// - public class AttributedModel : Model, IGeoModel + internal static class ModelInfoRegistry { - private struct ModelInfo - { - public long _schemaVersion; - public string _kind; - public HashSet _indexes; - public Dictionary _types; - public Dictionary _defaultValues; - public Dictionary _geoHashKeyLengths; - } + private static Dictionary _cachedInfo = []; - private static readonly IValueConverter[] _stringEnumValueConverters = new IValueConverter[] - { + private static readonly IValueConverter[] _stringEnumValueConverters = + [ new StringEnumValueConverter(), new StringEnumArrayValueConverter(), new StringEnumSetValueConverter(), - }; + ]; - private static Dictionary _cachedInfo = new Dictionary(); - private readonly Type _type; - private readonly ModelInfo _modelInfo; - - [UnconditionalSuppressMessage("Trimming", "IL2072:Target parameter argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "We're calling Activator.CreateInstance on the property type, where the property has a [Default] and thus must have a non-null value enforced by the C# compiler.")] - public AttributedModel() + public static ModelInfo InitModel< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(Model model) where T : Model { - _type = GetType(); + ArgumentNullException.ThrowIfNull(model, nameof(model)); + if (typeof(T) != model.GetType()) + { + throw new ArgumentException("The model value must have the exact same type as T.", nameof(model)); + } - if (!_cachedInfo.ContainsKey(_type)) + var modelInfo = GetModelInfo(); + + var conversionContext = new ClrValueConvertFromContext(); + foreach (var kv in modelInfo._defaultValues) { - lock (_cachedInfo) + var property = typeof(T).GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!; + var didHandle = false; + foreach (var valueConverter in _stringEnumValueConverters) { - var kindAttribute = _type.GetCustomAttributes(typeof(IKindAttribute), false).Cast().FirstOrDefault() - ?? throw new InvalidOperationException($"Missing [Kind(\"...\")] attribute on {_type.FullName} class."); - if (kindAttribute.Type != _type) + if (valueConverter.GetFieldType() == modelInfo._types[property.Name] && + valueConverter.IsConverterForClrType(property.PropertyType)) { - throw new InvalidOperationException($"Attribute [Kind(\"...\")] has T that differs from runtime type of class, which is {_type.FullName}."); + property.SetValue( + model, + valueConverter.ConvertFromClrDefaultValue( + conversionContext, + property.Name, + property.PropertyType, + kv.Value)); + didHandle = true; + break; } + } + if (!didHandle) + { + property.SetValue(model, kv.Value); + } + } + + return modelInfo; + } - var typeWithRuntimeInfo = kindAttribute.Type; + public static ModelInfo GetModelInfo< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>() + { + if (!_cachedInfo.ContainsKey(typeof(T))) + { + lock (_cachedInfo) + { + var kindAttribute = typeof(T).GetCustomAttributes(typeof(KindAttribute), false).Cast().FirstOrDefault() + ?? throw new InvalidOperationException($"Missing [Kind(\"...\")] attribute on {typeof(T).FullName} class."); - long schemaVersion = typeWithRuntimeInfo.GetCustomAttributes(typeof(SchemaVersionAttribute), false).Cast().FirstOrDefault()?.SchemaVersion ?? 1; + long schemaVersion = typeof(T).GetCustomAttributes(typeof(SchemaVersionAttribute), false).Cast().FirstOrDefault()?.SchemaVersion ?? 1; string kind = kindAttribute?.Kind!; if (string.IsNullOrWhiteSpace(kind)) { - throw new InvalidOperationException($"Attribute [Kind(\"...\")] on {_type.FullName} has an invalid value."); + throw new InvalidOperationException($"Attribute [Kind(\"...\")] on {typeof(T).FullName} has an invalid value."); } var indexes = new HashSet(); var types = new Dictionary(); var defaults = new Dictionary(); var geoHashKeyLengths = new Dictionary(); - foreach (var property in typeWithRuntimeInfo.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) + foreach (var property in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) { var isIndexed = property.GetCustomAttributes(typeof(IndexedAttribute), false).Length > 0; var type = property.GetCustomAttributes(typeof(TypeAttribute), false).Cast().FirstOrDefault(); @@ -89,7 +102,7 @@ public AttributedModel() { if (geopoint == null) { - throw new InvalidOperationException($"Missing [Geopoint(...)] attribute on Geopoint field {typeWithRuntimeInfo.FullName}.{property.Name}. This attribute is required for Geopoint fields."); + throw new InvalidOperationException($"Missing [Geopoint(...)] attribute on Geopoint field {typeof(T).FullName}.{property.Name}. This attribute is required for Geopoint fields."); } else { @@ -102,9 +115,7 @@ public AttributedModel() if (property.PropertyType.IsArray) { // We only support empty (non-null) arrays as defaults. -#pragma warning disable IL3050 // The Array.CreateInstance will be called for an array type explicitly used by the codebase. defaults.Add(property.Name, Array.CreateInstance(property.PropertyType.GetElementType()!, 0)); -#pragma warning restore IL3050 } else { @@ -116,13 +127,13 @@ public AttributedModel() if (property.PropertyType.IsValueType && property.PropertyType.Name != typeof(Nullable<>).Name) { - throw new InvalidOperationException($"Missing [Default(...)] attribute on {typeWithRuntimeInfo.FullName}.{property.Name}. Non-nullable value type properties must have the [Default] attribute. If you want to permit nulls, change this to a nullable value type instead (e.g. 'bool?' instead of 'bool')."); + throw new InvalidOperationException($"Missing [Default(...)] attribute on {typeof(T).FullName}.{property.Name}. Non-nullable value type properties must have the [Default] attribute. If you want to permit nulls, change this to a nullable value type instead (e.g. 'bool?' instead of 'bool')."); } } } } - _cachedInfo[typeWithRuntimeInfo] = new ModelInfo() + _cachedInfo[typeof(T)] = new ModelInfo() { _schemaVersion = schemaVersion, _kind = kind, @@ -130,70 +141,15 @@ public AttributedModel() _types = types, _defaultValues = defaults, _geoHashKeyLengths = geoHashKeyLengths, + _propertyInfos = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic), + _propertyInfoByName = typeof(T) + .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) + .ToDictionary(k => k.Name, v => v), }; } } - _modelInfo = _cachedInfo[_type]; - - var conversionContext = new ClrValueConvertFromContext(); - foreach (var kv in _modelInfo._defaultValues) - { -#pragma warning disable IL2080 // To get to this point, _type must already have been checked with kindAttribute.Type != _type - var property = _type.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!; - var didHandle = false; - foreach (var valueConverter in _stringEnumValueConverters) - { - if (valueConverter.GetFieldType() == _modelInfo._types[property.Name] && - valueConverter.IsConverterForClrType(property.PropertyType)) - { - property.SetValue( - this, - valueConverter.ConvertFromClrDefaultValue( - conversionContext, - property.Name, - property.PropertyType, - kv.Value)); - didHandle = true; - break; - } - } - if (!didHandle) - { - property.SetValue(this, kv.Value); - } -#pragma warning restore IL2080 - } - } - - public sealed override HashSet GetIndexes() - { - return _modelInfo._indexes; - } - - public sealed override string GetKind() - { - return _modelInfo._kind; - } - - public sealed override long GetSchemaVersion() - { - return _modelInfo._schemaVersion; - } - - public sealed override Dictionary GetTypes() - { - return _modelInfo._types; - } - - public sealed override Dictionary GetDefaultValues() - { - return _modelInfo._defaultValues; - } - - public Dictionary GetHashKeyLengthsForGeopointFields() - { - return _modelInfo._geoHashKeyLengths; + return _cachedInfo[typeof(T)]; } } } diff --git a/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs index a11c9efd..5b608074 100644 --- a/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs +++ b/UET/Redpoint.CloudFramework/Prefix/DefaultPrefix.cs @@ -59,7 +59,7 @@ public async Task ParseLimited(string identifier, string kind) return _globalPrefix.ParseLimited(ns, identifier, kind); } - public async Task ParseLimited(string identifier) where T : Model, new() + public async Task ParseLimited(string identifier) where T : class, IModel, new() { var currentTenant = await _currentProjectService.GetTenant().ConfigureAwait(false); if (currentTenant == null) diff --git a/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs index 49694481..4b4090b8 100644 --- a/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs +++ b/UET/Redpoint.CloudFramework/Prefix/GlobalPrefix.cs @@ -27,7 +27,7 @@ public GlobalPrefixRegistration() _reversePrefixes = new Dictionary(); } - public void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : Model, new() + public void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : class, IModel, new() { var kind = new T().GetKind(); @@ -114,7 +114,7 @@ public Key ParseLimited(string datastoreNamespace, string identifier, string kin /// The datastore namespace of the resulting key. /// The identifier to parse. /// A key object. - public Key ParseLimited(string datastoreNamespace, string identifier) where T : Model, new() + public Key ParseLimited(string datastoreNamespace, string identifier) where T : class, IModel, new() { return ParseLimited(datastoreNamespace, identifier, new T().GetKind()); } diff --git a/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs index 7cf7cd1f..349ad520 100644 --- a/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs +++ b/UET/Redpoint.CloudFramework/Prefix/IGlobalPrefix.cs @@ -10,6 +10,6 @@ public interface IGlobalPrefix Key Parse(string datastoreNamespace, string identifier); Key ParseInternal(string datastoreNamespace, string identifier); Key ParseLimited(string datastoreNamespace, string identifier, string kind); - Key ParseLimited(string datastoreNamespace, string identifier) where T : Model, new(); + Key ParseLimited(string datastoreNamespace, string identifier) where T : class, IModel, new(); } } diff --git a/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs b/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs index aa60b50e..ffce443a 100644 --- a/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs +++ b/UET/Redpoint.CloudFramework/Prefix/IPrefix.cs @@ -11,6 +11,6 @@ public interface IPrefix Task Parse(string identifier); Task ParseInternal(string identifier); Task ParseLimited(string identifier, string kind); - Task ParseLimited(string identifier) where T : Model, new(); + Task ParseLimited(string identifier) where T : class, IModel, new(); } } diff --git a/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs b/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs index 3bcc3002..89ba4181 100644 --- a/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs +++ b/UET/Redpoint.CloudFramework/Prefix/IPrefixRegistration.cs @@ -10,6 +10,6 @@ public interface IPrefixRegistration /// /// The model type. /// The prefix to use for the model. - void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : Model, new(); + void RegisterPrefix<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(string prefix) where T : class, IModel, new(); } } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs index 4d49cfa0..9b85972b 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/DefaultExpressionConverter.cs @@ -30,7 +30,7 @@ public DefaultExpressionConverter( } } - private string GetFieldReferencedInExpression(Expression expression, ParameterExpression modelExpression, T referenceModel) where T : Model + private string GetFieldReferencedInExpression(Expression expression, ParameterExpression modelExpression, T referenceModel) where T : IModel { if (expression.NodeType == ExpressionType.MemberAccess) { @@ -55,23 +55,23 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete throw new InvalidOperationException($"Expression must be a member access operation, and the expression that the member access is being performed on must be the model parameter expression. It was a '{access.Expression.NodeType}' type expression instead."); } - if (access.Member.Name == nameof(Model.Key)) + if (access.Member.Name == nameof(IModel.Key)) { throw new InvalidOperationException($"The 'Key' property can only have the 'HasAncestor' extension method called on it; it can not be used in a comparison."); } else { - if (access.Member.Name == nameof(Model.dateCreatedUtc) || - access.Member.Name == nameof(Model.dateModifiedUtc) || + if (access.Member.Name == nameof(IModel.dateCreatedUtc) || + access.Member.Name == nameof(IModel.dateModifiedUtc) || (referenceModel.GetIndexes().Contains(access.Member.Name) && referenceModel.GetTypes().ContainsKey(access.Member.Name))) { return access.Member.Name; } - if (access.Member.Name == nameof(Model.schemaVersion)) + if (access.Member.Name == nameof(IModel.schemaVersion)) { - return nameof(Model.schemaVersion); + return nameof(IModel.schemaVersion); } } @@ -126,7 +126,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete } } - public Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : Model + public Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : IModel { if (expression.NodeType == ExpressionType.Constant && ((ConstantExpression)expression).Type == typeof(bool) && (bool)((ConstantExpression)expression).Value! == true) { @@ -178,7 +178,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete if (callExpression.Method == typeof(RepositoryExtensions).GetMethod(nameof(RepositoryExtensions.HasAncestor), BindingFlags.Static | BindingFlags.Public) && callExpression.Arguments.Count == 2) { - if (propertyInfo.Name != nameof(Model.Key)) + if (propertyInfo.Name != nameof(IModel.Key)) { throw new InvalidOperationException($"You can only use 'HasAncestor' on the primary Key and not key properties. Attempted to use member access on property named '{propertyInfo.Name}'."); } @@ -279,7 +279,7 @@ private string GetFieldReferencedInExpression(Expression expression, Paramete throw new InvalidOperationException($"Expression of type '{expression.NodeType}' is not supported in QueryAsync calls."); } - public IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : Model + public IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : IModel { if (expression.NodeType == ExpressionType.Or) { diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs index 56c7b239..9ef99f5b 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/GeoQueryParameters.cs @@ -5,7 +5,7 @@ using Redpoint.CloudFramework.Models; using System; - internal class GeoQueryParameters where T : Model + internal class GeoQueryParameters where T : IModel { public required string GeoFieldName { get; set; } public required LatLng MinPoint { get; set; } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs index 24181e1f..1ea9457c 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Expression/IExpressionConverter.cs @@ -7,9 +7,9 @@ internal interface IExpressionConverter { - Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : Model; + Filter? ConvertExpressionToFilter(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters, ref bool hasAncestorQuery) where T : IModel; - IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : Model; + IEnumerable? ConvertExpressionToOrder(Expression expression, ParameterExpression modelExpression, T referenceModel, ref GeoQueryParameters? geoParameters) where T : IModel; Filter? SimplifyFilter(Filter? filter); } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs index a28bce9b..22653245 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/NamedEnumConverter.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using Redpoint.CloudFramework.Infrastructure; using System; + using System.Diagnostics.CodeAnalysis; using System.Reflection; internal class NamedEnumConverter : JsonConverter @@ -58,6 +59,7 @@ public override bool CanConvert(Type objectType) throw new JsonSerializationException("Unexpected token when parsing enum."); } + [SuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "This implementation only accesses enumeration members that will have already been accessed when passed into the 'value' argument.")] public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { if (value == null) diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs index f2cdc72a..64227300 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/JsonHelpers/VersionedJsonConverter.cs @@ -4,11 +4,13 @@ using Newtonsoft.Json.Linq; using Redpoint.CloudFramework.Models; using System; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; internal class VersionedJsonConverter : JsonConverter { + [SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.", Justification = "This API does not support trimmed applications.")] public override bool CanConvert(Type objectType) { // Check if the type has a SchemaVersion attribute. @@ -33,6 +35,7 @@ public override bool CanConvert(Type objectType) } } + [SuppressMessage("Trimming", "IL2070:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The parameter of method does not have matching annotations.", Justification = "This API does not support trimmed applications.")] public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { var obj = JToken.ReadFrom(reader); diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs index 6eea847e..5d1c2ea3 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/EntityModelConverter.cs @@ -28,7 +28,7 @@ public EntityModelConverter( _valueConverterProvider = valueConverterProvider; } - public T From(string @namespace, Entity entity) where T : Model, new() + public T From(string @namespace, Entity entity) where T : class, IModel, new() { var @ref = new T(); @ref._originalData = new Dictionary(); @@ -44,8 +44,7 @@ public EntityModelConverter( var types = @ref.GetTypes(); foreach (var kv in types) { - var typeInfo = @ref.GetType(); - var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var propInfo = @ref.GetPropertyInfo(kv.Key); if (propInfo == null) { _logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration"); @@ -120,7 +119,7 @@ public EntityModelConverter( return @ref; } - public Entity To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new() + public Entity To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new() { var entity = new Entity(); @@ -138,11 +137,10 @@ public EntityModelConverter( var indexes = model.GetIndexes(); foreach (var kv in types) { - var typeInfo = model.GetType(); - var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var propInfo = model.GetPropertyInfo(kv.Key); if (propInfo == null) { - throw new InvalidOperationException($"The property '{kv.Key}' could not be found on '{typeInfo.FullName}'. Ensure the datastore type declarations are correct."); + throw new InvalidOperationException($"The property '{kv.Key}' could not be found on '{model.GetType().FullName}'. Ensure the datastore type declarations are correct."); } var value = propInfo.GetValue(model); diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs index 94112c4c..ff3179bc 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/IModelConverter.cs @@ -6,8 +6,8 @@ internal interface IModelConverter { - T? From(string @namespace, TOther data) where T : Model, new(); + T? From(string @namespace, TOther data) where T : class, IModel, new(); - TOther To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new(); + TOther To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new(); } } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs index feebdf81..366b95d7 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Model/JsonModelConverter.cs @@ -31,7 +31,7 @@ public JsonModelConverter( _valueConverterProvider = valueConverterProvider; } - public T? From(string @namespace, string jsonCache) where T : Model, new() + public T? From(string @namespace, string jsonCache) where T : class, IModel, new() { var model = new T(); model._originalData = new Dictionary(); @@ -56,8 +56,7 @@ public JsonModelConverter( var types = model.GetTypes(); foreach (var kv in types) { - var typeInfo = model.GetType(); - var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var propInfo = model.GetPropertyInfo(kv.Key); if (propInfo == null) { _logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration"); @@ -132,7 +131,7 @@ public JsonModelConverter( return model; } - public string To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : Model, new() + public string To(string @namespace, T? model, bool isCreateContext, Func? incompleteKeyFactory) where T : class, IModel, new() { var hashset = new JObject(); @@ -152,8 +151,7 @@ public JsonModelConverter( var types = model.GetTypes(); foreach (var kv in types) { - var typeInfo = model.GetType(); - var propInfo = typeInfo.GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var propInfo = model.GetPropertyInfo(kv.Key); if (propInfo == null) { _logger.LogWarning($"Model {typeof(T).FullName} declares property {kv.Key} but is missing C# declaration"); diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs index f63a947f..14a46257 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/ConvertFromDelayedLoad.cs @@ -5,9 +5,9 @@ /// /// A delegate which is called by the conversion after all other fields are loaded, with the - /// local namespace value provided by . + /// local namespace value provided by . /// - /// The local namespace value provided by . + /// The local namespace value provided by . /// The CLR value that would normally have been directly returned from or . internal delegate object? ConvertFromDelayedLoad(string localNamespace); } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs index d58f48a3..a9bc1298 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/DatastoreValueConvertToContext.cs @@ -10,7 +10,7 @@ internal class DatastoreValueConvertToContext : ClrValueConvertFromContext { public required string ModelNamespace { get; init; } - public required Model Model { get; init; } + public required IModel Model { get; init; } public required Entity Entity { get; init; } } diff --git a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs index d65ee3b0..f11985d6 100644 --- a/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs +++ b/UET/Redpoint.CloudFramework/Repository/Converters/Value/Context/JsonValueConvertToContext.cs @@ -9,6 +9,6 @@ internal class JsonValueConvertToContext : ClrValueConvertFromContext { public required string ModelNamespace { get; init; } - public required Model Model { get; init; } + public required IModel Model { get; init; } } } diff --git a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs index 67c53024..2880f082 100644 --- a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs +++ b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreGlobalRepository.cs @@ -52,7 +52,7 @@ public IBatchedAsyncEnumerable QueryAsync( int? limit = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.QueryAsync(@namespace, where, order, limit, transaction, metrics, cancellationToken); } @@ -65,7 +65,7 @@ public Task> QueryPaginatedAsync( Expression>? order = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.QueryPaginatedAsync(@namespace, cursor, limit, where, order, transaction, metrics, cancellationToken); } @@ -75,7 +75,7 @@ public Task> QueryPaginatedAsync( Key key, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.LoadAsync(@namespace, key, transaction, metrics, cancellationToken); } @@ -85,7 +85,7 @@ public Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.LoadAsync(@namespace, keys, transaction, metrics, cancellationToken); } @@ -93,7 +93,7 @@ public Task> QueryPaginatedAsync( public IAsyncEnumerable> LoadAcrossNamespacesAsync( IAsyncEnumerable keys, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.LoadAcrossNamespacesAsync(keys, metrics, cancellationToken); } @@ -103,7 +103,7 @@ public async Task CreateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await Layer.CreateAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -113,7 +113,7 @@ public IAsyncEnumerable CreateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.CreateAsync(@namespace, models, transaction, metrics, cancellationToken); } @@ -123,7 +123,7 @@ public async Task UpsertAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await Layer.UpsertAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -133,7 +133,7 @@ public IAsyncEnumerable UpsertAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.UpsertAsync(@namespace, models, transaction, metrics, cancellationToken); } @@ -143,7 +143,7 @@ public async Task UpdateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await Layer.UpdateAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -153,7 +153,7 @@ public IAsyncEnumerable UpdateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.UpdateAsync(@namespace, models, transaction, metrics, cancellationToken); } @@ -163,7 +163,7 @@ public Task DeleteAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.DeleteAsync(@namespace, new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken); } @@ -173,7 +173,7 @@ public Task DeleteAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.DeleteAsync(@namespace, models, transaction, metrics, cancellationToken); } @@ -182,7 +182,7 @@ public Task AllocateKeyAsync( string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.AllocateKeyAsync(@namespace, transaction, metrics, cancellationToken); } @@ -190,7 +190,7 @@ public Task AllocateKeyAsync( public Task GetKeyFactoryAsync( string @namespace, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return Layer.GetKeyFactoryAsync(@namespace, metrics, cancellationToken); } diff --git a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreRepository.cs b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreRepository.cs index 29757050..b834041e 100644 --- a/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreRepository.cs +++ b/UET/Redpoint.CloudFramework/Repository/Datastore/DatastoreRepository.cs @@ -41,7 +41,7 @@ public IBatchedAsyncEnumerable QueryAsync( int? limit = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() => BatchedQueryAsync(where, order, limit, transaction, metrics, cancellationToken).AsBatchedAsyncEnumerable(); private async IAsyncEnumerable> BatchedQueryAsync( @@ -50,7 +50,7 @@ private async IAsyncEnumerable> BatchedQueryAsync( int? limit = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, IModel, new() { await foreach (var batch in _globalDatastore.QueryAsync(await GetDatastoreNamespace().ConfigureAwait(false), where, order, limit, transaction, metrics, cancellationToken).AsBatches().ConfigureAwait(false)) { @@ -65,7 +65,7 @@ public async Task> QueryPaginatedAsync( Expression>? order = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.QueryPaginatedAsync(await GetDatastoreNamespace().ConfigureAwait(false), cursor, limit, where, order, transaction, metrics, cancellationToken).ConfigureAwait(false); } @@ -74,7 +74,7 @@ public async Task> QueryPaginatedAsync( Key key, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.LoadAsync(await GetDatastoreNamespace().ConfigureAwait(false), key, transaction, metrics, cancellationToken).ConfigureAwait(false); } @@ -83,14 +83,14 @@ public async Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() => + CancellationToken cancellationToken = default) where T : class, IModel, new() => BatchedLoadAsync(keys, transaction, metrics, cancellationToken).AsBatchedAsyncEnumerable(); public async IAsyncEnumerable>> BatchedLoadAsync( IAsyncEnumerable keys, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, IModel, new() { await foreach (var batch in _globalDatastore.LoadAsync(await GetDatastoreNamespace().ConfigureAwait(false), keys, transaction, metrics, cancellationToken).AsBatches().ConfigureAwait(false)) { @@ -102,7 +102,7 @@ public async Task CreateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.CreateAsync(await GetDatastoreNamespace().ConfigureAwait(false), new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -111,7 +111,7 @@ public async IAsyncEnumerable CreateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, IModel, new() { await foreach (var value in _globalDatastore.CreateAsync(await GetDatastoreNamespace().ConfigureAwait(false), models, transaction, metrics, cancellationToken).ConfigureAwait(false)) { @@ -123,7 +123,7 @@ public async Task UpsertAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.UpsertAsync(await GetDatastoreNamespace().ConfigureAwait(false), new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -132,7 +132,7 @@ public async IAsyncEnumerable UpsertAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, IModel, new() { await foreach (var value in _globalDatastore.UpsertAsync(await GetDatastoreNamespace().ConfigureAwait(false), models, transaction, metrics, cancellationToken).ConfigureAwait(false)) { @@ -144,7 +144,7 @@ public async Task UpdateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.UpdateAsync(await GetDatastoreNamespace().ConfigureAwait(false), new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).FirstAsync(cancellationToken: cancellationToken).ConfigureAwait(false); } @@ -153,7 +153,7 @@ public async IAsyncEnumerable UpdateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, IModel, new() { await foreach (var value in _globalDatastore.UpdateAsync(await GetDatastoreNamespace().ConfigureAwait(false), models, transaction, metrics, cancellationToken).ConfigureAwait(false)) { @@ -165,7 +165,7 @@ public async Task DeleteAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { await _globalDatastore.DeleteAsync(await GetDatastoreNamespace().ConfigureAwait(false), new[] { model }.ToAsyncEnumerable(), transaction, metrics, cancellationToken).ConfigureAwait(false); } @@ -174,7 +174,7 @@ public async Task DeleteAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { await _globalDatastore.DeleteAsync(await GetDatastoreNamespace().ConfigureAwait(false), models, transaction, metrics, cancellationToken).ConfigureAwait(false); } @@ -182,14 +182,14 @@ public async Task DeleteAsync( public async Task AllocateKeyAsync( IModelTransaction? transaction, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.AllocateKeyAsync(await GetDatastoreNamespace().ConfigureAwait(false), transaction, metrics, cancellationToken).ConfigureAwait(false); } public async Task GetKeyFactoryAsync( RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new() + CancellationToken cancellationToken = default) where T : class, IModel, new() { return await _globalDatastore.GetKeyFactoryAsync(await GetDatastoreNamespace().ConfigureAwait(false), metrics, cancellationToken).ConfigureAwait(false); } diff --git a/UET/Redpoint.CloudFramework/Repository/Hooks/IGlobalRepositoryHook.cs b/UET/Redpoint.CloudFramework/Repository/Hooks/IGlobalRepositoryHook.cs index fe7eb6d7..666fb7a6 100644 --- a/UET/Redpoint.CloudFramework/Repository/Hooks/IGlobalRepositoryHook.cs +++ b/UET/Redpoint.CloudFramework/Repository/Hooks/IGlobalRepositoryHook.cs @@ -7,10 +7,10 @@ public interface IGlobalRepositoryHook { - Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new(); - Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : Model, new(); - Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : Model, new(); - Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : Model, new(); + Task PostCreate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new(); + Task PostUpsert(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new(); + Task PostUpdate(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new(); + Task PostDelete(string @namespace, T model, IModelTransaction? transaction) where T : class, IModel, new(); Task MutateEntityBeforeWrite(string @namespace, Entity entity); } diff --git a/UET/Redpoint.CloudFramework/Repository/IGlobalRepository.cs b/UET/Redpoint.CloudFramework/Repository/IGlobalRepository.cs index 5864dfdd..c0a186f0 100644 --- a/UET/Redpoint.CloudFramework/Repository/IGlobalRepository.cs +++ b/UET/Redpoint.CloudFramework/Repository/IGlobalRepository.cs @@ -21,7 +21,7 @@ IBatchedAsyncEnumerable QueryAsync( int? limit = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task> QueryPaginatedAsync( string @namespace, @@ -31,93 +31,93 @@ Task> QueryPaginatedAsync( Expression>? order = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task LoadAsync( string @namespace, Key key, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IBatchedAsyncEnumerable> LoadAsync( string @namespace, IAsyncEnumerable keys, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable> LoadAcrossNamespacesAsync( IAsyncEnumerable keys, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task CreateAsync( string @namespace, T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable CreateAsync( string @namespace, IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task UpsertAsync( string @namespace, T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable UpsertAsync( string @namespace, IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task UpdateAsync( string @namespace, T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable UpdateAsync( string @namespace, IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task DeleteAsync( string @namespace, T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task DeleteAsync( string @namespace, IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task AllocateKeyAsync( string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task GetKeyFactoryAsync( string @namespace, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task BeginTransactionAsync( string @namespace, diff --git a/UET/Redpoint.CloudFramework/Repository/IRepository.cs b/UET/Redpoint.CloudFramework/Repository/IRepository.cs index 640a8eeb..43c52e0a 100644 --- a/UET/Redpoint.CloudFramework/Repository/IRepository.cs +++ b/UET/Redpoint.CloudFramework/Repository/IRepository.cs @@ -20,7 +20,7 @@ IBatchedAsyncEnumerable QueryAsync( int? limit = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task> QueryPaginatedAsync( PaginatedQueryCursor cursor, @@ -29,76 +29,76 @@ Task> QueryPaginatedAsync( Expression>? order = null, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task LoadAsync( Key key, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IBatchedAsyncEnumerable> LoadAsync( IAsyncEnumerable keys, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task CreateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable CreateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task UpsertAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable UpsertAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task UpdateAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); IAsyncEnumerable UpdateAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task DeleteAsync( T model, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task DeleteAsync( IAsyncEnumerable models, IModelTransaction? transaction = null, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task AllocateKeyAsync( IModelTransaction? transaction, RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task GetKeyFactoryAsync( RepositoryOperationMetrics? metrics = null, - CancellationToken cancellationToken = default) where T : Model, new(); + CancellationToken cancellationToken = default) where T : class, IModel, new(); Task BeginTransactionAsync( TransactionMode mode = TransactionMode.ReadWrite, diff --git a/UET/Redpoint.CloudFramework/Repository/Layers/DatastoreRepositoryLayer.cs b/UET/Redpoint.CloudFramework/Repository/Layers/DatastoreRepositoryLayer.cs index 774ed994..e5f54315 100644 --- a/UET/Redpoint.CloudFramework/Repository/Layers/DatastoreRepositoryLayer.cs +++ b/UET/Redpoint.CloudFramework/Repository/Layers/DatastoreRepositoryLayer.cs @@ -101,7 +101,7 @@ private async IAsyncEnumerable> BatchedQueryAsync( int? limit, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (var span = _managedTracer.StartSpan("db.datastore.query", $"{@namespace},{typeof(T).Name}")) { @@ -268,7 +268,7 @@ public IBatchedAsyncEnumerable QueryAsync( int? limit, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() => + CancellationToken cancellationToken) where T : class, IModel, new() => BatchedQueryAsync( @namespace, where, @@ -287,7 +287,7 @@ private async IAsyncEnumerable> QueryGeohashRange( GeoQueryParameters geoQuery, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.query_geohash_range", $"{@namespace},{typeof(T).Name}")) { @@ -364,7 +364,7 @@ public async Task> QueryPaginatedAsync( Expression>? order, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.query_paginated", $"{@namespace},{typeof(T).Name}")) { @@ -458,7 +458,7 @@ public async Task> QueryPaginatedAsync( Key key, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.load", $"{@namespace},{typeof(T).Name}")) { @@ -524,7 +524,7 @@ public async Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() => BatchedLoadAsync( @namespace, keys, @@ -537,7 +537,7 @@ public async Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.load", $"{@namespace},{typeof(T).Name}")) { @@ -701,7 +701,7 @@ public async Task> QueryPaginatedAsync( public async IAsyncEnumerable> LoadAcrossNamespacesAsync( IAsyncEnumerable keys, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.load_across_namespaces", $"{typeof(T).Name}")) { @@ -803,7 +803,7 @@ public async IAsyncEnumerable CreateAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.create", $"{@namespace},{typeof(T).Name}")) { @@ -948,7 +948,7 @@ public async IAsyncEnumerable UpsertAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.upsert", $"{@namespace},{typeof(T).Name}")) { @@ -1101,7 +1101,7 @@ public async IAsyncEnumerable UpdateAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.update", $"{@namespace},{typeof(T).Name}")) { @@ -1233,7 +1233,7 @@ public async Task DeleteAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.delete", $"{@namespace},{typeof(T).Name}")) { @@ -1358,7 +1358,7 @@ public Task AllocateKeyAsync( string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.allocate_key", $"{@namespace},{typeof(T).Name}")) { @@ -1375,7 +1375,7 @@ public Task AllocateKeyAsync( public Task GetKeyFactoryAsync( string @namespace, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.datastore.get_key_factory", $"{@namespace},{typeof(T).Name}")) { diff --git a/UET/Redpoint.CloudFramework/Repository/Layers/IRepositoryLayer.cs b/UET/Redpoint.CloudFramework/Repository/Layers/IRepositoryLayer.cs index aebb242f..3309c2a8 100644 --- a/UET/Redpoint.CloudFramework/Repository/Layers/IRepositoryLayer.cs +++ b/UET/Redpoint.CloudFramework/Repository/Layers/IRepositoryLayer.cs @@ -75,7 +75,7 @@ IBatchedAsyncEnumerable QueryAsync( int? limit, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new(); + CancellationToken cancellationToken) where T : class, IModel, new(); Task> QueryPaginatedAsync( string @namespace, @@ -85,25 +85,25 @@ Task> QueryPaginatedAsync( Expression>? order, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new(); + CancellationToken cancellationToken) where T : class, IModel, new(); - Task LoadAsync(string @namespace, Key key, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + Task LoadAsync(string @namespace, Key key, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - IBatchedAsyncEnumerable> LoadAsync(string @namespace, IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + IBatchedAsyncEnumerable> LoadAsync(string @namespace, IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - IAsyncEnumerable> LoadAcrossNamespacesAsync(IAsyncEnumerable keys, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + IAsyncEnumerable> LoadAcrossNamespacesAsync(IAsyncEnumerable keys, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - IAsyncEnumerable CreateAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + IAsyncEnumerable CreateAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - IAsyncEnumerable UpsertAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + IAsyncEnumerable UpsertAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - IAsyncEnumerable UpdateAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + IAsyncEnumerable UpdateAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - Task DeleteAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + Task DeleteAsync(string @namespace, IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - Task AllocateKeyAsync(string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + Task AllocateKeyAsync(string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); - Task GetKeyFactoryAsync(string @namespace, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : Model, new(); + Task GetKeyFactoryAsync(string @namespace, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken) where T : class, IModel, new(); Task BeginTransactionAsync(string @namespace, TransactionMode mode, RepositoryOperationMetrics? metrics, CancellationToken cancellationToken); diff --git a/UET/Redpoint.CloudFramework/Repository/Layers/RedisCacheRepositoryLayer.cs b/UET/Redpoint.CloudFramework/Repository/Layers/RedisCacheRepositoryLayer.cs index 15832956..403cbc89 100644 --- a/UET/Redpoint.CloudFramework/Repository/Layers/RedisCacheRepositoryLayer.cs +++ b/UET/Redpoint.CloudFramework/Repository/Layers/RedisCacheRepositoryLayer.cs @@ -296,7 +296,7 @@ private class ComplexCacheKeyJson string @namespace, Expression> where, Expression>? order, - int? limit) where T : Model, new() + int? limit) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.get_complex_cache_hash_and_columns", $"{@namespace},{typeof(T).Name}")) { @@ -543,7 +543,7 @@ private string SerializeValue(Value value) return 'written' "; - private async Task<(RedisKey key, string lastWrite)> GetLastWriteAsync(IDatabase cache, string @namespace, Model model) + private async Task<(RedisKey key, string lastWrite)> GetLastWriteAsync(IDatabase cache, string @namespace, IModel model) { string queryLastWriteValue = "0"; var queryLastWriteKey = new RedisKey($"LASTWRITE:{model.GetKind()}"); @@ -562,7 +562,7 @@ private string SerializeValue(Value value) return (queryLastWriteKey, queryLastWriteValue); } - private static async Task IncrementLastWriteAsync(IDatabase cache, Model model) + private static async Task IncrementLastWriteAsync(IDatabase cache, IModel model) { await cache.StringIncrementAsync($"LASTWRITE:{model.GetKind()}").ConfigureAwait(false); } @@ -579,7 +579,7 @@ public IBatchedAsyncEnumerable QueryAsync( int? limit, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() => BatchedQueryAsync( @namespace, where, @@ -596,7 +596,7 @@ private async IAsyncEnumerable> BatchedQueryAsync( int? limit, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.query", $"{@namespace},{typeof(T).Name}")) { @@ -924,7 +924,7 @@ public async Task> QueryPaginatedAsync( Expression>? order, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.query_paginated", $"{@namespace},{typeof(T).Name}")) { @@ -949,7 +949,7 @@ public async Task> QueryPaginatedAsync( Key key, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.load", $"{@namespace},{typeof(T).Name}")) { @@ -1077,7 +1077,7 @@ public async Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() => BatchedLoadAsync( @namespace, keys, @@ -1090,7 +1090,7 @@ public async Task> QueryPaginatedAsync( IAsyncEnumerable keys, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.load", $"{@namespace},{typeof(T).Name}")) { @@ -1249,7 +1249,7 @@ public async Task> QueryPaginatedAsync( public async IAsyncEnumerable> LoadAcrossNamespacesAsync( IAsyncEnumerable keys, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.load_across_namespaces", $"{typeof(T).Name}")) { @@ -1376,7 +1376,7 @@ public async IAsyncEnumerable CreateAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.create", $"{@namespace},{typeof(T).Name}")) { @@ -1430,7 +1430,7 @@ public async IAsyncEnumerable UpsertAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.upsert", $"{@namespace},{typeof(T).Name}")) { @@ -1485,7 +1485,7 @@ public async IAsyncEnumerable UpdateAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - [EnumeratorCancellation] CancellationToken cancellationToken) where T : Model, new() + [EnumeratorCancellation] CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.update", $"{@namespace},{typeof(T).Name}")) { @@ -1512,7 +1512,7 @@ public async IAsyncEnumerable UpdateAsync( } else { - var newValue = entity.GetType().GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!.GetValue(entity); + var newValue = entity.GetPropertyInfo(kv.Key)!.GetValue(entity); if (newValue == null) { wasColumnModified = oldValue != null; @@ -1596,7 +1596,7 @@ public async Task DeleteAsync( IAsyncEnumerable models, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.delete", $"{@namespace},{typeof(T).Name}")) { @@ -1613,7 +1613,7 @@ public Task AllocateKeyAsync( string @namespace, IModelTransaction? transaction, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.allocate_key", $"{@namespace},{typeof(T).Name}")) { @@ -1624,7 +1624,7 @@ public Task AllocateKeyAsync( public Task GetKeyFactoryAsync( string @namespace, RepositoryOperationMetrics? metrics, - CancellationToken cancellationToken) where T : Model, new() + CancellationToken cancellationToken) where T : class, IModel, new() { using (_managedTracer.StartSpan($"db.rediscache.get_key_factory", $"{@namespace},{typeof(T).Name}")) { @@ -1726,7 +1726,7 @@ public async Task CommitAsync( } else { - var newValue = entity.GetType().GetProperty(kv.Key, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!.GetValue(entity); + var newValue = entity.GetPropertyInfo(kv.Key)!.GetValue(entity); if (newValue == null) { wasColumnModified = oldValue != null; diff --git a/UET/Redpoint.CloudFramework/Repository/Legacy/GlobalRepositoryLegacyExtensions.cs b/UET/Redpoint.CloudFramework/Repository/Legacy/GlobalRepositoryLegacyExtensions.cs index b57cc580..0fd21303 100644 --- a/UET/Redpoint.CloudFramework/Repository/Legacy/GlobalRepositoryLegacyExtensions.cs +++ b/UET/Redpoint.CloudFramework/Repository/Legacy/GlobalRepositoryLegacyExtensions.cs @@ -118,14 +118,14 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static Task> CreateQuery(this IGlobalRepository globalRepository, string @namespace) where T : Model, new() + public static Task> CreateQuery(this IGlobalRepository globalRepository, string @namespace) where T : class, IModel, new() { return Task.FromResult(new ModelQuery(@namespace, new Query(new T().GetKind()))); } [Obsolete("Use QueryAsync instead.")] public static async Task> RunUncachedQuery<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IGlobalRepository globalRepository, string @namespace, ModelQuery query, - ReadOptions.Types.ReadConsistency readConsistency, IModelTransaction? transaction = null) where T : Model, new() + ReadOptions.Types.ReadConsistency readConsistency, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); ArgumentNullException.ThrowIfNull(query); @@ -148,7 +148,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use GetKeyFactoryAsync instead.")] - public static async Task GetKeyFactory(this IGlobalRepository globalRepository, string @namespace) where T : Model, new() + public static async Task GetKeyFactory(this IGlobalRepository globalRepository, string @namespace) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -156,7 +156,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use LoadAsync instead.")] - public static async Task> LoadMany(this IGlobalRepository globalRepository, string @namespace, Key[] keys, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadMany(this IGlobalRepository globalRepository, string @namespace, Key[] keys, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -164,7 +164,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use LoadAcrossNamespacesAsync instead.")] - public static async Task> LoadManyAcrossNamespaces(this IGlobalRepository globalRepository, Key[] keys) where T : Model, new() + public static async Task> LoadManyAcrossNamespaces(this IGlobalRepository globalRepository, Key[] keys) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -172,7 +172,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use LoadAsync instead.")] - public static async Task LoadOneBy(this IGlobalRepository globalRepository, string @namespace, Key key, IModelTransaction? transaction = null) where T : Model, new() + public static async Task LoadOneBy(this IGlobalRepository globalRepository, string @namespace, Key key, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -180,7 +180,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static async Task LoadOneBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IGlobalRepository globalRepository, string @namespace, string field, TValue value, IModelTransaction? transaction = null) where T : Model, new() + public static async Task LoadOneBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IGlobalRepository globalRepository, string @namespace, string field, TValue value, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -206,7 +206,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAllBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IGlobalRepository globalRepository, string @namespace, string field, TValue? value, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAllBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IGlobalRepository globalRepository, string @namespace, string field, TValue? value, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -232,7 +232,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAll(this IGlobalRepository globalRepository, string @namespace, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAll(this IGlobalRepository globalRepository, string @namespace, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -240,7 +240,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAllUncached(this IGlobalRepository globalRepository, string @namespace, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAllUncached(this IGlobalRepository globalRepository, string @namespace, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -250,7 +250,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use QueryAsync instead.")] - public static async IAsyncEnumerable LoadAllByFiltersUncached<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IGlobalRepository globalRepository, string @namespace, Filter filter) where T : Model, new() + public static async IAsyncEnumerable LoadAllByFiltersUncached<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IGlobalRepository globalRepository, string @namespace, Filter filter) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -265,7 +265,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use CreateAsync instead.")] - public static async Task Create(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Create(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -273,7 +273,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use CreateAsync instead.")] - public static async Task CreateMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : Model, new() + public static async Task CreateMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -281,7 +281,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use UpsertAsync instead.")] - public static async Task Upsert(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Upsert(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -289,7 +289,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use UpdateAsync instead.")] - public static async Task Update(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Update(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -297,7 +297,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use UpdateAsync instead.")] - public static async Task UpdateMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : Model, new() + public static async Task UpdateMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -305,7 +305,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use DeleteAsync instead.")] - public static async Task Delete(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Delete(this IGlobalRepository globalRepository, string @namespace, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -313,7 +313,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use DeleteAsync instead.")] - public static async Task DeleteMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : Model, new() + public static async Task DeleteMany(this IGlobalRepository globalRepository, string @namespace, IList models) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); @@ -321,7 +321,7 @@ private static IRepositoryLayer R(IGlobalRepository globalRepository) } [Obsolete("Use GetKeyFactoryAsync and create the named key manually.")] - public static async Task CreateNamedKey(this IGlobalRepository globalRepository, string @namespace, string name) where T : Model, new() + public static async Task CreateNamedKey(this IGlobalRepository globalRepository, string @namespace, string name) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(globalRepository); diff --git a/UET/Redpoint.CloudFramework/Repository/Legacy/MappedDatastoreQueryResults.cs b/UET/Redpoint.CloudFramework/Repository/Legacy/MappedDatastoreQueryResults.cs index 9c6cfa03..9d03beb7 100644 --- a/UET/Redpoint.CloudFramework/Repository/Legacy/MappedDatastoreQueryResults.cs +++ b/UET/Redpoint.CloudFramework/Repository/Legacy/MappedDatastoreQueryResults.cs @@ -5,7 +5,7 @@ using Google.Protobuf; using Redpoint.CloudFramework.Models; - public sealed class MappedDatastoreQueryResults where T : Model, new() + public sealed class MappedDatastoreQueryResults where T : class, IModel, new() { public required ByteString EndCursor { get; set; } public required string? EndCursorForClients { get; set; } diff --git a/UET/Redpoint.CloudFramework/Repository/Legacy/ModelQuery.cs b/UET/Redpoint.CloudFramework/Repository/Legacy/ModelQuery.cs index 627d684a..aca40a1d 100644 --- a/UET/Redpoint.CloudFramework/Repository/Legacy/ModelQuery.cs +++ b/UET/Redpoint.CloudFramework/Repository/Legacy/ModelQuery.cs @@ -3,7 +3,7 @@ using Google.Cloud.Datastore.V1; using Redpoint.CloudFramework.Models; - public class ModelQuery where T : Model, new() + public class ModelQuery where T : class, IModel, new() { public ModelQuery(string @namespace, Query query) { diff --git a/UET/Redpoint.CloudFramework/Repository/Legacy/RepositoryLegacyExtensions.cs b/UET/Redpoint.CloudFramework/Repository/Legacy/RepositoryLegacyExtensions.cs index 6f51cf7d..63b981fe 100644 --- a/UET/Redpoint.CloudFramework/Repository/Legacy/RepositoryLegacyExtensions.cs +++ b/UET/Redpoint.CloudFramework/Repository/Legacy/RepositoryLegacyExtensions.cs @@ -20,7 +20,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> CreateQuery(this IRepository repository) where T : Model, new() + public static async Task> CreateQuery(this IRepository repository) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -29,7 +29,7 @@ private static DatastoreGlobalRepository G(IRepository repository) [Obsolete("Use QueryAsync instead.")] public static async Task> RunUncachedQuery<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IRepository repository, ModelQuery query, - ReadOptions.Types.ReadConsistency readConsistency, IModelTransaction? transaction = null) where T : Model, new() + ReadOptions.Types.ReadConsistency readConsistency, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -37,7 +37,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use GetKeyFactoryAsync instead.")] - public static async Task GetKeyFactory(this IRepository repository) where T : Model, new() + public static async Task GetKeyFactory(this IRepository repository) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -45,7 +45,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use LoadAsync instead.")] - public static async Task> LoadMany(this IRepository repository, Key[] keys, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadMany(this IRepository repository, Key[] keys, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -53,7 +53,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use LoadAsync instead.")] - public static async Task LoadOneBy(this IRepository repository, Key key, IModelTransaction? transaction = null) where T : Model, new() + public static async Task LoadOneBy(this IRepository repository, Key key, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -61,7 +61,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async Task LoadOneBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IRepository repository, string field, TValue value, IModelTransaction? transaction = null) where T : Model, new() + public static async Task LoadOneBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IRepository repository, string field, TValue value, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -69,7 +69,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAllBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IRepository repository, string field, TValue value, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAllBy<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T, TValue>(this IRepository repository, string field, TValue value, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -77,7 +77,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAll(this IRepository repository, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAll(this IRepository repository, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -85,7 +85,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async Task> LoadAllUncached(this IRepository repository, IModelTransaction? transaction = null) where T : Model, new() + public static async Task> LoadAllUncached(this IRepository repository, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -93,7 +93,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use QueryAsync instead.")] - public static async IAsyncEnumerable LoadAllByFiltersUncached<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IRepository repository, Filter filter) where T : Model, new() + public static async IAsyncEnumerable LoadAllByFiltersUncached<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] T>(this IRepository repository, Filter filter) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -104,7 +104,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use CreateAsync instead.")] - public static async Task Create(this IRepository repository, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Create(this IRepository repository, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -112,7 +112,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use CreateAsync instead.")] - public static async Task CreateMany(this IRepository repository, IList models) where T : Model, new() + public static async Task CreateMany(this IRepository repository, IList models) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -120,7 +120,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use UpsertAsync instead.")] - public static async Task Upsert(this IRepository repository, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Upsert(this IRepository repository, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -128,7 +128,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use UpdateAsync instead.")] - public static async Task Update(this IRepository repository, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Update(this IRepository repository, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); @@ -136,7 +136,7 @@ private static DatastoreGlobalRepository G(IRepository repository) } [Obsolete("Use DeleteAsync instead.")] - public static async Task Delete(this IRepository repository, T model, IModelTransaction? transaction = null) where T : Model, new() + public static async Task Delete(this IRepository repository, T model, IModelTransaction? transaction = null) where T : class, IModel, new() { ArgumentNullException.ThrowIfNull(repository); diff --git a/UET/Redpoint.CloudFramework/Repository/Migration/DatastoreStartupMigrator.cs b/UET/Redpoint.CloudFramework/Repository/Migration/DatastoreStartupMigrator.cs index 90ef6293..9aba97bd 100644 --- a/UET/Redpoint.CloudFramework/Repository/Migration/DatastoreStartupMigrator.cs +++ b/UET/Redpoint.CloudFramework/Repository/Migration/DatastoreStartupMigrator.cs @@ -41,27 +41,9 @@ public DatastoreStartupMigrator( /// /// This isn't a real model; we just use it to construct keys for locking. /// - private class RCFMigrationLockModel : Model + [Kind("rcf_migrationLock")] + private class RCFMigrationLockModel : Model { - public override HashSet GetIndexes() - { - throw new NotImplementedException(); - } - - public override string GetKind() - { - return "rcf_migrationLock"; - } - - public override long GetSchemaVersion() - { - throw new NotImplementedException(); - } - - public override Dictionary GetTypes() - { - throw new NotImplementedException(); - } } public async Task StartAsync(CancellationToken cancellationToken) @@ -84,7 +66,7 @@ public async Task StartAsync(CancellationToken cancellationToken) _logger.LogInformation($"Running database migrators for: {modelGroup.Key.FullName}"); #pragma warning disable IL2072 // DynamicallyAccessedMembers is set on ModelType. - var referenceModel = (Model)Activator.CreateInstance(modelGroup.Key)!; + var referenceModel = (IModel)Activator.CreateInstance(modelGroup.Key)!; #pragma warning restore IL2072 var currentSchemaVersion = referenceModel.GetSchemaVersion(); diff --git a/UET/Redpoint.CloudFramework/Repository/Migration/IModelMigrator.cs b/UET/Redpoint.CloudFramework/Repository/Migration/IModelMigrator.cs index 47dfe368..284053f8 100644 --- a/UET/Redpoint.CloudFramework/Repository/Migration/IModelMigrator.cs +++ b/UET/Redpoint.CloudFramework/Repository/Migration/IModelMigrator.cs @@ -3,7 +3,7 @@ using Redpoint.CloudFramework.Models; using System.Threading.Tasks; - public interface IModelMigrator where T : Model + public interface IModelMigrator where T : IModel { /// /// Migrate the specified model to the version this migrator was registered for. Returns true if the model had diff --git a/UET/Redpoint.CloudFramework/Repository/Migration/MigrationServiceExtensions.cs b/UET/Redpoint.CloudFramework/Repository/Migration/MigrationServiceExtensions.cs index 0b295414..d1caa085 100644 --- a/UET/Redpoint.CloudFramework/Repository/Migration/MigrationServiceExtensions.cs +++ b/UET/Redpoint.CloudFramework/Repository/Migration/MigrationServiceExtensions.cs @@ -6,7 +6,7 @@ public static class MigrationServiceExtensions { - public static IServiceCollection AddMigration<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TModel, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] TMigration>(this IServiceCollection services, int toSchemaVersion) where TModel : Model, new() where TMigration : IModelMigrator + public static IServiceCollection AddMigration<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TModel, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] TMigration>(this IServiceCollection services, int toSchemaVersion) where TModel : class, IModel, new() where TMigration : IModelMigrator { services.AddTransient(sp => { diff --git a/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigrator.cs b/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigrator.cs index b0c99109..b1a0573b 100644 --- a/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigrator.cs +++ b/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigrator.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; - internal sealed class RegisteredModelMigrator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T> : RegisteredModelMigratorBase where T : Model, new () + internal sealed class RegisteredModelMigrator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T> : RegisteredModelMigratorBase where T : class, IModel, new() { public override Type ModelType { get; } = typeof(T); @@ -15,7 +15,7 @@ public override async Task UpdateAsync(IGlobalRepository globalRepository, objec await globalRepository.UpdateAsync(string.Empty, (T)model, null, null, CancellationToken.None).ConfigureAwait(false); } - public override IAsyncEnumerable QueryForOutdatedModelsAsync(IDatastoreRepositoryLayer drl, long currentSchemaVersion) + public override IAsyncEnumerable QueryForOutdatedModelsAsync(IDatastoreRepositoryLayer drl, long currentSchemaVersion) { return drl.QueryAsync( string.Empty, @@ -24,7 +24,7 @@ public override IAsyncEnumerable QueryForOutdatedModelsAsync(IDatastoreRe null, null, null, - CancellationToken.None).Cast(); + CancellationToken.None).Cast(); } } } diff --git a/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigratorBase.cs b/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigratorBase.cs index 203c033b..a8fb91a4 100644 --- a/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigratorBase.cs +++ b/UET/Redpoint.CloudFramework/Repository/Migration/RegisteredModelMigratorBase.cs @@ -16,6 +16,6 @@ internal abstract class RegisteredModelMigratorBase public abstract Task UpdateAsync(IGlobalRepository globalRepository, object model); - public abstract IAsyncEnumerable QueryForOutdatedModelsAsync(IDatastoreRepositoryLayer drl, long currentSchemaVersion); + public abstract IAsyncEnumerable QueryForOutdatedModelsAsync(IDatastoreRepositoryLayer drl, long currentSchemaVersion); } } diff --git a/UET/Redpoint.CloudFramework/Repository/Pagination/PaginatedQueryResult.cs b/UET/Redpoint.CloudFramework/Repository/Pagination/PaginatedQueryResult.cs index 64e9ad4a..45aed374 100644 --- a/UET/Redpoint.CloudFramework/Repository/Pagination/PaginatedQueryResult.cs +++ b/UET/Redpoint.CloudFramework/Repository/Pagination/PaginatedQueryResult.cs @@ -3,7 +3,7 @@ using Redpoint.CloudFramework.Models; using System.Collections.Generic; - public record struct PaginatedQueryResult where T : Model, new() + public record struct PaginatedQueryResult where T : class, IModel, new() { public PaginatedQueryCursor? NextCursor { get; set; } diff --git a/UET/Redpoint.CloudFramework/Repository/Transaction/IModelTransaction.cs b/UET/Redpoint.CloudFramework/Repository/Transaction/IModelTransaction.cs index 89e08e11..ccf98b7b 100644 --- a/UET/Redpoint.CloudFramework/Repository/Transaction/IModelTransaction.cs +++ b/UET/Redpoint.CloudFramework/Repository/Transaction/IModelTransaction.cs @@ -22,8 +22,8 @@ public interface IModelTransaction : IAsyncDisposable /// /// A list of models that have been modified by this transaction. /// - IReadOnlyList ModifiedModels { get; } - internal List ModifiedModelsList { get; } + IReadOnlyList ModifiedModels { get; } + internal List ModifiedModelsList { get; } /// /// A list of queued operations to be performed immediately before diff --git a/UET/Redpoint.CloudFramework/Repository/Transaction/NestedModelTransaction.cs b/UET/Redpoint.CloudFramework/Repository/Transaction/NestedModelTransaction.cs index 0ef3881a..fc326e5c 100644 --- a/UET/Redpoint.CloudFramework/Repository/Transaction/NestedModelTransaction.cs +++ b/UET/Redpoint.CloudFramework/Repository/Transaction/NestedModelTransaction.cs @@ -20,8 +20,8 @@ internal NestedModelTransaction( public DatastoreTransaction Transaction => _transaction.Transaction; - public IReadOnlyList ModifiedModels => _transaction.ModifiedModels; - public List ModifiedModelsList => _transaction.ModifiedModelsList; + public IReadOnlyList ModifiedModels => _transaction.ModifiedModels; + public List ModifiedModelsList => _transaction.ModifiedModelsList; public IReadOnlyList> QueuedPreCommitOperations => _transaction.QueuedPreCommitOperations; public List> QueuedPreCommitOperationsList => _transaction.QueuedPreCommitOperationsList; diff --git a/UET/Redpoint.CloudFramework/Repository/Transaction/TopLevelModelTransaction.cs b/UET/Redpoint.CloudFramework/Repository/Transaction/TopLevelModelTransaction.cs index cbe7a8c4..60e60e4e 100644 --- a/UET/Redpoint.CloudFramework/Repository/Transaction/TopLevelModelTransaction.cs +++ b/UET/Redpoint.CloudFramework/Repository/Transaction/TopLevelModelTransaction.cs @@ -21,7 +21,7 @@ internal TopLevelModelTransaction( Namespace = @namespace; Transaction = transaction; - ModifiedModelsList = new List(); + ModifiedModelsList = new List(); QueuedPreCommitOperationsList = new List>(); HasCommitted = false; HasRolledBack = false; @@ -31,8 +31,8 @@ internal TopLevelModelTransaction( public DatastoreTransaction Transaction { get; } - public IReadOnlyList ModifiedModels => ModifiedModelsList; - public List ModifiedModelsList { get; } + public IReadOnlyList ModifiedModels => ModifiedModelsList; + public List ModifiedModelsList { get; } public IReadOnlyList> QueuedPreCommitOperations => QueuedPreCommitOperationsList; public List> QueuedPreCommitOperationsList { get; } diff --git a/UET/Redpoint.CloudFramework/Startup/DefaultWebAppConfigurator.cs b/UET/Redpoint.CloudFramework/Startup/DefaultWebAppConfigurator.cs index cf7f3983..0c75919e 100644 --- a/UET/Redpoint.CloudFramework/Startup/DefaultWebAppConfigurator.cs +++ b/UET/Redpoint.CloudFramework/Startup/DefaultWebAppConfigurator.cs @@ -287,9 +287,6 @@ protected override void PostStartupConfigureServices(IServiceCollection services { services.AddScoped(); services.AddScoped(); -#pragma warning disable CS0618 // Type or member is obsolete - services.AddScoped(); -#pragma warning restore CS0618 // Type or member is obsolete services.AddScoped(); } services.AddScoped(); diff --git a/UET/Redpoint.Uefs.Daemon/Redpoint.Uefs.Daemon.csproj b/UET/Redpoint.Uefs.Daemon/Redpoint.Uefs.Daemon.csproj index 6d1571df..454352fb 100644 --- a/UET/Redpoint.Uefs.Daemon/Redpoint.Uefs.Daemon.csproj +++ b/UET/Redpoint.Uefs.Daemon/Redpoint.Uefs.Daemon.csproj @@ -15,7 +15,7 @@ - + diff --git a/UET/UET.sln b/UET/UET.sln index 2219ad21..87ca3d5d 100644 --- a/UET/UET.sln +++ b/UET/UET.sln @@ -329,6 +329,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.ThirdParty.React.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.ThirdParty.React.AspNet.Middleware", "Lib\Redpoint.ThirdParty.React.AspNet.Middleware\Redpoint.ThirdParty.React.AspNet.Middleware.csproj", "{3170055B-07AC-4BEB-ACFA-233108B09BCA}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.CloudFramework.Analyzer", "Redpoint.CloudFramework.Analyzer\Redpoint.CloudFramework.Analyzer.csproj", "{9B5C7773-7DF7-40B8-8C40-59A2E9823444}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.CloudFramework.Analyzer.CodeFixes", "Redpoint.CloudFramework.Analyzer.CodeFixes\Redpoint.CloudFramework.Analyzer.CodeFixes.csproj", "{A0A63A95-390D-494F-8AC0-7D0B0836770D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.CloudFramework.Analyzer.Package", "Redpoint.CloudFramework.Analyzer.Package\Redpoint.CloudFramework.Analyzer.Package.csproj", "{C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Redpoint.CloudFramework.Analyzer.Tests", "Redpoint.CloudFramework.Analyzer.Tests\Redpoint.CloudFramework.Analyzer.Tests.csproj", "{1078157C-2CCD-4A35-9910-2880C2E7EC03}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -895,6 +903,22 @@ Global {3170055B-07AC-4BEB-ACFA-233108B09BCA}.Debug|Any CPU.Build.0 = Debug|Any CPU {3170055B-07AC-4BEB-ACFA-233108B09BCA}.Release|Any CPU.ActiveCfg = Release|Any CPU {3170055B-07AC-4BEB-ACFA-233108B09BCA}.Release|Any CPU.Build.0 = Release|Any CPU + {9B5C7773-7DF7-40B8-8C40-59A2E9823444}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B5C7773-7DF7-40B8-8C40-59A2E9823444}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B5C7773-7DF7-40B8-8C40-59A2E9823444}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B5C7773-7DF7-40B8-8C40-59A2E9823444}.Release|Any CPU.Build.0 = Release|Any CPU + {A0A63A95-390D-494F-8AC0-7D0B0836770D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0A63A95-390D-494F-8AC0-7D0B0836770D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0A63A95-390D-494F-8AC0-7D0B0836770D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0A63A95-390D-494F-8AC0-7D0B0836770D}.Release|Any CPU.Build.0 = Release|Any CPU + {C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5}.Release|Any CPU.Build.0 = Release|Any CPU + {1078157C-2CCD-4A35-9910-2880C2E7EC03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1078157C-2CCD-4A35-9910-2880C2E7EC03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1078157C-2CCD-4A35-9910-2880C2E7EC03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1078157C-2CCD-4A35-9910-2880C2E7EC03}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1010,6 +1034,10 @@ Global {6DAC890A-6F44-48E4-8856-CA0F4B386ABD} = {39698A12-7C9B-47F5-BA1A-9F4884A770AF} {E8A9F0BC-A88B-4ECC-AF23-5CE16D034AD4} = {39698A12-7C9B-47F5-BA1A-9F4884A770AF} {3170055B-07AC-4BEB-ACFA-233108B09BCA} = {39698A12-7C9B-47F5-BA1A-9F4884A770AF} + {9B5C7773-7DF7-40B8-8C40-59A2E9823444} = {A93D92E5-865B-4681-AC53-4B1689F1B3E8} + {A0A63A95-390D-494F-8AC0-7D0B0836770D} = {A93D92E5-865B-4681-AC53-4B1689F1B3E8} + {C303BBC5-B739-45DB-A1DC-DFCA71DAA1C5} = {A93D92E5-865B-4681-AC53-4B1689F1B3E8} + {1078157C-2CCD-4A35-9910-2880C2E7EC03} = {A93D92E5-865B-4681-AC53-4B1689F1B3E8} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8598A278-509A-48A6-A7B3-3E3B0D1011F1}