Skip to content

Commit 46378bf

Browse files
feat: enable static analysis (#44)
* feat: implement fixes from static analysis * feat: add static analysis configuration * feat: add static analysis configuration * fix: remove duplicate .sln file * Update user-management/src/Stickerlandia.UserManagement.Api/Configurations/RemoveSwaggerDefinitionsFilter.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5dc72ca commit 46378bf

84 files changed

Lines changed: 680 additions & 2757 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

user-management/CodeAnalysis.props

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
3+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
4+
<PropertyGroup Condition=" '$(MSBuildProjectExtension)' == '.csproj' ">
5+
<EnableNETAnalyzers>true</EnableNETAnalyzers>
6+
</PropertyGroup>
7+
<ItemGroup Condition=" '$(MSBuildProjectExtension)' == '.csproj' ">
8+
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0" />
9+
</ItemGroup>
10+
<PropertyGroup Condition=" '$(MSBuildProjectExtension)' == '.csproj' ">
11+
<AnalysisMode>All</AnalysisMode>
12+
<AnalysisLevel>9.0</AnalysisLevel>
13+
<_SkipUpgradeNetAnalyzersNuGetWarning>false</_SkipUpgradeNetAnalyzersNuGetWarning>
14+
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
15+
<!-- CA2007 is calling .ConfigureAwait(false), which isn't required for non-libraries -->
16+
<NoWarn>$(NoWarn);CA2007</NoWarn>
17+
</PropertyGroup>
18+
</Project>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<Project>
2+
<PropertyGroup>
3+
<ImplicitUsings>enable</ImplicitUsings>
4+
<Nullable>enable</Nullable>
5+
6+
<!-- Configure code analysis. -->
7+
<AnalysisLevel>latest</AnalysisLevel>
8+
<AnalysisMode>All</AnalysisMode>
9+
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
10+
<CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors>
11+
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
12+
</PropertyGroup>
13+
<Import Project="$(MSBuildThisFileDirectory)\CodeAnalysis.props"
14+
Condition=" '$(SkipCodeAnalysis)' != 'True' " />
15+
</Project>

user-management/src/Stickerlandia.UserManagement.Agnostic/DbContextFactory.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
using Microsoft.EntityFrameworkCore;
22
using Microsoft.Extensions.Configuration;
33
using Microsoft.Extensions.DependencyInjection;
4-
using Stickerlandia.UserManagement.Core;
5-
using Stickerlandia.UserManagement.Core.Outbox;
64

75
namespace Stickerlandia.UserManagement.Agnostic;
86

user-management/src/Stickerlandia.UserManagement.Agnostic/KafakStickerClaimedWorker.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2025 Datadog, Inc.
44

5-
using System.Text;
5+
// Catching generic exceptions is not recommended, but in this case we want to catch all exceptions so that a failure in outbox processing does not crash the application.
6+
#pragma warning disable CA1031
7+
68
using System.Text.Json;
79
using Confluent.Kafka;
810
using Datadog.Trace;
911
using Microsoft.Extensions.DependencyInjection;
1012
using Microsoft.Extensions.Logging;
1113
using Saunter.Attributes;
14+
using Stickerlandia.UserManagement.Core.Observability;
1215
using Stickerlandia.UserManagement.Core;
1316
using Stickerlandia.UserManagement.Core.StickerClaimedEvent;
1417

@@ -22,18 +25,17 @@ public class KafakStickerClaimedWorker(
2225
ProducerConfig producerConfig)
2326
: IMessagingWorker
2427
{
25-
private readonly ProducerConfig _producerConfig = producerConfig;
2628
private const string topic = "users.stickerClaimed.v1";
2729
private const string dlqTopic = "users.stickerClaimed.v1.dlq";
2830

2931
[Channel("users.stickerClaimed.v1")]
3032
[SubscribeOperation(typeof(StickerClaimedEventV1))]
31-
private async Task<bool> ProcessMessageAsync(StickerClaimedEventHandler handler,
33+
private async Task<bool> ProcessMessageAsync(StickerClaimedHandler handler,
3234
ConsumeResult<string, string> consumeResult)
3335
{
3436
using var processSpan = Tracer.Instance.StartActive($"process users.stickerClaimed.v1");
3537

36-
logger.LogInformation("Received message: {body}", consumeResult.Message.Value);
38+
Log.ReceivedMessage(logger, "kafka");
3739

3840
var evtData = JsonSerializer.Deserialize<StickerClaimedEventV1>(consumeResult.Message.Value);
3941

@@ -45,7 +47,7 @@ private async Task<bool> ProcessMessageAsync(StickerClaimedEventHandler handler,
4547
}
4648
catch (InvalidUserException ex)
4749
{
48-
logger.LogWarning("Invalid user in sticker claimed event: {Message}", ex.Message);
50+
Log.InvalidUser(logger, ex);
4951

5052
SendToDLQ(consumeResult, evtData);
5153
}
@@ -62,17 +64,17 @@ private void SendToDLQ(ConsumeResult<string, string> consumeResult, StickerClaim
6264
(deliveryReport) =>
6365
{
6466
if (deliveryReport.Error.Code != ErrorCode.NoError)
65-
logger.LogError($"Failed to deliver message: {deliveryReport.Error.Reason}");
67+
Log.MessageDeliveryFailure(logger, deliveryReport.Error.Reason, null);
6668
else
67-
logger.LogWarning($"Produced event to topic {dlqTopic}");
69+
Log.MessageSentToDlq(logger, "", null);
6870
});
6971

7072
producer.Flush(TimeSpan.FromSeconds(10));
7173
}
7274

7375
public Task StartAsync()
7476
{
75-
logger.LogInformation("Starting Kafka processor");
77+
Log.StartingMessageProcessor(logger, "kafka");
7678

7779
return Task.CompletedTask;
7880
}
@@ -84,12 +86,12 @@ public async Task PollAsync(CancellationToken stoppingToken)
8486
return;
8587

8688
using var scope = serviceScopeFactory.CreateScope();
87-
var handler = scope.ServiceProvider.GetRequiredService<StickerClaimedEventHandler>();
89+
var handler = scope.ServiceProvider.GetRequiredService<StickerClaimedHandler>();
8890

8991
try
9092
{
9193
using var consumer = new ConsumerBuilder<string, string>(consumerConfig)
92-
.SetErrorHandler((_, e) => logger.LogError("Kafka error: {Error}", e.Reason))
94+
.SetErrorHandler((_, e) => Log.MessageProcessingException(logger, e.Reason, null))
9395
.Build();
9496

9597
consumer.Subscribe(topic);
@@ -107,16 +109,16 @@ public async Task PollAsync(CancellationToken stoppingToken)
107109
}
108110
catch (ConsumeException ex)
109111
{
110-
logger.LogError(ex, "Error consuming message");
112+
Log.MessageProcessingException(logger, "Error consuming message", ex);
111113
}
112114
catch (OperationCanceledException)
113115
{
114116
// This is expected when the token is canceled
115-
logger.LogInformation("Polling canceled");
117+
Log.TokenCancelled(logger);
116118
}
117119
catch (Exception ex)
118120
{
119-
logger.LogError(ex, "Error processing message");
121+
Log.MessageProcessingException(logger, "Error consuming message", ex);
120122
}
121123
finally
122124
{
@@ -126,7 +128,8 @@ public async Task PollAsync(CancellationToken stoppingToken)
126128
}
127129
catch (Exception ex)
128130
{
129-
logger.LogError(ex, "Failed to create Kafka consumer");
131+
Log.FailureStartingWorker(logger, "kafka", ex);
132+
throw;
130133
}
131134
}
132135

user-management/src/Stickerlandia.UserManagement.Agnostic/KafkaEventPublisher.cs

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Datadog.Trace;
1010
using Microsoft.Extensions.Logging;
1111
using Saunter.Attributes;
12+
using Stickerlandia.UserManagement.Core.Observability;
1213
using Stickerlandia.UserManagement.Core;
1314
using Stickerlandia.UserManagement.Core.RegisterUser;
1415

@@ -20,19 +21,21 @@ public class KafkaEventPublisher(ProducerConfig config, ILogger<KafkaEventPublis
2021
[PublishOperation(typeof(UserRegisteredEvent))]
2122
public async Task PublishUserRegisteredEventV1(UserRegisteredEvent userRegisteredEvent)
2223
{
24+
ArgumentNullException.ThrowIfNull(userRegisteredEvent, nameof(userRegisteredEvent));
25+
2326
var cloudEvent = new CloudEvent(CloudEventsSpecVersion.V1_0)
2427
{
2528
Id = Guid.NewGuid().ToString(),
2629
Source = new Uri("https://stickerlandia.com"),
2730
Type = userRegisteredEvent.EventName,
2831
Time = DateTime.UtcNow,
29-
Data = userRegisteredEvent,
32+
Data = userRegisteredEvent
3033
};
31-
32-
await this.Publish(cloudEvent);
34+
35+
await Publish(cloudEvent);
3336
}
3437

35-
private Task Publish(CloudEvent cloudEvent)
38+
private async Task Publish(CloudEvent cloudEvent)
3639
{
3740
var activeSpan = Tracer.Instance.ActiveScope?.Span;
3841
IScope? processScope = null;
@@ -41,42 +44,58 @@ private Task Publish(CloudEvent cloudEvent)
4144
{
4245
if (activeSpan != null)
4346
{
44-
processScope = Tracer.Instance.StartActive($"publish {cloudEvent.Type}", new SpanCreationSettings()
47+
processScope = Tracer.Instance.StartActive($"publish {cloudEvent.Type}", new SpanCreationSettings
4548
{
4649
Parent = activeSpan.Context
4750
});
4851

4952
cloudEvent.SetAttributeFromString("traceparent", $"00-{activeSpan.TraceId}-{activeSpan.SpanId}[01");
5053
}
51-
54+
5255
var formatter = new JsonEventFormatter<UserRegisteredEvent>();
5356
var data = formatter.EncodeBinaryModeEventData(cloudEvent);
5457

5558
using var producer = new ProducerBuilder<string, string>(config).Build();
59+
60+
try
61+
{
62+
var deliveryReport = await producer.ProduceAsync(cloudEvent.Type,
63+
new Message<string, string> { Key = cloudEvent.Id!, Value = Encoding.UTF8.GetString(data.Span) },
64+
default);
5665

57-
producer.Produce(cloudEvent.Type, new Message<string, string> { Key = cloudEvent.Id!, Value = Encoding.UTF8.GetString(data.Span) },
58-
(deliveryReport) =>
66+
if (deliveryReport.Status == PersistenceStatus.PossiblyPersisted) {
67+
// Handle potential timeout errors
68+
throw new MessageProcessingException("Kafka message possibly persisted, but not confirmed.");
69+
}
70+
else if (deliveryReport.Status == PersistenceStatus.NotPersisted) {
71+
// Handle message not persisted errors.
72+
throw new MessageProcessingException($"Message not persisted: {deliveryReport.TopicPartitionOffset}");
73+
}
74+
}
75+
catch (ProduceException<string, string> ex)
76+
{
77+
switch (ex.Error.Code)
5978
{
60-
if (deliveryReport.Error.Code != ErrorCode.NoError) {
61-
logger.LogError($"Failed to deliver message: {deliveryReport.Error.Reason}");
62-
}
63-
else {
64-
logger.LogInformation($"Produced event to topic {cloudEvent.Type}");
65-
}
66-
});
67-
79+
case ErrorCode.ClusterAuthorizationFailed:
80+
throw new MessageProcessingException("Cluster authorization failed", ex);
81+
case ErrorCode.BrokerNotAvailable:
82+
throw new MessageProcessingException("Broker not available", ex);
83+
default:
84+
throw new MessageProcessingException("Error publishing messages", ex);
85+
}
86+
}
87+
6888
producer.Flush(TimeSpan.FromSeconds(10));
6989
}
7090
catch (Exception ex)
7191
{
72-
logger.LogError(ex, "Failure publishing message");
92+
Log.MessagePublishingError(logger, "Error publishing message", ex);
7393
processScope?.Span.SetException(ex);
94+
throw;
7495
}
7596
finally
7697
{
7798
processScope?.Close();
7899
}
79-
80-
return Task.CompletedTask;
81100
}
82101
}

user-management/src/Stickerlandia.UserManagement.Agnostic/MicrsoftIdentityAuthService.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,15 @@
66
using System.Security.Claims;
77
using Microsoft.AspNetCore.Authentication;
88
using Microsoft.AspNetCore.Identity;
9-
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
10-
using Microsoft.EntityFrameworkCore;
11-
using Microsoft.Extensions.Logging;
129
using OpenIddict.Abstractions;
1310
using OpenIddict.Server.AspNetCore;
1411
using Stickerlandia.UserManagement.Core;
1512

1613
namespace Stickerlandia.UserManagement.Agnostic;
1714

1815
public class MicrosoftIdentityAuthService(
19-
ILogger<MicrosoftIdentityAuthService> logger,
2016
IOpenIddictApplicationManager applicationManager,
2117
IOpenIddictScopeManager scopeManager,
22-
UserManagementDbContext dbContext,
2318
UserManager<PostgresUserAccount> userManager) : IAuthService
2419
{
2520
public async Task<ClaimsIdentity?> VerifyClient(string clientId)
@@ -40,7 +35,7 @@ public class MicrosoftIdentityAuthService(
4035
{
4136
// Allow the "name" claim to be stored in both the access and identity tokens
4237
// when the "profile" scope was granted (by calling principal.SetScopes(...)).
43-
OpenIddictConstants.Claims.Name when claim.Subject.HasScope(OpenIddictConstants.Permissions.Scopes
38+
OpenIddictConstants.Claims.Name when claim.Subject!.HasScope(OpenIddictConstants.Permissions.Scopes
4439
.Profile)
4540
=> [OpenIddictConstants.Destinations.AccessToken, OpenIddictConstants.Destinations.IdentityToken],
4641

@@ -78,7 +73,7 @@ OpenIddictConstants.Claims.Name when claim.Subject.HasScope(OpenIddictConstants.
7873
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Name, user.Id));
7974
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Subject, user.Id));
8075
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Audience, "Resource"));
81-
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Email, user.Email));
76+
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Email, user.Email ?? ""));
8277
identity.AddClaim(new Claim(OpenIddictConstants.Claims.Username, user.Id));
8378

8479
// Setting destinations of claims i.e. identity token or access token

user-management/src/Stickerlandia.UserManagement.Agnostic/Migrations/20250527125041_InitialCreate.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using Microsoft.EntityFrameworkCore.Migrations;
33
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
44

5+
// Disable warnings for the migration code, it is auto-generated and may not follow all conventions.
6+
#pragma warning disable
57
#nullable disable
68

79
namespace Stickerlandia.UserManagement.Agnostic.Migrations
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
2+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
3+
// Copyright 2025 Datadog, Inc.
4+
5+
using Microsoft.Extensions.Logging;
6+
using Stickerlandia.UserManagement.Core;
7+
8+
namespace Stickerlandia.UserManagement.Agnostic.Observability;
9+
10+
public static partial class Log
11+
{
12+
}

0 commit comments

Comments
 (0)