Skip to content

Commit 92e67b3

Browse files
heuveaGrabauskasgithub-code-quality[bot]
authored
FUND-2189 DRC: added validation on document taal which should be 3-char iso639 format (#126)
* added validation on document taal which should be 3-char iso639 format * copilit fix * logged clients who sending nl/en instead of eng/nld. Auto conversion for nld and eng * Convert2letterTo3Letter helper added to share on other places * made improvement in IsIso639LanguageCode validator * Potential fix for pull request finding 'Dereferenced variable may be null' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * Potential fix for pull request finding 'Missed opportunity to use Select' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> * usse CustomClaimTypes instead of hard-coded string * ignore case --------- Co-authored-by: Giedrius Grabauskas <43740166+Grabauskas@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
1 parent 78904e5 commit 92e67b3

15 files changed

+755
-10
lines changed

src/OneGround.ZGW.Common.Web/Controllers/ZGWControllerBase.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using MediatR;
66
using Microsoft.AspNetCore.Mvc;
77
using Microsoft.Extensions.Logging;
8+
using OneGround.ZGW.Common.Authentication;
89
using OneGround.ZGW.Common.Extensions;
910
using OneGround.ZGW.Common.Web.Services;
1011

@@ -76,4 +77,23 @@ protected int GetSridFromAcceptCrsHeader()
7677

7778
protected static HashSet<string> ExpandLookup(string expand) =>
7879
expand != null ? expand.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).ToHashSet() : [];
80+
81+
// Note: Temporary log method. We should investigate who send the 2-letter language code so we log for these situations
82+
protected void LogInvalidTaalCode(string taalRequested, string taalMapped)
83+
{
84+
if (taalRequested?.Length != taalMapped?.Length)
85+
{
86+
var clientId =
87+
User.Claims.FirstOrDefault(c => c.Type.Equals(CustomClaimTypes.ClientId, StringComparison.OrdinalIgnoreCase))?.Value ?? "unknown";
88+
89+
_logger.LogWarning(
90+
"Language code mismatch: Request has {RequestLength}-character code '{RequestTaal}', but mapped to {MappedLength}-character code '{MappedTaal}' for ClientId: {ClientId}",
91+
taalRequested?.Length ?? 0,
92+
taalRequested ?? "null",
93+
taalMapped?.Length ?? 0,
94+
taalMapped ?? "null",
95+
clientId
96+
);
97+
}
98+
}
7999
}

src/OneGround.ZGW.Common.Web/Resources/iso-639-3.tab

Lines changed: 548 additions & 0 deletions
Large diffs are not rendered by default.

