< Summary - ZhEaIsNsAaBn.Libraries coverage (affected: 23 packages)

Information
Line coverage
100%
Covered lines: 50
Uncovered lines: 0
Coverable lines: 50
Total lines: 129
Line coverage: 100%
Branch coverage
92%
Covered branches: 35
Total branches: 38
Branch coverage: 92.1%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
FromAssembly(...)87.5%88100%
Parse(...)92.85%2828100%
Deserialize(...)100%22100%
NormalizeFolder(...)100%11100%

File(s)

/home/runner/work/ZhEaIsNsAaBn.Libraries.V1/ZhEaIsNsAaBn.Libraries.V1/src/DataBasePackages/ZhEaIsNsAaBn.DbSeeder/Migrations/SeedMigrationSource.cs

#LineLine coverage
 1#region Usings
 2using System.Text.Json.Serialization;
 3using System.Text.RegularExpressions;
 4#endregion
 5
 6namespace ZhEaIsNsAaBn.DbSeeder;
 7
 8/// <summary>
 9/// Reads the seed migrations embedded in an assembly by <c>buildTransitive/ZhEaIsNsAaBn.DbSeeder.props</c>: resources n
 10/// <c>{folder}/{id}/manifest.json</c> and <c>{folder}/{id}/{Table}.json</c> (separators normalised to <c>/</c>).
 11/// </summary>
 12internal static partial class SeedMigrationSource
 13{
 1014    internal static readonly JsonSerializerOptions JsonOptions = new()
 1015                                                                {
 1016                                                                    UnmappedMemberHandling = JsonUnmappedMemberHandling.
 1017                                                                    ReadCommentHandling    = JsonCommentHandling.Disallo
 1018                                                                    AllowTrailingCommas    = false
 1019                                                                };
 20
 21
 22    /// Reads every embedded file under <paramref name="folder"/> and parses the migrations of <paramref name="contextTy
 23    internal static LayerResponse<IReadOnlyList<SeedMigrationFiles>> FromAssembly( Assembly assembly, string folder, str
 24    {
 13125        var files  = new Dictionary<string, string>( StringComparer.Ordinal );
 13126        var prefix = NormalizeFolder( folder ) + "/";
 27
 28
 338429        foreach ( var name in assembly.GetManifestResourceNames() )
 30        {
 156131            var path = name.Replace( '\\', '/' );
 32
 156133            if ( !path.StartsWith( prefix, StringComparison.Ordinal ) || !path.EndsWith( ".json", StringComparison.Ordin
 34
 35
 156136            using var stream = assembly.GetManifestResourceStream( name ) ?? throw new InvalidOperationException( $"Reso
 156137            using var reader = new StreamReader( stream );
 38
 156139            files[ path ] = reader.ReadToEnd();
 40        }
 41
 42
 13143        return Parse( files, folder, contextType );
 44    }
 45
 46    /// Parses the migrations of <paramref name="contextType"/> from <paramref name="files"/> (resource path → text), in
 47    internal static LayerResponse<IReadOnlyList<SeedMigrationFiles>> Parse( IReadOnlyDictionary<string, string> files, s
 48    {
 58149        var prefix     = NormalizeFolder( folder ) + "/";
 58150        var migrations = new List<SeedMigrationFiles>();
 51
 52
 318253        foreach ( var manifestPath in files.Keys.Where( p => p.StartsWith( prefix, StringComparison.Ordinal ) && p.EndsW
 58154                                           .Order( StringComparer.Ordinal ) )
 55        {
 102556            var folderName = manifestPath[ prefix.Length..^( SeedChecksum.ManifestFile.Length + 1 ) ];
 57
 102558            if ( folderName.Contains( '/', StringComparison.Ordinal ) ) { continue; }   // only {folder}/{id}/manifest.j
 59
 60
 102261            var parsed = Deserialize<SeedManifest>( manifestPath, files[ manifestPath ] );
 62
 102263            if ( parsed.IsFailure ) { return LayerResponse<IReadOnlyList<SeedMigrationFiles>>.Failure( parsed ); }
 64
 65
 102266            var manifest = parsed.Result!;
 67
 102268            if ( !string.Equals( manifest.ContextType, contextType, StringComparison.Ordinal ) ) { continue; }
 69
 83270            if ( !IdPattern().IsMatch( manifest.Id ) )
 71            {
 372                return SeedErrors.InvalidDataFile( manifestPath, $"Id '{manifest.Id}' isn't yyyyMMddHHmmss_Name." );
 73            }
 74
 82975            if ( manifest.Id != folderName )
 76            {
 377                return SeedErrors.InvalidDataFile( manifestPath, $"Id '{manifest.Id}' doesn't match the folder '{folderN
 78            }
 79
 80
 82681            var tables    = new List<(string File, string Path, SeedTableFile Table)>();
 82682            var checksums = new List<KeyValuePair<string, string>> { new( SeedChecksum.ManifestFile, files[ manifestPath
 83
 586084            foreach ( var file in manifest.Tables )
 85            {
 211686                if ( file.Contains( '/', StringComparison.Ordinal ) || file.Contains( '\\', StringComparison.Ordinal ) |
 211687                  || tables.Any( t => t.File == file ) )
 88                {
 689                    return SeedErrors.InvalidDataFile( manifestPath, $"Table file '{file}' must be a distinct file name 
 90                }
 91
 211092                var path = $"{prefix}{folderName}/{file}";
 93
 211394                if ( !files.TryGetValue( path, out var text ) ) { return SeedErrors.MigrationNotFound( manifest.Id, file
 95
 96
 210797                var table = Deserialize<SeedTableFile>( path, text );
 98
 212299                if ( table.IsFailure ) { return LayerResponse<IReadOnlyList<SeedMigrationFiles>>.Failure( table ); }
 100
 2092101                tables.Add( ( file, path, table.Result! ) );
 2092102                checksums.Add( new KeyValuePair<string, string>( file, text ) );
 103            }
 104
 105
 802106            migrations.Add( new SeedMigrationFiles { Id = manifest.Id, Manifest = manifest, Tables = tables, Checksum = 
 107        }
 108
 109
 551110        return migrations;
 30111    }
 112
 113    private static LayerResponse<T> Deserialize< T >( string path, string text )
 114        where T : class
 115    {
 116        try
 117        {
 3129118            return JsonSerializer.Deserialize<T>( SeedChecksum.Normalize( text ), JsonOptions ) is { } value
 3129119                       ? value
 3129120                       : SeedErrors.InvalidDataFile( path, "The file is empty (null)." );
 121        }
 24122        catch ( JsonException ex ) { return SeedErrors.InvalidDataFile( path, ex.Message, ex ); }
 3129123    }
 124
 712125    private static string NormalizeFolder( string folder ) => folder.Replace( '\\', '/' ).Trim( '/' );
 126
 127    [ GeneratedRegex( "^[0-9]{14}_[A-Za-z][A-Za-z0-9_]*$", RegexOptions.CultureInvariant ) ]
 128    private static partial Regex IdPattern();
 129}