src/OneGround.ZGW.Common.Web/Validations/IRuleBuilderExtensions.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,4 +390,46 @@ public static IRuleBuilderOptions<T, int> IsInRange<T>(this IRuleBuilder<T, int>
390390
.WithMessage($"De waarde dient te liggen tussen {minValue} en {maxValue}.")
391391
.WithErrorCode(ErrorCode.Invalid);
392392
}
393+
394+
/// <summary>
395+
/// Validates if value is a valid ISO 639 language code (3 letter code supported only).
396+
/// </summary>
397+
public static IRuleBuilderOptions<T, string> IsIso639LanguageCode<T>(
398+
this IRuleBuilderInitial<T, string> ruleBuilderInitial,
399+
bool required,
400+
Dictionary<string, string> taal2letterTo3LetterMap = null
401+
)
402+
{
403+
return ruleBuilderInitial
404+
.Cascade(CascadeMode.Stop)
405+
.NotNull()
406+
.When(_ => required, ApplyConditionTo.CurrentValidator)
407+
.NotEmpty()
408+
.When(_ => required, ApplyConditionTo.CurrentValidator)
409+
.Must(value =>
410+
{
411+
if (string.IsNullOrEmpty(value))
412+
return !required;
413+
414+
// Map 2-letter codes to 3-letter codes if mapping is provided
415+
if (taal2letterTo3LetterMap != null && taal2letterTo3LetterMap.TryGetValue(value.ToLower(), out var result))
416+
{
417+
value = result;
418+
}
419+
420+
// Check if the length is exactly 3 characters long
421+
if (value.Length != 3)
422+
return false;
423+
424+
// Check if all characters are letters
425+
if (!value.All(char.IsLetter))
426+
return false;
427+
428+
bool valid = Iso639Helper.GetAllThreeLetterCodes().Any(c => c.Equals(value, StringComparison.OrdinalIgnoreCase));
429+
430+
return valid;
431+
})
432+
.WithMessage("Waarde is geen geldige ISO 639 drieletterige taalcode.")
433+
.WithErrorCode(ErrorCode.Invalid);
434+
}
393435
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
7+
namespace OneGround.ZGW.Common.Web.Validations;
8+
9+
public static class Iso639Helper
10+
{
11+
private static readonly Lazy<IReadOnlyList<Iso639Language>> _languages = new Lazy<IReadOnlyList<Iso639Language>>(LoadLanguages);
12+
13+
public static IReadOnlyList<Iso639Language> GetAll() => _languages.Value;
14+
15+
public static IReadOnlyList<string> GetAllThreeLetterCodes() =>
16+
_languages
17+
.Value
18+
// Note: Ignore meta data ["B" (bibliographic) or "T" (terminology)]. So take first 3-character code from column-value. An example of this is these two lines:
19+
// dut (B)
20+
// nld (T)\tnl\tDutch; Flemish\tnéerlandais; flamand\tNiederländisch
21+
.Select(l => l.ISO6392Code.Split(' ')[0])
22+
.Distinct()
23+
.OrderBy(x => x)
24+
.ToList();
25+
26+
private static IReadOnlyList<Iso639Language> LoadLanguages()
27+
{
28+
// Note: Table could easily be extracted from https://www.loc.gov/standards/iso639-2/php/code_list.php and exported to iso-639-3.tab (as Embedded Resource) without any changes
29+
var assembly = Assembly.GetExecutingAssembly();
30+
var resourceName = assembly.GetManifestResourceNames().Single(n => n.EndsWith("Resources.iso-639-3.tab"));
31+
32+
using var stream = assembly.GetManifestResourceStream(resourceName);
33+
using var reader = new StreamReader(stream);
34+
35+
var lines = reader.ReadToEnd().Split('\n', StringSplitOptions.RemoveEmptyEntries).Skip(1); // header
36+
37+
return lines
38+
.Select(line =>
39+
{
40+
var cols = line.Split('\t');
41+
return new Iso639Language
42+
{
43+
ISO6392Code = cols.ElementAtOrDefault(0),
44+
ISO6391Code = cols.ElementAtOrDefault(1),
45+
EnglishNameOfLanguage = cols.ElementAtOrDefault(2),
46+
FrenchNameOfLanguage = cols.ElementAtOrDefault(3),
47+
GermanNameOfLanguage = cols.ElementAtOrDefault(4),
48+
};
49+
})
50+
.ToList();
51+
}
52+
}
53+
54+
public sealed class Iso639Language
55+
{
56+
public string ISO6392Code { get; init; }
57+
public string ISO6391Code { get; init; }
58+
public string EnglishNameOfLanguage { get; init; }
59+
public string FrenchNameOfLanguage { get; init; }
60+
public string GermanNameOfLanguage { get; init; }
61+
}

src/OneGround.ZGW.Common.Web/ZGW.Common.Web.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
<AssemblyName>OneGround.ZGW.Common.Web</AssemblyName>
55
<RootNamespace>OneGround.ZGW.Common.Web</RootNamespace>
66
</PropertyGroup>
7+
<ItemGroup>
8+
<None Remove="Resources\iso-639-3.tab" />
9+
</ItemGroup>
10+
<ItemGroup>
11+
<EmbeddedResource Include="Resources\iso-639-3.tab" />
12+
</ItemGroup>
713
<ItemGroup>
814
<FrameworkReference Include="Microsoft.AspNetCore.App" />
915
</ItemGroup>

src/OneGround.ZGW.Common/Helpers/ProfileHelper.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,4 +133,18 @@ public static string EmptyWhenNull(string value)
133133
{
134134
return value ?? string.Empty;
135135
}
136+
137+
public static string Convert2letterTo3Letter(string taal, Dictionary<string, string> dictionary)
138+
{
139+
if (dictionary.TryGetValue(taal.ToLower(), out var result))
140+
{
141+
return result;
142+
}
143+
else
144+
{
145+
return taal.ToLower();
146+
}
147+
}
148+
149+
public static Dictionary<string, string> Taal2letterTo3LetterMap => new() { { "nl", "nld" }, { "en", "eng" } };
136150
}

src/OneGround.ZGW.Documenten.Web/Controllers/v1/1/EnkelvoudigInformatieObjectenController.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Net.Mime;
@@ -223,6 +223,9 @@ public async Task<IActionResult> AddAsync([FromBody] EnkelvoudigInformatieObject
223223

224224
var enkelvoudigInformatieObjectVersie = _mapper.Map<EnkelvoudigInformatieObjectVersie>(enkelvoudigInformatieObjectRequest);
225225

226+
// Note: we should investigate who send the 2-letter language code so we log for these situations
227+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
228+
226229
var result = await _mediator.Send(
227230
new CreateEnkelvoudigInformatieObjectCommand { EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObjectVersie }
228231
);
@@ -270,6 +273,9 @@ public async Task<IActionResult> UpdateAsync([FromBody] EnkelvoudigInformatieObj
270273
enkelvoudigInformatieObjectRequest
271274
);
272275

276+
// Note: we should investigate who send the 2-letter language code so we log for these situations
277+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
278+
273279
var result = await _mediator.Send(
274280
new UpdateEnkelvoudigInformatieObjectCommand
275281
{
@@ -345,6 +351,9 @@ public async Task<IActionResult> PartialUpdateAsync([FromBody] dynamic partialEn
345351
mergedEnkelvoudigInformatieObjectRequest
346352
);
347353

354+
// Note: we should investigate who send the 2-letter language code so we log for these situations
355+
LogInvalidTaalCode(mergedEnkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
356+
348357
var result = await _mediator.Send(
349358
new UpdateEnkelvoudigInformatieObjectCommand
350359
{

src/OneGround.ZGW.Documenten.Web/Controllers/v1/5/EnkelvoudigInformatieObjectenController.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Net.Mime;
@@ -298,6 +298,9 @@ public async Task<IActionResult> AddAsync([FromBody] EnkelvoudigInformatieObject
298298

299299
var enkelvoudigInformatieObjectVersie = _mapper.Map<EnkelvoudigInformatieObjectVersie>(enkelvoudigInformatieObjectRequest);
300300

301+
// Note: we should investigate who send the 2-letter language code so we log for these situations
302+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
303+
301304
var result = await _mediator.Send(
302305
new CreateEnkelvoudigInformatieObjectCommand { EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObjectVersie }
303306
);
@@ -345,6 +348,9 @@ public async Task<IActionResult> UpdateAsync([FromBody] EnkelvoudigInformatieObj
345348
enkelvoudigInformatieObjectRequest
346349
);
347350

351+
// Note: we should investigate who send the 2-letter language code so we log for these situations
352+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
353+
348354
var result = await _mediator.Send(
349355
new UpdateEnkelvoudigInformatieObjectCommand
350356
{
@@ -420,6 +426,9 @@ public async Task<IActionResult> PartialUpdateAsync([FromBody] dynamic partialEn
420426
mergedEnkelvoudigInformatieObjectRequest
421427
);
422428

429+
// Note: we should investigate who send the 2-letter language code so we log for these situations
430+
LogInvalidTaalCode(mergedEnkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
431+
423432
var result = await _mediator.Send(
424433
new UpdateEnkelvoudigInformatieObjectCommand
425434
{

src/OneGround.ZGW.Documenten.Web/Controllers/v1/EnkelvoudigInformatieObjectenController.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Net.Mime;
@@ -197,6 +197,9 @@ public async Task<IActionResult> AddAsync([FromBody] EnkelvoudigInformatieObject
197197

198198
var enkelvoudigInformatieObjectVersie = _mapper.Map<EnkelvoudigInformatieObjectVersie>(enkelvoudigInformatieObjectRequest);
199199

200+
// Note: we should investigate who send the 2-letter language code so we log for these situations
201+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
202+
200203
var result = await _mediator.Send(
201204
new CreateEnkelvoudigInformatieObjectCommand { EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObjectVersie }
202205
);
@@ -240,15 +243,18 @@ public async Task<IActionResult> UpdateAsync([FromBody] EnkelvoudigInformatieObj
240243
enkelvoudigInformatieObjectRequest.Bronorganisatie
241244
);
242245

243-
EnkelvoudigInformatieObjectVersie enkelvoudigInformatieObject = _mapper.Map<EnkelvoudigInformatieObjectVersie>(
246+
EnkelvoudigInformatieObjectVersie enkelvoudigInformatieObjectVersie = _mapper.Map<EnkelvoudigInformatieObjectVersie>(
244247
enkelvoudigInformatieObjectRequest
245248
);
246249

250+
// Note: we should investigate who send the 2-letter language code so we log for these situations
251+
LogInvalidTaalCode(enkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
252+
247253
var result = await _mediator.Send(
248254
new CreateEnkelvoudigInformatieObjectCommand
249255
{
250256
ExistingEnkelvoudigInformatieObjectId = id,
251-
EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObject,
257+
EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObjectVersie,
252258
}
253259
);
254260

@@ -310,15 +316,18 @@ public async Task<IActionResult> PartialUpdateAsync([FromBody] dynamic partialEn
310316
return _errorResponseBuilder.BadRequest(validationResult);
311317
}
312318

313-
EnkelvoudigInformatieObjectVersie enkelvoudigInformatieObject = _mapper.Map<EnkelvoudigInformatieObjectVersie>(
319+
EnkelvoudigInformatieObjectVersie enkelvoudigInformatieObjectVersie = _mapper.Map<EnkelvoudigInformatieObjectVersie>(
314320
mergedEnkelvoudigInformatieObjectRequest
315321
);
316322

323+
// Note: we should investigate who send the 2-letter language code so we log for these situations
324+
LogInvalidTaalCode(mergedEnkelvoudigInformatieObjectRequest.Taal, enkelvoudigInformatieObjectVersie.Taal);
325+
317326
var result = await _mediator.Send(
318327
new CreateEnkelvoudigInformatieObjectCommand
319328
{
320329
ExistingEnkelvoudigInformatieObjectId = id,
321-
EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObject,
330+
EnkelvoudigInformatieObjectVersie = enkelvoudigInformatieObjectVersie,
322331
IsPartialUpdate = true,
323332
}
324333
);

src/OneGround.ZGW.Documenten.Web/MappingProfiles/v1/1/RequestToDomainProfile.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public RequestToDomainProfile()
5656
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => StatusFromString(src.Status)))
5757
.ForMember(dest => dest.Integriteit_Algoritme, opt => opt.MapFrom(src => AlgoritmeFromString(src.Integriteit.Algoritme)))
5858
.ForMember(dest => dest.Versie, opt => opt.Ignore())
59+
.ForMember(
60+
dest => dest.Taal,
61+
opt => opt.MapFrom(src => ProfileHelper.Convert2letterTo3Letter(src.Taal, ProfileHelper.Taal2letterTo3LetterMap))
62+
)
5963
.ForMember(dest => dest.BeginRegistratie, opt => opt.Ignore())
6064
.ForMember(dest => dest.Bestandsomvang, opt => opt.MapFrom(src => src.Bestandsomvang))
6165
.ForMember(dest => dest.EnkelvoudigInformatieObjectId, opt => opt.Ignore())
@@ -102,6 +106,10 @@ public RequestToDomainProfile()
102106
.ForMember(dest => dest.Status, opt => opt.MapFrom(src => StatusFromString(src.Status)))
103107
.ForMember(dest => dest.Integriteit_Algoritme, opt => opt.MapFrom(src => AlgoritmeFromString(src.Integriteit.Algoritme)))
104108
.ForMember(dest => dest.Versie, opt => opt.Ignore())
109+
.ForMember(
110+
dest => dest.Taal,
111+
opt => opt.MapFrom(src => ProfileHelper.Convert2letterTo3Letter(src.Taal, ProfileHelper.Taal2letterTo3LetterMap))
112+
)
105113
.ForMember(dest => dest.BeginRegistratie, opt => opt.Ignore())
106114
.ForMember(dest => dest.Bestandsomvang, opt => opt.MapFrom(src => src.Bestandsomvang))
107115
.ForMember(dest => dest.EnkelvoudigInformatieObjectId, opt => opt.Ignore())

0 commit comments

Comments
 (0)